From cb4046f76ade9d60689cb58d05cf8eaacf009f6f Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Tue, 21 Apr 2026 20:39:26 +0000 Subject: [PATCH] Account for global effects in LinearExecutionWalker --- src/ir/linear-execution.h | 31 ++++++++-- .../simplify-locals-global-effects-eh.wast | 57 +++++++++++++++++++ 2 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 test/lit/passes/simplify-locals-global-effects-eh.wast diff --git a/src/ir/linear-execution.h b/src/ir/linear-execution.h index e8b1923aacf..663d6d91969 100644 --- a/src/ir/linear-execution.h +++ b/src/ir/linear-execution.h @@ -80,11 +80,10 @@ struct LinearExecutionWalker : public PostWalker { static void scan(SubType* self, Expression** currp) { Expression* curr = *currp; - auto handleCall = [&](bool isReturn) { + auto handleCall = [&](bool mayThrow, bool isReturn) { if (!self->connectAdjacentBlocks) { - // Control is nonlinear if we return, or if EH is enabled or may be. - if (isReturn || !self->getModule() || - self->getModule()->features.hasExceptionHandling()) { + // Control is nonlinear if we return or throw. + if (isReturn || !self->getModule() || mayThrow) { self->pushTask(SubType::doNoteNonLinear, currp); } } @@ -153,11 +152,31 @@ struct LinearExecutionWalker : public PostWalker { break; } case Expression::Id::CallId: { - handleCall(curr->cast()->isReturn); + auto* call = curr->cast(); + + bool mayThrow = !self->getModule() || + self->getModule()->features.hasExceptionHandling(); + if (mayThrow && self->getModule()) { + auto* effects = + self->getModule()->getFunction(call->target)->effects.get(); + + if (effects && !effects->throws_) { + mayThrow = false; + } + } + + handleCall(mayThrow, call->isReturn); return; } case Expression::Id::CallRefId: { - handleCall(curr->cast()->isReturn); + auto* callRef = curr->cast(); + + // TODO: Effect analysis for indirect calls isn't implemented yet. + // Assume any indirect call my throw for now. + bool mayThrow = !self->getModule() || + self->getModule()->features.hasExceptionHandling(); + + handleCall(mayThrow, callRef->isReturn); return; } case Expression::Id::TryId: { diff --git a/test/lit/passes/simplify-locals-global-effects-eh.wast b/test/lit/passes/simplify-locals-global-effects-eh.wast new file mode 100644 index 00000000000..41efe09a0c2 --- /dev/null +++ b/test/lit/passes/simplify-locals-global-effects-eh.wast @@ -0,0 +1,57 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: foreach %s %t wasm-opt --enable-exception-handling --generate-global-effects --simplify-locals -S -o - | filecheck %s + +(module + ;; CHECK: (global $g (mut i32) (i32.const 0)) + + ;; CHECK: (tag $t (type $0)) + (tag $t) + + (global $g (mut i32) (i32.const 0)) + + ;; CHECK: (func $nop + ;; CHECK-NEXT: ) + (func $nop + ) + + ;; CHECK: (func $throws + ;; CHECK-NEXT: (throw $t) + ;; CHECK-NEXT: ) + (func $throws + (throw $t) + ) + + ;; CHECK: (func $read-g (result i32) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $nop) + ;; CHECK-NEXT: (global.get $g) + ;; CHECK-NEXT: ) + (func $read-g (result i32) + (local $x i32) + (local.set $x (global.get $g)) + + ;; With --global-effects, we can tell that this doesn't throw, so it + ;; doesn't act as a barrier to optimize. The local is optimized away. + (call $nop) + (local.get $x) + ) + + ;; CHECK: (func $read-g-with-throw-in-between (result i32) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (global.get $g) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $throws) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + (func $read-g-with-throw-in-between (result i32) + (local $x i32) + (local.set $x (global.get $g)) + + ;; A potential throw halts our optimizations. + (call $throws) + + (local.get $x) + ) +)