From 2941d167c767a4b46ab47d281ab689b473a25970 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Tue, 9 Jun 2026 18:18:31 +0000 Subject: [PATCH] Fix effects for inexactly imported functions --- src/ir/subtypes.h | 4 +- src/passes/GlobalEffects.cpp | 24 +++++ .../global-effects-closed-world-tnh.wast | 92 ++++++++++++++++++- 3 files changed, 118 insertions(+), 2 deletions(-) diff --git a/src/ir/subtypes.h b/src/ir/subtypes.h index 912d61f878d..74263839379 100644 --- a/src/ir/subtypes.h +++ b/src/ir/subtypes.h @@ -34,7 +34,9 @@ struct SubTypes { } } - SubTypes(Module& wasm) : SubTypes(ModuleUtils::collectHeapTypes(wasm)) {} + // TODO: fix const-correctness here. + SubTypes(const Module& wasm) + : SubTypes(ModuleUtils::collectHeapTypes(const_cast(wasm))) {} const std::vector& getImmediateSubTypes(HeapType type) const { // When we return an empty result, use a canonical constant empty vec to diff --git a/src/passes/GlobalEffects.cpp b/src/passes/GlobalEffects.cpp index fd05d5afd6c..efcc45eadd7 100644 --- a/src/passes/GlobalEffects.cpp +++ b/src/passes/GlobalEffects.cpp @@ -21,6 +21,7 @@ #include "ir/effects.h" #include "ir/module-utils.h" +#include "ir/subtypes.h" #include "pass.h" #include "support/graph_traversal.h" #include "support/strongly_connected_components.h" @@ -276,6 +277,29 @@ CallGraph buildCallGraph(const Module& module, }); (void)superTypeGraph.traverseDepthFirst(); + // Add Type -> Function edges to account for inexact imports. For (ref.func) + // on a *defined* function, we know its exact type and can add a single + // Type -> Function edge in the graph (done above). We know that indirect + // calls to strict subtypes of the function can't reach the function. + // + // OTOH for inexactly imported functions, they may be downcasted to a subtype. + // To account for this, add Type -> Function edges to all subtypes for + // inexactly imported functions. + SubTypes subtypes(module); + ModuleUtils::iterImportedFunctions(module, [&](Function* func) { + if (func->type.isExact()) { + return; + } + if (!referencedFuncs.contains(func)) { + return; + } + + subtypes.iterSubTypes(func->type.getHeapType(), [&](auto subtype, int _) { + callGraph[subtype].insert(func); + return true; + }); + }); + return callGraph; } diff --git a/test/lit/passes/global-effects-closed-world-tnh.wast b/test/lit/passes/global-effects-closed-world-tnh.wast index 39508c0c565..16b67fbbd51 100644 --- a/test/lit/passes/global-effects-closed-world-tnh.wast +++ b/test/lit/passes/global-effects-closed-world-tnh.wast @@ -1,5 +1,5 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. -;; RUN: wasm-opt %s -all --closed-world --traps-never-happen --generate-global-effects --vacuum -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --closed-world --traps-never-happen --generate-global-effects --vacuum -S -o - | filecheck %s ;; Tests for aggregating effects from indirect calls in GlobalEffects when ;; --closed-world is true. Continued from global-effects-closed-world.wast, but @@ -35,3 +35,93 @@ (call_indirect (type $nopType) (i32.const 1) (i32.const 0)) ) ) + +(module + ;; CHECK: (type $super (sub (func))) + (type $super (sub (func))) + ;; CHECK: (type $sub (sub $super (func))) + (type $sub (sub $super (func))) + + ;; CHECK: (import "" "" (func $import (type $super))) + (import "" "" (func $import (type $super))) + + (func + (drop (ref.func $import)) + ) + + ;; CHECK: (func $downcast-import (type $3) (param $ref (ref $super)) + ;; CHECK-NEXT: (call_ref $sub + ;; CHECK-NEXT: (ref.cast (ref $sub) + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $downcast-import (param $ref (ref $super)) + ;; $import's exact type could be $sub although we imported it as $super. + ;; Since we don't know, we need to propagate $import's effects to $sub as + ;; well. + (call_ref $sub (ref.cast (ref $sub) (local.get $ref))) + ) +) + +(module + ;; CHECK: (type $super (sub (func))) + (type $super (sub (func))) + ;; CHECK: (type $sub (sub $super (func))) + (type $sub (sub $super (func))) + + ;; CHECK: (import "" "" (func $import (exact (type $super)))) + (import "" "" (func $import (exact (type $super)))) + + ;; CHECK: (func $nop (type $sub) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $nop (type $sub) + (nop) + ) + + (func + (drop (ref.func $nop)) + (drop (ref.func $import)) + ) + + ;; CHECK: (func $downcast (type $3) (param $ref (ref $super)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $downcast (param $ref (ref $super)) + ;; $ref could be $import, in which case the downcast will trap. + ;; Or it could be $nop in which case this call_ref is a nop. + ;; We're free to optimize this to nop. + (call_ref $sub (ref.cast (ref $sub) (local.get $ref))) + ) +) + +(module + ;; CHECK: (type $super (sub (func))) + (type $super (sub (func))) + ;; CHECK: (type $sub (sub $super (func))) + (type $sub (sub $super (func))) + + ;; CHECK: (import "" "" (func $import (type $super))) + (import "" "" (func $import (type $super))) + + ;; CHECK: (func $nop (type $sub) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $nop (type $sub) + (nop) + ) + + (func + (drop (ref.func $nop)) + ) + + ;; CHECK: (func $downcast (type $3) (param $ref (ref $super)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $downcast (param $ref (ref $super)) + ;; Similar to above exact that $import is never referenced, so the only + ;; possible callee here is $nop. + (call_ref $sub (ref.cast (ref $sub) (local.get $ref))) + ) +)