From 2ea0db0267bf8ed92e4d687b4b760aeff565fa4a Mon Sep 17 00:00:00 2001 From: CountBleck Date: Sat, 6 Sep 2025 22:51:57 -0700 Subject: [PATCH 1/2] fix: simplify && and || when the LHS is constant We now precompute the LHS and have an additional optimization to check the truthiness of the LHS and use that to simplify the expression to either the LHS or the RHS, without generating an `if`. Fixes #2946. --- src/compiler.ts | 44 +++-- tests/compiler/logical.debug.wat | 213 ++---------------------- tests/compiler/logical.release.wat | 4 +- tests/compiler/resolve-binary.debug.wat | 24 +-- 4 files changed, 52 insertions(+), 233 deletions(-) diff --git a/src/compiler.ts b/src/compiler.ts index d3a5525164..7e87baf7fd 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -4574,19 +4574,31 @@ export class Compiler extends DiagnosticEmitter { } leftExpr = this.convertExpression(leftExpr, leftType, commonType, false, left); leftType = commonType; + + // This is sometimes needed to make the left trivial + let leftPrecompExpr = module.runExpression(leftExpr, ExpressionRunnerFlags.PreserveSideeffects); + if (leftPrecompExpr) leftExpr = leftPrecompExpr; + rightExpr = this.convertExpression(rightExpr, rightType, commonType, false, right); rightType = commonType; - // simplify if copying left is trivial - if (expr = module.tryCopyTrivialExpression(leftExpr)) { + let condExpr = this.makeIsTrueish(leftExpr, this.currentType, left); + let condKind = this.evaluateCondition(condExpr); + + if (condKind != ConditionKind.Unknown) { + // simplify if left is a constant + expr = condKind == ConditionKind.True + ? rightExpr + : leftExpr; + } else if (expr = module.tryCopyTrivialExpression(leftExpr)) { + // simplify if copying left is trivial expr = module.if( - this.makeIsTrueish(leftExpr, this.currentType, left), + condExpr, rightExpr, expr ); - - // if not possible, tee left to a temp } else { + // if not possible, tee left to a temp let tempLocal = flow.getTempLocal(leftType); if (!flow.canOverflow(leftExpr, leftType)) flow.setLocalFlag(tempLocal.index, LocalFlags.Wrapped); if (flow.isNonnull(leftExpr, leftType)) flow.setLocalFlag(tempLocal.index, LocalFlags.NonNull); @@ -4654,19 +4666,31 @@ export class Compiler extends DiagnosticEmitter { let possiblyNull = leftType.is(TypeFlags.Nullable) && rightType.is(TypeFlags.Nullable); leftExpr = this.convertExpression(leftExpr, leftType, commonType, false, left); leftType = commonType; + + // This is sometimes needed to make the left trivial + let leftPrecompExpr = module.runExpression(leftExpr, ExpressionRunnerFlags.PreserveSideeffects); + if (leftPrecompExpr) leftExpr = leftPrecompExpr; + rightExpr = this.convertExpression(rightExpr, rightType, commonType, false, right); rightType = commonType; - // simplify if copying left is trivial - if (expr = module.tryCopyTrivialExpression(leftExpr)) { + let condExpr = this.makeIsTrueish(leftExpr, this.currentType, left); + let condKind = this.evaluateCondition(condExpr); + + if (condKind != ConditionKind.Unknown) { + // simplify if left is a constant + expr = condKind == ConditionKind.True + ? leftExpr + : rightExpr; + } else if (expr = module.tryCopyTrivialExpression(leftExpr)) { + // otherwise, simplify if copying left is trivial expr = module.if( - this.makeIsTrueish(leftExpr, leftType, left), + condExpr, expr, rightExpr ); - - // if not possible, tee left to a temp. local } else { + // if not possible, tee left to a temp. local let temp = flow.getTempLocal(leftType); let tempIndex = temp.index; if (!flow.canOverflow(leftExpr, leftType)) flow.setLocalFlag(tempIndex, LocalFlags.Wrapped); diff --git a/tests/compiler/logical.debug.wat b/tests/compiler/logical.debug.wat index 78f3bc8b0e..3a315c42f9 100644 --- a/tests/compiler/logical.debug.wat +++ b/tests/compiler/logical.debug.wat @@ -2583,26 +2583,9 @@ i64.const -9007199254740994 i64.le_u drop - i32.const 1 - if (result i32) - i32.const 2 - else - i32.const 1 - end + i32.const 2 drop - f64.const 1 - i64.reinterpret_f64 - i64.const 1 - i64.shl - i64.const 2 - i64.sub - i64.const -9007199254740994 - i64.le_u - if (result f64) - f64.const 2 - else - f64.const 1 - end + f64.const 2 i64.reinterpret_f64 i64.const 1 i64.shl @@ -2611,12 +2594,7 @@ i64.const -9007199254740994 i64.le_u drop - i32.const 1 - if (result i32) - i32.const 2 - else - i32.const 1 - end + i32.const 2 global.set $logical/i global.get $logical/i i32.const 2 @@ -2630,12 +2608,7 @@ call $~lib/builtins/abort unreachable end - i32.const 0 - if (result i32) - i32.const 0 - else - i32.const 1 - end + i32.const 1 global.set $logical/i global.get $logical/i i32.const 1 @@ -2649,14 +2622,7 @@ call $~lib/builtins/abort unreachable end - i64.const 1 - i64.const 0 - i64.ne - if (result i64) - i64.const 2 - else - i64.const 1 - end + i64.const 2 global.set $logical/I global.get $logical/I i64.const 2 @@ -2670,14 +2636,7 @@ call $~lib/builtins/abort unreachable end - i64.const 0 - i64.const 0 - i64.ne - if (result i64) - i64.const 0 - else - i64.const 1 - end + i64.const 1 global.set $logical/I global.get $logical/I i64.const 1 @@ -2691,19 +2650,7 @@ call $~lib/builtins/abort unreachable end - f32.const 1 - i32.reinterpret_f32 - i32.const 1 - i32.shl - i32.const 2 - i32.sub - i32.const -16777218 - i32.le_u - if (result f32) - f32.const 2 - else - f32.const 1 - end + f32.const 2 global.set $logical/f global.get $logical/f f32.const 2 @@ -2717,19 +2664,7 @@ call $~lib/builtins/abort unreachable end - f32.const 0 - i32.reinterpret_f32 - i32.const 1 - i32.shl - i32.const 2 - i32.sub - i32.const -16777218 - i32.le_u - if (result f32) - f32.const 0 - else - f32.const 1 - end + f32.const 1 global.set $logical/f global.get $logical/f f32.const 1 @@ -2743,19 +2678,7 @@ call $~lib/builtins/abort unreachable end - f64.const 1 - i64.reinterpret_f64 - i64.const 1 - i64.shl - i64.const 2 - i64.sub - i64.const -9007199254740994 - i64.le_u - if (result f64) - f64.const 2 - else - f64.const 1 - end + f64.const 2 global.set $logical/F global.get $logical/F f64.const 2 @@ -2769,19 +2692,7 @@ call $~lib/builtins/abort unreachable end - f64.const 0 - i64.reinterpret_f64 - i64.const 1 - i64.shl - i64.const 2 - i64.sub - i64.const -9007199254740994 - i64.le_u - if (result f64) - f64.const 0 - else - f64.const 1 - end + f64.const 1 global.set $logical/F global.get $logical/F f64.const 1 @@ -2795,19 +2706,7 @@ call $~lib/builtins/abort unreachable end - f32.const nan:0x400000 - i32.reinterpret_f32 - i32.const 1 - i32.shl - i32.const 2 - i32.sub - i32.const -16777218 - i32.le_u - if (result f32) - f32.const nan:0x400000 - else - f32.const 1 - end + f32.const 1 global.set $logical/f global.get $logical/f f32.const 1 @@ -2822,18 +2721,6 @@ unreachable end f32.const 1 - i32.reinterpret_f32 - i32.const 1 - i32.shl - i32.const 2 - i32.sub - i32.const -16777218 - i32.le_u - if (result f32) - f32.const 1 - else - f32.const nan:0x400000 - end global.set $logical/f global.get $logical/f f32.const 1 @@ -2847,19 +2734,7 @@ call $~lib/builtins/abort unreachable end - f64.const nan:0x8000000000000 - i64.reinterpret_f64 - i64.const 1 - i64.shl - i64.const 2 - i64.sub - i64.const -9007199254740994 - i64.le_u - if (result f64) - f64.const nan:0x8000000000000 - else - f64.const 1 - end + f64.const 1 global.set $logical/F global.get $logical/F f64.const 1 @@ -2874,18 +2749,6 @@ unreachable end f64.const 1 - i64.reinterpret_f64 - i64.const 1 - i64.shl - i64.const 2 - i64.sub - i64.const -9007199254740994 - i64.le_u - if (result f64) - f64.const 1 - else - f64.const nan:0x8000000000000 - end global.set $logical/F global.get $logical/F f64.const 1 @@ -2899,19 +2762,7 @@ call $~lib/builtins/abort unreachable end - f32.const 1 - i32.reinterpret_f32 - i32.const 1 - i32.shl - i32.const 2 - i32.sub - i32.const -16777218 - i32.le_u - if (result f32) - f32.const nan:0x400000 - else - f32.const 1 - end + f32.const nan:0x400000 global.set $logical/f global.get $logical/f local.tee $0 @@ -2927,18 +2778,6 @@ unreachable end f32.const nan:0x400000 - i32.reinterpret_f32 - i32.const 1 - i32.shl - i32.const 2 - i32.sub - i32.const -16777218 - i32.le_u - if (result f32) - f32.const 1 - else - f32.const nan:0x400000 - end global.set $logical/f global.get $logical/f local.tee $1 @@ -2953,19 +2792,7 @@ call $~lib/builtins/abort unreachable end - f64.const 1 - i64.reinterpret_f64 - i64.const 1 - i64.shl - i64.const 2 - i64.sub - i64.const -9007199254740994 - i64.le_u - if (result f64) - f64.const nan:0x8000000000000 - else - f64.const 1 - end + f64.const nan:0x8000000000000 global.set $logical/F global.get $logical/F local.tee $2 @@ -2981,18 +2808,6 @@ unreachable end f64.const nan:0x8000000000000 - i64.reinterpret_f64 - i64.const 1 - i64.shl - i64.const 2 - i64.sub - i64.const -9007199254740994 - i64.le_u - if (result f64) - f64.const 1 - else - f64.const nan:0x8000000000000 - end global.set $logical/F global.get $logical/F local.tee $3 diff --git a/tests/compiler/logical.release.wat b/tests/compiler/logical.release.wat index 185b69545e..051224642f 100644 --- a/tests/compiler/logical.release.wat +++ b/tests/compiler/logical.release.wat @@ -1420,7 +1420,7 @@ ) (func $~start (local $0 i32) - block $__inlined_func$start:logical + block $__inlined_func$start:logical$1 global.get $~lib/memory/__stack_pointer i32.const 4 i32.sub @@ -1595,7 +1595,7 @@ i32.const 4 i32.add global.set $~lib/memory/__stack_pointer - br $__inlined_func$start:logical + br $__inlined_func$start:logical$1 end i32.const 34320 i32.const 34368 diff --git a/tests/compiler/resolve-binary.debug.wat b/tests/compiler/resolve-binary.debug.wat index 86ec23b334..4b6d25a00f 100644 --- a/tests/compiler/resolve-binary.debug.wat +++ b/tests/compiler/resolve-binary.debug.wat @@ -6623,12 +6623,7 @@ call $~lib/builtins/abort unreachable end - i32.const 1 - if (result i32) - i32.const 2 - else - i32.const 1 - end + i32.const 2 i32.const 10 call $~lib/number/I32#toString local.set $0 @@ -6648,11 +6643,6 @@ unreachable end i32.const 0 - if (result i32) - i32.const 2 - else - i32.const 0 - end i32.const 10 call $~lib/number/I32#toString local.set $0 @@ -6672,11 +6662,6 @@ unreachable end i32.const 1 - if (result i32) - i32.const 1 - else - i32.const 2 - end i32.const 10 call $~lib/number/I32#toString local.set $0 @@ -6695,12 +6680,7 @@ call $~lib/builtins/abort unreachable end - i32.const 0 - if (result i32) - i32.const 0 - else - i32.const 2 - end + i32.const 2 i32.const 10 call $~lib/number/I32#toString local.set $0 From fd9c479f6c374b69a9f3b289af7f47a0439b9a7d Mon Sep 17 00:00:00 2001 From: CountBleck Date: Sat, 6 Sep 2025 23:06:16 -0700 Subject: [PATCH 2/2] Add tests for simplifying non-boolean && and || --- tests/compiler/logical.debug.wat | 16 ++++++++++++---- tests/compiler/logical.release.wat | 12 ++++++++++-- tests/compiler/logical.ts | 8 ++++++++ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/tests/compiler/logical.debug.wat b/tests/compiler/logical.debug.wat index 3a315c42f9..d5fc0a82e6 100644 --- a/tests/compiler/logical.debug.wat +++ b/tests/compiler/logical.debug.wat @@ -28,6 +28,10 @@ (global $~lib/rt/itcms/fromSpace (mut i32) (i32.const 0)) (global $~lib/rt/tlsf/ROOT (mut i32) (i32.const 0)) (global $~lib/native/ASC_LOW_MEMORY_LIMIT i32 (i32.const 0)) + (global $logical/foo (mut i32) (i32.const 456)) + (global $logical/bar (mut f64) (f64.const -0)) + (global $logical/baz (mut i32) (i32.const 321)) + (global $logical/qux (mut f64) (f64.const 2.718)) (global $logical/b (mut i32) (i32.const 0)) (global $logical/c (mut i32) (i32.const 0)) (global $~lib/rt/__rtti_base i32 (i32.const 464)) @@ -47,6 +51,10 @@ (data $9 (i32.const 464) "\08\00\00\00 \00\00\00 \00\00\00 \00\00\00\00\00\00\00 \00\00\00 \00\00\00 \00\00\00 \00\00\00") (table $0 1 1 funcref) (elem $0 (i32.const 1)) + (export "foo" (global $logical/foo)) + (export "bar" (global $logical/bar)) + (export "baz" (global $logical/baz)) + (export "qux" (global $logical/qux)) (export "memory" (memory $0)) (start $~start) (func $logical/testShortcutAnd (param $a i64) (param $b i32) (result i32) @@ -2926,7 +2934,7 @@ if i32.const 0 i32.const 32 - i32.const 106 + i32.const 114 i32.const 1 call $~lib/builtins/abort unreachable @@ -2939,7 +2947,7 @@ if i32.const 0 i32.const 32 - i32.const 107 + i32.const 115 i32.const 1 call $~lib/builtins/abort unreachable @@ -2957,7 +2965,7 @@ if i32.const 0 i32.const 32 - i32.const 112 + i32.const 120 i32.const 1 call $~lib/builtins/abort unreachable @@ -2970,7 +2978,7 @@ if i32.const 0 i32.const 32 - i32.const 113 + i32.const 121 i32.const 1 call $~lib/builtins/abort unreachable diff --git a/tests/compiler/logical.release.wat b/tests/compiler/logical.release.wat index 051224642f..a244889d24 100644 --- a/tests/compiler/logical.release.wat +++ b/tests/compiler/logical.release.wat @@ -17,6 +17,10 @@ (global $~lib/rt/itcms/white (mut i32) (i32.const 0)) (global $~lib/rt/itcms/fromSpace (mut i32) (i32.const 0)) (global $~lib/rt/tlsf/ROOT (mut i32) (i32.const 0)) + (global $logical/foo (mut i32) (i32.const 456)) + (global $logical/bar (mut f64) (f64.const -0)) + (global $logical/baz (mut i32) (i32.const 321)) + (global $logical/qux (mut f64) (f64.const 2.718)) (global $logical/b (mut i32) (i32.const 0)) (global $logical/c (mut i32) (i32.const 0)) (global $~lib/memory/__stack_pointer (mut i32) (i32.const 34292)) @@ -34,6 +38,10 @@ (data $8 (i32.const 1420) "<") (data $8.1 (i32.const 1432) "\02\00\00\00\1e\00\00\00~\00l\00i\00b\00/\00r\00t\00/\00t\00l\00s\00f\00.\00t\00s") (data $9 (i32.const 1488) "\08\00\00\00 \00\00\00 \00\00\00 \00\00\00\00\00\00\00 \00\00\00 \00\00\00 \00\00\00 ") + (export "foo" (global $logical/foo)) + (export "bar" (global $logical/bar)) + (export "baz" (global $logical/baz)) + (export "qux" (global $logical/qux)) (export "memory" (memory $0)) (start $~start) (func $~lib/rt/itcms/visitRoots @@ -1568,7 +1576,7 @@ if i32.const 0 i32.const 1056 - i32.const 106 + i32.const 114 i32.const 1 call $~lib/builtins/abort unreachable @@ -1586,7 +1594,7 @@ if i32.const 0 i32.const 1056 - i32.const 112 + i32.const 120 i32.const 1 call $~lib/builtins/abort unreachable diff --git a/tests/compiler/logical.ts b/tests/compiler/logical.ts index 62a26f9d66..a5943a4f3f 100644 --- a/tests/compiler/logical.ts +++ b/tests/compiler/logical.ts @@ -91,6 +91,14 @@ function testContextualBoolOr(someObj: Obj, someInt: i32): bool { } assert(testContextualBoolOr(new Obj(), 0)); +// Test simplification with precomputable LHS operands +// see: https://github.com/AssemblyScript/assemblyscript/issues/2946 + +export let foo = 123 && 456; +export let bar = -0.0 && 1.23; +export let baz = 321 || 654; +export let qux = NaN || 2.718; + // Common type class A {}