diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 68689579053..da8679ff369 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -333,6 +333,8 @@ def is_git_repo(): 'exception-handling.wast', 'translate-to-new-eh.wast', 'rse-eh.wast', + # The fuzzer does not support non-WTF16 strings. + 'gufa-cast-all-strings.wast', ] diff --git a/src/passes/GUFA.cpp b/src/passes/GUFA.cpp index 513b2cf3dc2..24b3ec3850c 100644 --- a/src/passes/GUFA.cpp +++ b/src/passes/GUFA.cpp @@ -385,7 +385,19 @@ struct GUFAOptimizer auto oracleType = parent.getContents(curr).getType(); if (oracleType.isRef() && oracleType != curr->type && Type::isSubType(oracleType, curr->type)) { - replaceCurrent(Builder(*getModule()).makeRefCast(curr, oracleType)); + if (oracleType.getHeapType() == curr->type.getHeapType()) { + // These only differ in nullability: use RefAsNonNull. (We do not + // rely on OptimizeInstructions to turn a RefCast into a + // RefAsNonNull, as there are cases where RefCast is actually not + // valid, e.g. on a string view, and we want this passes output to + // be valid.) + assert(oracleType.getNullability() == NonNullable); + assert(curr->type.getNullability() == Nullable); + replaceCurrent(Builder(*getModule()).makeRefAs(RefAsNonNull, curr)); + } else { + // These differ in the heap type: use RefCast. + replaceCurrent(Builder(*getModule()).makeRefCast(curr, oracleType)); + } optimized = true; } } diff --git a/test/lit/passes/gufa-cast-all-strings.wast b/test/lit/passes/gufa-cast-all-strings.wast new file mode 100644 index 00000000000..d80da186e5b --- /dev/null +++ b/test/lit/passes/gufa-cast-all-strings.wast @@ -0,0 +1,90 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt -all --gufa-cast-all -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (func (param stringref stringview_wtf8 stringview_wtf16 stringview_iter))) + + ;; CHECK: (export "strings-caller" (func $strings-caller)) + + ;; CHECK: (func $strings-target (type $0) (param $a stringref) (param $b stringview_wtf8) (param $c stringview_wtf16) (param $d stringview_iter) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $b) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $c) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $strings-target + (param $a (ref null string)) + (param $b (ref null stringview_wtf8)) + (param $c (ref null stringview_wtf16)) + (param $d (ref null stringview_iter)) + ;; These params are only called with non-null values, so we can cast them + ;; to non-null. We must not use ref.cast for that, which is disallowed on + ;; strings and views. + (drop + (local.get $a) + ) + (drop + (local.get $b) + ) + (drop + (local.get $c) + ) + (drop + (local.get $d) + ) + ) + + ;; CHECK: (func $strings-caller (type $0) (param $a stringref) (param $b stringview_wtf8) (param $c stringview_wtf16) (param $d stringview_iter) + ;; CHECK-NEXT: (call $strings-target + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $b) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $c) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $strings-caller (export "strings-caller") + (param $a (ref null string)) + (param $b (ref null stringview_wtf8)) + (param $c (ref null stringview_wtf16)) + (param $d (ref null stringview_iter)) + (call $strings-target + (ref.as_non_null + (local.get $a) + ) + (ref.as_non_null + (local.get $b) + ) + (ref.as_non_null + (local.get $c) + ) + (ref.as_non_null + (local.get $d) + ) + ) + ) +) diff --git a/test/lit/passes/gufa-cast-all.wast b/test/lit/passes/gufa-cast-all.wast index 21103089dbb..7bad9c8e1ee 100644 --- a/test/lit/passes/gufa-cast-all.wast +++ b/test/lit/passes/gufa-cast-all.wast @@ -21,6 +21,8 @@ ;; CHECK: (export "export1" (func $ref)) + ;; CHECK: (export "export1.5" (func $ref.null)) + ;; CHECK: (export "export2" (func $int)) ;; CHECK: (export "export3" (func $func)) @@ -51,6 +53,29 @@ ) ) + ;; CHECK: (func $ref.null (type $none_=>_none) + ;; CHECK-NEXT: (local $a (ref null $A)) + ;; CHECK-NEXT: (local.set $a + ;; CHECK-NEXT: (struct.new_default $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref.null (export "export1.5") + (local $a (ref null $A)) + (local.set $a + (struct.new $A) + ) + (drop + ;; We can infer that this contains a non-nullable A, and add a cast to + ;; non-null on it. + (local.get $a) + ) + ) + ;; CHECK: (func $int (type $none_=>_none) ;; CHECK-NEXT: (local $a i32) ;; CHECK-NEXT: (local.set $a