diff options
-rw-r--r-- | src/wasm/wasm.cpp | 16 | ||||
-rw-r--r-- | test/heap-types.wast.from-wast | 2 | ||||
-rw-r--r-- | test/heap-types.wast.fromBinary | 2 | ||||
-rw-r--r-- | test/heap-types.wast.fromBinary.noDebugInfo | 2 | ||||
-rw-r--r-- | test/lit/binary/legacy-static-casts.test | 4 | ||||
-rw-r--r-- | test/lit/cast-to-basic.wast | 8 | ||||
-rw-r--r-- | test/lit/heap-types.wast | 5 | ||||
-rw-r--r-- | test/lit/legacy-static-casts.wast | 4 | ||||
-rw-r--r-- | test/lit/passes/gufa-refs.wast | 10 | ||||
-rw-r--r-- | test/lit/passes/gufa-vs-cfp.wast | 12 | ||||
-rw-r--r-- | test/lit/passes/optimize-casts.wast | 2 | ||||
-rw-r--r-- | test/lit/passes/optimize-instructions-gc-iit.wast | 4 | ||||
-rw-r--r-- | test/lit/passes/optimize-instructions-gc-tnh.wast | 4 | ||||
-rw-r--r-- | test/lit/passes/remove-unused-brs-gc.wast | 2 | ||||
-rw-r--r-- | test/lit/passes/type-refining.wast | 42 |
15 files changed, 89 insertions, 30 deletions
diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index c3f262998..552ec7e57 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -947,10 +947,24 @@ void RefCast::finalize() { type = Type::unreachable; return; } - // Do not unnecessarily lose non-nullability information. + + // Do not unnecessarily lose non-nullability info. We could leave this for + // optimizations, but doing it here as part of finalization/refinalization + // ensures that type information flows through in an optimal manner and can be + // used as soon as possible. if (ref->type.isNonNullable() && type.isNullable()) { type = Type(type.getHeapType(), NonNullable); } + + // Do not unnecessarily lose heap type info, as above for nullability. Note + // that we must check if the ref has a heap type, as we reach this before + // validation, which will error if the ref does not in fact have a heap type. + // (This is a downside of propagating type information here, as opposed to + // leaving it for an optimization pass.) + if (ref->type.isRef() && + HeapType::isSubType(ref->type.getHeapType(), type.getHeapType())) { + type = Type(ref->type.getHeapType(), type.getNullability()); + } } void BrOn::finalize() { diff --git a/test/heap-types.wast.from-wast b/test/heap-types.wast.from-wast index 7d7fd62de..975d89b49 100644 --- a/test/heap-types.wast.from-wast +++ b/test/heap-types.wast.from-wast @@ -371,7 +371,7 @@ ) ) (drop - (ref.cast null $struct.B + (ref.cast null none (ref.null none) ) ) diff --git a/test/heap-types.wast.fromBinary b/test/heap-types.wast.fromBinary index e69cf99b1..99637f92b 100644 --- a/test/heap-types.wast.fromBinary +++ b/test/heap-types.wast.fromBinary @@ -324,7 +324,7 @@ ) ) (drop - (ref.cast null $struct.B + (ref.cast null none (ref.null none) ) ) diff --git a/test/heap-types.wast.fromBinary.noDebugInfo b/test/heap-types.wast.fromBinary.noDebugInfo index 6596733ff..3107269be 100644 --- a/test/heap-types.wast.fromBinary.noDebugInfo +++ b/test/heap-types.wast.fromBinary.noDebugInfo @@ -324,7 +324,7 @@ ) ) (drop - (ref.cast null ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|} + (ref.cast null none (ref.null none) ) ) diff --git a/test/lit/binary/legacy-static-casts.test b/test/lit/binary/legacy-static-casts.test index ad2209594..49ed204a7 100644 --- a/test/lit/binary/legacy-static-casts.test +++ b/test/lit/binary/legacy-static-casts.test @@ -15,12 +15,12 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop -;; CHECK-NEXT: (ref.cast null ${} +;; CHECK-NEXT: (ref.cast null none ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop -;; CHECK-NEXT: (ref.cast_nop ${} +;; CHECK-NEXT: (ref.cast_nop none ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/cast-to-basic.wast b/test/lit/cast-to-basic.wast index 23adb3968..aa58e1f8e 100644 --- a/test/lit/cast-to-basic.wast +++ b/test/lit/cast-to-basic.wast @@ -16,17 +16,17 @@ ) ) - ;; CHECK: (func $cast (type $none_=>_none) + ;; CHECK: (func $cast (type $structref_=>_none) (param $x structref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast null struct - ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $cast + (func $cast (param $x (ref null struct)) (drop (ref.cast null struct - (ref.null none) + (local.get $x) ) ) ) diff --git a/test/lit/heap-types.wast b/test/lit/heap-types.wast index 0c3df85e4..63e12c67e 100644 --- a/test/lit/heap-types.wast +++ b/test/lit/heap-types.wast @@ -26,17 +26,18 @@ ) (module - ;; CHECK: (type $struct.A (struct (field i32))) (type $struct.A (struct i32)) (type $struct.B (struct i32)) ;; CHECK: (func $test (type $none_=>_none) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast null $struct.A + ;; CHECK-NEXT: (ref.cast null none ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test + ;; Note that this will not round-trip precisely because Binaryen IR will + ;; apply the more refined type to the cast automatically (in finalize). (drop (ref.cast null $struct.B (ref.null $struct.A)) ) diff --git a/test/lit/legacy-static-casts.wast b/test/lit/legacy-static-casts.wast index 0cbd30a2d..dfef240d3 100644 --- a/test/lit/legacy-static-casts.wast +++ b/test/lit/legacy-static-casts.wast @@ -17,12 +17,12 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast null $struct + ;; CHECK-NEXT: (ref.cast null none ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast_nop $struct + ;; CHECK-NEXT: (ref.cast_nop none ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/gufa-refs.wast b/test/lit/passes/gufa-refs.wast index fe210e149..89b249f2a 100644 --- a/test/lit/passes/gufa-refs.wast +++ b/test/lit/passes/gufa-refs.wast @@ -1101,7 +1101,7 @@ ;; CHECK-NEXT: (br $block1 ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast null $child + ;; CHECK-NEXT: (ref.cast null none ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2452,11 +2452,11 @@ (type $substruct (struct_subtype (field i32) (field i32) $struct)) ;; CHECK: (type $none_=>_none (func)) - ;; CHECK: (type $i32_=>_none (func (param i32))) - ;; CHECK: (type $subsubstruct (struct_subtype (field i32) (field i32) (field i32) $substruct)) (type $subsubstruct (struct_subtype (field i32) (field i32) (field i32) $substruct)) + ;; CHECK: (type $i32_=>_none (func (param i32))) + ;; CHECK: (type $other (struct )) (type $other (struct_subtype data)) @@ -2494,7 +2494,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast $substruct + ;; CHECK-NEXT: (ref.cast $subsubstruct ;; CHECK-NEXT: (struct.new $subsubstruct ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: (i32.const 4) @@ -2539,7 +2539,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast null $struct + ;; CHECK-NEXT: (ref.cast null none ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $import) diff --git a/test/lit/passes/gufa-vs-cfp.wast b/test/lit/passes/gufa-vs-cfp.wast index 73253aaa7..4a2a6c863 100644 --- a/test/lit/passes/gufa-vs-cfp.wast +++ b/test/lit/passes/gufa-vs-cfp.wast @@ -2039,17 +2039,19 @@ ) ) ;; CHECK: (func $set (type $none_=>_none) - ;; CHECK-NEXT: (struct.set $A 0 - ;; CHECK-NEXT: (ref.cast $A + ;; CHECK-NEXT: (struct.set $C 0 + ;; CHECK-NEXT: (ref.cast $C ;; CHECK-NEXT: (call $create-C) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $set - ;; Set of $A, but the reference is actually a $C. We add a cast to make sure - ;; the type is $A, which should not confuse us: this set does alias the data - ;; in $C, which means we cannot optimize in the function $get below. + ;; Set of $A, but the reference is actually a $C. We add a cast to try to + ;; make sure the type is $A, which should not confuse us: this set does + ;; alias the data in $C, which means we cannot optimize in the function $get + ;; below. (Note that finalize will turn the cast into a cast of $C + ;; automatically; that is not part of GUFA.) (struct.set $A 0 (ref.cast $A (call $create-C) diff --git a/test/lit/passes/optimize-casts.wast b/test/lit/passes/optimize-casts.wast index 527c2d157..27e38f7f5 100644 --- a/test/lit/passes/optimize-casts.wast +++ b/test/lit/passes/optimize-casts.wast @@ -218,7 +218,7 @@ ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast $A + ;; CHECK-NEXT: (ref.cast $B ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/optimize-instructions-gc-iit.wast b/test/lit/passes/optimize-instructions-gc-iit.wast index 0e315dd5d..23161f1be 100644 --- a/test/lit/passes/optimize-instructions-gc-iit.wast +++ b/test/lit/passes/optimize-instructions-gc-iit.wast @@ -39,7 +39,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block (result (ref $other)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $child) ;; CHECK-NEXT: ) @@ -60,7 +60,7 @@ ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: (drop - ;; TNH-NEXT: (block + ;; TNH-NEXT: (block (result (ref $other)) ;; TNH-NEXT: (drop ;; TNH-NEXT: (local.get $child) ;; TNH-NEXT: ) diff --git a/test/lit/passes/optimize-instructions-gc-tnh.wast b/test/lit/passes/optimize-instructions-gc-tnh.wast index 34071f2af..dd5407f31 100644 --- a/test/lit/passes/optimize-instructions-gc-tnh.wast +++ b/test/lit/passes/optimize-instructions-gc-tnh.wast @@ -904,7 +904,7 @@ ;; TNH: (func $if.null.child.but.no.flow (type $void) ;; TNH-NEXT: (drop - ;; TNH-NEXT: (block (result (ref func)) + ;; TNH-NEXT: (block (result (ref nofunc)) ;; TNH-NEXT: (drop ;; TNH-NEXT: (if (result (ref nofunc)) ;; TNH-NEXT: (i32.const 1) @@ -918,7 +918,7 @@ ;; TNH-NEXT: ) ;; NO_TNH: (func $if.null.child.but.no.flow (type $void) ;; NO_TNH-NEXT: (drop - ;; NO_TNH-NEXT: (block (result (ref func)) + ;; NO_TNH-NEXT: (block (result (ref nofunc)) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (if (result (ref nofunc)) ;; NO_TNH-NEXT: (i32.const 1) diff --git a/test/lit/passes/remove-unused-brs-gc.wast b/test/lit/passes/remove-unused-brs-gc.wast index 02dc55cbd..4fe7bd938 100644 --- a/test/lit/passes/remove-unused-brs-gc.wast +++ b/test/lit/passes/remove-unused-brs-gc.wast @@ -338,7 +338,7 @@ ;; CHECK-NEXT: (if (result anyref) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: (ref.cast null $struct + ;; CHECK-NEXT: (ref.cast null none ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/type-refining.wast b/test/lit/passes/type-refining.wast index ca9b98590..8258a3a54 100644 --- a/test/lit/passes/type-refining.wast +++ b/test/lit/passes/type-refining.wast @@ -1125,3 +1125,45 @@ ) ) ) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (ref $B)))) + (type $A (struct (field (ref struct)))) + ;; CHECK: (type $B (struct )) + (type $B (struct)) + ) + + ;; CHECK: (type $none_=>_ref|$A| (func (result (ref $A)))) + + ;; CHECK: (func $0 (type $none_=>_ref|$A|) (result (ref $A)) + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (ref.cast $B + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (struct.new_default $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $0 (result (ref $A)) + ;; The cast in the middle here will be skipped in the analysis, as we have + ;; a copy: a read from $A.0 to a write, with only a cast in the middle + ;; (which does not alter the value). While that is valid to do, we need to + ;; properly propagate the new output type of the struct.get (which goes from + ;; (ref struct) to (ref $B) to the cast, so that the cast has that type as + ;; well, as otherwise the struct.new will not validate - we can't write a + ;; (ref struct) to a field of (ref $B). + (struct.new $A + (ref.cast struct + (struct.get $A 0 + (struct.new $A + (struct.new_default $B) + ) + ) + ) + ) + ) +) |