diff --git a/src/ir/bits.cpp b/src/ir/bits.cpp index afc2de08590..132812a5de8 100644 --- a/src/ir/bits.cpp +++ b/src/ir/bits.cpp @@ -214,7 +214,9 @@ Index Provider::getMaxBits(Expression* curr) const { if (maxBitsLeft == 32) { return 32; } - auto bitsRight = Index(ceilLog2(c->value.geti32())); + int32_t rhs = c->value.geti32(); + uint32_t absRhs = rhs < 0 ? -uint32_t(rhs) : uint32_t(rhs); + auto bitsRight = Index(ceilLog2(absRhs)); return std::min(maxBitsLeft, bitsRight); } return 32; @@ -310,7 +312,9 @@ Index Provider::getMaxBits(Expression* curr) const { if (maxBitsLeft == 64) { return 64; } - auto bitsRight = Index(ceilLog2(c->value.geti64())); + int64_t rhs = c->value.geti64(); + uint64_t absRhs = rhs < 0 ? -uint64_t(rhs) : uint64_t(rhs); + auto bitsRight = Index(ceilLog2(absRhs)); return std::min(maxBitsLeft, bitsRight); } return 64; diff --git a/test/example/cpp-unit.cpp b/test/example/cpp-unit.cpp index 1a2b9927432..6b35136791b 100644 --- a/test/example/cpp-unit.cpp +++ b/test/example/cpp-unit.cpp @@ -199,6 +199,21 @@ void test_bits() { assert_equal(getMaxBits(&b), 32); c0.value = Literal(int32_t(3)); c1.value = Literal(int32_t(-1)); + assert_equal(getMaxBits(&b), 0); + c0.value = Literal(int32_t(0xFF)); + c1.value = Literal(int32_t(-4)); + assert_equal(getMaxBits(&b), 2); + c0.value = Literal(int32_t(0xFF)); + c1.value = Literal(int32_t(-5)); + assert_equal(getMaxBits(&b), 3); + c0.value = Literal(int32_t(-1)); + c1.value = Literal(int32_t(-4)); + assert_equal(getMaxBits(&b), 32); + c0.value = Literal(int32_t(0x7FFFFFFF)); + c1.value = Literal(int32_t(0x80000000)); + assert_equal(getMaxBits(&b), 31); + c0.value = Literal(int32_t(3)); + c1.value = Literal(int32_t(0x80000000)); assert_equal(getMaxBits(&b), 2); c0.value = Literal(int32_t(0x7FFFFFFF)); c1.value = Literal(int32_t(0x7FFFFFFF)); @@ -412,6 +427,21 @@ void test_bits() { assert_equal(getMaxBits(&b), 64); c0.value = Literal(int64_t(3)); c1.value = Literal(int64_t(-1)); + assert_equal(getMaxBits(&b), 0); + c0.value = Literal(int64_t(0xFF)); + c1.value = Literal(int64_t(-4)); + assert_equal(getMaxBits(&b), 2); + c0.value = Literal(int64_t(0xFF)); + c1.value = Literal(int64_t(-5)); + assert_equal(getMaxBits(&b), 3); + c0.value = Literal(int64_t(-1)); + c1.value = Literal(int64_t(-4)); + assert_equal(getMaxBits(&b), 64); + c0.value = Literal(int64_t(0x7FFFFFFFFFFFFFFF)); + c1.value = Literal(int64_t(0x8000000000000000)); + assert_equal(getMaxBits(&b), 63); + c0.value = Literal(int64_t(3)); + c1.value = Literal(int64_t(0x8000000000000000)); assert_equal(getMaxBits(&b), 2); c0.value = Literal(int64_t(0x7FFFFFFFFFFFFFFF)); c1.value = Literal(int64_t(0x7FFFFFFFFFFFFFFF)); diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index 442b7fedc9a..13e6692182e 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -9435,6 +9435,245 @@ (i64.const 0x8000000000000000) )) ) + ;; CHECK: (func $rem-s-neg-divisor-max-bits-i32 (param $x i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.rem_s + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.rem_s + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.rem_s + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 2147483647) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -2147483648) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.rem_s + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.rem_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const -5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rem-s-neg-divisor-max-bits-i32 (param $x i32) + ;; Non-negative dividend with negative constant divisors: getMaxBits uses + ;; abs(divisor), allowing redundant masks and zero-bit remainders to fold. + ;; abs(-5) = 5 -> max 3 bits (remainder in [0, 4]), so masking with 7 is redundant. + (drop + (i32.and + (i32.rem_s + (i32.and (local.get $x) (i32.const 0xff)) + (i32.const -5) + ) + (i32.const 7) + ) + ) + ;; abs(-4) = 4 -> max 2 bits (remainder in [0, 3]), so masking with 3 is redundant. + (drop + (i32.and + (i32.rem_s + (i32.and (local.get $x) (i32.const 0xff)) + (i32.const -4) + ) + (i32.const 3) + ) + ) + ;; Impure non-negative dividend % -1 has 0 max bits and folds to 0 via + ;; replaceZeroBitsWithZero while keeping the local.set side effect. + (drop + (i32.rem_s + (local.tee $x (i32.const 3)) + (i32.const -1) + ) + ) + ;; Minimum signed i32 divisor (-2147483648) has unsigned abs 2^31 (31 bits). + (drop + (i32.and + (i32.rem_s + (i32.and (local.get $x) (i32.const 0x7fffffff)) + (i32.const 0x80000000) + ) + (i32.const 0x7fffffff) + ) + ) + ;; Negative case: mask of 3 is too narrow for remainder in [0, 4]. + (drop + (i32.and + (i32.rem_s + (i32.and (local.get $x) (i32.const 0xff)) + (i32.const -5) + ) + (i32.const 3) + ) + ) + ;; Negative case: dividend may be negative, so remainder may be negative and + ;; masking with 7 is not redundant. + (drop + (i32.and + (i32.rem_s + (local.get $x) + (i32.const -5) + ) + (i32.const 7) + ) + ) + ) + ;; CHECK: (func $rem-s-neg-divisor-max-bits-i64 (param $y i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.rem_s + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i64.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const -5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.rem_s + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i64.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const -4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $y + ;; CHECK-NEXT: (i64.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.rem_s + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i64.const 9223372036854775807) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const -9223372036854775808) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (i64.rem_s + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i64.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const -5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (i64.rem_s + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i64.const -5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rem-s-neg-divisor-max-bits-i64 (param $y i64) + ;; Same cases for i64.rem_s. + (drop + (i64.and + (i64.rem_s + (i64.and (local.get $y) (i64.const 0xff)) + (i64.const -5) + ) + (i64.const 7) + ) + ) + (drop + (i64.and + (i64.rem_s + (i64.and (local.get $y) (i64.const 0xff)) + (i64.const -4) + ) + (i64.const 3) + ) + ) + (drop + (i64.rem_s + (local.tee $y (i64.const 3)) + (i64.const -1) + ) + ) + (drop + (i64.and + (i64.rem_s + (i64.and (local.get $y) (i64.const 0x7fffffffffffffff)) + (i64.const 0x8000000000000000) + ) + (i64.const 0x7fffffffffffffff) + ) + ) + ;; Negative case: mask of 3 is too narrow for remainder in [0, 4]. + (drop + (i64.and + (i64.rem_s + (i64.and (local.get $y) (i64.const 0xff)) + (i64.const -5) + ) + (i64.const 3) + ) + ) + ;; Negative case: dividend may be negative, so masking with 7 is needed. + (drop + (i64.and + (i64.rem_s + (local.get $y) + (i64.const -5) + ) + (i64.const 7) + ) + ) + ) ;; CHECK: (func $srem-by-pot-eq-ne-zero (param $x i32) (param $y i64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.eqz