From 1dbb9bdc23dc319eb71298f69ea74a84c8449cd4 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 22 May 2026 17:05:49 -0700 Subject: [PATCH 1/2] OptimizeCasts: Migrate from invalidates to orderedBefore Replace the coarse 'invalidates' check in 'OptimizeCasts' with 'orderedBefore'. This is a cleanup to use the more precise directional effects check. Also add a new lit test to verify that casts (which have trap effects) are correctly allowed to move past global state reads but blocked by global state writes. TAG=agy --- src/passes/OptimizeCasts.cpp | 6 +- test/lit/passes/optimize-casts-order.wast | 149 ++++++++++++++++++++++ 2 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 test/lit/passes/optimize-casts-order.wast diff --git a/src/passes/OptimizeCasts.cpp b/src/passes/OptimizeCasts.cpp index e2e7c8c43ab..a6ea8fa2ac2 100644 --- a/src/passes/OptimizeCasts.cpp +++ b/src/passes/OptimizeCasts.cpp @@ -237,16 +237,16 @@ struct EarlyCastFinder void visitExpression(Expression* curr) { // A new one is instantiated for each expression to determine - // if a cast can be moved past it. + // if a cast can be moved backward past it. ShallowEffectAnalyzer currAnalyzer(options, *getModule(), curr); - if (testRefCast.invalidates(currAnalyzer)) { + if (currAnalyzer.orderedBefore(testRefCast)) { for (size_t i = 0; i < numLocals; i++) { flushRefCastResult(i, *getModule()); } } - if (testRefAs.invalidates(currAnalyzer)) { + if (currAnalyzer.orderedBefore(testRefAs)) { for (size_t i = 0; i < numLocals; i++) { flushRefAsResult(i, *getModule()); } diff --git a/test/lit/passes/optimize-casts-order.wast b/test/lit/passes/optimize-casts-order.wast new file mode 100644 index 00000000000..c74eea29572 --- /dev/null +++ b/test/lit/passes/optimize-casts-order.wast @@ -0,0 +1,149 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --optimize-casts -all -S -o - | filecheck %s + +(module + ;; CHECK: (type $A (sub (struct))) + (type $A (sub (struct))) + ;; CHECK: (type $B (sub $A (struct))) + (type $B (sub $A (struct))) + + (memory 1) + + ;; CHECK: (global $g (mut i32) (i32.const 0)) + (global $g (mut i32) (i32.const 0)) + + ;; CHECK: (func $cast_allowed (type $2) (param $x (ref null $A)) + ;; CHECK-NEXT: (local $temp (ref null $A)) + ;; CHECK-NEXT: (local $2 (ref null $B)) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (ref.cast (ref null $B) + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref null $B) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast_allowed (param $x (ref null $A)) + ;; ref.cast can move back past load. + (local $temp (ref null $A)) + (local.set $temp (local.get $x)) + (block + (drop (local.get $temp)) + (drop (i32.load (i32.const 0))) + (drop (ref.cast (ref null $B) (local.get $temp))) + ) + ) + + ;; CHECK: (func $cast_disallowed (type $2) (param $x (ref null $A)) + ;; CHECK-NEXT: (local $temp (ref null $A)) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref null $B) + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast_disallowed (param $x (ref null $A)) + ;; ref.cast cannot move back past a store. + (local $temp (ref null $A)) + (local.set $temp (local.get $x)) + (block + (drop (local.get $temp)) + (i32.store (i32.const 0) (i32.const 42)) + (drop (ref.cast (ref null $B) (local.get $temp))) + ) + ) + + ;; Test 3: ref.as positive (moves past mutable global read) + ;; CHECK: (func $as_allowed (type $3) (param $x anyref) + ;; CHECK-NEXT: (local $temp anyref) + ;; CHECK-NEXT: (local $2 (ref any)) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (global.get $g) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $as_allowed (param $x anyref) + ;; ref.as_non_null can move back past a global.get. + (local $temp anyref) + (local.set $temp (local.get $x)) + (block + (drop (local.get $temp)) + (drop (global.get $g)) + (drop (ref.as_non_null (local.get $temp))) + ) + ) + + ;; Test 4: ref.as negative (blocked by mutable global write) + ;; CHECK: (func $as_disallowed (type $3) (param $x anyref) + ;; CHECK-NEXT: (local $temp anyref) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $g + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $as_disallowed (param $x anyref) + ;; ref.as_non_null cannot move back past a global.set. + (local $temp anyref) + (local.set $temp (local.get $x)) + (block + (drop (local.get $temp)) + (global.set $g (i32.const 42)) + (drop (ref.as_non_null (local.get $temp))) + ) + ) +) From b589630c761e912c176cbb6b8db4091bdfeb3e2d Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 26 May 2026 12:00:42 -0700 Subject: [PATCH 2/2] fix test --- test/lit/passes/merge-blocks-atomics.wast | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/lit/passes/merge-blocks-atomics.wast b/test/lit/passes/merge-blocks-atomics.wast index 6435a9d7202..e8ed209d7ef 100644 --- a/test/lit/passes/merge-blocks-atomics.wast +++ b/test/lit/passes/merge-blocks-atomics.wast @@ -14,7 +14,7 @@ ;; CHECK: (func $disallowed (type $1) (param $x (ref $struct)) ;; CHECK-NEXT: (call $foo - ;; CHECK-NEXT: (block $label1 (result i32) + ;; CHECK-NEXT: (block $block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.atomic.load acqrel ;; CHECK-NEXT: (i32.const 0) @@ -55,7 +55,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $foo - ;; CHECK-NEXT: (block $label2 (result i32) + ;; CHECK-NEXT: (block $block (result i32) ;; CHECK-NEXT: (i32.atomic.store acqrel ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (i32.const 42)