diff --git a/src/ir/bits.h b/src/ir/bits.h index 6add10a8be9..7f3c1f4b2cf 100644 --- a/src/ir/bits.h +++ b/src/ir/bits.h @@ -25,15 +25,29 @@ namespace wasm::Bits { -// get a mask to keep only the low # of bits +// Get a mask to keep only the low # of bits. inline int32_t lowBitMask(int32_t bits) { uint32_t ret = -1; + if (bits <= 0) { + return 0; + } if (bits >= 32) { return ret; } return ret >> (32 - bits); } +inline int64_t lowBitMask64(int64_t bits) { + uint64_t ret = -1; + if (bits <= 0) { + return 0; + } + if (bits >= 64) { + return ret; + } + return ret >> (64 - bits); +} + // checks if the input is a mask of lower bits, i.e., all 1s up to some high // bit, and all zeros from there. returns the number of masked bits, or 0 if // this is not such a mask diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index fc06b1da524..feb9af5bc17 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -742,25 +742,23 @@ struct OptimizeInstructions } // a bunch of operations on a constant right side can be simplified if (auto* right = curr->right->dynCast()) { - if (curr->op == AndInt32) { - auto mask = right->value.geti32(); - // and with -1 does nothing (common in asm.js output) - if (mask == -1) { - return replaceCurrent(curr->left); - } - // small loads do not need to be masked, the load itself masks - if (auto* load = curr->left->dynCast()) { - if ((load->bytes == 1 && mask == 0xff) || - (load->bytes == 2 && mask == 0xffff)) { + using namespace Abstract; + using namespace Match; + Expression* left; + int64_t mask; + if (matches(curr, binary(And, any(&left), ival(&mask)))) { + // Small loads do not need to be masked, the load itself masks. + if (auto* load = left->dynCast()) { + if (mask == Bits::lowBitMask64(load->bytes * 8)) { load->signed_ = false; - return replaceCurrent(curr->left); - } - } else if (auto maskedBits = Bits::getMaskedBits(mask)) { - if (Bits::getMaxBits(curr->left, this) <= maskedBits) { - // a mask of lower bits is not needed if we are already smaller - return replaceCurrent(curr->left); + return replaceCurrent(left); } } + auto lowMask = Bits::lowBitMask64(Bits::getMaxBits(left, this)); + if ((mask & lowMask) == lowMask) { + // A mask of more bits than can be present is not needed. + return replaceCurrent(left); + } } // some math operations have trivial results if (auto* ret = optimizeWithConstantOnRight(curr)) { @@ -4525,11 +4523,8 @@ struct OptimizeInstructions } // i32(bool(x)) == 1 ==> i32(bool(x)) // i32(bool(x)) != 0 ==> i32(bool(x)) - // i32(bool(x)) & 1 ==> i32(bool(x)) - // i64(bool(x)) & 1 ==> i64(bool(x)) if ((matches(curr, binary(EqInt32, any(&left), i32(1))) || - matches(curr, binary(NeInt32, any(&left), i32(0))) || - matches(curr, binary(And, any(&left), ival(1)))) && + matches(curr, binary(NeInt32, any(&left), i32(0)))) && Bits::getMaxBits(left, this) == 1) { return left; } @@ -4566,10 +4561,6 @@ struct OptimizeInstructions } // Operations on all 1s - // x & -1 ==> x - if (matches(curr, binary(And, any(&left), ival(-1)))) { - return left; - } // x | -1 ==> -1 if (matches(curr, binary(Or, any(&left), ival(-1)))) { return getDroppedChildrenAndAppend(curr, right); diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index ecbfd73502b..875a238468f 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -1493,6 +1493,135 @@ (func $load16_u-and-1 (result i32) (i32.and (i32.load16_u (i32.const 7)) (i32.const 1)) ) + ;; CHECK: (func $i64-load-and-mask (param $p i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load8_u + ;; CHECK-NEXT: (local.get $p) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load8_u + ;; CHECK-NEXT: (local.get $p) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load16_u + ;; CHECK-NEXT: (local.get $p) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load16_u + ;; CHECK-NEXT: (local.get $p) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load32_u + ;; CHECK-NEXT: (local.get $p) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load32_u + ;; CHECK-NEXT: (local.get $p) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (i64.load16_s + ;; CHECK-NEXT: (local.get $p) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (i64.load16_s + ;; CHECK-NEXT: (local.get $p) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 4294967295) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (i64.load32_s + ;; CHECK-NEXT: (local.get $p) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 65535) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (i64.load32_s + ;; CHECK-NEXT: (local.get $p) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 4294967294) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i64-load-and-mask (param $p i32) + ;; Narrow i64 loads masked with their exact byte width become unsigned + ;; loads. + (drop + (i64.and + (i64.load8_s (local.get $p)) + (i64.const 0xff) + ) + ) + (drop + (i64.and + (i64.load8_u (local.get $p)) + (i64.const 0xff) + ) + ) + (drop + (i64.and + (i64.load16_s (local.get $p)) + (i64.const 0xffff) + ) + ) + (drop + (i64.and + (i64.load16_u (local.get $p)) + (i64.const 0xffff) + ) + ) + (drop + (i64.and + (i64.load32_s (local.get $p)) + (i64.const 0xffffffff) + ) + ) + (drop + (i64.and + (i64.load32_u (local.get $p)) + (i64.const 0xffffffff) + ) + ) + ;; Negative cases: signed loads where the mask does not match the load size. + (drop + (i64.and + (i64.load16_s (local.get $p)) + (i64.const 0xff) + ) + ) + (drop + (i64.and + (i64.load16_s (local.get $p)) + (i64.const 0xffffffff) + ) + ) + (drop + (i64.and + (i64.load32_s (local.get $p)) + (i64.const 0xffff) + ) + ) + (drop + (i64.and + (i64.load32_s (local.get $p)) + (i64.const 0xfffffffe) + ) + ) + ) ;; CHECK: (func $store8-and-255 ;; CHECK-NEXT: (i32.store8 ;; CHECK-NEXT: (i32.const 8) @@ -4915,11 +5044,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (i32.and - ;; CHECK-NEXT: (local.tee $0 - ;; CHECK-NEXT: (i32.const 128) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 1279) + ;; CHECK-NEXT: (local.tee $0 + ;; CHECK-NEXT: (i32.const 128) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -5015,6 +5141,162 @@ ) ) ) + ;; CHECK: (func $and-redundant-low-mask (param $x i32) (param $y i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.extend_i32_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.extend_i32_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.extend_i32_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.shr_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.shr_u + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i64.const 56) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $y + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load8_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load8_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (i64.extend_i32_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 4294967294) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (i64.extend_i32_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 4294967295) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.shr_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 4350) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (i64.shr_u + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i64.const 56) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 4350) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $and-redundant-low-mask (param $x i32) (param $y i64) + ;; Masks that preserve all possible non-zero low bits of the input are + ;; redundant, including for i64, non-contiguous masks, and unsigned loads + ;; with wider masks. + (drop + (i64.and + (i64.extend_i32_u (local.get $x)) + (i64.const 0xffffffff) + ) + ) + (drop + (i64.and + (i64.extend_i32_u (local.get $x)) + (i64.const 0x1ffffffff) + ) + ) + (drop + (i64.and + (i64.extend_i32_u (local.get $x)) + (i64.const 0x80000000ffffffff) + ) + ) + (drop + (i32.and + (i32.shr_u (local.get $x) (i32.const 24)) + (i32.const 0x10ff) + ) + ) + (drop + (i64.and + (i64.shr_u (local.get $y) (i64.const 56)) + (i64.const 0x10ff) + ) + ) + (drop + (i64.and + (local.tee $y (i64.const 0)) + (i64.const 0x1234) + ) + ) + (drop + (i32.and + (i32.load8_u (local.get $x)) + (i32.const 0xffff) + ) + ) + (drop + (i64.and + (i64.load8_u (local.get $x)) + (i64.const 0xffff) + ) + ) + ;; Negative cases: masks that clear potentially set low bits. + (drop + (i64.and + (i64.extend_i32_u (local.get $x)) + (i64.const 0xfffffffe) + ) + ) + (drop + (i64.and + (i64.extend_i32_s (local.get $x)) + (i64.const 0xffffffff) + ) + ) + (drop + (i32.and + (i32.shr_u (local.get $x) (i32.const 24)) + (i32.const 0x10fe) + ) + ) + (drop + (i64.and + (i64.shr_u (local.get $y) (i64.const 56)) + (i64.const 0x10fe) + ) + ) + ) ;; CHECK: (func $local-info-zero-ext (param $0 i32) (param $1 i32) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local $y i32) @@ -7183,7 +7465,7 @@ ;; CHECK-NEXT: (i64.const 63) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $and-popcount64 (result i64) ;; these are TODOs + (func $and-popcount64 (result i64) (i64.and (i64.popcnt (i64.const -1) @@ -7192,11 +7474,8 @@ ) ) ;; CHECK: (func $and-popcount64-big (result i64) - ;; CHECK-NEXT: (i64.and - ;; CHECK-NEXT: (i64.popcnt - ;; CHECK-NEXT: (i64.const -1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i64.const 127) + ;; CHECK-NEXT: (i64.popcnt + ;; CHECK-NEXT: (i64.const -1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $and-popcount64-big (result i64) @@ -7208,11 +7487,8 @@ ) ) ;; CHECK: (func $and-popcount64-bigger (result i64) - ;; CHECK-NEXT: (i64.and - ;; CHECK-NEXT: (i64.popcnt - ;; CHECK-NEXT: (i64.const -1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i64.const 255) + ;; CHECK-NEXT: (i64.popcnt + ;; CHECK-NEXT: (i64.const -1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $and-popcount64-bigger (result i64)