// Tests that exercise GVN and LICM. The general pattern for these tests is to // create values near the top of the function and then create situations where // equivalent values are created below, including in loops. // ref.null { let numAnyref = 0; let numExternref = 0; const { test1, test2 } = wasmEvalText(`(module (import "" "dummy1" (func $dummy1 (param anyref))) (import "" "dummy2" (func $dummy2 (param externref))) (import "" "countAnyref" (func $countAnyref)) (import "" "countExternref" (func $countExternref)) (func (export "test1") ;; Ensure that we keep two MWasmNullConstants with two different ;; hierarchies at the top of the graph. (call $dummy1 (ref.null none)) (call $dummy2 (ref.null noextern)) (ref.test anyref (ref.null none)) if call $countAnyref end ;; If you're not careful, this null might be GVN'd with the null from the ;; other hierarchy, causing bad behavior. (ref.test externref (ref.null noextern)) if call $countExternref end ) (func (export "test2") (local i32 anyref externref) (local.tee 1 (ref.null none)) call $dummy1 (local.tee 2 (ref.null noextern)) call $dummy2 loop (ref.test anyref (local.get 1)) if call $countAnyref end (ref.test externref (local.get 2)) if call $countExternref end (i32.ge_s (local.get 0) (i32.const 5)) if ;; Note that these use the top type instead of the bottom type. (local.set 1 (ref.null any)) (local.set 2 (ref.null extern)) end (local.tee 0 (i32.add (local.get 0) (i32.const 1))) (i32.lt_s (i32.const 10)) br_if 0 end ) )`, { "": { dummy1() {}, dummy2() {}, countAnyref() { numAnyref += 1; }, countExternref() { numExternref += 1; }, }, }).exports; for (let i = 0; i < 100; i++) { test1(); } assertEq(numAnyref, 100); assertEq(numExternref, 100); for (let i = 0; i < 100; i++) { test2(); } assertEq(numAnyref, 1100); assertEq(numExternref, 1100); } // structs and struct fields { const counts = { 100: 0, 200: 0, 300: 0, 400: 0 }; const { test1, test2 } = wasmEvalText(`(module (type $a (struct (field i32))) (type $s (struct (field (ref $a)) (field (ref $a)))) (import "" "dummy" (func $dummy (param anyref))) (import "" "count" (func $count (param i32))) (func (export "test1") (local anyref) (struct.new $a (i32.const 100)) (struct.new $a (i32.const 200)) struct.new $s local.tee 0 call $dummy local.get 0 ref.cast (ref $s) struct.get $s 0 ref.cast (ref $a) struct.get $a 0 call $count local.get 0 ref.cast (ref $s) struct.get $s 1 ref.cast (ref $a) struct.get $a 0 call $count ) (func (export "test2") (local anyref i32) (struct.new $a (i32.const 100)) (struct.new $a (i32.const 200)) struct.new $s local.tee 0 call $dummy loop local.get 0 ref.cast (ref $s) struct.get $s 0 ref.cast (ref $a) struct.get $a 0 call $count local.get 0 ref.cast (ref $s) struct.get $s 1 ref.cast (ref $a) struct.get $a 0 call $count (i32.ge_s (local.get 1) (i32.const 4)) if (struct.new $a (i32.const 300)) (struct.new $a (i32.const 400)) struct.new $s local.set 0 end (local.tee 1 (i32.add (local.get 1) (i32.const 1))) (i32.lt_s (i32.const 10)) br_if 0 end ) )`, { "": { dummy() {}, count(n) { counts[n] += 1; }, }, }).exports; for (let i = 0; i < 100; i++) { test1(); } assertEq(counts[100], 100); assertEq(counts[200], 100); assertEq(counts[300], 0); assertEq(counts[400], 0); for (let i = 0; i < 100; i++) { test2(); } assertEq(counts[100], 600); assertEq(counts[200], 600); assertEq(counts[300], 500); assertEq(counts[400], 500); } // arrays and array elems { const counts = { 100: 0, 200: 0, 300: 0, 400: 0 }; const { test1, test2 } = wasmEvalText(`(module (type $e (array i32)) (type $a (array (ref $e))) (import "" "dummy" (func $dummy (param anyref))) (import "" "count" (func $count (param i32))) (func (export "test1") (local anyref) (array.new_fixed $e 1 (i32.const 100)) (array.new_fixed $e 1 (i32.const 200)) array.new_fixed $a 2 local.tee 0 call $dummy local.get 0 ref.cast (ref $a) (array.get $a (i32.const 0)) ref.cast (ref $e) (array.get $e (i32.const 0)) call $count local.get 0 ref.cast (ref $a) (array.get $a (i32.const 1)) ref.cast (ref $e) (array.get $e (i32.const 0)) call $count ) (func (export "test2") (local anyref i32) (array.new_fixed $e 1 (i32.const 100)) (array.new_fixed $e 1 (i32.const 200)) array.new_fixed $a 2 local.tee 0 call $dummy loop local.get 0 ref.cast (ref $a) (array.get $a (i32.const 0)) ref.cast (ref $e) (array.get $e (i32.const 0)) call $count local.get 0 ref.cast (ref $a) (array.get $a (i32.const 1)) ref.cast (ref $e) (array.get $e (i32.const 0)) call $count (i32.ge_s (local.get 1) (i32.const 4)) if (array.new_fixed $e 1 (i32.const 300)) (array.new_fixed $e 1 (i32.const 400)) array.new_fixed $a 2 local.set 0 end (local.tee 1 (i32.add (local.get 1) (i32.const 1))) (i32.lt_s (i32.const 10)) br_if 0 end ) )`, { "": { dummy() {}, count(n) { counts[n] += 1; }, }, }).exports; for (let i = 0; i < 100; i++) { test1(); } assertEq(counts[100], 100); assertEq(counts[200], 100); assertEq(counts[300], 0); assertEq(counts[400], 0); for (let i = 0; i < 100; i++) { test2(); } assertEq(counts[100], 600); assertEq(counts[200], 600); assertEq(counts[300], 500); assertEq(counts[400], 500); } // table elems { const counts = { 100: 0, 200: 0, 300: 0, 400: 0 }; const { test1, test2 } = wasmEvalText(`(module (type $f (func)) (import "" "dummy" (func $dummy (param anyref))) (import "" "count" (func $count (param i32))) (table funcref (elem (ref.func $count100) (ref.func $count200))) (table (ref null $f) (elem (ref.func $count300) (ref.func $count400))) (func $count100 (type $f) (call $count (i32.const 100)) ) (func $count200 (type $f) (call $count (i32.const 200)) ) (func $count300 (type $f) (call $count (i32.const 300)) ) (func $count400 (type $f) (call $count (i32.const 400)) ) (func (export "test1") (table.get 0 (i32.const 0)) ref.cast (ref null $f) call_ref $f (table.get 0 (i32.const 1)) ref.cast (ref null $f) call_ref $f (table.get 1 (i32.const 0)) ref.cast (ref null $f) call_ref $f (table.get 1 (i32.const 1)) ref.cast (ref null $f) call_ref $f ) (func (export "test2") (local i32) (table.get 0 (i32.const 0)) ref.cast (ref null $f) call_ref $f (table.get 1 (i32.const 0)) ref.cast (ref null $f) call_ref $f loop (table.get 0 (i32.const 0)) ref.cast (ref null $f) call_ref $f (table.get 0 (i32.const 1)) ref.cast (ref null $f) call_ref $f (table.get 1 (i32.const 0)) ref.cast (ref null $f) call_ref $f (table.get 1 (i32.const 1)) ref.cast (ref null $f) call_ref $f (local.tee 0 (i32.add (local.get 0) (i32.const 1))) (i32.lt_s (i32.const 10)) br_if 0 end ) )`, { "": { dummy() {}, count(n) { counts[n] += 1; }, }, }).exports; for (let i = 0; i < 100; i++) { test1(); } assertEq(counts[100], 100); assertEq(counts[200], 100); assertEq(counts[300], 100); assertEq(counts[400], 100); for (let i = 0; i < 100; i++) { test2(); } assertEq(counts[100], 1200); assertEq(counts[200], 1100); assertEq(counts[300], 1200); assertEq(counts[400], 1100); } // ref.i31 { const countsI31 = { 100: 0, 200: 0, 300: 0 }; const countsI32 = { 100: 0, 200: 0, 300: 0 }; const countsU32 = { 100: 0, 200: 0, 300: 0 }; const { test1, test2 } = wasmEvalText(`(module (import "" "dummy" (func $dummy (param i31ref))) (import "" "countI31" (func $countI31 (param i31ref))) (import "" "countI32" (func $countI32 (param i32))) (import "" "countU32" (func $countU32 (param i32))) (func (export "test1") (call $dummy (ref.i31 (i32.const 100))) (call $dummy (ref.i31 (i32.const 200))) (call $countI31 (ref.i31 (i32.const 100))) (call $countI31 (ref.i31 (i32.const 200))) (call $countI32 (i31.get_s (ref.i31 (i32.const 100)))) (call $countI32 (i31.get_s (ref.i31 (i32.const 200)))) (call $countU32 (i31.get_u (ref.i31 (i32.const 100)))) (call $countU32 (i31.get_u (ref.i31 (i32.const 200)))) ) (func (export "test2") (local i32 i31ref) (call $dummy (ref.i31 (i32.const 100))) (local.tee 1 (ref.i31 (i32.const 200))) call $dummy loop (call $countI31 (ref.i31 (i32.const 100))) (call $countI31 (local.get 1)) (call $countI32 (i31.get_s (ref.i31 (i32.const 100)))) (call $countI32 (i31.get_s (local.get 1))) (call $countU32 (i31.get_u (ref.i31 (i32.const 100)))) (call $countU32 (i31.get_u (local.get 1))) (local.set 1 (ref.i31 (i32.const 300))) (local.tee 0 (i32.add (local.get 0) (i32.const 1))) (i32.lt_s (i32.const 10)) br_if 0 end ) )`, { "": { dummy() {}, countI31(n) { countsI31[n] += 1; }, countI32(n) { countsI32[n] += 1; }, countU32(n) { countsU32[n] += 1; }, }, }).exports; for (let i = 0; i < 100; i++) { test1(); } assertEq(countsI31[100], 100); assertEq(countsI31[200], 100); assertEq(countsI31[300], 0); assertEq(countsI32[100], 100); assertEq(countsI32[200], 100); assertEq(countsI32[300], 0); assertEq(countsU32[100], 100); assertEq(countsU32[200], 100); assertEq(countsU32[300], 0); for (let i = 0; i < 100; i++) { test2(); } assertEq(countsI31[100], 1100); assertEq(countsI31[200], 200); assertEq(countsI31[300], 900); assertEq(countsI32[100], 1100); assertEq(countsI32[200], 200); assertEq(countsI32[300], 900); assertEq(countsU32[100], 1100); assertEq(countsU32[200], 200); assertEq(countsU32[300], 900); } // parameters and calls { let numNull = 0; let numNonNull = 0; // Note that we are ok with inlining kicking in eventually (and have // constructed these tests to trigger that). const { test1, test2 } = wasmEvalText(`(module (type $f_anyref (func (param anyref))) (type $f_nullref (func (param nullref))) (import "" "thing" (global $thing externref)) (import "" "countNull" (func $countNull)) (import "" "countNonNull" (func $countNonNull)) (table funcref (elem (ref.func $testNull_ref.is_null) (ref.func $mustNull_ref.is_null) (ref.func $testNull_ref.test) (ref.func $mustNull_ref.test) )) (func $makeNull_Loose (result anyref) ref.null any ) (func $makeNonNull_Loose (result anyref) (any.convert_extern (global.get $thing)) ) (func $makeNull_Precise (result nullref) ref.null none ) (func $makeNonNull_Precise (result (ref any)) (ref.as_non_null (any.convert_extern (global.get $thing))) ) (func $testNull_ref.is_null (param anyref) (ref.is_null (local.get 0)) if call $countNull else call $countNonNull end ) (func $mustNull_ref.is_null (param nullref) (ref.is_null (local.get 0)) ;; trivial! if call $countNull end ) (func $testNull_ref.test (param anyref) (ref.test nullref (local.get 0)) ;; trivial! if call $countNull else call $countNonNull end ) (func $mustNull_ref.test (param nullref) (ref.test nullref (local.get 0)) ;; trivial! if call $countNull end ) (func (export "test1") (local anyref) (local.set 0 (call $makeNull_Loose)) (call $testNull_ref.is_null (local.get 0)) (call $testNull_ref.test (local.get 0)) (call_indirect (type $f_anyref) (local.get 0) (i32.const 0)) (call_indirect (type $f_anyref) (local.get 0) (i32.const 2)) (ref.is_null (local.get 0)) ;; guaranteed to succeed if (call $mustNull_ref.is_null (ref.cast nullref (local.get 0))) (call $mustNull_ref.test (ref.cast nullref (local.get 0))) (call_indirect (type $f_nullref) (ref.cast nullref (local.get 0)) (i32.const 1)) (call_indirect (type $f_nullref) (ref.cast nullref (local.get 0)) (i32.const 3)) end (local.set 0 (call $makeNull_Precise)) (call $testNull_ref.is_null (local.get 0)) (call $testNull_ref.test (local.get 0)) (call_indirect (type $f_anyref) (local.get 0) (i32.const 0)) (call_indirect (type $f_anyref) (local.get 0) (i32.const 2)) (ref.is_null (local.get 0)) ;; guaranteed to succeed if (call $mustNull_ref.is_null (ref.cast nullref (local.get 0))) (call $mustNull_ref.test (ref.cast nullref (local.get 0))) (call_indirect (type $f_nullref) (ref.cast nullref (local.get 0)) (i32.const 1)) (call_indirect (type $f_nullref) (ref.cast nullref (local.get 0)) (i32.const 3)) end (local.set 0 (call $makeNonNull_Loose)) (call $testNull_ref.is_null (local.get 0)) (call $testNull_ref.test (local.get 0)) (call_indirect (type $f_anyref) (local.get 0) (i32.const 0)) (call_indirect (type $f_anyref) (local.get 0) (i32.const 2)) (ref.is_null (local.get 0)) ;; guaranteed to fail if (call $mustNull_ref.is_null (ref.cast nullref (local.get 0))) (call $mustNull_ref.test (ref.cast nullref (local.get 0))) (call_indirect (type $f_nullref) (ref.cast nullref (local.get 0)) (i32.const 1)) (call_indirect (type $f_nullref) (ref.cast nullref (local.get 0)) (i32.const 3)) end (local.set 0 (call $makeNonNull_Precise)) (call $testNull_ref.is_null (local.get 0)) (call $testNull_ref.test (local.get 0)) (call_indirect (type $f_anyref) (local.get 0) (i32.const 0)) (call_indirect (type $f_anyref) (local.get 0) (i32.const 2)) (ref.is_null (local.get 0)) ;; guaranteed to fail if (call $mustNull_ref.is_null (ref.cast nullref (local.get 0))) (call $mustNull_ref.test (ref.cast nullref (local.get 0))) (call_indirect (type $f_nullref) (ref.cast nullref (local.get 0)) (i32.const 1)) (call_indirect (type $f_nullref) (ref.cast nullref (local.get 0)) (i32.const 3)) end ) (func (export "test2") (local anyref i32) block loop (i32.eq (local.get 1) (i32.const 0)) if (local.set 0 (call $makeNull_Loose)) else (i32.eq (local.get 1) (i32.const 1)) if (local.set 0 (call $makeNull_Precise)) else (i32.eq (local.get 1) (i32.const 2)) if (local.set 0 (call $makeNonNull_Loose)) else (i32.eq (local.get 1) (i32.const 3)) if (local.set 0 (call $makeNonNull_Precise)) else return end end end end (call $testNull_ref.is_null (local.get 0)) (call $testNull_ref.test (local.get 0)) (call_indirect (type $f_anyref) (local.get 0) (i32.const 0)) (call_indirect (type $f_anyref) (local.get 0) (i32.const 2)) (ref.is_null (local.get 0)) if (call $mustNull_ref.is_null (ref.cast nullref (local.get 0))) (call $mustNull_ref.test (ref.cast nullref (local.get 0))) (call_indirect (type $f_nullref) (ref.cast nullref (local.get 0)) (i32.const 1)) (call_indirect (type $f_nullref) (ref.cast nullref (local.get 0)) (i32.const 3)) end (local.set 1 (i32.add (local.get 1) (i32.const 1))) br 0 end end ) )`, { "": { "thing": "hello I am extern guy", countNull() { numNull += 1; }, countNonNull() { numNonNull += 1; }, }, }).exports; for (let i = 0; i < 100; i++) { test1(); } assertEq(numNull, 1600); assertEq(numNonNull, 800); for (let i = 0; i < 100; i++) { test2(); } assertEq(numNull, 3200); assertEq(numNonNull, 1600); } // globals { const counts = { 100: 0, 200: 0 }; const { test1, test2 } = wasmEvalText(`(module (type $f (func)) (import "" "dummy" (func $dummy (param anyref))) (import "" "count" (func $count (param i32))) (global $a (mut (ref $f)) (ref.func $count100)) (global $b (mut (ref $f)) (ref.func $count200)) (func $count100 (type $f) (call $count (i32.const 100)) ) (func $count200 (type $f) (call $count (i32.const 200)) ) (func (export "test1") global.get $a call_ref $f global.get $b call_ref $f ) (func (export "test2") (local funcref i32) (local.set 0 (global.get $a)) loop local.get 0 ref.cast (ref $f) call_ref $f (i32.ge_s (local.get 1) (i32.const 4)) if (local.set 0 (global.get $b)) end (local.tee 1 (i32.add (local.get 1) (i32.const 1))) (i32.lt_s (i32.const 10)) br_if 0 end ) )`, { "": { dummy() {}, count(n) { counts[n] += 1; }, }, }).exports; for (let i = 0; i < 100; i++) { test1(); } assertEq(counts[100], 100); assertEq(counts[200], 100); for (let i = 0; i < 100; i++) { test2(); } assertEq(counts[100], 600); assertEq(counts[200], 600); } // any.convert_extern and extern.convert_any { let numAnyref = 0; let numExternref = 0; const { test1, test2 } = wasmEvalText(`(module (import "" "thing" (global $extern1 externref)) (import "" "thing" (global $extern2 externref)) (import "" "dummy1" (func $dummy1 (param anyref))) (import "" "dummy2" (func $dummy2 (param externref))) (import "" "countAnyref" (func $countAnyref)) (import "" "countExternref" (func $countExternref)) (global $any1 anyref (any.convert_extern (global.get $extern1))) (global $any2 anyref (any.convert_extern (global.get $extern2))) (func (export "test1") (call $dummy1 (any.convert_extern (global.get $extern1))) (call $dummy2 (extern.convert_any (global.get $any1))) ;; These tests are trivial. (ref.test anyref (any.convert_extern (global.get $extern1))) if call $countAnyref end (ref.test externref (extern.convert_any (global.get $any1))) if call $countExternref end ) (func (export "test2") (local i32 anyref externref) (local.tee 1 (any.convert_extern (global.get $extern1))) call $dummy1 (local.tee 2 (extern.convert_any (global.get $any1))) call $dummy2 loop ;; These tests are again trivial. (ref.test anyref (local.get 1)) if call $countAnyref end (ref.test externref (local.get 2)) if call $countExternref end (i32.ge_s (local.get 0) (i32.const 5)) if ;; These are different values but have the same type. It will not ;; affect the types that flow into the tests. (local.set 1 (any.convert_extern (global.get $extern2))) (local.set 2 (extern.convert_any (global.get $any2))) end (local.tee 0 (i32.add (local.get 0) (i32.const 1))) (i32.lt_s (i32.const 10)) br_if 0 end ) )`, { "": { "thing": "hello I am extern guy", dummy1() {}, dummy2() {}, countAnyref() { numAnyref += 1; }, countExternref() { numExternref += 1; }, }, }).exports; for (let i = 0; i < 100; i++) { test1(); } assertEq(numAnyref, 100); assertEq(numExternref, 100); for (let i = 0; i < 100; i++) { test2(); } assertEq(numAnyref, 1100); assertEq(numExternref, 1100); } // Test uses of ref.null, which always produces nodes of type (ref null none) { new WebAssembly.Module(wasmTextToBinary(`(module (type $s1 (struct (field anyref))) (type $s2 (struct (field i31ref))) (type $s3 (struct (field structref))) (func ref.null $s1 struct.get $s1 0 drop ref.null $s2 struct.get $s2 0 drop ) (func ref.null $s2 struct.get $s2 0 drop ref.null $s3 struct.get $s3 0 drop ) (func ref.null $s1 struct.get $s1 0 drop ref.null $s2 struct.get $s2 0 drop ref.null $s3 struct.get $s3 0 drop ) )`)); } { const { test0, test1 } = wasmEvalText(`(module (type $s (struct (field i32))) (func (export "test0") (param i32) (result anyref anyref) local.get 0 if (result anyref anyref) ref.null any (select (result anyref) (struct.new $s (i32.const 1337)) (ref.null any) (i32.const 0)) else ref.null any ref.null any end ) (func (export "test1") (param i32) (result anyref anyref) local.get 0 if (result anyref anyref) ref.null any (select (result anyref) (struct.new $s (i32.const 1337)) (ref.null any) (i32.const 1)) else ref.null any ref.null any end ) )`).exports; const [a00, b00] = test0(0); assertEq(a00, null); assertEq(b00, null); const [a01, b01] = test0(1); assertEq(a01, null); assertEq(b01, null); const [a10, b10] = test1(0); assertEq(a10, null); assertEq(b10, null); const [a11, b11] = test1(1); assertEq(a11, null); assertEq(wasmGcReadField(b11, 0), 1337); } { wasmEvalText(`(module (func ref.null any ref.as_non_null drop ref.null eq ref.as_non_null drop ) )`); } if (getBuildConfiguration("jitspew")) { // Test type calculations from phis (uses LUB to compute common type for all values) { const code = wasmTextToBinary(`(module (type $s1 (struct (field i32))) (type $s2 (struct (field i64))) (func (param i32) (result structref) (local $v anyref) local.get 0 if (struct.new $s1 (i32.const 1)) local.set $v else (struct.new $s2 (i64.const 1)) local.set $v end ;; Because two different values are pulled together into one phi, the LUB ;; will be used to compute a value for the phi, which should be ;; (ref struct). ;; This test will fold to a constant 0 and the branch will be eliminated. local.get $v ref.test arrayref if unreachable end ;; This cast is trivial and will be eliminated. local.get $v ref.cast (ref struct) ;; However, this cast is not trivial and must be preserved. ref.cast (ref $s1) ) )`); const ionJSON = wasmGetIon(code, 0); const unoptimized = wasmIonGetFirstMIRPass(ionJSON); const optimized = wasmIonGetLastMIRPass(ionJSON); assertOpcodesInOrder(unoptimized, [ "WasmNewStructObject", "WasmNewStructObject", "Phi", "WasmRefTestAbstract", "WasmTrap", "WasmRefCastAbstract", "WasmRefCastConcrete", ]); assertOpcodesInOrder(optimized, [ "WasmNewStructObject", "WasmNewStructObject", "Phi", "!WasmRefTestAbstract", "!WasmTrap", "!WasmRefCastAbstract", "WasmRefCastConcrete", ]); } // Test cases of tests/casts dominating tests/casts for the same value. { // test -> cast const code = wasmTextToBinary(`(module (type $s (struct (field i32))) (func (export "test") (param anyref) (result i32) local.get 0 ref.test (ref null $s) if local.get 0 ref.cast (ref null $s) struct.get $s 0 return end i32.const -1 ) (func (export "make") (result anyref) (struct.new $s (i32.const 123)) ) )`); const ionJSON = wasmGetIon(code, 0); const unoptimized = wasmIonGetFirstMIRPass(ionJSON); const optimized = wasmIonGetLastMIRPass(ionJSON); assertOpcodesInOrder(unoptimized, ["WasmRefTestConcrete", "WasmRefCastConcrete"]); assertOpcodesInOrder(optimized, ["WasmRefTestConcrete", "WasmRefCastInfallible"]); const { test, make } = wasmEvalBinary(code).exports; assertEq(test(make()), 123); } { // test -> test const code = wasmTextToBinary(`(module (type $s (struct (field i32))) (func (export "test") (param anyref) (result i32) local.get 0 ref.test (ref $s) if local.get 0 ref.test (ref $s) ;; known to succeed if local.get 0 ref.cast (ref null $s) ;; note: wider type than the test struct.get $s 0 return end else local.get 0 ref.test (ref $s) ;; known to fail if unreachable end end i32.const -1 ) (func (export "make") (result anyref) (struct.new $s (i32.const 123)) ) )`); const ionJSON = wasmGetIon(code, 0); const unoptimized = wasmIonGetFirstMIRPass(ionJSON); const optimized = wasmIonGetLastMIRPass(ionJSON); assertOpcodesInOrder(unoptimized, [ "WasmRefTestConcrete", "WasmRefTestConcrete", "WasmRefCastConcrete", "WasmRefTestConcrete", "WasmTrap", "WasmReturn", ]); assertOpcodesInOrder(optimized, [ "WasmRefTestConcrete", "!WasmRefTestConcrete", "!WasmRefCastConcrete", "WasmRefCastInfallible", "!WasmRefTestConcrete", "!WasmTrap", "WasmReturn", ]); const { test, make } = wasmEvalBinary(code).exports; assertEq(test(make()), 123); } { // cast -> cast const code = wasmTextToBinary(`(module (type $s (struct (field i32))) (func (export "test") (param anyref) (result i32) local.get 0 ref.cast (ref $s) struct.get $s 0 if local.get 0 ref.cast (ref null $s) ;; note: wider type than the previous cast struct.get $s 0 return end i32.const -1 ) (func (export "make") (result anyref) (struct.new $s (i32.const 123)) ) )`); const ionJSON = wasmGetIon(code, 0); const unoptimized = wasmIonGetFirstMIRPass(ionJSON); const optimized = wasmIonGetLastMIRPass(ionJSON); assertOpcodesInOrder(unoptimized, ["WasmRefCastConcrete", "WasmRefCastConcrete"]); assertOpcodesInOrder(optimized, ["WasmRefCastConcrete", "!WasmRefCastConcrete", "!WasmRefCastInfallible"]); const { test, make } = wasmEvalBinary(code).exports; assertEq(test(make()), 123); } { // cast -> test const code = wasmTextToBinary(`(module (type $s (struct (field i32))) (func (export "test") (param anyref) (result i32) local.get 0 ref.cast (ref $s) struct.get $s 0 if local.get 0 ref.test (ref null $s) ;; note: wider type than the cast if local.get 0 ref.cast (ref $s) struct.get $s 0 return end end i32.const -1 ) (func (export "make") (result anyref) (struct.new $s (i32.const 123)) ) )`); const ionJSON = wasmGetIon(code, 0); const unoptimized = wasmIonGetFirstMIRPass(ionJSON); const optimized = wasmIonGetLastMIRPass(ionJSON); assertOpcodesInOrder(unoptimized, ["WasmRefCastConcrete", "WasmRefTestConcrete", "WasmRefCastConcrete"]); assertOpcodesInOrder(optimized, ["WasmRefCastConcrete", "!WasmRefTestConcrete", "!WasmRefCastConcrete", "!WasmRefCastInfallible"]); const { test, make } = wasmEvalBinary(code).exports; assertEq(test(make()), 123); } { // ref.is_null is emitted as `ref.test nullref`, so it should participate in // the same kinds of optimizations. const code = wasmTextToBinary(`(module (type $s (struct (field i32))) (func (export "test") (param anyref) (result i32) local.get 0 ref.is_null if local.get 0 ref.cast nullref ;; trivial, will disappear struct.get $s 0 return else local.get 0 ref.is_null ;; known to fail if unreachable end end i32.const -1 ) (func (export "make") (result anyref) (struct.new $s (i32.const 123)) ) )`); const ionJSON = wasmGetIon(code, 0); const unoptimized = wasmIonGetFirstMIRPass(ionJSON); const optimized = wasmIonGetLastMIRPass(ionJSON); assertOpcodesInOrder(unoptimized, [ "WasmRefTestAbstract", "WasmRefCastAbstract", "WasmRefTestAbstract", "WasmTrap", "WasmReturn", ]); assertOpcodesInOrder(optimized, [ "WasmRefTestAbstract", "!WasmRefCastAbstract", "!WasmRefTestAbstract", "!WasmTrap", "WasmReturn", ]); const { test, make } = wasmEvalBinary(code).exports; assertEq(test(make()), -1); } }