diff options
-rw-r--r-- | src/passes/OptimizeInstructions.cpp | 22 | ||||
-rw-r--r-- | test/lit/passes/optimize-instructions-gc-tnh.wast | 86 |
2 files changed, 102 insertions, 6 deletions
diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index ebe551d0c..f2ca4e0be 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -1853,10 +1853,6 @@ struct OptimizeInstructions return; } - // What the reference points to does not depend on the type, so casts may be - // removable. - skipCast(curr->value); - // Optimizating RefIs is not that obvious, since even if we know the result // evaluates to 0 or 1 then the replacement may not actually save code size, // since RefIsNull is a single byte (the others are 2), while adding a Const @@ -1874,6 +1870,21 @@ struct OptimizeInstructions replaceCurrent(builder.makeSequence( builder.makeDrop(curr->value), builder.makeConst(Literal::makeZero(Type::i32)))); + } else { + // What the reference points to does not depend on the type, so casts + // may be removable. Do this right before returning because removing a + // cast may remove info that we could have used to optimize. For + // example: + // + // (ref.is_func + // (ref.as_func + // (local.get $anyref))) + // + // The local has no useful type info. The cast forces it to be a + // function, so we can replace the ref.is with 1. But if we removed the + // ref.as first then we'd not have the type info we need to optimize + // that way. + skipCast(curr->value); } return; } @@ -1913,6 +1924,9 @@ struct OptimizeInstructions } } } + + // See above comment. + skipCast(curr->value); } void visitRefAs(RefAs* curr) { diff --git a/test/lit/passes/optimize-instructions-gc-tnh.wast b/test/lit/passes/optimize-instructions-gc-tnh.wast index 73ce3acf4..aad02c17c 100644 --- a/test/lit/passes/optimize-instructions-gc-tnh.wast +++ b/test/lit/passes/optimize-instructions-gc-tnh.wast @@ -136,9 +136,14 @@ ) ;; TNH: (func $ref.is (type $eqref_=>_i32) (param $a eqref) (result i32) - ;; TNH-NEXT: (ref.is_null - ;; TNH-NEXT: (local.get $a) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (ref.cast_static $struct + ;; TNH-NEXT: (ref.as_data + ;; TNH-NEXT: (local.get $a) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) ;; TNH-NEXT: ) + ;; TNH-NEXT: (i32.const 0) ;; TNH-NEXT: ) ;; NO_TNH: (func $ref.is (type $eqref_=>_i32) (param $a eqref) (result i32) ;; NO_TNH-NEXT: (drop @@ -151,6 +156,8 @@ ;; NO_TNH-NEXT: (i32.const 0) ;; NO_TNH-NEXT: ) (func $ref.is (param $a (ref null eq)) (result i32) + ;; In this case non-nullability is enough to tell that the ref.is will + ;; return 0. TNH does not help here. (ref.is_null (ref.cast_static $struct (ref.as_non_null @@ -161,4 +168,79 @@ ) ) ) + + ;; TNH: (func $ref.is_b (type $eqref_=>_i32) (param $a eqref) (result i32) + ;; TNH-NEXT: (ref.is_null + ;; TNH-NEXT: (local.get $a) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; NO_TNH: (func $ref.is_b (type $eqref_=>_i32) (param $a eqref) (result i32) + ;; NO_TNH-NEXT: (ref.is_null + ;; NO_TNH-NEXT: (ref.cast_static $struct + ;; NO_TNH-NEXT: (local.get $a) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $ref.is_b(param $a (ref null eq)) (result i32) + ;; Here we only have a cast, and no ref.as operations that force the value + ;; to be non-nullable. That means we cannot remove the ref.is, but we can + ;; remove the cast in TNH. + (ref.is_null + (ref.cast_static $struct + (local.get $a) + ) + ) + ) + + ;; TNH: (func $ref.is_func_a (type $anyref_=>_i32) (param $a anyref) (result i32) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (ref.as_func + ;; TNH-NEXT: (local.get $a) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (i32.const 1) + ;; TNH-NEXT: ) + ;; NO_TNH: (func $ref.is_func_a (type $anyref_=>_i32) (param $a anyref) (result i32) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (ref.as_func + ;; NO_TNH-NEXT: (local.get $a) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (i32.const 1) + ;; NO_TNH-NEXT: ) + (func $ref.is_func_a (param $a (ref null any)) (result i32) + ;; The check must succeed. We can return 1 here, and drop the rest, with or + ;; without TNH (in particular, TNH should not just remove the cast but not + ;; return a 1). + (ref.is_func + (ref.as_func + (local.get $a) + ) + ) + ) + + ;; TNH: (func $ref.is_func_b (type $anyref_=>_i32) (param $a anyref) (result i32) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (ref.as_data + ;; TNH-NEXT: (local.get $a) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (i32.const 0) + ;; TNH-NEXT: ) + ;; NO_TNH: (func $ref.is_func_b (type $anyref_=>_i32) (param $a anyref) (result i32) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (ref.as_data + ;; NO_TNH-NEXT: (local.get $a) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: ) + (func $ref.is_func_b (param $a (ref null any)) (result i32) + ;; A case where the type cannot match, and we return 0. + (ref.is_func + (ref.as_data + (local.get $a) + ) + ) + ) ) |