From 06c44ec39e4973fdf63d8f9ec2cce5bdcdb3889d Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 4 Aug 2026 20:45:37 -0700 Subject: [PATCH 1/7] Improve filter for disas test --- .../component-model/sync-adapter-calls.wat | 135 +----------------- 1 file changed, 2 insertions(+), 133 deletions(-) diff --git a/tests/disas/component-model/sync-adapter-calls.wat b/tests/disas/component-model/sync-adapter-calls.wat index 36536000f4cc..f5c9398bc6b1 100644 --- a/tests/disas/component-model/sync-adapter-calls.wat +++ b/tests/disas/component-model/sync-adapter-calls.wat @@ -1,6 +1,6 @@ ;;! target = "x86_64" ;;! test = "optimize" -;;! filter = "function" +;;! filter = "wasm[1]--function" ;;! flags = "-C inlining=y -Wconcurrency-support=y" (component @@ -50,23 +50,7 @@ (export "g" (func $b "g")) ) -;; function u0:0(i64 vmctx, i64, i32) -> i32 tail { -;; region0 = 8 "VMContext+0x8" -;; region1 = 67108888 "VMStoreContext+0x18" -;; gv0 = vmctx -;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 -;; gv2 = load.i64 notrap aligned region1 gv1+24 -;; stack_limit = gv2 -;; -;; block0(v0: i64, v1: i64, v2: i32): -;; @003b jump block1 -;; -;; block1: -;; @0038 v3 = iconst.i32 42 -;; v4 = iadd.i32 v2, v3 ; v3 = 42 -;; @003b return v4 -;; } -;; + ;; function u1:0(i64 vmctx, i64) -> i32 tail { ;; ss0 = explicit_slot 32, align = 8 ;; region0 = 8 "VMContext+0x8" @@ -195,118 +179,3 @@ ;; v54 = iconst.i32 1276 ;; @00f0 return v54 ; v54 = 1276 ;; } -;; -;; function u2:0(i64 vmctx, i64, i32) -> i32 tail { -;; ss0 = explicit_slot 32, align = 8 -;; region0 = 8 "VMContext+0x8" -;; region1 = 67108888 "VMStoreContext+0x18" -;; region2 = 1476395008 "VMGlobalImport+0x0" -;; region3 = 402653184 "PublicGlobal" -;; region4 = 1207959576 "VMFunctionImport+0x18" -;; region5 = 1207959560 "VMFunctionImport+0x8" -;; region6 = 67109000 "VMStoreContext+0x88" -;; region7 = 1006632960 "VMDeferredThread+0x0" -;; region8 = 1006632968 "VMDeferredThread+0x8" -;; region9 = 1006632972 "VMDeferredThread+0xc" -;; region10 = 1006632976 "VMDeferredThread+0x10" -;; region11 = 67108992 "VMStoreContext+0x80" -;; region12 = 1006632980 "VMDeferredThread+0x14" -;; region13 = 67108996 "VMStoreContext+0x84" -;; region14 = 1006632984 "VMDeferredThread+0x18" -;; gv0 = vmctx -;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 -;; gv2 = load.i64 notrap aligned region1 gv1+24 -;; gv3 = vmctx -;; gv4 = load.i64 notrap aligned readonly can_move region0 gv3+8 -;; gv5 = load.i64 notrap aligned region1 gv4+24 -;; sig0 = (i64 vmctx, i64, i32) tail -;; sig1 = (i64 vmctx, i64, i32, i32, i32) tail -;; sig2 = (i64 vmctx, i64, i32) -> i32 tail -;; sig3 = (i64 vmctx, i64) tail -;; fn0 = colocated u0:0 sig2 -;; stack_limit = gv2 -;; -;; block0(v0: i64, v1: i64, v2: i32): -;; @00cf jump block4 -;; -;; block6(v4: i64): -;; @00cf jump block3 -;; -;; block4: -;; @00d6 v6 = load.i64 notrap aligned readonly can_move region2 v0+200 -;; @00d6 v7 = load.i32 notrap aligned region3 v6 -;; @00da brif v7, block7, block8 -;; -;; block8: -;; @00de v10 = load.i64 notrap aligned readonly can_move region5 v0+88 -;; @00de v9 = load.i64 notrap aligned readonly can_move region4 v0+104 -;; @00dc v8 = iconst.i32 23 -;; @00de try_call_indirect v10(v9, v0, v8), sig0, block9, [ context v0, default: block6(exn0) ] ; v8 = 23 -;; -;; block9: -;; @00e0 trap user12 -;; -;; block7: -;; @00e2 v11 = load.i64 notrap aligned readonly can_move region2 v0+224 -;; @00e2 v12 = load.i32 notrap aligned region3 v11 -;; @00c9 v3 = iconst.i32 0 -;; @00e8 store notrap aligned region3 v3, v11 ; v3 = 0 -;; @00f0 v20 = load.i64 notrap aligned readonly can_move region0 v0+8 -;; @00f0 v21 = load.i64 notrap aligned region6 v20+136 -;; @00f0 v19 = stack_addr.i64 ss0 -;; @00f0 store notrap aligned region7 v21, v19 -;; @00ea v15 = iconst.i32 2 -;; @00f0 store notrap aligned region8 v15, v19+8 ; v15 = 2 -;; @00f0 store notrap aligned region9 v3, v19+12 ; v3 = 0 -;; @00ee v17 = iconst.i32 1 -;; @00f0 store notrap aligned region10 v17, v19+16 ; v17 = 1 -;; @00f0 v22 = load.i32 notrap aligned region11 v20+128 -;; @00f0 store notrap aligned region12 v22, v19+20 -;; @00f0 store notrap aligned region11 v3, v20+128 ; v3 = 0 -;; @00f0 v24 = load.i32 notrap aligned region13 v20+132 -;; @00f0 store notrap aligned region14 v24, v19+24 -;; @00f0 store notrap aligned region13 v3, v20+132 ; v3 = 0 -;; @00f0 store notrap aligned region6 v19, v20+136 -;; @00f2 v26 = load.i64 notrap aligned readonly can_move region2 v0+176 -;; @00f2 v27 = load.i32 notrap aligned region3 v26 -;; @00fe store notrap aligned region3 v27, v26 -;; @0100 jump block15 -;; -;; block15: -;; jump block16 -;; -;; block16: -;; jump block10 -;; -;; block10: -;; @0104 jump block11 -;; -;; block11: -;; @0104 store.i64 notrap aligned region6 v21, v20+136 -;; @0104 store.i32 notrap aligned region11 v22, v20+128 -;; @0104 store.i32 notrap aligned region13 v24, v20+132 -;; @0104 jump block13 -;; -;; block13: -;; @010e store.i32 notrap aligned region3 v7, v6 -;; @0112 store.i32 notrap aligned region3 v12, v11 -;; @0114 jump block5 -;; -;; block5: -;; @0115 jump block2 -;; -;; block3: -;; v55 = load.i64 notrap aligned readonly can_move region5 v0+88 -;; v56 = load.i64 notrap aligned readonly can_move region4 v0+104 -;; @0118 v49 = iconst.i32 49 -;; @011a call_indirect sig0, v55(v56, v0, v49) ; v49 = 49 -;; @011c trap user12 -;; -;; block2: -;; @011e jump block1 -;; -;; block1: -;; v52 = iconst.i32 42 -;; v53 = iadd.i32 v2, v52 ; v52 = 42 -;; @011e return v53 -;; } From 16d7c5d5121da4a31bea84542900c03b3016ba34 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 4 Aug 2026 23:44:36 -0700 Subject: [PATCH 2/7] Add sync adapter disas test for x64 --- .../sync-adapter-calls-x64.wat | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 tests/disas/component-model/sync-adapter-calls-x64.wat diff --git a/tests/disas/component-model/sync-adapter-calls-x64.wat b/tests/disas/component-model/sync-adapter-calls-x64.wat new file mode 100644 index 000000000000..432e14bd40d4 --- /dev/null +++ b/tests/disas/component-model/sync-adapter-calls-x64.wat @@ -0,0 +1,123 @@ +;;! target = "x86_64" +;;! test = "compile" +;;! filter = "wasm[1]" +;;! flags = "-C inlining=y -Wconcurrency-support=y" + +(component + (component $A + (core module $M + (func (export "f'") (param i32) (result i32) + (i32.add (local.get 0) (i32.const 42)) + ) + ) + + (core instance $m (instantiate $M)) + + (func (export "f") (param "x" u32) (result u32) + (canon lift (core func $m "f'")) + ) + ) + + (component $B + (import "f" (func $f (param "x" u32) (result u32))) + + (core func $f' (canon lower (func $f))) + + (core module $N + (import "" "f'" (func $f' (param i32) (result i32))) + (func (export "g'") (result i32) + (call $f' (i32.const 1234)) + ) + ) + + (core instance $n + (instantiate $N + (with "" (instance (export "f'" (func $f')))) + ) + ) + + (func (export "g") (result u32) + (canon lift (core func $n "g'")) + ) + ) + + (instance $a (instantiate $A)) + (instance $b + (instantiate $B + (with "f" (func $a "f")) + ) + ) + + (export "g" (func $b "g")) +) + +;; wasm[1]::function[1]: +;; pushq %rbp +;; movq %rsp, %rbp +;; movq 8(%rdi), %r10 +;; movq 0x18(%r10), %r10 +;; addq $0x70, %r10 +;; cmpq %rsp, %r10 +;; ja 0x156 +;; 39: subq $0x60, %rsp +;; movq %rbx, 0x30(%rsp) +;; movq %r12, 0x38(%rsp) +;; movq %r13, 0x40(%rsp) +;; movq %r14, 0x48(%rsp) +;; movq %r15, 0x50(%rsp) +;; movq 0x48(%rdi), %rsi +;; movq 0xc8(%rsi), %rax +;; movl (%rax), %ecx +;; testl %ecx, %ecx +;; jne 0x84 +;; 6b: movq 0x58(%rsi), %rax +;; movq 0x68(%rsi), %rdi +;; movq %rsi, 0x20(%rsp) +;; movl $0x17, %edx +;; callq *%rax +;; ├─╼ exception frame offset: SP = FP - 0x60 +;; ╰─╼ exception handler: default handler, context at [SP+0x20], handler=0x140 +;; jmp 0x13e +;; 84: movq 0xe0(%rsi), %rdx +;; movl (%rdx), %r8d +;; movl $0, (%rdx) +;; movq 8(%rsi), %rdi +;; movq 0x88(%rdi), %r9 +;; leaq (%rsp), %rbx +;; movq %r9, (%rsp) +;; movl $2, 8(%rsp) +;; movl $0, 0xc(%rsp) +;; movl $1, 0x10(%rsp) +;; movl 0x80(%rdi), %r10d +;; movl %r10d, 0x14(%rsp) +;; movl $0, 0x80(%rdi) +;; movl 0x84(%rdi), %r11d +;; movl %r11d, 0x18(%rsp) +;; movl $0, 0x84(%rdi) +;; movq %rbx, 0x88(%rdi) +;; movq 0xb0(%rsi), %rsi +;; movl (%rsi), %ebx +;; movl %ebx, (%rsi) +;; movq %r9, 0x88(%rdi) +;; movl %r10d, 0x80(%rdi) +;; movl %r11d, 0x84(%rdi) +;; movl %ecx, (%rax) +;; movl %r8d, (%rdx) +;; movl $0x4fc, %eax +;; movq 0x30(%rsp), %rbx +;; movq 0x38(%rsp), %r12 +;; movq 0x40(%rsp), %r13 +;; movq 0x48(%rsp), %r14 +;; movq 0x50(%rsp), %r15 +;; addq $0x60, %rsp +;; movq %rbp, %rsp +;; popq %rbp +;; retq +;; 13e: ud2 +;; 140: movq 0x20(%rsp), %rsi +;; 145: movq 0x58(%rsi), %rax +;; 149: movq 0x68(%rsi), %rdi +;; 14d: movl $0x31, %edx +;; 152: callq *%rax +;; 154: ud2 +;; 156: ud2 From 0b6bfd114b8898fcae9180067e33e84025a66e0c Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 4 Aug 2026 22:47:10 -0700 Subject: [PATCH 3/7] Cranelift: unwind last-store state after removing a dead store Alias analysis's dead-store elimination removed the dead store's `mem_values` entry, but left the region's last-store slot naming the instruction it had just deleted. Leaving the removed-store meant that when we then reprocess the overwriting store, we keyed its lookup on a removed instruction, found nothing, and failed to notice that (for example) the overwriting store became idempotent and could also be removed. With this commit, each store now records the memory version it displaced, and eliminating a dead store rolls that version back, so a chain like v1 = load.i32 region0 v0 store region0 v2, v0 ;; dead store region0 v1, v0 ;; idempotent once the dead store is gone collapses in the single pass we actually make, rather than removing only one link in the chain and requiring that we do N passes to fully clean up a chain of N dead/idempotent stores. This code pattern the shape fused sync adapters emit around the `MAY_LEAVE` flag and the relevant disas tests each lose a store as a result. --- cranelift/codegen/src/alias_analysis.rs | 113 ++++++++++- .../alias/check-unset-reset-flag.clif | 2 +- .../dead-store-then-idempotent-store.clif | 181 ++++++++++++++++++ .../direct-adapter-calls-inlining.wat | 1 - .../direct-adapter-calls-x64.wat | 55 +++--- .../component-model/direct-adapter-calls.wat | 1 - .../sync-adapter-calls-x64.wat | 23 ++- .../component-model/sync-adapter-calls.wat | 1 - 8 files changed, 323 insertions(+), 54 deletions(-) create mode 100644 cranelift/filetests/filetests/alias/dead-store-then-idempotent-store.clif diff --git a/cranelift/codegen/src/alias_analysis.rs b/cranelift/codegen/src/alias_analysis.rs index c15769fd70f9..626455a778e8 100644 --- a/cranelift/codegen/src/alias_analysis.rs +++ b/cranelift/codegen/src/alias_analysis.rs @@ -424,6 +424,32 @@ impl LastStores { } } + /// Roll this state back to the memory version from just before `dead`, + /// which is a store being removed from the function by dead-store + /// elimination. + /// + /// `prev_last_store` must be the last-store instruction that immediately + /// preceded `dead`, as recorded when `dead` itself was processed. + /// + /// Only `dead`'s own alias-region slot is restored. A store with no alias + /// region is treated as a fence by `update`, which clears *every* region + /// slot, and we do not undo that; in that case, we leave this state + /// alone. Similarly, stores marked observed while processing `dead` stay + /// observed. + fn undo_store(&mut self, func: &Function, dead: Inst, prev_last_store: PackedOption) { + debug_assert!(func.dfg.insts[dead].opcode().can_store()); + + let Some(region) = func.dfg.insts[dead].alias_region(&func.dfg) else { + return; + }; + + // Only roll back if `dead` really is the current last store to its + // region. + if self.regions[region].expand() == Some(dead) { + self.regions[region] = prev_last_store; + } + } + /// Get the last-store instruction for the given `inst`'s alias region, if /// any. fn get_last_store(&self, func: &Function, inst: Inst) -> PackedOption { @@ -527,6 +553,26 @@ struct MemoryLoc { extending_opcode: Option, } +/// What is known to be in memory at an associated `MemoryLoc`. +#[derive(Clone, Copy, Debug)] +struct KnownValue { + /// The value held at the associated `MemoryLoc`. + value: Value, + + /// The instruction that produced `value`: either the load that read it out + /// of memory or the store that wrote it there. + /// + /// Kept around for quick dominance checks. + def_inst: Inst, + + /// When this entry was created by a store, the last-store instruction that + /// immediately preceded that store: that is, the memory version this + /// location was at just *before* `def_inst` overwrote it. + /// + /// `None` for entries created by loads. + prev_last_store: Option>, +} + /// The result of processing an instruction through alias analysis. pub enum OptResult { /// No optimization applied. @@ -576,9 +622,7 @@ pub struct AliasAnalysis<'a> { /// Known memory-value equivalences. This is the result of the /// analysis. This is a mapping from (last store, address /// expression, offset, type) to SSA `Value`. - /// - /// We keep the defining inst around for quick dominance checks. - mem_values: FxHashMap, + mem_values: FxHashMap, } impl<'a> AliasAnalysis<'a> { @@ -754,7 +798,36 @@ impl<'a> AliasAnalysis<'a> { ty, extending_opcode: get_ext_opcode(opcode), }; - self.mem_values.remove(&dead_loc); + let dead_entry = self.mem_values.remove(&dead_loc); + + // Roll our last-store state back to the memory version + // just before the dead store, so that `state` describes + // memory as if the dead store had never happened. + // + // Our callers remove the dead store from the layout and + // then reprocess this overwriting store. Without the + // rollback, that reprocessing keys its `mem_values` + // lookup on the instruction we just removed, finds + // nothing, and so fails to notice that the overwriter + // has now become an idempotent store. Chains like + // + // v1 = load.i32 region0 v0 + // store region0 v2, v0 ;; dead + // store region0 v1, v0 ;; idempotent, once the + // ;; dead store is gone + // + // would then need a whole additional pass over the + // function to collapse each link. + // + // A missing entry means we never processed the dead + // store as a store in this pass (it can come from a + // precomputed `block_input` snapshot, for a predecessor + // block we have not walked yet), so we have no previous + // version to roll back to and simply don't. + if let Some(prev) = dead_entry.and_then(|e| e.prev_last_store) { + state.undo_store(func, last_store, prev); + } + return OptResult::DeadStore { dead: last_store, overwriter: inst, @@ -769,7 +842,12 @@ impl<'a> AliasAnalysis<'a> { ty, extending_opcode: get_ext_opcode(opcode), }; - if let Some((def_inst, known_value)) = self.mem_values.get(&check_loc).cloned() { + if let Some(KnownValue { + def_inst, + value: known_value, + .. + }) = self.mem_values.get(&check_loc).cloned() + { // Check for idempotent stores, where we are // storing the exact same value back to a location // that already has that value. @@ -806,7 +884,14 @@ impl<'a> AliasAnalysis<'a> { extending_opcode: get_ext_opcode(opcode), }; trace!(" --> updating known values in memory: {mem_loc:?} = {store_data}"); - self.mem_values.insert(mem_loc, (inst, store_data)); + self.mem_values.insert( + mem_loc, + KnownValue { + def_inst: inst, + value: store_data, + prev_last_store: Some(last_store), + }, + ); OptResult::None } else if opcode.can_load() { @@ -831,8 +916,9 @@ impl<'a> AliasAnalysis<'a> { // load (stores will always dominate though if // their `last_store` survives through // meet-points to this use-site). - let aliased = if let Some((def_inst, value)) = - self.mem_values.get(&mem_loc).cloned() + let aliased = if let Some(KnownValue { + def_inst, value, .. + }) = self.mem_values.get(&mem_loc).cloned() { trace!(" see known value {value} from {def_inst}"); if self.domtree.dominates(def_inst, inst, &func.layout) { @@ -851,7 +937,16 @@ impl<'a> AliasAnalysis<'a> { // as a new equivalent value. if aliased.is_none() { trace!(" --> inserting load result {load_result} at loc {mem_loc:?}"); - self.mem_values.insert(mem_loc, (inst, load_result)); + self.mem_values.insert( + mem_loc, + KnownValue { + def_inst: inst, + value: load_result, + // A load does not advance the memory version, so + // there is no previous version to roll back to. + prev_last_store: None, + }, + ); } match aliased { diff --git a/cranelift/filetests/filetests/alias/check-unset-reset-flag.clif b/cranelift/filetests/filetests/alias/check-unset-reset-flag.clif index af926b0a0bf8..08188dd75cb8 100644 --- a/cranelift/filetests/filetests/alias/check-unset-reset-flag.clif +++ b/cranelift/filetests/filetests/alias/check-unset-reset-flag.clif @@ -21,7 +21,7 @@ block0(v0: i64, v1: i32): ; block0(v0: i64, v1: i32): ; v2 = load.i64 notrap aligned region0 v0 ; trapz v2, user42 -; store notrap aligned region0 v2, v0 ; v4 = iadd v1, v1 ; return v4 ; } + diff --git a/cranelift/filetests/filetests/alias/dead-store-then-idempotent-store.clif b/cranelift/filetests/filetests/alias/dead-store-then-idempotent-store.clif new file mode 100644 index 000000000000..02e5ecd5b8a5 --- /dev/null +++ b/cranelift/filetests/filetests/alias/dead-store-then-idempotent-store.clif @@ -0,0 +1,181 @@ +test optimize precise-output +set opt_level=speed +target x86_64 + +;; Removing a dead store must expose the *previous* memory version to the store +;; that overwrote it, so that a save/clear/restore sequence collapses entirely in +;; a single pass rather than one link per pass. +function %save_clear_restore(i64) { + region0 = 0 "flags" +block0(v0: i64): + v1 = load.i32 notrap aligned region0 v0 + v2 = iconst.i32 0 + store notrap aligned region0 v2, v0 + store notrap aligned region0 v1, v0 + return +} + +; function %save_clear_restore(i64) fast { +; region0 = 0 "flags" +; +; block0(v0: i64): +; v1 = load.i32 notrap aligned region0 v0 +; return +; } + +;; The same, but with several dead stores between the load and the restore. +function %save_clobber_many_restore(i64, i32, i32) { + region0 = 0 "flags" +block0(v0: i64, v1: i32, v2: i32): + v3 = load.i32 notrap aligned region0 v0 + store notrap aligned region0 v1, v0 + store notrap aligned region0 v2, v0 + store notrap aligned region0 v1, v0 + store notrap aligned region0 v3, v0 + return +} + +; function %save_clobber_many_restore(i64, i32, i32) fast { +; region0 = 0 "flags" +; +; block0(v0: i64, v1: i32, v2: i32): +; v3 = load.i32 notrap aligned region0 v0 +; return +; } + +;; Two independent flags, each in its own alias region, are both collapsed. +;; +;; Note that the accesses are interleaved: unwinding one region's dead store +;; must not disturb the other region's last-store state. +function %two_regions_interleaved(i64, i64) { + region0 = 0 "flags0" + region1 = 1 "flags1" +block0(v0: i64, v1: i64): + v2 = load.i32 notrap aligned region0 v0 + v3 = load.i32 notrap aligned region1 v1 + v4 = iconst.i32 0 + store notrap aligned region0 v4, v0 + store notrap aligned region1 v4, v1 + store notrap aligned region0 v2, v0 + store notrap aligned region1 v3, v1 + return +} + +; function %two_regions_interleaved(i64, i64) fast { +; region0 = 0 "flags0" +; region1 = 1 "flags1" +; +; block0(v0: i64, v1: i64): +; v2 = load.i32 notrap aligned region0 v0 +; v3 = load.i32 notrap aligned region1 v1 +; return +; } + +;; The restore is folded across intervening blocks, so long as nothing in them +;; observes the flag. +function %save_clear_restore_cross_block(i64) { + region0 = 0 "flags" +block0(v0: i64): + v1 = load.i32 notrap aligned region0 v0 + v2 = iconst.i32 0 + store notrap aligned region0 v2, v0 + jump block1 + +block1: + jump block2 + +block2: + store notrap aligned region0 v1, v0 + return +} + +; function %save_clear_restore_cross_block(i64) fast { +; region0 = 0 "flags" +; +; block0(v0: i64): +; v1 = load.i32 notrap aligned region0 v0 +; jump block1 +; +; block1: +; jump block2 +; +; block2: +; return +; } + +;; Negative test: a call between the clear and the restore observes the cleared +;; flag, so neither store may be removed. +function %call_observes_cleared_flag(i64) { + region0 = 0 "flags" + fn0 = %g(i64) +block0(v0: i64): + v1 = load.i32 notrap aligned region0 v0 + v2 = iconst.i32 0 + store notrap aligned region0 v2, v0 + call fn0(v0) + store notrap aligned region0 v1, v0 + return +} + +; function %call_observes_cleared_flag(i64) fast { +; region0 = 0 "flags" +; sig0 = (i64) fast +; fn0 = %g sig0 +; +; block0(v0: i64): +; v1 = load.i32 notrap aligned region0 v0 +; v2 = iconst.i32 0 +; store notrap aligned region0 v2, v0 ; v2 = 0 +; call fn0(v0) +; store notrap aligned region0 v1, v0 +; return +; } + +;; Negative test: the final store writes a value other than the saved one, so it +;; is not idempotent. Only the dead middle store is removed. +function %restore_wrong_value(i64, i32) { + region0 = 0 "flags" +block0(v0: i64, v1: i32): + v2 = load.i32 notrap aligned region0 v0 + v3 = iconst.i32 0 + store notrap aligned region0 v3, v0 + store notrap aligned region0 v1, v0 + return +} + +; function %restore_wrong_value(i64, i32) fast { +; region0 = 0 "flags" +; +; block0(v0: i64, v1: i32): +; v2 = load.i32 notrap aligned region0 v0 +; store notrap aligned region0 v1, v0 +; return +; } + +;; Negative test: rolling back to the previous memory version must not resurrect +;; knowledge across a store to a *different* address in the same region. The +;; region's last-store slot is per-region, not per-address, so after the store to +;; `v0+8` the analysis no longer knows what is at `v0`, and the final store to +;; `v0` cannot be proven idempotent. +function %same_region_different_address(i64, i32) { + region0 = 0 "flags" +block0(v0: i64, v1: i32): + v2 = load.i32 notrap aligned region0 v0 + v3 = iconst.i32 0 + store notrap aligned region0 v3, v0 + store notrap aligned region0 v1, v0+8 + store notrap aligned region0 v2, v0 + return +} + +; function %same_region_different_address(i64, i32) fast { +; region0 = 0 "flags" +; +; block0(v0: i64, v1: i32): +; v2 = load.i32 notrap aligned region0 v0 +; v3 = iconst.i32 0 +; store notrap aligned region0 v3, v0 ; v3 = 0 +; store notrap aligned region0 v1, v0+8 +; store notrap aligned region0 v2, v0 +; return +; } diff --git a/tests/disas/component-model/direct-adapter-calls-inlining.wat b/tests/disas/component-model/direct-adapter-calls-inlining.wat index 04435c56b5d6..d7900b80b5ba 100644 --- a/tests/disas/component-model/direct-adapter-calls-inlining.wat +++ b/tests/disas/component-model/direct-adapter-calls-inlining.wat @@ -104,7 +104,6 @@ ;; block9: ;; v11 = load.i64 notrap aligned readonly can_move region3 v3+112 ;; v12 = load.i32 notrap aligned region4 v11 -;; store notrap aligned region4 v12, v11 ;; jump block13 ;; ;; block13: diff --git a/tests/disas/component-model/direct-adapter-calls-x64.wat b/tests/disas/component-model/direct-adapter-calls-x64.wat index cfa4fead7efa..8831a79b6fc8 100644 --- a/tests/disas/component-model/direct-adapter-calls-x64.wat +++ b/tests/disas/component-model/direct-adapter-calls-x64.wat @@ -87,7 +87,7 @@ ;; movq 0x18(%r10), %r10 ;; addq $0x60, %r10 ;; cmpq %rsp, %r10 -;; ja 0x147 +;; ja 0x13e ;; 79: subq $0x50, %rsp ;; movq %rbx, 0x20(%rsp) ;; movq %r12, 0x28(%rsp) @@ -97,10 +97,10 @@ ;; movq %rdi, (%rsp) ;; movq (%rsp), %rdi ;; movq 0x88(%rdi), %rcx -;; movl (%rcx), %eax +;; movl (%rcx), %esi ;; movq %rcx, 0x10(%rsp) -;; testl %eax, %eax -;; movq %rax, 8(%rsp) +;; testl %esi, %esi +;; movq %rsi, 8(%rsp) ;; jne 0xd5 ;; b9: movq (%rsp), %rdi ;; movq 0x58(%rdi), %rax @@ -109,24 +109,19 @@ ;; movq (%rsp), %rsi ;; callq *%rax ;; ├─╼ exception frame offset: SP = FP - 0x50 -;; ╰─╼ exception handler: default handler, context at [SP+0x0], handler=0x132 -;; jmp 0x130 -;; d5: movq (%rsp), %rsi -;; movq 0x70(%rsi), %rax -;; movl (%rax), %ecx -;; movl %ecx, (%rax) -;; movq 0x48(%rsi), %rdi +;; ╰─╼ exception handler: default handler, context at [SP+0x0], handler=0x121 +;; jmp 0x11f +;; d5: movq (%rsp), %rcx +;; movq 0x70(%rcx), %rax +;; movl (%rax), %eax +;; movq 0x48(%rcx), %rdi +;; movq (%rsp), %rsi ;; callq 0 ;; ├─╼ exception frame offset: SP = FP - 0x50 -;; ╰─╼ exception handler: default handler, context at [SP+0x0], handler=0xef -;; jmp 0xf7 -;; ef: movq %rax, %rdx -;; jmp 0x132 -;; f7: movq %rax, %rdx -;; movq 8(%rsp), %rcx -;; movq 0x10(%rsp), %rax -;; movl %ecx, (%rax) -;; movq %rdx, %rax +;; ╰─╼ exception handler: default handler, context at [SP+0x0], handler=0x121 +;; movq 0x10(%rsp), %rcx +;; movq 8(%rsp), %rsi +;; movl %esi, (%rcx) ;; movq 0x20(%rsp), %rbx ;; movq 0x28(%rsp), %r12 ;; movq 0x30(%rsp), %r13 @@ -136,12 +131,14 @@ ;; movq %rbp, %rsp ;; popq %rbp ;; retq -;; 12b: jmp 0x132 -;; 130: ud2 -;; 132: movq (%rsp), %rsi -;; 136: movq 0x58(%rsi), %rcx -;; 13a: movq 0x68(%rsi), %rdi -;; 13e: movl $0x31, %edx -;; 143: callq *%rcx -;; 145: ud2 -;; 147: ud2 +;; 11a: jmp 0x121 +;; 11f: ud2 +;; 121: movq (%rsp), %rcx +;; 125: movq 0x58(%rcx), %rcx +;; 129: movq (%rsp), %rax +;; 12d: movq 0x68(%rax), %rdi +;; 131: movl $0x31, %edx +;; 136: movq (%rsp), %rsi +;; 13a: callq *%rcx +;; 13c: ud2 +;; 13e: ud2 diff --git a/tests/disas/component-model/direct-adapter-calls.wat b/tests/disas/component-model/direct-adapter-calls.wat index e3ce501b14d6..870554c73541 100644 --- a/tests/disas/component-model/direct-adapter-calls.wat +++ b/tests/disas/component-model/direct-adapter-calls.wat @@ -133,7 +133,6 @@ ;; block7: ;; @008e v11 = load.i64 notrap aligned readonly can_move region2 v0+112 ;; @008e v12 = load.i32 notrap aligned region3 v11 -;; @009a store notrap aligned region3 v12, v11 ;; @009c v16 = load.i64 notrap aligned readonly can_move region4 v0+72 ;; @009c try_call fn0(v16, v0, v2), sig1, block10(ret0), [ context v0, default: block6(exn0) ] ;; diff --git a/tests/disas/component-model/sync-adapter-calls-x64.wat b/tests/disas/component-model/sync-adapter-calls-x64.wat index 432e14bd40d4..a67c0ce87aff 100644 --- a/tests/disas/component-model/sync-adapter-calls-x64.wat +++ b/tests/disas/component-model/sync-adapter-calls-x64.wat @@ -58,7 +58,7 @@ ;; movq 0x18(%r10), %r10 ;; addq $0x70, %r10 ;; cmpq %rsp, %r10 -;; ja 0x156 +;; ja 0x154 ;; 39: subq $0x60, %rsp ;; movq %rbx, 0x30(%rsp) ;; movq %r12, 0x38(%rsp) @@ -76,8 +76,8 @@ ;; movl $0x17, %edx ;; callq *%rax ;; ├─╼ exception frame offset: SP = FP - 0x60 -;; ╰─╼ exception handler: default handler, context at [SP+0x20], handler=0x140 -;; jmp 0x13e +;; ╰─╼ exception handler: default handler, context at [SP+0x20], handler=0x13e +;; jmp 0x13c ;; 84: movq 0xe0(%rsi), %rdx ;; movl (%rdx), %r8d ;; movl $0, (%rdx) @@ -96,8 +96,7 @@ ;; movl $0, 0x84(%rdi) ;; movq %rbx, 0x88(%rdi) ;; movq 0xb0(%rsi), %rsi -;; movl (%rsi), %ebx -;; movl %ebx, (%rsi) +;; movl (%rsi), %esi ;; movq %r9, 0x88(%rdi) ;; movl %r10d, 0x80(%rdi) ;; movl %r11d, 0x84(%rdi) @@ -113,11 +112,11 @@ ;; movq %rbp, %rsp ;; popq %rbp ;; retq -;; 13e: ud2 -;; 140: movq 0x20(%rsp), %rsi -;; 145: movq 0x58(%rsi), %rax -;; 149: movq 0x68(%rsi), %rdi -;; 14d: movl $0x31, %edx -;; 152: callq *%rax +;; 13c: ud2 +;; 13e: movq 0x20(%rsp), %rsi +;; 143: movq 0x58(%rsi), %rax +;; 147: movq 0x68(%rsi), %rdi +;; 14b: movl $0x31, %edx +;; 150: callq *%rax +;; 152: ud2 ;; 154: ud2 -;; 156: ud2 diff --git a/tests/disas/component-model/sync-adapter-calls.wat b/tests/disas/component-model/sync-adapter-calls.wat index f5c9398bc6b1..73df6c06ee47 100644 --- a/tests/disas/component-model/sync-adapter-calls.wat +++ b/tests/disas/component-model/sync-adapter-calls.wat @@ -133,7 +133,6 @@ ;; store notrap aligned region5 v19, v20+136 ;; v26 = load.i64 notrap aligned readonly can_move region3 v3+176 ;; v27 = load.i32 notrap aligned region4 v26 -;; store notrap aligned region4 v27, v26 ;; jump block17 ;; ;; block17: From 2eb1c972193998f618d22f4b50d74dfebda2c566 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 4 Aug 2026 23:08:53 -0700 Subject: [PATCH 4/7] Lower fused adapter `trap` intrinsics to CLIF `trap` instructions Before, we would emit a call to a host function, passing the trap code as a constant argument. Now we emit a `trap ` instruction directly. This is a large improvement for our sync adapter disas tests. --- crates/cranelift/src/func_environ.rs | 121 +++++++++++++----- crates/cranelift/src/lib.rs | 10 ++ .../src/translate/code_translator.rs | 17 ++- crates/environ/src/compile/module_environ.rs | 3 + crates/environ/src/component/translate.rs | 1 + .../direct-adapter-calls-inlining.wat | 39 ++---- .../direct-adapter-calls-x64.wat | 33 ++--- .../component-model/direct-adapter-calls.wat | 31 ++--- .../sync-adapter-calls-x64.wat | 74 ++++------- .../component-model/sync-adapter-calls.wat | 44 ++----- 10 files changed, 182 insertions(+), 191 deletions(-) diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index 64311df3e192..52ac84585966 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -9,7 +9,7 @@ use crate::translate::{ }; use crate::trap::TranslateTrap; use crate::{ - BuiltinFunctionSignatures, TRAP_ARRAY_OUT_OF_BOUNDS, TRAP_GC_HEAP_CORRUPT, + BuiltinFunctionSignatures, Reachability, TRAP_ARRAY_OUT_OF_BOUNDS, TRAP_GC_HEAP_CORRUPT, TRAP_TABLE_OUT_OF_BOUNDS, }; use cranelift_codegen::cursor::FuncCursor; @@ -1807,7 +1807,7 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> { callee_index: FuncIndex, sig_ref: ir::SigRef, wasm_call_args: &[ir::Value], - ) -> WasmResult { + ) -> WasmResult> { let mut real_call_args = Vec::with_capacity(wasm_call_args.len() + 2); let caller_vmctx = self .builder @@ -1831,7 +1831,9 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> { let callee = self .env .get_or_create_defined_func_ref(self.builder.func, def_func_index); - return Ok(self.direct_call_inst(callee, &real_call_args)); + return Ok(Reachability::Reachable( + self.direct_call_inst(callee, &real_call_args), + )); } // Handle direct calls to imported functions. We use an indirect call @@ -1873,9 +1875,11 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> { let result = intrinsic_compiler .translate(*intrinsic, &real_call_args) .unwrap(); - Ok(result.into_iter().collect()) + Ok(Reachability::Reachable(result.into_iter().collect())) } else { - Ok(self.direct_call_inst(callee, &real_call_args)) + Ok(Reachability::Reachable( + self.direct_call_inst(callee, &real_call_args), + )) } } @@ -1887,37 +1891,53 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> { let callee = self .env .get_or_create_imported_func_ref(self.builder.func, callee_index); - Ok(self.direct_call_inst(callee, &real_call_args)) + Ok(Reachability::Reachable( + self.direct_call_inst(callee, &real_call_args), + )) } - // The guest-to-guest sync fast path: these adapter intrinsics are - // lowered inline rather than called, but only when concurrency - // support is enabled (the deferred thread state only exists then) - // and this isn't a tail call (the deferred frame must outlive the - // call). Otherwise fall back to the indirect call, which is also - // the out-of-line slow path the inline `exit` branches to. + // Fused adapter intrinsics that are lowered inline rather than + // called. Some(KnownFunc::FactIntrinsic(intrinsic)) => { - if self.env.tunables.concurrency_support { - debug_assert!(!self.tail); - match intrinsic { - FactInlineIntrinsic::EnterSyncCall => { - return Ok(self.lower_fact_enter_sync_call(&real_call_args)); - } - FactInlineIntrinsic::ExitSyncCall => { - return Ok(self.lower_fact_exit_sync_call( - callee_index, - sig_ref, - &real_call_args, - )); - } + match intrinsic { + FactInlineIntrinsic::Trap => { + self.lower_fact_trap(&real_call_args); + return Ok(Reachability::Unreachable); } + + // The guest-to-guest sync fast path: these adapter + // intrinsics are lowered inline rather than called, but + // only when concurrency support is enabled (the deferred + // thread state only exists then) and this isn't a tail call + // (the deferred frame must outlive the call). Otherwise + // fall back to the indirect call, which is also the + // out-of-line slow path the inline `exit` branches to. + FactInlineIntrinsic::EnterSyncCall if self.env.tunables.concurrency_support => { + debug_assert!(!self.tail); + return Ok(Reachability::Reachable( + self.lower_fact_enter_sync_call(&real_call_args), + )); + } + FactInlineIntrinsic::ExitSyncCall if self.env.tunables.concurrency_support => { + debug_assert!(!self.tail); + return Ok(Reachability::Reachable(self.lower_fact_exit_sync_call( + callee_index, + sig_ref, + &real_call_args, + ))); + } + FactInlineIntrinsic::EnterSyncCall | FactInlineIntrinsic::ExitSyncCall => {} } let func_addr = self.env.alias_regions.vmctx_vmfunction_import_wasm_call( &mut self.builder.cursor(), vmctx, callee_index, ); - Ok(self.indirect_call_inst(sig_ref, func_addr, &real_call_args)) + Ok(Reachability::Reachable(self.indirect_call_inst( + sig_ref, + func_addr, + &real_call_args, + ))) } Some(key) => panic!("unexpected kind of known-import function: {key:?}"), @@ -1931,7 +1951,11 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> { vmctx, callee_index, ); - Ok(self.indirect_call_inst(sig_ref, func_addr, &real_call_args)) + Ok(Reachability::Reachable(self.indirect_call_inst( + sig_ref, + func_addr, + &real_call_args, + ))) } } } @@ -1950,6 +1974,37 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> { abi == wasmtime_environ::Abi::Wasm && !self.tail && !self.env.tunables.debug_guest } + /// Inline lowering of a FACT adapter's `trap` intrinsic: raise the trap + /// directly rather than calling out to the host just to have it turn a + /// constant into an error. + fn lower_fact_trap(&mut self, real_call_args: &[ir::Value]) { + let &[_callee_vmctx, _caller_vmctx, trap_code] = real_call_args else { + panic!("wrong number of arguments for the FACT `trap` intrinsic"); + }; + + let def = self + .builder + .func + .dfg + .value_def(trap_code) + .inst() + .expect("FACT emits an instruction for this argument"); + let ir::InstructionData::UnaryImm { + opcode: ir::Opcode::Iconst, + imm, + } = self.builder.func.dfg.insts[def] + else { + panic!("FACT emits a UnaryImm for this argument") + }; + let byte = u8::try_from(imm.bits()) + .expect("FACT emits an immediate that fits in u8 for this argument"); + let trap = wasmtime_environ::Trap::from_u8(byte) + .expect("FACT emits a valid Trap discriminant for this argument"); + + self.env + .trap(self.builder, crate::env_trap_to_clif_trap(trap)); + } + /// Inline lowering of a FACT adapter's `enter-sync-call` intrinsic: push a /// `VMDeferredThread` onto an explicit stack slot and publish it as the /// store's current thread, deferring the heavyweight task bookkeeping the @@ -3406,6 +3461,8 @@ impl FuncEnvironment<'_> { ) } + /// Returns `None` when the call was lowered to an unconditional trap and so + /// everything after it is unreachable. See `Call::direct_call`. pub fn translate_call<'a>( &mut self, builder: &'a mut FunctionBuilder, @@ -3413,7 +3470,7 @@ impl FuncEnvironment<'_> { callee_index: FuncIndex, sig_ref: ir::SigRef, call_args: &[ir::Value], - ) -> WasmResult { + ) -> WasmResult> { Call::new(builder, self, srcloc).direct_call(callee_index, sig_ref, call_args) } @@ -3436,7 +3493,8 @@ impl FuncEnvironment<'_> { sig_ref: ir::SigRef, call_args: &[ir::Value], ) -> WasmResult<()> { - Call::new_tail(builder, self, srcloc).direct_call(callee_index, sig_ref, call_args)?; + let _ = + Call::new_tail(builder, self, srcloc).direct_call(callee_index, sig_ref, call_args)?; Ok(()) } @@ -6088,7 +6146,10 @@ impl FuncEnvironment<'_> { .signature .unwrap_module_type_index(); let sig_ref = self.get_or_create_interned_sig_ref(builder.func, ty); - self.translate_call(builder, Default::default(), func, sig_ref, &[])?; + match self.translate_call(builder, Default::default(), func, sig_ref, &[])? { + Reachability::Reachable(_) => {} + Reachability::Unreachable => return Ok(()), + } if self.tunables.consume_fuel { self.fuel_load_into_var(builder); } diff --git a/crates/cranelift/src/lib.rs b/crates/cranelift/src/lib.rs index 50c0b26cbe58..9e090c6f6a68 100644 --- a/crates/cranelift/src/lib.rs +++ b/crates/cranelift/src/lib.rs @@ -78,6 +78,16 @@ pub const TRAP_CAST_FAILURE: TrapCode = pub const TRAP_UNCAUGHT_EXCEPTION: TrapCode = TrapCode::unwrap_user(Trap::UncaughtException as u8 + TRAP_OFFSET); +/// The CLIF trap code for a Wasmtime trap code. +/// +/// This is the inverse of `clif_trap_to_env_trap`'s fallback arm, and is what +/// all of the `TRAP_*` constants above compute for their particular trap. Use +/// it when the trap isn't statically known, e.g. when it came out of an +/// adapter-module immediate. +const fn env_trap_to_clif_trap(trap: Trap) -> TrapCode { + TrapCode::unwrap_user(trap as u8 + TRAP_OFFSET) +} + /// Creates a new cranelift `Signature` with no wasm params/results for the /// given calling convention. /// diff --git a/crates/cranelift/src/translate/code_translator.rs b/crates/cranelift/src/translate/code_translator.rs index 6e1b49cb7070..088e0f2bd488 100644 --- a/crates/cranelift/src/translate/code_translator.rs +++ b/crates/cranelift/src/translate/code_translator.rs @@ -715,13 +715,16 @@ pub fn translate_operator( let mut args = environ.stacks.peekn(num_args).to_vec(); bitcast_wasm_params(environ, sig_ref, &mut args, builder); - let inst_results = environ.translate_call( - builder, - environ.next_srcloc, - function_index, - sig_ref, - &args, - )?; + let inst_results = unwrap_or_return_unreachable_state!( + environ, + environ.translate_call( + builder, + environ.next_srcloc, + function_index, + sig_ref, + &args, + )? + ); debug_assert_eq!( inst_results.len(), diff --git a/crates/environ/src/compile/module_environ.rs b/crates/environ/src/compile/module_environ.rs index 1c7961323aed..46209cea1b73 100644 --- a/crates/environ/src/compile/module_environ.rs +++ b/crates/environ/src/compile/module_environ.rs @@ -47,6 +47,9 @@ pub enum FactInlineIntrinsic { /// fall back to the out-of-line `exit-sync-call` libcall when the thread /// was promoted. ExitSyncCall, + /// `trap`: raise the trap named by the (always constant) trap-code + /// argument. + Trap, } /// A statically-known function import. diff --git a/crates/environ/src/component/translate.rs b/crates/environ/src/component/translate.rs index c4d7ed007edf..58a7ccb6b02e 100644 --- a/crates/environ/src/component/translate.rs +++ b/crates/environ/src/component/translate.rs @@ -635,6 +635,7 @@ impl<'a, 'data> Translator<'a, 'data> { CoreDef::Trampoline(index) => match translation.trampolines[*index] { Trampoline::EnterSyncCall => FactInlineIntrinsic::EnterSyncCall.into(), Trampoline::ExitSyncCall => FactInlineIntrinsic::ExitSyncCall.into(), + Trampoline::Trap => FactInlineIntrinsic::Trap.into(), _ => continue, }, diff --git a/tests/disas/component-model/direct-adapter-calls-inlining.wat b/tests/disas/component-model/direct-adapter-calls-inlining.wat index d7900b80b5ba..b69739b04ee6 100644 --- a/tests/disas/component-model/direct-adapter-calls-inlining.wat +++ b/tests/disas/component-model/direct-adapter-calls-inlining.wat @@ -60,7 +60,6 @@ ;; region2 = 1207959576 "VMFunctionImport+0x18" ;; region3 = 1476395008 "VMGlobalImport+0x0" ;; region4 = 402653184 "PublicGlobal" -;; region5 = 1207959560 "VMFunctionImport+0x8" ;; gv0 = vmctx ;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 ;; gv2 = load.i64 notrap aligned region1 gv1+24 @@ -83,57 +82,41 @@ ;; block2: ;; jump block6 ;; -;; block8(v5: i64): -;; jump block5 -;; ;; block6: ;; @00ee v3 = load.i64 notrap aligned readonly can_move region2 v0+72 ;; v9 = load.i64 notrap aligned readonly can_move region3 v3+136 ;; v10 = load.i32 notrap aligned region4 v9 -;; brif v10, block9, block10 -;; -;; block10: -;; v24 = load.i64 notrap aligned readonly can_move region5 v3+88 -;; v23 = load.i64 notrap aligned readonly can_move region2 v3+104 -;; v22 = iconst.i32 23 -;; try_call_indirect v24(v23, v3, v22), sig1, block11, [ context v3, default: block8(exn0) ] ; v22 = 23 -;; -;; block11: -;; trap user12 +;; trapz v10, user26 +;; jump block9 ;; ;; block9: ;; v11 = load.i64 notrap aligned readonly can_move region3 v3+112 ;; v12 = load.i32 notrap aligned region4 v11 +;; jump block12 +;; +;; block12: ;; jump block13 ;; ;; block13: -;; jump block14 +;; jump block11 ;; -;; block14: -;; jump block12 -;; -;; block12: +;; block11: ;; store.i32 notrap aligned region4 v10, v9 ;; jump block7 ;; ;; block7: ;; jump block4 ;; -;; block5: -;; v26 = iconst.i32 49 -;; call_indirect.i64 sig1, v24(v23, v3, v26) ; v26 = 49 -;; trap user12 -;; ;; block4: ;; jump block3 ;; ;; block3: -;; jump block15 +;; jump block14 ;; -;; block15: +;; block14: ;; @00f0 jump block1 ;; ;; block1: -;; v29 = iconst.i32 1276 -;; @00f0 return v29 ; v29 = 1276 +;; v24 = iconst.i32 1276 +;; @00f0 return v24 ; v24 = 1276 ;; } diff --git a/tests/disas/component-model/direct-adapter-calls-x64.wat b/tests/disas/component-model/direct-adapter-calls-x64.wat index 8831a79b6fc8..6dc56ab9e72d 100644 --- a/tests/disas/component-model/direct-adapter-calls-x64.wat +++ b/tests/disas/component-model/direct-adapter-calls-x64.wat @@ -87,7 +87,7 @@ ;; movq 0x18(%r10), %r10 ;; addq $0x60, %r10 ;; cmpq %rsp, %r10 -;; ja 0x13e +;; ja 0x100 ;; 79: subq $0x50, %rsp ;; movq %rbx, 0x20(%rsp) ;; movq %r12, 0x28(%rsp) @@ -101,24 +101,15 @@ ;; movq %rcx, 0x10(%rsp) ;; testl %esi, %esi ;; movq %rsi, 8(%rsp) -;; jne 0xd5 +;; je 0x102 ;; b9: movq (%rsp), %rdi -;; movq 0x58(%rdi), %rax -;; movq 0x68(%rdi), %rdi -;; movl $0x17, %edx -;; movq (%rsp), %rsi -;; callq *%rax -;; ├─╼ exception frame offset: SP = FP - 0x50 -;; ╰─╼ exception handler: default handler, context at [SP+0x0], handler=0x121 -;; jmp 0x11f -;; d5: movq (%rsp), %rcx -;; movq 0x70(%rcx), %rax +;; movq 0x70(%rdi), %rax ;; movl (%rax), %eax -;; movq 0x48(%rcx), %rdi +;; movq 0x48(%rdi), %rdi ;; movq (%rsp), %rsi ;; callq 0 ;; ├─╼ exception frame offset: SP = FP - 0x50 -;; ╰─╼ exception handler: default handler, context at [SP+0x0], handler=0x121 +;; ╰─╼ exception handler: default handler, context at [SP+0x0], handler=0xfe ;; movq 0x10(%rsp), %rcx ;; movq 8(%rsp), %rsi ;; movl %esi, (%rcx) @@ -131,14 +122,6 @@ ;; movq %rbp, %rsp ;; popq %rbp ;; retq -;; 11a: jmp 0x121 -;; 11f: ud2 -;; 121: movq (%rsp), %rcx -;; 125: movq 0x58(%rcx), %rcx -;; 129: movq (%rsp), %rax -;; 12d: movq 0x68(%rax), %rdi -;; 131: movl $0x31, %edx -;; 136: movq (%rsp), %rsi -;; 13a: callq *%rcx -;; 13c: ud2 -;; 13e: ud2 +;; fe: ud2 +;; 100: ud2 +;; 102: ud2 diff --git a/tests/disas/component-model/direct-adapter-calls.wat b/tests/disas/component-model/direct-adapter-calls.wat index 870554c73541..ca4167811926 100644 --- a/tests/disas/component-model/direct-adapter-calls.wat +++ b/tests/disas/component-model/direct-adapter-calls.wat @@ -101,7 +101,6 @@ ;; region2 = 1476395008 "VMGlobalImport+0x0" ;; region3 = 402653184 "PublicGlobal" ;; region4 = 1207959576 "VMFunctionImport+0x18" -;; region5 = 1207959560 "VMFunctionImport+0x8" ;; gv0 = vmctx ;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 ;; gv2 = load.i64 notrap aligned region1 gv1+24 @@ -119,24 +118,16 @@ ;; block4: ;; @0082 v6 = load.i64 notrap aligned readonly can_move region2 v0+136 ;; @0082 v7 = load.i32 notrap aligned region3 v6 -;; @0086 brif v7, block7, block8 -;; -;; block8: -;; @008a v10 = load.i64 notrap aligned readonly can_move region5 v0+88 -;; @008a v9 = load.i64 notrap aligned readonly can_move region4 v0+104 -;; @0088 v8 = iconst.i32 23 -;; @008a try_call_indirect v10(v9, v0, v8), sig0, block9, [ context v0, default: block6(exn0) ] ; v8 = 23 -;; -;; block9: -;; @008c trap user12 +;; @0086 trapz v7, user26 +;; @0086 jump block7 ;; ;; block7: -;; @008e v11 = load.i64 notrap aligned readonly can_move region2 v0+112 -;; @008e v12 = load.i32 notrap aligned region3 v11 -;; @009c v16 = load.i64 notrap aligned readonly can_move region4 v0+72 -;; @009c try_call fn0(v16, v0, v2), sig1, block10(ret0), [ context v0, default: block6(exn0) ] +;; @008e v10 = load.i64 notrap aligned readonly can_move region2 v0+112 +;; @008e v11 = load.i32 notrap aligned region3 v10 +;; @009c v15 = load.i64 notrap aligned readonly can_move region4 v0+72 +;; @009c try_call fn0(v15, v0, v2), sig1, block9(ret0), [ context v0, default: block6(exn0) ] ;; -;; block10(v17: i32): +;; block9(v16: i32): ;; @00a8 store.i32 notrap aligned region3 v7, v6 ;; @00aa jump block5 ;; @@ -144,15 +135,11 @@ ;; @00ab jump block2 ;; ;; block3: -;; v24 = load.i64 notrap aligned readonly can_move region5 v0+88 -;; v25 = load.i64 notrap aligned readonly can_move region4 v0+104 -;; @00ae v21 = iconst.i32 49 -;; @00b0 call_indirect sig0, v24(v25, v0, v21) ; v21 = 49 -;; @00b2 trap user12 +;; @00b0 trap user52 ;; ;; block2: ;; @00b4 jump block1 ;; ;; block1: -;; @00b4 return v17 +;; @00b4 return v16 ;; } diff --git a/tests/disas/component-model/sync-adapter-calls-x64.wat b/tests/disas/component-model/sync-adapter-calls-x64.wat index a67c0ce87aff..0715d21103a1 100644 --- a/tests/disas/component-model/sync-adapter-calls-x64.wat +++ b/tests/disas/component-model/sync-adapter-calls-x64.wat @@ -56,67 +56,45 @@ ;; movq %rsp, %rbp ;; movq 8(%rdi), %r10 ;; movq 0x18(%r10), %r10 -;; addq $0x70, %r10 +;; addq $0x30, %r10 ;; cmpq %rsp, %r10 -;; ja 0x154 -;; 39: subq $0x60, %rsp -;; movq %rbx, 0x30(%rsp) -;; movq %r12, 0x38(%rsp) -;; movq %r13, 0x40(%rsp) -;; movq %r14, 0x48(%rsp) -;; movq %r15, 0x50(%rsp) -;; movq 0x48(%rdi), %rsi -;; movq 0xc8(%rsi), %rax +;; ja 0xfb +;; 39: subq $0x30, %rsp +;; movq %rbx, 0x20(%rsp) +;; movq 0x48(%rdi), %rdi +;; movq 0xc8(%rdi), %rax ;; movl (%rax), %ecx ;; testl %ecx, %ecx -;; jne 0x84 -;; 6b: movq 0x58(%rsi), %rax -;; movq 0x68(%rsi), %rdi -;; movq %rsi, 0x20(%rsp) -;; movl $0x17, %edx -;; callq *%rax -;; ├─╼ exception frame offset: SP = FP - 0x60 -;; ╰─╼ exception handler: default handler, context at [SP+0x20], handler=0x13e -;; jmp 0x13c -;; 84: movq 0xe0(%rsi), %rdx -;; movl (%rdx), %r8d +;; je 0xfd +;; 57: movq 0xe0(%rdi), %rdx +;; movl (%rdx), %esi ;; movl $0, (%rdx) -;; movq 8(%rsi), %rdi -;; movq 0x88(%rdi), %r9 +;; movq 8(%rdi), %r8 +;; movq 0x88(%r8), %r9 ;; leaq (%rsp), %rbx ;; movq %r9, (%rsp) ;; movl $2, 8(%rsp) ;; movl $0, 0xc(%rsp) ;; movl $1, 0x10(%rsp) -;; movl 0x80(%rdi), %r10d +;; movl 0x80(%r8), %r10d ;; movl %r10d, 0x14(%rsp) -;; movl $0, 0x80(%rdi) -;; movl 0x84(%rdi), %r11d +;; movl $0, 0x80(%r8) +;; movl 0x84(%r8), %r11d ;; movl %r11d, 0x18(%rsp) -;; movl $0, 0x84(%rdi) -;; movq %rbx, 0x88(%rdi) -;; movq 0xb0(%rsi), %rsi -;; movl (%rsi), %esi -;; movq %r9, 0x88(%rdi) -;; movl %r10d, 0x80(%rdi) -;; movl %r11d, 0x84(%rdi) +;; movl $0, 0x84(%r8) +;; movq %rbx, 0x88(%r8) +;; movq 0xb0(%rdi), %rdi +;; movl (%rdi), %edi +;; movq %r9, 0x88(%r8) +;; movl %r10d, 0x80(%r8) +;; movl %r11d, 0x84(%r8) ;; movl %ecx, (%rax) -;; movl %r8d, (%rdx) +;; movl %esi, (%rdx) ;; movl $0x4fc, %eax -;; movq 0x30(%rsp), %rbx -;; movq 0x38(%rsp), %r12 -;; movq 0x40(%rsp), %r13 -;; movq 0x48(%rsp), %r14 -;; movq 0x50(%rsp), %r15 -;; addq $0x60, %rsp +;; movq 0x20(%rsp), %rbx +;; addq $0x30, %rsp ;; movq %rbp, %rsp ;; popq %rbp ;; retq -;; 13c: ud2 -;; 13e: movq 0x20(%rsp), %rsi -;; 143: movq 0x58(%rsi), %rax -;; 147: movq 0x68(%rsi), %rdi -;; 14b: movl $0x31, %edx -;; 150: callq *%rax -;; 152: ud2 -;; 154: ud2 +;; fb: ud2 +;; fd: ud2 diff --git a/tests/disas/component-model/sync-adapter-calls.wat b/tests/disas/component-model/sync-adapter-calls.wat index 73df6c06ee47..024d7db65d88 100644 --- a/tests/disas/component-model/sync-adapter-calls.wat +++ b/tests/disas/component-model/sync-adapter-calls.wat @@ -92,23 +92,12 @@ ;; block2: ;; jump block6 ;; -;; block8(v5: i64): -;; jump block5 -;; ;; block6: ;; @00ee v3 = load.i64 notrap aligned readonly can_move region2 v0+72 ;; v9 = load.i64 notrap aligned readonly can_move region3 v3+200 ;; v10 = load.i32 notrap aligned region4 v9 -;; brif v10, block9, block10 -;; -;; block10: -;; v53 = load.i64 notrap aligned readonly can_move region14 v3+88 -;; v52 = load.i64 notrap aligned readonly can_move region2 v3+104 -;; v51 = iconst.i32 23 -;; try_call_indirect v53(v52, v3, v51), sig1, block11, [ context v3, default: block8(exn0) ] ; v51 = 23 -;; -;; block11: -;; trap user12 +;; trapz v10, user26 +;; jump block9 ;; ;; block9: ;; v11 = load.i64 notrap aligned readonly can_move region3 v3+224 @@ -133,24 +122,24 @@ ;; store notrap aligned region5 v19, v20+136 ;; v26 = load.i64 notrap aligned readonly can_move region3 v3+176 ;; v27 = load.i32 notrap aligned region4 v26 +;; jump block16 +;; +;; block16: ;; jump block17 ;; ;; block17: -;; jump block18 +;; jump block11 ;; -;; block18: +;; block11: ;; jump block12 ;; ;; block12: -;; jump block13 -;; -;; block13: ;; store.i64 notrap aligned region5 v21, v20+136 ;; store.i32 notrap aligned region10 v22, v20+128 ;; store.i32 notrap aligned region12 v24, v20+132 -;; jump block15 +;; jump block14 ;; -;; block15: +;; block14: ;; store.i32 notrap aligned region4 v10, v9 ;; store.i32 notrap aligned region4 v12, v11 ;; jump block7 @@ -158,23 +147,16 @@ ;; block7: ;; jump block4 ;; -;; block5: -;; v61 = load.i64 notrap aligned readonly can_move region14 v3+88 -;; v62 = load.i64 notrap aligned readonly can_move region2 v3+104 -;; v48 = iconst.i32 49 -;; call_indirect sig1, v61(v62, v3, v48) ; v48 = 49 -;; trap user12 -;; ;; block4: ;; jump block3 ;; ;; block3: -;; jump block19 +;; jump block18 ;; -;; block19: +;; block18: ;; @00f0 jump block1 ;; ;; block1: -;; v54 = iconst.i32 1276 -;; @00f0 return v54 ; v54 = 1276 +;; v52 = iconst.i32 1276 +;; @00f0 return v52 ; v52 = 1276 ;; } From 8ad07498a360f6377179eb56d53add31ffcd79af Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 4 Aug 2026 23:27:45 -0700 Subject: [PATCH 5/7] Cranelift: don't lower dead `notrap` loads If a load is marked `notrap` then it is not side-effecting and we need not emit it when its loaded value is unused. Note that we *do* still have to increment the side effect color for these instructions to prevent merging/sinking loads across stores that could change the value they observe. This drops two dead vmctx flag loads from our component-model fused adapter's fast path. --- cranelift/codegen/src/inst_predicates.rs | 16 ++ cranelift/codegen/src/machinst/lower.rs | 25 ++- .../isa/aarch64/dead-notrap-load.clif | 111 ++++++++++++ .../isa/riscv64/dead-notrap-load.clif | 113 ++++++++++++ .../filetests/isa/s390x/dead-notrap-load.clif | 111 ++++++++++++ .../filetests/isa/x64/dead-notrap-load.clif | 164 ++++++++++++++++++ .../direct-adapter-calls-x64.wat | 32 ++-- .../sync-adapter-calls-x64.wat | 46 +++-- 8 files changed, 567 insertions(+), 51 deletions(-) create mode 100644 cranelift/filetests/filetests/isa/aarch64/dead-notrap-load.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/dead-notrap-load.clif create mode 100644 cranelift/filetests/filetests/isa/s390x/dead-notrap-load.clif create mode 100644 cranelift/filetests/filetests/isa/x64/dead-notrap-load.clif diff --git a/cranelift/codegen/src/inst_predicates.rs b/cranelift/codegen/src/inst_predicates.rs index 239e514f1715..244385cd536b 100644 --- a/cranelift/codegen/src/inst_predicates.rs +++ b/cranelift/codegen/src/inst_predicates.rs @@ -94,11 +94,27 @@ pub fn is_mergeable_for_egraph(func: &Function, inst: Inst) -> bool { /// Does the given instruction have any side-effect as per [has_side_effect], or else is a load, /// but not the get_pinned_reg opcode? +/// +/// Loads are included so that lowering colors them as side-effecting, which is +/// what keeps a load from being merged into a consumer across an intervening +/// store. Deciding whether a load has to be *emitted* is a different question; +/// see [`must_lower_even_if_unused`]. pub fn has_lowering_side_effect(func: &Function, inst: Inst) -> bool { let op = func.dfg.insts[inst].opcode(); op != Opcode::GetPinnedReg && (has_side_effect(func, inst) || op.can_load()) } +/// Must lowering emit the given instruction even when none of its results are +/// used? +/// +/// This is [has_lowering_side_effect] without its "or is a load" clause: a load +/// that is defined not to trap has no effect of its own, so if nothing wants the +/// value it read there is no reason to emit it. +pub fn must_lower_even_if_unused(func: &Function, inst: Inst) -> bool { + let op = func.dfg.insts[inst].opcode(); + op != Opcode::GetPinnedReg && has_side_effect(func, inst) +} + /// Is the given instruction a constant value (`iconst`, `fconst`) that can be /// represented in 64 bits? pub fn is_constant_64bit(func: &Function, inst: Inst) -> Option { diff --git a/cranelift/codegen/src/machinst/lower.rs b/cranelift/codegen/src/machinst/lower.rs index ad2ffd99e1f0..c006c6b64083 100644 --- a/cranelift/codegen/src/machinst/lower.rs +++ b/cranelift/codegen/src/machinst/lower.rs @@ -6,7 +6,9 @@ // top of it, e.g. the side-effect/coloring analysis and the scan support. use crate::entity::SecondaryMap; -use crate::inst_predicates::{has_lowering_side_effect, is_constant_64bit}; +use crate::inst_predicates::{ + has_lowering_side_effect, is_constant_64bit, must_lower_even_if_unused, +}; use crate::ir::{ ArgumentPurpose, Block, BlockArg, Constant, ConstantData, DataFlowGraph, ExternalName, Function, GlobalValue, GlobalValueData, Immediate, Inst, InstructionData, RelSourceLoc, SigRef, @@ -750,14 +752,19 @@ impl<'func, I: VCodeInst> Lower<'func, I> { // Are any outputs used at least once? let value_needed = self.is_any_inst_result_needed(inst); + + // Do we have to emit this instruction even though nothing uses its + // results? Note that this is not the same question as + // `has_side_effect` above: loads are colored as side-effecting so + // that load merging cannot move one across a store, but a load that + // is defined not to trap can simply be dropped when it is dead. + let must_lower = must_lower_even_if_unused(self.f, inst); + trace!( - "lower_clif_block: block {} inst {} ({:?}) is_branch {} side_effect {} value_needed {}", - block, - inst, - data, + "lower_clif_block: {block}, {inst}, ({data:?}), is_branch {}, \ + has_side_effect {has_side_effect}, must_lower {must_lower}, \ + value_needed {value_needed}", data.opcode().is_branch(), - has_side_effect, - value_needed, ); // Update scan state to color prior to this inst (as we are scanning @@ -777,11 +784,11 @@ impl<'func, I: VCodeInst> Lower<'func, I> { // order, and therefore **before** in reversed order. // Only emit value label aliases if the instruction will be lowered // (otherwise we want to keep using the earlier label instead). - self.emit_value_label_live_range_start_for_inst(inst, has_side_effect || value_needed); + self.emit_value_label_live_range_start_for_inst(inst, must_lower || value_needed); // Normal instruction: codegen if the instruction is side-effecting // or any of its outputs is used. - if has_side_effect || value_needed { + if must_lower || value_needed { trace!("lowering: inst {}: {}", inst, self.f.dfg.display_inst(inst)); let temp_regs = match backend.lower(self, inst) { Some(regs) => regs, diff --git a/cranelift/filetests/filetests/isa/aarch64/dead-notrap-load.clif b/cranelift/filetests/filetests/isa/aarch64/dead-notrap-load.clif new file mode 100644 index 000000000000..bd17c43e9ce0 --- /dev/null +++ b/cranelift/filetests/filetests/isa/aarch64/dead-notrap-load.clif @@ -0,0 +1,111 @@ +test compile precise-output +target aarch64 + +;; A `notrap` load has no side effect of its own, so when nothing uses the value +;; it read, lowering drops it. + +function %dead_notrap_load(i64) { +block0(v0: i64): + v1 = load.i64 notrap aligned v0 + return +} + +; VCode: +; block0: +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; ret + +;; Extending loads too. + +function %dead_notrap_uload8(i64) { +block0(v0: i64): + v1 = uload8.i32 notrap aligned v0 + return +} + +; VCode: +; block0: +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; ret + +;; Chains collapse within the single backward pass over the block: dropping the +;; second load is what makes the first one dead. + +function %dead_notrap_load_chain(i64) { +block0(v0: i64): + v1 = load.i64 notrap aligned readonly v0+176 + v2 = load.i32 notrap aligned v1 + return +} + +; VCode: +; block0: +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; ret + +;; The address computation survives if it is live for some other reason. + +function %dead_notrap_load_live_address(i64) -> i64 { +block0(v0: i64): + v1 = iconst.i64 8 + v2 = iadd v0, v1 + v3 = load.i64 notrap aligned v2 + return v2 +} + +; VCode: +; block0: +; add x0, x0, #8 +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; add x0, x0, #8 +; ret + +;; A load without `notrap` is defined to trap on inaccessible memory, so it is +;; still emitted even though its result is unused. + +function %dead_trapping_load(i64) { +block0(v0: i64): + v1 = load.i64 aligned v0 + return +} + +; VCode: +; block0: +; ldr x2, [x0] +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; ldr x2, [x0] ; trap: heap_oob +; ret + +;; Atomic loads are always side-effecting, `notrap` or not. + +function %dead_atomic_load(i64) { +block0(v0: i64): + v1 = atomic_load.i64 notrap aligned v0 + return +} + +; VCode: +; block0: +; ldar x2, [x0] +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; ldar x2, [x0] +; ret + diff --git a/cranelift/filetests/filetests/isa/riscv64/dead-notrap-load.clif b/cranelift/filetests/filetests/isa/riscv64/dead-notrap-load.clif new file mode 100644 index 000000000000..76c2791a86c0 --- /dev/null +++ b/cranelift/filetests/filetests/isa/riscv64/dead-notrap-load.clif @@ -0,0 +1,113 @@ +test compile precise-output +target riscv64 + +;; A `notrap` load has no side effect of its own, so when nothing uses the value +;; it read, lowering drops it. + +function %dead_notrap_load(i64) { +block0(v0: i64): + v1 = load.i64 notrap aligned v0 + return +} + +; VCode: +; block0: +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; ret + +;; Extending loads too. + +function %dead_notrap_uload8(i64) { +block0(v0: i64): + v1 = uload8.i32 notrap aligned v0 + return +} + +; VCode: +; block0: +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; ret + +;; Chains collapse within the single backward pass over the block: dropping the +;; second load is what makes the first one dead. + +function %dead_notrap_load_chain(i64) { +block0(v0: i64): + v1 = load.i64 notrap aligned readonly v0+176 + v2 = load.i32 notrap aligned v1 + return +} + +; VCode: +; block0: +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; ret + +;; The address computation survives if it is live for some other reason. + +function %dead_notrap_load_live_address(i64) -> i64 { +block0(v0: i64): + v1 = iconst.i64 8 + v2 = iadd v0, v1 + v3 = load.i64 notrap aligned v2 + return v2 +} + +; VCode: +; block0: +; addi a0,a0,8 +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; addi a0, a0, 8 +; ret + +;; A load without `notrap` is defined to trap on inaccessible memory, so it is +;; still emitted even though its result is unused. + +function %dead_trapping_load(i64) { +block0(v0: i64): + v1 = load.i64 aligned v0 + return +} + +; VCode: +; block0: +; ld a0,0(a0) +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; ld a0, 0(a0) ; trap: heap_oob +; ret + +;; Atomic loads are always side-effecting, `notrap` or not. + +function %dead_atomic_load(i64) { +block0(v0: i64): + v1 = atomic_load.i64 notrap aligned v0 + return +} + +; VCode: +; block0: +; atomic_load.i64 a0,(a0) +; ret +; +; Disassembled: +; block0: ; offset 0x0 +; fence rw, rw +; ld a0, 0(a0) ; trap: heap_oob +; fence r, rw +; ret + diff --git a/cranelift/filetests/filetests/isa/s390x/dead-notrap-load.clif b/cranelift/filetests/filetests/isa/s390x/dead-notrap-load.clif new file mode 100644 index 000000000000..941492749165 --- /dev/null +++ b/cranelift/filetests/filetests/isa/s390x/dead-notrap-load.clif @@ -0,0 +1,111 @@ +test compile precise-output +target s390x + +;; A `notrap` load has no side effect of its own, so when nothing uses the value +;; it read, lowering drops it. + +function %dead_notrap_load(i64) { +block0(v0: i64): + v1 = load.i64 notrap aligned v0 + return +} + +; VCode: +; block0: +; br %r14 +; +; Disassembled: +; block0: ; offset 0x0 +; br %r14 + +;; Extending loads too. + +function %dead_notrap_uload8(i64) { +block0(v0: i64): + v1 = uload8.i32 notrap aligned v0 + return +} + +; VCode: +; block0: +; br %r14 +; +; Disassembled: +; block0: ; offset 0x0 +; br %r14 + +;; Chains collapse within the single backward pass over the block: dropping the +;; second load is what makes the first one dead. + +function %dead_notrap_load_chain(i64) { +block0(v0: i64): + v1 = load.i64 notrap aligned readonly v0+176 + v2 = load.i32 notrap aligned v1 + return +} + +; VCode: +; block0: +; br %r14 +; +; Disassembled: +; block0: ; offset 0x0 +; br %r14 + +;; The address computation survives if it is live for some other reason. + +function %dead_notrap_load_live_address(i64) -> i64 { +block0(v0: i64): + v1 = iconst.i64 8 + v2 = iadd v0, v1 + v3 = load.i64 notrap aligned v2 + return v2 +} + +; VCode: +; block0: +; aghi %r2, 8 +; br %r14 +; +; Disassembled: +; block0: ; offset 0x0 +; aghi %r2, 8 +; br %r14 + +;; A load without `notrap` is defined to trap on inaccessible memory, so it is +;; still emitted even though its result is unused. + +function %dead_trapping_load(i64) { +block0(v0: i64): + v1 = load.i64 aligned v0 + return +} + +; VCode: +; block0: +; lg %r2, 0(%r2) +; br %r14 +; +; Disassembled: +; block0: ; offset 0x0 +; lg %r2, 0(%r2) ; trap: heap_oob +; br %r14 + +;; Atomic loads are always side-effecting, `notrap` or not. + +function %dead_atomic_load(i64) { +block0(v0: i64): + v1 = atomic_load.i64 notrap aligned v0 + return +} + +; VCode: +; block0: +; lg %r2, 0(%r2) +; br %r14 +; +; Disassembled: +; block0: ; offset 0x0 +; lg %r2, 0(%r2) +; br %r14 + diff --git a/cranelift/filetests/filetests/isa/x64/dead-notrap-load.clif b/cranelift/filetests/filetests/isa/x64/dead-notrap-load.clif new file mode 100644 index 000000000000..64f82a18e4c0 --- /dev/null +++ b/cranelift/filetests/filetests/isa/x64/dead-notrap-load.clif @@ -0,0 +1,164 @@ +test compile precise-output +target x86_64 + +;; A `notrap` load has no side effect of its own, so when nothing uses the value +;; it read, lowering drops it. + +function %dead_notrap_load(i64) { +block0(v0: i64): + v1 = load.i64 notrap aligned v0 + return +} + +; VCode: +; pushq %rbp +; movq %rsp, %rbp +; block0: +; movq %rbp, %rsp +; popq %rbp +; retq +; +; Disassembled: +; block0: ; offset 0x0 +; pushq %rbp +; movq %rsp, %rbp +; block1: ; offset 0x4 +; movq %rbp, %rsp +; popq %rbp +; retq + +;; Extending loads too. + +function %dead_notrap_uload8(i64) { +block0(v0: i64): + v1 = uload8.i32 notrap aligned v0 + return +} + +; VCode: +; pushq %rbp +; movq %rsp, %rbp +; block0: +; movq %rbp, %rsp +; popq %rbp +; retq +; +; Disassembled: +; block0: ; offset 0x0 +; pushq %rbp +; movq %rsp, %rbp +; block1: ; offset 0x4 +; movq %rbp, %rsp +; popq %rbp +; retq + +;; Chains collapse within the single backward pass over the block: dropping the +;; second load is what makes the first one dead. + +function %dead_notrap_load_chain(i64) { +block0(v0: i64): + v1 = load.i64 notrap aligned readonly v0+176 + v2 = load.i32 notrap aligned v1 + return +} + +; VCode: +; pushq %rbp +; movq %rsp, %rbp +; block0: +; movq %rbp, %rsp +; popq %rbp +; retq +; +; Disassembled: +; block0: ; offset 0x0 +; pushq %rbp +; movq %rsp, %rbp +; block1: ; offset 0x4 +; movq %rbp, %rsp +; popq %rbp +; retq + +;; The address computation survives if it is live for some other reason. + +function %dead_notrap_load_live_address(i64) -> i64 { +block0(v0: i64): + v1 = iconst.i64 8 + v2 = iadd v0, v1 + v3 = load.i64 notrap aligned v2 + return v2 +} + +; VCode: +; pushq %rbp +; movq %rsp, %rbp +; block0: +; leaq 8(%rdi), %rax +; movq %rbp, %rsp +; popq %rbp +; retq +; +; Disassembled: +; block0: ; offset 0x0 +; pushq %rbp +; movq %rsp, %rbp +; block1: ; offset 0x4 +; leaq 8(%rdi), %rax +; movq %rbp, %rsp +; popq %rbp +; retq + +;; A load without `notrap` is defined to trap on inaccessible memory, so it is +;; still emitted even though its result is unused. + +function %dead_trapping_load(i64) { +block0(v0: i64): + v1 = load.i64 aligned v0 + return +} + +; VCode: +; pushq %rbp +; movq %rsp, %rbp +; block0: +; movq (%rdi), %rdx +; movq %rbp, %rsp +; popq %rbp +; retq +; +; Disassembled: +; block0: ; offset 0x0 +; pushq %rbp +; movq %rsp, %rbp +; block1: ; offset 0x4 +; movq (%rdi), %rdx ; trap: heap_oob +; movq %rbp, %rsp +; popq %rbp +; retq + +;; Atomic loads are always side-effecting, `notrap` or not. + +function %dead_atomic_load(i64) { +block0(v0: i64): + v1 = atomic_load.i64 notrap aligned v0 + return +} + +; VCode: +; pushq %rbp +; movq %rsp, %rbp +; block0: +; movq (%rdi), %rdx +; movq %rbp, %rsp +; popq %rbp +; retq +; +; Disassembled: +; block0: ; offset 0x0 +; pushq %rbp +; movq %rsp, %rbp +; block1: ; offset 0x4 +; movq (%rdi), %rdx +; movq %rbp, %rsp +; popq %rbp +; retq diff --git a/tests/disas/component-model/direct-adapter-calls-x64.wat b/tests/disas/component-model/direct-adapter-calls-x64.wat index 6dc56ab9e72d..c5cb56ee8fbe 100644 --- a/tests/disas/component-model/direct-adapter-calls-x64.wat +++ b/tests/disas/component-model/direct-adapter-calls-x64.wat @@ -87,7 +87,7 @@ ;; movq 0x18(%r10), %r10 ;; addq $0x60, %r10 ;; cmpq %rsp, %r10 -;; ja 0x100 +;; ja 0xfd ;; 79: subq $0x50, %rsp ;; movq %rbx, 0x20(%rsp) ;; movq %r12, 0x28(%rsp) @@ -96,23 +96,21 @@ ;; movq %r15, 0x40(%rsp) ;; movq %rdi, (%rsp) ;; movq (%rsp), %rdi -;; movq 0x88(%rdi), %rcx -;; movl (%rcx), %esi -;; movq %rcx, 0x10(%rsp) -;; testl %esi, %esi -;; movq %rsi, 8(%rsp) -;; je 0x102 -;; b9: movq (%rsp), %rdi -;; movq 0x70(%rdi), %rax -;; movl (%rax), %eax +;; movq 0x88(%rdi), %r10 +;; movl (%r10), %r11d +;; movq %r10, 0x10(%rsp) +;; testl %r11d, %r11d +;; movq %r11, 8(%rsp) +;; je 0xff +;; bb: movq (%rsp), %rdi ;; movq 0x48(%rdi), %rdi ;; movq (%rsp), %rsi ;; callq 0 ;; ├─╼ exception frame offset: SP = FP - 0x50 -;; ╰─╼ exception handler: default handler, context at [SP+0x0], handler=0xfe -;; movq 0x10(%rsp), %rcx -;; movq 8(%rsp), %rsi -;; movl %esi, (%rcx) +;; ╰─╼ exception handler: default handler, context at [SP+0x0], handler=0xfb +;; movq 0x10(%rsp), %r10 +;; movq 8(%rsp), %r11 +;; movl %r11d, (%r10) ;; movq 0x20(%rsp), %rbx ;; movq 0x28(%rsp), %r12 ;; movq 0x30(%rsp), %r13 @@ -122,6 +120,6 @@ ;; movq %rbp, %rsp ;; popq %rbp ;; retq -;; fe: ud2 -;; 100: ud2 -;; 102: ud2 +;; fb: ud2 +;; fd: ud2 +;; ff: ud2 diff --git a/tests/disas/component-model/sync-adapter-calls-x64.wat b/tests/disas/component-model/sync-adapter-calls-x64.wat index 0715d21103a1..918098dce113 100644 --- a/tests/disas/component-model/sync-adapter-calls-x64.wat +++ b/tests/disas/component-model/sync-adapter-calls-x64.wat @@ -56,45 +56,41 @@ ;; movq %rsp, %rbp ;; movq 8(%rdi), %r10 ;; movq 0x18(%r10), %r10 -;; addq $0x30, %r10 +;; addq $0x20, %r10 ;; cmpq %rsp, %r10 -;; ja 0xfb -;; 39: subq $0x30, %rsp -;; movq %rbx, 0x20(%rsp) +;; ja 0xe6 +;; 39: subq $0x20, %rsp ;; movq 0x48(%rdi), %rdi ;; movq 0xc8(%rdi), %rax ;; movl (%rax), %ecx ;; testl %ecx, %ecx -;; je 0xfd -;; 57: movq 0xe0(%rdi), %rdx +;; je 0xe8 +;; 52: movq 0xe0(%rdi), %rdx ;; movl (%rdx), %esi ;; movl $0, (%rdx) -;; movq 8(%rdi), %r8 -;; movq 0x88(%r8), %r9 -;; leaq (%rsp), %rbx -;; movq %r9, (%rsp) +;; movq 8(%rdi), %rdi +;; movq 0x88(%rdi), %r8 +;; leaq (%rsp), %r10 +;; movq %r8, (%rsp) ;; movl $2, 8(%rsp) ;; movl $0, 0xc(%rsp) ;; movl $1, 0x10(%rsp) -;; movl 0x80(%r8), %r10d -;; movl %r10d, 0x14(%rsp) -;; movl $0, 0x80(%r8) -;; movl 0x84(%r8), %r11d +;; movl 0x80(%rdi), %r9d +;; movl %r9d, 0x14(%rsp) +;; movl $0, 0x80(%rdi) +;; movl 0x84(%rdi), %r11d ;; movl %r11d, 0x18(%rsp) -;; movl $0, 0x84(%r8) -;; movq %rbx, 0x88(%r8) -;; movq 0xb0(%rdi), %rdi -;; movl (%rdi), %edi -;; movq %r9, 0x88(%r8) -;; movl %r10d, 0x80(%r8) -;; movl %r11d, 0x84(%r8) +;; movl $0, 0x84(%rdi) +;; movq %r10, 0x88(%rdi) +;; movq %r8, 0x88(%rdi) +;; movl %r9d, 0x80(%rdi) +;; movl %r11d, 0x84(%rdi) ;; movl %ecx, (%rax) ;; movl %esi, (%rdx) ;; movl $0x4fc, %eax -;; movq 0x20(%rsp), %rbx -;; addq $0x30, %rsp +;; addq $0x20, %rsp ;; movq %rbp, %rsp ;; popq %rbp ;; retq -;; fb: ud2 -;; fd: ud2 +;; e6: ud2 +;; e8: ud2 From c946840053ae2f9c53f2446728fddfb37e9bd911 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Wed, 5 Aug 2026 00:45:35 -0700 Subject: [PATCH 6/7] Use precise alias regions for statically-known entity imports Accesses of statically-known globals, memories, and tables now use `AliasRegionKey::Defined{Global,Memory,Table}` rather than the conservative `Public{Global,Memory,Table}` region shared by every entity of that kind which crosses a module boundary. However, unlike `known_imported_functions`, this requires an extra condition: every module that ever imports an entity must always import that same entity. Otherwise a function that accesses, e.g., a memory via the conservative region could be inlined into one that uses the precise region, and accessing the same bytes through two different alias regions is invalid and leads to miscompiles. That is, all of the importing modules and the defining module must agree on the alias region. --- crates/cranelift/src/alias_region.rs | 33 +- crates/cranelift/src/func_environ.rs | 114 +++-- crates/environ/src/collections/entity_set.rs | 13 +- crates/environ/src/compile/module_environ.rs | 69 ++- crates/environ/src/component/translate.rs | 406 ++++++++++++++---- crates/environ/src/module.rs | 7 + .../known-imported-adapter-memory.wat | 266 ++++++++++++ .../known-imported-canonical-abi-memory.wat | 66 +++ .../known-imported-entities.wat | 202 +++++++++ ...e-instantiations-makes-imports-unknown.wat | 300 +++++++++++++ 10 files changed, 1323 insertions(+), 153 deletions(-) create mode 100644 tests/disas/component-model/known-imported-adapter-memory.wat create mode 100644 tests/disas/component-model/known-imported-canonical-abi-memory.wat create mode 100644 tests/disas/component-model/known-imported-entities.wat create mode 100644 tests/disas/component-model/multiple-instantiations-makes-imports-unknown.wat diff --git a/crates/cranelift/src/alias_region.rs b/crates/cranelift/src/alias_region.rs index 873f34b7a21b..89dc2a2a3096 100644 --- a/crates/cranelift/src/alias_region.rs +++ b/crates/cranelift/src/alias_region.rs @@ -76,8 +76,8 @@ enum AliasRegionKey { offset: u32, }, - /// An imported or exported memory access (shared across all - /// imported/exported memories). + /// An access of a memory that crosses a module boundary and whose + /// definition we do not statically know (shared across all such memories). PublicMemory, /// A defined memory access. @@ -88,8 +88,8 @@ enum AliasRegionKey { index: DefinedMemoryIndex, }, - /// An imported or exported table access (shared across all - /// imported/exported tables). + /// An access of a table that crosses a module boundary and whose definition + /// we do not statically know (shared across all such tables). PublicTable, /// A defined table access. @@ -100,8 +100,8 @@ enum AliasRegionKey { index: DefinedTableIndex, }, - /// An imported or exported global access (shared across all - /// imported/exported globals). + /// An access of a global that crosses a module boundary and whose definition + /// we do not statically know (shared across all such globals). PublicGlobal, /// A defined global access. @@ -722,14 +722,13 @@ where self.region(func, AliasRegionKey::GcHeap) } - /// Get the alias region for an imported or exported memory access (shared - /// across all imported/exported memories). + /// Get the alias region shared by all memories that cross a module boundary + /// and whose definition we do not statically know. pub fn public_memory_region(&mut self, func: &mut ir::Function) -> ir::AliasRegion { self.region(func, AliasRegionKey::PublicMemory) } - /// Get the alias region for accessing a defined memory that is not - /// exported. + /// Get the alias region for accessing a particular defined memory. pub fn defined_memory_region( &mut self, func: &mut ir::Function, @@ -739,14 +738,13 @@ where self.region(func, AliasRegionKey::DefinedMemory { module, index }) } - /// Get the alias region for an imported or exported table access (shared - /// across all imported/exported memories). + /// Get the alias region shared by all tables that cross a module boundary + /// and whose definition we do not statically know. pub fn public_table_region(&mut self, func: &mut ir::Function) -> ir::AliasRegion { self.region(func, AliasRegionKey::PublicTable) } - /// Get the alias region for accessing a defined table that is not - /// exported. + /// Get the alias region for accessing a particular defined table. pub fn defined_table_region( &mut self, func: &mut ir::Function, @@ -756,14 +754,13 @@ where self.region(func, AliasRegionKey::DefinedTable { module, index }) } - /// Get the alias region for an imported or exported global access (shared - /// across all imported/exported memories). + /// Get the alias region shared by all globals that cross a module boundary + /// and whose definition we do not statically know. pub fn public_global_region(&mut self, func: &mut ir::Function) -> ir::AliasRegion { self.region(func, AliasRegionKey::PublicGlobal) } - /// Get the alias region for accessing a defined global that is not - /// exported. + /// Get the alias region for accessing a particular defined global. pub fn defined_global_region( &mut self, func: &mut ir::Function, diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index 52ac84585966..a6be5923757e 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -347,25 +347,39 @@ impl<'module_environment> FuncEnvironment<'module_environment> { func: &mut Function, memory: MemoryIndex, ) -> ir::AliasRegion { - if self.module.is_exported_memory(memory) { - // A function that operates on an exported defined memory can be - // inlined into a different module caller, where that that caller's - // module also imports that exported memory. That caller will access - // the memory with `AliasRegionKey::PublicMemory`, so we must also - // conservatively do the same here, even though we potentially know - // the precise static module index and defined memory index, because - // memory accessed with two different alias regions must not - // actually alias, or else we will get miscompiles. - self.alias_regions.public_memory_region(func) - } else { - match self.module.defined_memory_index(memory) { - Some(def) => self.alias_regions.defined_memory_region( - func, - self.translation.module_index(), - def, - ), - None => self.alias_regions.public_memory_region(func), + match self.module.defined_memory_index(memory) { + // A memory defined by this module. When it is exported, a function + // that operates on it can be inlined into a caller in a different + // module that imports that memory, and vice versa. That other module + // accesses the memory with `AliasRegionKey::PublicMemory` unless it + // statically knows that its import is always this memory, so we can + // only use this memory's precise region when every module that may + // import it does know that. Memory accessed with two different alias + // regions must not actually alias, or else we will get miscompiles. + Some(def) => { + if self.module.is_exported_memory(memory) + && !self.translation.memories_known_to_importers.contains(def) + { + self.alias_regions.public_memory_region(func) + } else { + self.alias_regions.defined_memory_region( + func, + self.translation.module_index(), + def, + ) + } } + + // A memory imported by this module: use the precise region when we + // statically know which defined memory always satisfies the import + // and everything else that imports it knows the same. + None => match self.translation.known_imported_memories[memory] { + Some(known) => { + self.alias_regions + .defined_memory_region(func, known.module, known.index) + } + None => self.alias_regions.public_memory_region(func), + }, } } @@ -374,18 +388,28 @@ impl<'module_environment> FuncEnvironment<'module_environment> { func: &mut Function, table: TableIndex, ) -> ir::AliasRegion { - if self.module.is_exported_table(table) { - // See the comment in `memory_alias_region` for details. - self.alias_regions.public_table_region(func) - } else { - match self.module.defined_table_index(table) { - Some(def) => self.alias_regions.defined_table_region( - func, - self.translation.module_index(), - def, - ), - None => self.alias_regions.public_table_region(func), + // See the comments in `memory_alias_region` for details. + match self.module.defined_table_index(table) { + Some(def) => { + if self.module.is_exported_table(table) + && !self.translation.tables_known_to_importers.contains(def) + { + self.alias_regions.public_table_region(func) + } else { + self.alias_regions.defined_table_region( + func, + self.translation.module_index(), + def, + ) + } } + None => match self.translation.known_imported_tables[table] { + Some(known) => { + self.alias_regions + .defined_table_region(func, known.module, known.index) + } + None => self.alias_regions.public_table_region(func), + }, } } @@ -394,18 +418,28 @@ impl<'module_environment> FuncEnvironment<'module_environment> { func: &mut Function, global: GlobalIndex, ) -> ir::AliasRegion { - if self.module.is_exported_global(global) { - // See the comment in `memory_alias_region` for details. - self.alias_regions.public_global_region(func) - } else { - match self.module.defined_global_index(global) { - Some(def) => self.alias_regions.defined_global_region( - func, - self.translation.module_index(), - def, - ), - None => self.alias_regions.public_global_region(func), + // See the comments in `memory_alias_region` for details. + match self.module.defined_global_index(global) { + Some(def) => { + if self.module.is_exported_global(global) + && !self.translation.globals_known_to_importers.contains(def) + { + self.alias_regions.public_global_region(func) + } else { + self.alias_regions.defined_global_region( + func, + self.translation.module_index(), + def, + ) + } } + None => match self.translation.known_imported_globals[global] { + Some(known) => { + self.alias_regions + .defined_global_region(func, known.module, known.index) + } + None => self.alias_regions.public_global_region(func), + }, } } diff --git a/crates/environ/src/collections/entity_set.rs b/crates/environ/src/collections/entity_set.rs index af4a2d03efe5..fd699dba4c69 100644 --- a/crates/environ/src/collections/entity_set.rs +++ b/crates/environ/src/collections/entity_set.rs @@ -3,7 +3,7 @@ use wasmtime_core::error::OutOfMemory; /// Like `cranelift_entity::EntitySet` but enforces fallible allocation for all /// methods that allocate. -#[derive(Debug, Default)] +#[derive(Debug)] pub struct TryEntitySet where K: EntityRef, @@ -11,6 +11,17 @@ where inner: cranelift_entity::EntitySet, } +impl Default for TryEntitySet +where + K: EntityRef, +{ + fn default() -> Self { + Self { + inner: Default::default(), + } + } +} + impl TryEntitySet where K: EntityRef, diff --git a/crates/environ/src/compile/module_environ.rs b/crates/environ/src/compile/module_environ.rs index 46209cea1b73..9a6e58b36958 100644 --- a/crates/environ/src/compile/module_environ.rs +++ b/crates/environ/src/compile/module_environ.rs @@ -4,12 +4,13 @@ use crate::module::{ }; use crate::prelude::*; use crate::{ - ConstExpr, ConstOp, DataIndex, DefinedFuncIndex, DefinedGlobalIndex, ElemIndex, - EngineOrModuleTypeIndex, EntityIndex, EntityType, FuncIndex, FuncKey, GlobalIndex, IndexType, - MemoryIndex, MemoryInitializer, ModuleInternedTypeIndex, ModuleStartup, ModuleTypesBuilder, - PanicOnOom as _, PassiveElemIndex, PrimaryMap, RuntimeDataIndex, StaticModuleIndex, TableIndex, - TableInitialValue, TableInitialization, Tag, TagIndex, Tunables, TypeConvert, TypeIndex, - WasmHeapTopType, WasmHeapType, WasmResult, WasmValType, WasmparserTypeConverter, + ConstExpr, ConstOp, DataIndex, DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, + DefinedTableIndex, ElemIndex, EngineOrModuleTypeIndex, EntityIndex, EntityType, FuncIndex, + FuncKey, GlobalIndex, IndexType, MemoryIndex, MemoryInitializer, ModuleInternedTypeIndex, + ModuleStartup, ModuleTypesBuilder, PanicOnOom as _, PassiveElemIndex, PrimaryMap, + RuntimeDataIndex, StaticModuleIndex, TableIndex, TableInitialValue, TableInitialization, Tag, + TagIndex, Tunables, TypeConvert, TypeIndex, WasmHeapTopType, WasmHeapType, WasmResult, + WasmValType, WasmparserTypeConverter, }; use alloc::borrow::Cow; use cranelift_entity::SecondaryMap; @@ -73,6 +74,15 @@ impl From for KnownFunc { } } +/// A statically-known import of a core Wasm global, memory, or table. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct KnownEntity { + /// The module that defines this entity. + pub module: StaticModuleIndex, + /// The entity's index in the defining module's defined-entity index space. + pub index: T, +} + /// The result of translating via `ModuleEnvironment`. /// /// Function bodies are not yet translated, and data initializers have not yet @@ -108,6 +118,47 @@ pub struct ModuleTranslation<'data> { /// `FuncKey::FactInlineIntrinsic`s. pub known_imported_functions: SecondaryMap>, + /// For each imported global, memory, or table, the single statically-known + /// defined entity that always satisfies that import, if any. + /// + /// This is used to access the entity via the defining module's precise + /// `AliasRegionKey::Defined{Global,Memory,Table}` region instead of the + /// conservative `AliasRegionKey::Public{Global,Memory,Table}` region that is + /// shared by every entity of that kind which crosses a module boundary. + /// + /// XXX: Being known requires more here than it does for functions: it is + /// not enough that *this* module's import is always the same entity, + /// *every* module that may import that entity must also always import that + /// same entity. Otherwise a function from one of those other modules, which + /// accesses the entity via the conservative region, could be inlined next + /// to one of our accesses via the precise region, and accessing the same + /// memory through two different alias regions is invalid. + pub known_imported_globals: SecondaryMap>>, + + /// Same as `known_imported_globals`, but for memories. + pub known_imported_memories: SecondaryMap>>, + + /// Same as `known_imported_globals`, but for tables. + pub known_imported_tables: SecondaryMap>>, + + /// For each global defined by this module, whether every module that may + /// import this global always imports exactly this global. + /// + /// When this holds, accesses of the global may use its precise + /// `AliasRegionKey::DefinedGlobal` region even when the global is exported, + /// because every module that can reach it agrees on that same region. This + /// is vacuously true of globals that nothing in the component imports. + /// + /// This can only be determined by looking at the whole component, so it is + /// always `false` for standalone modules. + pub globals_known_to_importers: TryEntitySet, + + /// Same as [`Self::globals_known_to_importers`], but for memories. + pub memories_known_to_importers: TryEntitySet, + + /// Same as [`Self::globals_known_to_importers`], but for tables. + pub tables_known_to_importers: TryEntitySet, + /// A list of type signatures which are considered exported from this /// module, or those that can possibly be called. This list is sorted, and /// trampolines for each of these signatures are required. @@ -229,6 +280,12 @@ impl<'data> ModuleTranslation<'data> { wasm_module_offset: 0, function_body_inputs: PrimaryMap::default(), known_imported_functions: SecondaryMap::default(), + known_imported_globals: SecondaryMap::default(), + known_imported_memories: SecondaryMap::default(), + known_imported_tables: SecondaryMap::default(), + globals_known_to_importers: TryEntitySet::default(), + memories_known_to_importers: TryEntitySet::default(), + tables_known_to_importers: TryEntitySet::default(), exported_signatures: Vec::default(), debuginfo: DebugInfoData::default(), has_unparsed_debuginfo: false, diff --git a/crates/environ/src/component/translate.rs b/crates/environ/src/component/translate.rs index 58a7ccb6b02e..f355051647a4 100644 --- a/crates/environ/src/component/translate.rs +++ b/crates/environ/src/component/translate.rs @@ -3,15 +3,16 @@ use crate::component::dfg::AbstractInstantiations; use crate::component::*; use crate::prelude::*; use crate::{ - EngineOrModuleTypeIndex, EntityIndex, FactInlineIntrinsic, FuncKey, ModuleEnvironment, + DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, EngineOrModuleTypeIndex, + EntityIndex, FactInlineIntrinsic, FuncKey, KnownEntity, ModuleEnvironment, ModuleInternedTypeIndex, ModuleTranslation, ModuleTypesBuilder, PrimaryMap, ScopeVec, TagIndex, Tunables, TypeConvert, WasmHeapType, WasmResult, WasmValType, }; use core::str::FromStr; -use cranelift_entity::SecondaryMap; use cranelift_entity::packed_option::PackedOption; +use cranelift_entity::{EntityRef, SecondaryMap}; use indexmap::IndexMap; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::mem; use wasmparser::component_types::{ AliasableResourceId, ComponentCoreModuleTypeId, ComponentDefinedTypeId, ComponentEntityType, @@ -19,6 +20,7 @@ use wasmparser::component_types::{ }; use wasmparser::types::Types; use wasmparser::{Chunk, ComponentExternName, Encoding, Parser, Payload, Validator}; +use wasmtime_core::alloc::PanicOnOom; mod adapt; pub use self::adapt::*; @@ -549,12 +551,17 @@ impl<'a, 'data> Translator<'a, 'data> { let translation = component.finish(self.types.types_mut_for_inlining(), self.result.types_ref())?; - self.analyze_function_imports(&translation); + self.analyze_imports(&translation); Ok((translation, self.static_modules)) } - fn analyze_function_imports(&mut self, translation: &ComponentTranslation) { + /// Record everything we statically know about each module's imports. + /// + /// See `ModuleTranslation::known_imported_functions` and + /// `ModuleTranslation::known_imported_globals` for how we can optimize + /// lowering based on this information. + fn analyze_imports(&mut self, translation: &ComponentTranslation) { // First, abstract interpret the initializers to create a map from each // static module to its abstract set of instantiations. let mut instantiations = SecondaryMap::::new(); @@ -583,106 +590,211 @@ impl<'a, 'data> Translator<'a, 'data> { } } + // Third, find the globals, memories, and tables whose identity is not + // statically known to everything that can access them. Note that this + // is a property of the whole component and not of a single module's + // instantiations; see `ModuleTranslation::known_imported_globals` for + // details. + let ambiguous = ambiguous_entities(translation, &instantiations, &instance_to_module); + + // Fourth, record which of each module's own defined entities all of + // their importers agree on, which lets those modules use a precise alias + // region for them even when they are exported. + for (module, translation) in self.static_modules.iter_mut() { + for i in 0..translation.module.num_defined_globals() { + let index = DefinedGlobalIndex::new(i); + let global = translation.module.global_index(index); + if !ambiguous.contains(&(module, EntityIndex::Global(global))) { + translation + .globals_known_to_importers + .insert(index) + .panic_on_oom(); + } + } + for i in 0..translation.module.num_defined_memories() { + let index = DefinedMemoryIndex::new(i); + let memory = translation.module.memory_index(index); + if !ambiguous.contains(&(module, EntityIndex::Memory(memory))) { + translation + .memories_known_to_importers + .insert(index) + .panic_on_oom(); + } + } + for i in 0..translation.module.num_defined_tables() { + let index = DefinedTableIndex::new(i); + let table = translation.module.table_index(index); + if !ambiguous.contains(&(module, EntityIndex::Table(table))) { + translation + .tables_known_to_importers + .insert(index) + .panic_on_oom(); + } + } + } + // Finally, iterate over our instantiations and record statically-known - // function imports so that they can get translated into direct calls - // (and eventually get inlined) rather than indirect calls through the - // imports table. + // imports: function imports so that they can get translated into direct + // calls (and eventually get inlined) rather than indirect calls through + // the imports table; and global, memory, and table imports so that they + // can get precise alias regions instead of the conservative regions + // shared by everything that crosses a module boundary. for (module, instantiations) in instantiations.iter() { let args = match instantiations { dfg::AbstractInstantiations::Many | dfg::AbstractInstantiations::None => continue, dfg::AbstractInstantiations::One(args) => args, }; - let mut imported_func_counter = 0_u32; for (i, arg) in args.iter().enumerate() { - // Only consider function imports. - let (_, _, crate::types::EntityType::Function(_)) = - self.static_modules[module].module.import(i).unwrap() - else { - continue; - }; - - let imported_func = FuncIndex::from_u32(imported_func_counter); - imported_func_counter += 1; - debug_assert!( - self.static_modules[module] - .module - .defined_func_index(imported_func) - .is_none() - ); - - let known_func = match arg { - CoreDef::InstanceFlags(_) => unreachable!("instance flags are not a function"), - CoreDef::TaskMayBlock => unreachable!("task_may_block is not a function"), - - // We could in theory inline these trampolines, so it could - // potentially make sense to record that we know this - // imported function is this particular trampoline. However, - // everything else is based around (module, - // defined-function) pairs and these trampolines don't fit - // that paradigm. Also, inlining trampolines gets really - // tricky when we consider the stack pointer, frame pointer, - // and return address note-taking that they do for the - // purposes of stack walking. We could, with enough effort, - // turn them into direct calls even though we probably - // wouldn't ever inline them, but it just doesn't seem worth - // the effort. - // - // That said, a couple of adapter trampolines are lowered - // inline during translation. We record these here so - // `FuncEnvironment` recognizes them. All other trampolines - // remain indirect calls. - CoreDef::Trampoline(index) => match translation.trampolines[*index] { - Trampoline::EnterSyncCall => FactInlineIntrinsic::EnterSyncCall.into(), - Trampoline::ExitSyncCall => FactInlineIntrinsic::ExitSyncCall.into(), - Trampoline::Trap => FactInlineIntrinsic::Trap.into(), - _ => continue, - }, - - // This import is a compile-time builtin intrinsic, we - // should inline its implementation during function - // translation. - CoreDef::UnsafeIntrinsic(i) => FuncKey::UnsafeIntrinsic(Abi::Wasm, *i).into(), - - // This imported function is an export from another - // instance, a perfect candidate for becoming an inlinable - // direct call! - CoreDef::Export(export) => { - let Some(arg_module) = &instance_to_module[export.instance].expand() else { - // Instance of a dynamic module that is not part of - // this component, not a statically-known module - // inside this component. We have to do an indirect - // call. - continue; - }; - - let ExportItem::Index(EntityIndex::Function(arg_func)) = &export.item + // Record that this global, memory, or table import is always the + // same defined entity, when we know that and when everything + // else that imports that entity knows it too. + macro_rules! record_known_entity { + ($variant:ident, $imported:expr, $defined_index:ident, $known:ident) => {{ + let Some((arg_module, EntityIndex::$variant(arg_entity))) = + unambiguous_entity(&instance_to_module, &ambiguous, arg) else { - unreachable!("function imports must be functions") + continue; }; - - let Some(arg_module_def_func) = self.static_modules[*arg_module] + let Some(index) = self.static_modules[arg_module] .module - .defined_func_index(*arg_func) + .$defined_index(arg_entity) else { - // TODO: we should ideally follow re-export chains - // to bottom out the instantiation argument in - // either a definition or an import at the root - // component boundary. In practice, this pattern is - // rare, so following these chains is left for the - // Future. continue; }; + assert!(self.static_modules[module].$known[$imported].is_none()); + self.static_modules[module].$known[$imported] = Some(KnownEntity { + module: arg_module, + index, + }); + }}; + } + + match self.static_modules[module].module.import_index(i).unwrap() { + EntityIndex::Function(imported_func) => { + debug_assert!( + self.static_modules[module] + .module + .defined_func_index(imported_func) + .is_none() + ); + + let known_func = match arg { + CoreDef::InstanceFlags(_) => { + unreachable!("instance flags are not a function") + } + CoreDef::TaskMayBlock => { + unreachable!("task_may_block is not a function") + } + + // We could in theory inline these trampolines, so it + // could potentially make sense to record that we + // know this imported function is this particular + // trampoline. However, everything else is based + // around (module, defined-function) pairs and these + // trampolines don't fit that paradigm. Also, + // inlining trampolines gets really tricky when we + // consider the stack pointer, frame pointer, and + // return address note-taking that they do for the + // purposes of stack walking. We could, with enough + // effort, turn them into direct calls even though we + // probably wouldn't ever inline them, but it just + // doesn't seem worth the effort. + // + // That said, a couple of adapter trampolines are + // lowered inline during translation. We record these + // here so `FuncEnvironment` recognizes them. All + // other trampolines remain indirect calls. + CoreDef::Trampoline(index) => match translation.trampolines[*index] { + Trampoline::EnterSyncCall => { + FactInlineIntrinsic::EnterSyncCall.into() + } + Trampoline::ExitSyncCall => { + FactInlineIntrinsic::ExitSyncCall.into() + } + Trampoline::Trap => FactInlineIntrinsic::Trap.into(), + _ => continue, + }, + + // This import is a compile-time builtin intrinsic, + // we should inline its implementation during + // function translation. + CoreDef::UnsafeIntrinsic(i) => { + FuncKey::UnsafeIntrinsic(Abi::Wasm, *i).into() + } + + // This imported function is an export from another + // instance, a perfect candidate for becoming an + // inlinable direct call! + CoreDef::Export(export) => { + let Some((arg_module, arg_entity)) = + resolve_core_export(&instance_to_module, export) + else { + // Instance of a dynamic module that is not + // part of this component, not a + // statically-known module inside this + // component. We have to do an indirect call. + continue; + }; + + let EntityIndex::Function(arg_func) = arg_entity else { + unreachable!("function imports must be functions") + }; + + let Some(arg_module_def_func) = self.static_modules[arg_module] + .module + .defined_func_index(arg_func) + else { + // TODO: we should ideally follow re-export + // chains to bottom out the instantiation + // argument in either a definition or an + // import at the root component boundary. In + // practice, this pattern is rare, so + // following these chains is left for the + // Future. + continue; + }; + + FuncKey::DefinedWasmFunction(arg_module, arg_module_def_func).into() + } + }; - FuncKey::DefinedWasmFunction(*arg_module, arg_module_def_func).into() + assert!( + self.static_modules[module].known_imported_functions[imported_func] + .is_none() + ); + self.static_modules[module].known_imported_functions[imported_func] = + Some(known_func); } - }; - assert!( - self.static_modules[module].known_imported_functions[imported_func].is_none() - ); - self.static_modules[module].known_imported_functions[imported_func] = - Some(known_func); + // Note that a global import is not necessarily satisfied by a + // wasm global: it can also be one of the component-model + // flags that live in the `VMComponentContext`, which have + // nothing to do with defined-global alias regions. + EntityIndex::Global(imported_global) => record_known_entity!( + Global, + imported_global, + defined_global_index, + known_imported_globals + ), + + EntityIndex::Memory(imported_memory) => record_known_entity!( + Memory, + imported_memory, + defined_memory_index, + known_imported_memories + ), + + EntityIndex::Table(imported_table) => record_known_entity!( + Table, + imported_table, + defined_table_index, + known_imported_tables + ), + + // Tags don't have alias regions of their own. + EntityIndex::Tag(_) => {} + } } } } @@ -1814,3 +1926,121 @@ mod pre_inlining { } } use pre_inlining::PreInliningComponentTypes; + +/// Resolve a `CoreExport` to the static module that defines it and the entity +/// index it refers to within that module, when we can see through it statically. +fn resolve_core_export( + instance_to_module: &PrimaryMap>, + export: &CoreExport, +) -> Option<(StaticModuleIndex, EntityIndex)> { + // This can be an instance of a dynamic module that is not part of this + // component, rather than a statically-known module inside of it. + let module = instance_to_module[export.instance].expand()?; + match &export.item { + ExportItem::Index(index) => Some((module, *index)), + // Names are only used for instances of modules whose shape we don't + // statically know, which we already filtered out. + ExportItem::Name(_) => None, + } +} + +/// Same as `resolve_core_export`, but for a `CoreDef` that must additionally be +/// unambiguous. +fn unambiguous_entity( + instance_to_module: &PrimaryMap>, + ambiguous: &HashSet<(StaticModuleIndex, EntityIndex)>, + def: &CoreDef, +) -> Option<(StaticModuleIndex, EntityIndex)> { + let CoreDef::Export(export) = def else { + return None; + }; + let entity = resolve_core_export(instance_to_module, export)?; + if ambiguous.contains(&entity) { + return None; + } + Some(entity) +} + +/// Find every core wasm entity in this component whose identity is *not* +/// statically known to every module that may import it. +/// +/// An entity is unambiguous when every argument it flows into belongs to a +/// module that we only ever instantiate with that same entity: +/// +/// * An argument to a module that we may instantiate differently elsewhere is +/// ambiguous because that module cannot statically know which one of these +/// entities it was given at runtime. +/// +/// * An argument to an imported module is ambiguous because that module is +/// compiled separately from this component, and it may re-export the entity +/// back to us under a name we cannot see through, which we may then hand to a +/// module whose imports we do otherwise know. +/// +/// Note that ambiguity is never partial: if a module importing an entity has to +/// conservatively tag its accesses with that entity's public alias region, then +/// the module defining the entity must do the same, or else inlining one of +/// them into the other would access the same bytes through two different alias +/// regions, which is invalid. +fn ambiguous_entities( + translation: &ComponentTranslation, + instantiations: &SecondaryMap>, + instance_to_module: &PrimaryMap>, +) -> HashSet<(StaticModuleIndex, EntityIndex)> { + let mut ambiguous = HashSet::default(); + + let mut mark = |def: &CoreDef| match def { + CoreDef::Export(export) => { + if let Some(entity) = resolve_core_export(instance_to_module, export) { + ambiguous.insert(entity); + } + } + + // None of these are entities that get an alias region keyed by a + // defining module and index. + CoreDef::InstanceFlags(_) + | CoreDef::Trampoline(_) + | CoreDef::UnsafeIntrinsic(_) + | CoreDef::TaskMayBlock => {} + }; + + for init in &translation.component.initializers { + match init { + GlobalInitializer::InstantiateModule(instantiation, _) => match instantiation { + InstantiateModule::Static(module, args) => { + // Arguments to modules that we only instantiate one way are + // exactly the references that keep an entity unambiguous, so + // they are the one case we do not mark here. Everything else + // gets whichever of a number of different entities it was + // handed at runtime, and so has to be conservative. + if !matches!(instantiations[*module], dfg::AbstractInstantiations::One(_)) { + for arg in args.iter() { + mark(arg); + } + } + } + + // We cannot see through an imported module's exports, so an + // entity we pass into one and that comes back out to a module + // whose imports we do know would be accessed via two different + // alias regions. + InstantiateModule::Import(_, args) => { + for arg in args.values().flat_map(|args| args.values()) { + mark(arg); + } + } + }, + + // The remaining initializers do not involve the global/table/memory + // alias regions. + GlobalInitializer::ExtractMemory(_) + | GlobalInitializer::ExtractTable(_) + | GlobalInitializer::ExtractRealloc(_) + | GlobalInitializer::ExtractCallback(_) + | GlobalInitializer::ExtractPostReturn(_) + | GlobalInitializer::Resource(_) + | GlobalInitializer::LowerImport { .. } => {} + } + } + + ambiguous +} diff --git a/crates/environ/src/module.rs b/crates/environ/src/module.rs index 98e551003d9b..90443ebf75f2 100644 --- a/crates/environ/src/module.rs +++ b/crates/environ/src/module.rs @@ -511,6 +511,13 @@ impl Module { } } + /// Get the entity index for this module's `i`th import. + pub fn import_index(&self, i: usize) -> Option { + match self.initializers.get(i)? { + Initializer::Import { index, .. } => Some(*index), + } + } + /// Returns the type of an item based on its index pub fn type_of(&self, index: EntityIndex) -> EntityType { match index { diff --git a/tests/disas/component-model/known-imported-adapter-memory.wat b/tests/disas/component-model/known-imported-adapter-memory.wat new file mode 100644 index 000000000000..d1a8738fd197 --- /dev/null +++ b/tests/disas/component-model/known-imported-adapter-memory.wat @@ -0,0 +1,266 @@ +;;! target = "x86_64" +;;! test = "optimize" +;;! filter = "function" +;;! flags = "-C inlining=n -Wconcurrency-support=n" + +;; Every access of memory contents below (in the modules that define and import +;; the memories, and in the adapter that copies the returned tuple from one to +;; the other) should use the same `DefinedMemory` region rather than the +;; conservative `PublicMemory` region. + +(component + (component $A + (core module $M + (memory (export "mem") 1) + (func (export "realloc") (param i32 i32 i32 i32) (result i32) + (i32.const 0)) + (func (export "f") (param i32) (result i32) + (i32.store (i32.const 8) (local.get 0)) + (i32.store offset=4 (i32.const 8) (local.get 0)) + (i32.const 8)) + ) + (core instance $m (instantiate $M)) + (func (export "f") (param "a" u32) (result (tuple u32 u32)) + (canon lift (core func $m "f") + (memory $m "mem") + (realloc (func $m "realloc")))) + ) + + (instance $a (instantiate $A)) + + (component $B + (import "f" (func $f (param "a" u32) (result (tuple u32 u32)))) + + (core module $Mem + (memory (export "mem") 1) + (func (export "realloc") (param i32 i32 i32 i32) (result i32) + (i32.const 0)) + ) + (core instance $mem (instantiate $Mem)) + + (core func $f' (canon lower (func $f) + (memory $mem "mem") + (realloc (func $mem "realloc")))) + + (core module $N + (import "" "mem" (memory 1)) + (import "" "f'" (func $f' (param i32 i32))) + (func (export "g") (result i32) + (call $f' (i32.const 42) (i32.const 0)) + (i32.load (i32.const 0))) + ) + (core instance $n (instantiate $N + (with "" (instance + (export "mem" (memory $mem "mem")) + (export "f'" (func $f')) + )) + )) + ) + + (instance $b (instantiate $B (with "f" (func $a "f")))) +) +;; function u0:0(i64 vmctx, i64, i32, i32, i32, i32) -> i32 tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64, v2: i32, v3: i32, v4: i32, v5: i32): +;; @0055 jump block1 +;; +;; block1: +;; @0053 v6 = iconst.i32 0 +;; @0055 return v6 ; v6 = 0 +;; } +;; +;; function u0:1(i64 vmctx, i64, i32) -> i32 tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; region2 = 603979776 "VMMemoryDefinition+0x0" +;; region3 = 603979784 "VMMemoryDefinition+0x8" +;; region4 = 201326592 "DefinedMemory(StaticModuleIndex(0), DefinedMemoryIndex(0))" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64, v2: i32): +;; @005c v5 = load.i64 notrap aligned readonly can_move region2 v0+56 +;; v14 = iconst.i64 8 +;; @005c v6 = iadd v5, v14 ; v14 = 8 +;; @005c store little region4 v2, v6 +;; v16 = iconst.i64 12 +;; v21 = iadd v5, v16 ; v16 = 12 +;; @0063 store little region4 v2, v21 +;; @0068 jump block1 +;; +;; block1: +;; @0058 v3 = iconst.i32 8 +;; @0068 return v3 ; v3 = 8 +;; } +;; +;; function u1:0(i64 vmctx, i64, i32, i32, i32, i32) -> i32 tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64, v2: i32, v3: i32, v4: i32, v5: i32): +;; @013e jump block1 +;; +;; block1: +;; @013c v6 = iconst.i32 0 +;; @013e return v6 ; v6 = 0 +;; } +;; +;; function u2:0(i64 vmctx, i64) -> i32 tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; region2 = 1207959576 "VMFunctionImport+0x18" +;; region3 = 1275068416 "VMMemoryImport+0x0" +;; region4 = 603979776 "VMMemoryDefinition+0x0" +;; region5 = 603979784 "VMMemoryDefinition+0x8" +;; region6 = 201588736 "DefinedMemory(StaticModuleIndex(1), DefinedMemoryIndex(0))" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; sig0 = (i64 vmctx, i64, i32, i32) tail +;; fn0 = colocated u3:0 sig0 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64): +;; @01af v4 = load.i64 notrap aligned readonly can_move region2 v0+96 +;; @01ab v2 = iconst.i32 42 +;; @01ad v3 = iconst.i32 0 +;; @01af call fn0(v4, v0, v2, v3) ; v2 = 42, v3 = 0 +;; @01b3 v7 = load.i64 notrap aligned readonly can_move region3 v0+48 +;; @01b3 v8 = load.i64 notrap aligned readonly can_move region4 v7 +;; @01b3 v10 = load.i32 little region6 v8 +;; @01b6 jump block1 +;; +;; block1: +;; @01b6 return v10 +;; } +;; +;; function u3:0(i64 vmctx, i64, i32, i32) tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; region2 = 1476395008 "VMGlobalImport+0x0" +;; region3 = 402653184 "PublicGlobal" +;; region4 = 1207959576 "VMFunctionImport+0x18" +;; region5 = 1275068416 "VMMemoryImport+0x0" +;; region6 = 603979776 "VMMemoryDefinition+0x0" +;; region7 = 603979784 "VMMemoryDefinition+0x8" +;; region8 = 201326592 "DefinedMemory(StaticModuleIndex(0), DefinedMemoryIndex(0))" +;; region9 = 201588736 "DefinedMemory(StaticModuleIndex(1), DefinedMemoryIndex(0))" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; sig0 = (i64 vmctx, i64, i32) tail +;; sig1 = (i64 vmctx, i64, i32) -> i32 tail +;; fn0 = colocated u0:1 sig1 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64, v2: i32, v3: i32): +;; @00be jump block4 +;; +;; block6(v5: i64): +;; @00be jump block3 +;; +;; block4: +;; @00c5 v7 = load.i64 notrap aligned readonly can_move region2 v0+248 +;; @00c5 v8 = load.i32 notrap aligned region3 v7 +;; @00c9 trapz v8, user26 +;; @00c9 jump block7 +;; +;; block7: +;; @00d1 v11 = load.i64 notrap aligned readonly can_move region2 v0+224 +;; @00d1 v12 = load.i32 notrap aligned region3 v11 +;; @00df v16 = load.i64 notrap aligned readonly can_move region4 v0+184 +;; @00df try_call fn0(v16, v0, v2), sig1, block9(ret0), [ context v0, default: block6(exn0) ] +;; +;; block9(v17: i32): +;; @00b8 v4 = iconst.i32 0 +;; @00e5 store notrap aligned region3 v4, v7 ; v4 = 0 +;; @00e9 v20 = iconst.i32 3 +;; @00eb v21 = band v17, v20 ; v20 = 3 +;; @00ec trapnz v21, user36 +;; @00ec jump block11 +;; +;; block11: +;; @00f8 v24 = load.i64 notrap aligned readonly can_move region5 v0+48 +;; @00f8 v25 = load.i64 notrap aligned region7 v24+8 +;; @00f8 v26 = iconst.i64 16 +;; @00f8 v27 = ushr v25, v26 ; v26 = 16 +;; @00f8 v28 = ireduce.i32 v27 +;; @00fa v29 = uextend.i64 v28 +;; @00fd v31 = ishl v29, v26 ; v26 = 16 +;; @0100 v32 = uextend.i64 v17 +;; v85 = iconst.i64 8 +;; @0104 v35 = iadd v32, v85 ; v85 = 8 +;; @0105 v36 = icmp uge v31, v35 +;; @0106 brif v36, block12, block14 +;; +;; block14: +;; @0108 jump block13 +;; +;; block13: +;; @010b trap user4 +;; +;; block12: +;; v86 = iconst.i32 3 +;; v87 = band.i32 v3, v86 ; v86 = 3 +;; @0114 trapnz v87, user36 +;; @0114 jump block16 +;; +;; block16: +;; @0120 v44 = load.i64 notrap aligned readonly can_move region5 v0+72 +;; @0120 v45 = load.i64 notrap aligned region7 v44+8 +;; v88 = iconst.i64 16 +;; v89 = ushr v45, v88 ; v88 = 16 +;; @0120 v48 = ireduce.i32 v89 +;; @0122 v49 = uextend.i64 v48 +;; v90 = ishl v49, v88 ; v88 = 16 +;; @0128 v52 = uextend.i64 v3 +;; v91 = iconst.i64 8 +;; v92 = iadd v52, v91 ; v91 = 8 +;; @012d v56 = icmp uge v90, v92 +;; @012e brif v56, block17, block19 +;; +;; block19: +;; @0130 jump block18 +;; +;; block18: +;; @0133 trap user4 +;; +;; block17: +;; @013b v62 = load.i64 notrap aligned readonly can_move region6 v24 +;; @013b v63 = iadd v62, v32 +;; @013b v64 = load.i32 little region8 v63 +;; @013e v67 = load.i64 notrap aligned readonly can_move region6 v44 +;; @013e v68 = iadd v67, v52 +;; @013e store little region9 v64, v68 +;; @0146 v73 = iconst.i64 4 +;; @0146 v74 = iadd v63, v73 ; v73 = 4 +;; @0146 v75 = load.i32 little region8 v74 +;; @0149 v81 = iadd v68, v73 ; v73 = 4 +;; @0149 store little region9 v75, v81 +;; @014f store.i32 notrap aligned region3 v8, v7 +;; @0151 jump block5 +;; +;; block5: +;; @0152 jump block2 +;; +;; block3: +;; @0157 trap user52 +;; +;; block2: +;; @015b jump block1 +;; +;; block1: +;; @015b return +;; } diff --git a/tests/disas/component-model/known-imported-canonical-abi-memory.wat b/tests/disas/component-model/known-imported-canonical-abi-memory.wat new file mode 100644 index 000000000000..3cf1e1e84f42 --- /dev/null +++ b/tests/disas/component-model/known-imported-canonical-abi-memory.wat @@ -0,0 +1,66 @@ +;;! target = "x86_64" +;;! test = "optimize" +;;! filter = "function" +;;! flags = "-C inlining=n -Wconcurrency-support=n" + +;; `$M`'s memory is unambiguous despite being exported and used by component +;; model libcall intrinsics when transcoding strings, and `$M` gets the precise +;; `DefinedMemory` alias region for it. + +(component + (core module $M + (memory (export "mem") 1) + (func (export "realloc") (param i32 i32 i32 i32) (result i32) + (i32.const 0) + ) + (func (export "f") (param i32 i32) + (i32.store (local.get 0) (local.get 1)) + ) + ) + + (core instance $m (instantiate $M)) + + (func (export "f") (param "s" string) + (canon lift (core func $m "f") + (memory $m "mem") + (realloc (func $m "realloc")) + ) + ) +) +;; function u0:0(i64 vmctx, i64, i32, i32, i32, i32) -> i32 tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64, v2: i32, v3: i32, v4: i32, v5: i32): +;; @004a jump block1 +;; +;; block1: +;; @0048 v6 = iconst.i32 0 +;; @004a return v6 ; v6 = 0 +;; } +;; +;; function u0:1(i64 vmctx, i64, i32, i32) tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; region2 = 603979776 "VMMemoryDefinition+0x0" +;; region3 = 603979784 "VMMemoryDefinition+0x8" +;; region4 = 201326592 "DefinedMemory(StaticModuleIndex(0), DefinedMemoryIndex(0))" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64, v2: i32, v3: i32): +;; @0051 v5 = load.i64 notrap aligned readonly can_move region2 v0+56 +;; @0051 v4 = uextend.i64 v2 +;; @0051 v6 = iadd v5, v4 +;; @0051 store little region4 v3, v6 +;; @0054 jump block1 +;; +;; block1: +;; @0054 return +;; } diff --git a/tests/disas/component-model/known-imported-entities.wat b/tests/disas/component-model/known-imported-entities.wat new file mode 100644 index 000000000000..0a3df05d8397 --- /dev/null +++ b/tests/disas/component-model/known-imported-entities.wat @@ -0,0 +1,202 @@ +;;! target = "x86_64" +;;! test = "optimize" +;;! filter = "function" +;;! flags = "-C inlining=n -Wconcurrency-support=n" + +;; Module `$M` defines a memory, global, and table and module `$N` imports them. +;; Each module is instantiated exactly once, and nothing else in the component +;; can get its hands on those entities, so both modules statically know that +;; `$N`'s imports are always `$M`'s definitions. Both modules should therefore +;; use the precise `DefinedMemory`/`DefinedGlobal`/`DefinedTable` alias regions, +;; and use the same region as each other for the same entity, rather than +;; falling back to the conservative `PublicMemory`/`PublicGlobal`/`PublicTable` +;; regions. + +(component + (core module $M + (memory (export "mem") 1) + (global (export "g") (mut i32) (i32.const 0)) + (table (export "t") 1 funcref) + + (func (export "load-mem") (result i32) + (i32.load (i32.const 0))) + (func (export "get-global") (result i32) + (global.get 0)) + (func (export "get-table") (result funcref) + (table.get 0 (i32.const 0))) + ) + + (core instance $m (instantiate $M)) + + (core module $N + (import "" "mem" (memory 1)) + (import "" "g" (global (mut i32))) + (import "" "t" (table 1 funcref)) + + (func (export "load-mem") (result i32) + (i32.load (i32.const 0))) + (func (export "get-global") (result i32) + (global.get 0)) + (func (export "get-table") (result funcref) + (table.get 0 (i32.const 0))) + ) + + (core instance $n (instantiate $N (with "" (instance $m)))) +) +;; function u0:0(i64 vmctx, i64) -> i32 tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; region2 = 603979776 "VMMemoryDefinition+0x0" +;; region3 = 603979784 "VMMemoryDefinition+0x8" +;; region4 = 201326592 "DefinedMemory(StaticModuleIndex(0), DefinedMemoryIndex(0))" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64): +;; @0073 v4 = load.i64 notrap aligned readonly can_move region2 v0+56 +;; @0073 v6 = load.i32 little region4 v4 +;; @0076 jump block1 +;; +;; block1: +;; @0076 return v6 +;; } +;; +;; function u0:1(i64 vmctx, i64) -> i32 tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; region2 = 469762048 "DefinedGlobal(StaticModuleIndex(0), DefinedGlobalIndex(0))" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64): +;; @0079 v2 = load.i32 notrap aligned region2 v0+96 +;; @007b jump block1 +;; +;; block1: +;; @007b return v2 +;; } +;; +;; function u0:2(i64 vmctx, i64) -> i64 tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; region2 = 671088640 "VMTableDefinition+0x0" +;; region3 = 671088648 "VMTableDefinition+0x8" +;; region4 = 335544320 "DefinedTable(StaticModuleIndex(0), DefinedTableIndex(0))" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; sig0 = (i64 vmctx, i32, i64) -> i64 tail +;; fn0 = colocated u805306368:7 sig0 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64): +;; @0080 v3 = load.i64 notrap aligned region3 v0+80 +;; @0080 v7 = load.i64 notrap aligned region2 v0+72 +;; @0080 v4 = ireduce.i32 v3 +;; @007e v2 = iconst.i32 0 +;; v21 = icmp eq v4, v2 ; v2 = 0 +;; v24 = iconst.i64 0 +;; @0080 v12 = select_spectre_guard v21, v24, v7 ; v24 = 0 +;; @0080 v13 = load.i64 user6 aligned region4 v12 +;; @0080 v14 = iconst.i64 -2 +;; @0080 v15 = band v13, v14 ; v14 = -2 +;; @0080 brif v13, block3(v15), block2 +;; +;; block2 cold: +;; v25 = iconst.i32 0 +;; v26 = iconst.i64 0 +;; @0080 v19 = call fn0(v0, v25, v26) ; v25 = 0, v26 = 0 +;; @0080 jump block3(v19) +;; +;; block3(v16: i64): +;; @0082 jump block1 +;; +;; block1: +;; @0082 return v16 +;; } +;; +;; function u1:0(i64 vmctx, i64) -> i32 tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; region2 = 1275068416 "VMMemoryImport+0x0" +;; region3 = 603979776 "VMMemoryDefinition+0x0" +;; region4 = 603979784 "VMMemoryDefinition+0x8" +;; region5 = 201326592 "DefinedMemory(StaticModuleIndex(0), DefinedMemoryIndex(0))" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64): +;; @00f5 v4 = load.i64 notrap aligned readonly can_move region2 v0+48 +;; @00f5 v5 = load.i64 notrap aligned readonly can_move region3 v4 +;; @00f5 v7 = load.i32 little region5 v5 +;; @00f8 jump block1 +;; +;; block1: +;; @00f8 return v7 +;; } +;; +;; function u1:1(i64 vmctx, i64) -> i32 tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; region2 = 1476395008 "VMGlobalImport+0x0" +;; region3 = 469762048 "DefinedGlobal(StaticModuleIndex(0), DefinedGlobalIndex(0))" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64): +;; @00fb v2 = load.i64 notrap aligned readonly can_move region2 v0+96 +;; @00fb v3 = load.i32 notrap aligned region3 v2 +;; @00fd jump block1 +;; +;; block1: +;; @00fd return v3 +;; } +;; +;; function u1:2(i64 vmctx, i64) -> i64 tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; region2 = 1342177280 "VMTableImport+0x0" +;; region3 = 671088640 "VMTableDefinition+0x0" +;; region4 = 671088648 "VMTableDefinition+0x8" +;; region5 = 335544320 "DefinedTable(StaticModuleIndex(0), DefinedTableIndex(0))" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; sig0 = (i64 vmctx, i32, i64) -> i64 tail +;; fn0 = colocated u805306368:7 sig0 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64): +;; @0102 v3 = load.i64 notrap aligned readonly can_move region2 v0+72 +;; @0102 v4 = load.i64 notrap aligned region4 v3+8 +;; @0102 v9 = load.i64 notrap aligned region3 v3 +;; @0102 v5 = ireduce.i32 v4 +;; @0100 v2 = iconst.i32 0 +;; v23 = icmp eq v5, v2 ; v2 = 0 +;; v26 = iconst.i64 0 +;; @0102 v14 = select_spectre_guard v23, v26, v9 ; v26 = 0 +;; @0102 v15 = load.i64 user6 aligned region5 v14 +;; @0102 v16 = iconst.i64 -2 +;; @0102 v17 = band v15, v16 ; v16 = -2 +;; @0102 brif v15, block3(v17), block2 +;; +;; block2 cold: +;; v27 = iconst.i32 0 +;; v28 = iconst.i64 0 +;; @0102 v21 = call fn0(v0, v27, v28) ; v27 = 0, v28 = 0 +;; @0102 jump block3(v21) +;; +;; block3(v18: i64): +;; @0104 jump block1 +;; +;; block1: +;; @0104 return v18 +;; } diff --git a/tests/disas/component-model/multiple-instantiations-makes-imports-unknown.wat b/tests/disas/component-model/multiple-instantiations-makes-imports-unknown.wat new file mode 100644 index 000000000000..07f20f75e633 --- /dev/null +++ b/tests/disas/component-model/multiple-instantiations-makes-imports-unknown.wat @@ -0,0 +1,300 @@ +;;! target = "x86_64" +;;! test = "optimize" +;;! filter = "function" +;;! flags = "-C inlining=n -Wconcurrency-support=n" + +;; Same as `known-imported-entities.wat` except that `$N` is instantiated twice +;; with the exports of two different modules, so `$N` does not always import the +;; same entities and we cannot statically know what its imports are. Both the +;; defining modules and `$N` must fall back to the conservative `PublicMemory` / +;; `PublicGlobal` / `PublicTable` alias regions: if only `$N` did, then inlining +;; one of `$M1`'s or `$M2`'s functions into one of `$N`'s functions would end up +;; accessing the same entity through two different alias regions, which is +;; invalid. + +(component + (core module $M1 + (memory (export "mem") 1) + (global (export "g") (mut i32) (i32.const 0)) + (table (export "t") 1 funcref) + + (func (export "load-mem") (result i32) + (i32.load (i32.const 0)) + ) + (func (export "get-global") (result i32) + (global.get 0) + ) + (func (export "get-table") (result funcref) + (table.get 0 (i32.const 0)) + ) + ) + (core instance $m1 (instantiate $M1)) + + (core module $M2 + (memory (export "mem") 1) + (global (export "g") (mut i32) (i32.const 0)) + (table (export "t") 1 funcref) + + (func (export "load-mem") (result i32) + (i32.load (i32.const 0)) + ) + (func (export "get-global") (result i32) + (global.get 0) + ) + (func (export "get-table") (result funcref) + (table.get 0 (i32.const 0)) + ) + ) + (core instance $m2 (instantiate $M2)) + + (core module $N + (import "" "mem" (memory 1)) + (import "" "g" (global (mut i32))) + (import "" "t" (table 1 funcref)) + + (func (export "load-mem") (result i32) + (i32.load (i32.const 0)) + ) + (func (export "get-global") (result i32) + (global.get 0) + ) + (func (export "get-table") (result funcref) + (table.get 0 (i32.const 0)) + ) + ) + (core instance $n1 (instantiate $N (with "" (instance $m1)))) + (core instance $n2 (instantiate $N (with "" (instance $m2)))) +) +;; function u0:0(i64 vmctx, i64) -> i32 tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; region2 = 603979776 "VMMemoryDefinition+0x0" +;; region3 = 603979784 "VMMemoryDefinition+0x8" +;; region4 = 134217728 "PublicMemory" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64): +;; @0073 v4 = load.i64 notrap aligned readonly can_move region2 v0+56 +;; @0073 v6 = load.i32 little region4 v4 +;; @0076 jump block1 +;; +;; block1: +;; @0076 return v6 +;; } +;; +;; function u0:1(i64 vmctx, i64) -> i32 tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; region2 = 402653184 "PublicGlobal" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64): +;; @0079 v2 = load.i32 notrap aligned region2 v0+96 +;; @007b jump block1 +;; +;; block1: +;; @007b return v2 +;; } +;; +;; function u0:2(i64 vmctx, i64) -> i64 tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; region2 = 671088640 "VMTableDefinition+0x0" +;; region3 = 671088648 "VMTableDefinition+0x8" +;; region4 = 268435456 "PublicTable" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; sig0 = (i64 vmctx, i32, i64) -> i64 tail +;; fn0 = colocated u805306368:7 sig0 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64): +;; @0080 v3 = load.i64 notrap aligned region3 v0+80 +;; @0080 v7 = load.i64 notrap aligned region2 v0+72 +;; @0080 v4 = ireduce.i32 v3 +;; @007e v2 = iconst.i32 0 +;; v21 = icmp eq v4, v2 ; v2 = 0 +;; v24 = iconst.i64 0 +;; @0080 v12 = select_spectre_guard v21, v24, v7 ; v24 = 0 +;; @0080 v13 = load.i64 user6 aligned region4 v12 +;; @0080 v14 = iconst.i64 -2 +;; @0080 v15 = band v13, v14 ; v14 = -2 +;; @0080 brif v13, block3(v15), block2 +;; +;; block2 cold: +;; v25 = iconst.i32 0 +;; v26 = iconst.i64 0 +;; @0080 v19 = call fn0(v0, v25, v26) ; v25 = 0, v26 = 0 +;; @0080 jump block3(v19) +;; +;; block3(v16: i64): +;; @0082 jump block1 +;; +;; block1: +;; @0082 return v16 +;; } +;; +;; function u1:0(i64 vmctx, i64) -> i32 tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; region2 = 603979776 "VMMemoryDefinition+0x0" +;; region3 = 603979784 "VMMemoryDefinition+0x8" +;; region4 = 134217728 "PublicMemory" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64): +;; @0100 v4 = load.i64 notrap aligned readonly can_move region2 v0+56 +;; @0100 v6 = load.i32 little region4 v4 +;; @0103 jump block1 +;; +;; block1: +;; @0103 return v6 +;; } +;; +;; function u1:1(i64 vmctx, i64) -> i32 tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; region2 = 402653184 "PublicGlobal" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64): +;; @0106 v2 = load.i32 notrap aligned region2 v0+96 +;; @0108 jump block1 +;; +;; block1: +;; @0108 return v2 +;; } +;; +;; function u1:2(i64 vmctx, i64) -> i64 tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; region2 = 671088640 "VMTableDefinition+0x0" +;; region3 = 671088648 "VMTableDefinition+0x8" +;; region4 = 268435456 "PublicTable" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; sig0 = (i64 vmctx, i32, i64) -> i64 tail +;; fn0 = colocated u805306368:7 sig0 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64): +;; @010d v3 = load.i64 notrap aligned region3 v0+80 +;; @010d v7 = load.i64 notrap aligned region2 v0+72 +;; @010d v4 = ireduce.i32 v3 +;; @010b v2 = iconst.i32 0 +;; v21 = icmp eq v4, v2 ; v2 = 0 +;; v24 = iconst.i64 0 +;; @010d v12 = select_spectre_guard v21, v24, v7 ; v24 = 0 +;; @010d v13 = load.i64 user6 aligned region4 v12 +;; @010d v14 = iconst.i64 -2 +;; @010d v15 = band v13, v14 ; v14 = -2 +;; @010d brif v13, block3(v15), block2 +;; +;; block2 cold: +;; v25 = iconst.i32 0 +;; v26 = iconst.i64 0 +;; @010d v19 = call fn0(v0, v25, v26) ; v25 = 0, v26 = 0 +;; @010d jump block3(v19) +;; +;; block3(v16: i64): +;; @010f jump block1 +;; +;; block1: +;; @010f return v16 +;; } +;; +;; function u2:0(i64 vmctx, i64) -> i32 tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; region2 = 1275068416 "VMMemoryImport+0x0" +;; region3 = 603979776 "VMMemoryDefinition+0x0" +;; region4 = 603979784 "VMMemoryDefinition+0x8" +;; region5 = 134217728 "PublicMemory" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64): +;; @0183 v4 = load.i64 notrap aligned readonly can_move region2 v0+48 +;; @0183 v5 = load.i64 notrap aligned readonly can_move region3 v4 +;; @0183 v7 = load.i32 little region5 v5 +;; @0186 jump block1 +;; +;; block1: +;; @0186 return v7 +;; } +;; +;; function u2:1(i64 vmctx, i64) -> i32 tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; region2 = 1476395008 "VMGlobalImport+0x0" +;; region3 = 402653184 "PublicGlobal" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64): +;; @0189 v2 = load.i64 notrap aligned readonly can_move region2 v0+96 +;; @0189 v3 = load.i32 notrap aligned region3 v2 +;; @018b jump block1 +;; +;; block1: +;; @018b return v3 +;; } +;; +;; function u2:2(i64 vmctx, i64) -> i64 tail { +;; region0 = 8 "VMContext+0x8" +;; region1 = 67108888 "VMStoreContext+0x18" +;; region2 = 1342177280 "VMTableImport+0x0" +;; region3 = 671088640 "VMTableDefinition+0x0" +;; region4 = 671088648 "VMTableDefinition+0x8" +;; region5 = 268435456 "PublicTable" +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 +;; gv2 = load.i64 notrap aligned region1 gv1+24 +;; sig0 = (i64 vmctx, i32, i64) -> i64 tail +;; fn0 = colocated u805306368:7 sig0 +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64): +;; @0190 v3 = load.i64 notrap aligned readonly can_move region2 v0+72 +;; @0190 v4 = load.i64 notrap aligned region4 v3+8 +;; @0190 v9 = load.i64 notrap aligned region3 v3 +;; @0190 v5 = ireduce.i32 v4 +;; @018e v2 = iconst.i32 0 +;; v23 = icmp eq v5, v2 ; v2 = 0 +;; v26 = iconst.i64 0 +;; @0190 v14 = select_spectre_guard v23, v26, v9 ; v26 = 0 +;; @0190 v15 = load.i64 user6 aligned region5 v14 +;; @0190 v16 = iconst.i64 -2 +;; @0190 v17 = band v15, v16 ; v16 = -2 +;; @0190 brif v15, block3(v17), block2 +;; +;; block2 cold: +;; v27 = iconst.i32 0 +;; v28 = iconst.i64 0 +;; @0190 v21 = call fn0(v0, v27, v28) ; v27 = 0, v28 = 0 +;; @0190 jump block3(v21) +;; +;; block3(v18: i64): +;; @0192 jump block1 +;; +;; block1: +;; @0192 return v18 +;; } From 5e8533903d9325872a56e2935566cdc8e33f4307 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Wed, 5 Aug 2026 06:36:41 -0700 Subject: [PATCH 7/7] Access component-model flags via their `VmType` alias region in adapters These flags were imported by adapters and accessed with the `PublicGlobal` alias region, but this led to ambiguity between loads and stores to different flags. The flags are stored in the `VMComponentContext` and we generally know when we are importing flags as globals unambiguously, so do the same thing we do to get precise alias regions for defined globals for component-model flags, but use the `AliasRegionKey::Vm { ty: VMComponentContext, .. }` alias region. A final wrinkle: core Wasm does not have the enclosing `VMComponentContext`'s offsets on hand, so we move `task_may_block` ahead of `may_leave` so both flags are before every component-shape-dependent field and the flags' offsets are a function of the pointer size alone. --- crates/cranelift/src/alias_region.rs | 48 +++++++-- crates/cranelift/src/func_environ.rs | 10 +- crates/environ/src/compile/module_environ.rs | 21 +++- crates/environ/src/component/translate.rs | 100 +++++++++++++----- .../src/component/vmcomponent_offsets.rs | 66 +++++++++++- ...-may-leave-without-signals-based-traps.wat | 2 +- .../direct-adapter-calls-inlining.wat | 6 +- .../component-model/direct-adapter-calls.wat | 5 +- .../known-imported-adapter-memory.wat | 35 +++--- .../component-model/sync-adapter-calls.wat | 62 +++++------ .../disas/riscv64-component-builtins-asm.wat | 2 +- tests/disas/riscv64-component-builtins.wat | 4 +- 12 files changed, 258 insertions(+), 103 deletions(-) diff --git a/crates/cranelift/src/alias_region.rs b/crates/cranelift/src/alias_region.rs index 89dc2a2a3096..3410a1a044a0 100644 --- a/crates/cranelift/src/alias_region.rs +++ b/crates/cranelift/src/alias_region.rs @@ -771,6 +771,44 @@ where } } +/// Methods for the component-model flags that live in the `VMComponentContext`. +impl AliasRegions +where + Offsets: GetPtrSize, +{ + /// Get the alias region for the given offset into the `VMComponentContext`. + fn vmcomponent_region(&mut self, func: &mut ir::Function, offset: u32) -> ir::AliasRegion { + self.region( + func, + AliasRegionKey::Vm { + ty: VmType::VMComponentContext, + offset, + }, + ) + } + + /// Get the alias region for accessing a particular component instance's + /// runtime-managed flags, such as its `may_leave` flag. + pub fn component_instance_flags_region( + &mut self, + func: &mut ir::Function, + instance: RuntimeComponentInstanceIndex, + ) -> ir::AliasRegion { + let offset = VMComponentOffsets::::may_leave_offset( + self.offsets.get_ptr_size(), + instance, + ); + self.vmcomponent_region(func, offset) + } + + /// Get the alias region for accessing the current task's `may_block` flag. + pub fn task_may_block_region(&mut self, func: &mut ir::Function) -> ir::AliasRegion { + let offset = + VMComponentOffsets::::task_may_block_offset(self.offsets.get_ptr_size()); + self.vmcomponent_region(func, offset) + } +} + /// `VMContext`-related methods that are valid for any `VMContext`, regardless /// of its particular `VMOffsets`. impl AliasRegions @@ -2332,16 +2370,6 @@ where /// `VMComponentContext`-related methods, used when compiling component /// trampolines. impl AliasRegions> { - fn vmcomponent_region(&mut self, func: &mut ir::Function, offset: u32) -> ir::AliasRegion { - self.region( - func, - AliasRegionKey::Vm { - ty: VmType::VMComponentContext, - offset, - }, - ) - } - fn vmcomponent_load( &mut self, cursor: &mut FuncCursor<'_>, diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index a6be5923757e..564a502d6393 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -37,8 +37,8 @@ use wasmtime_environ::{ BuiltinFunctionIndex, ComponentPC, ConstExpr, ConstOp, DataIndex, DefinedFuncIndex, DefinedGlobalIndex, DefinedTableIndex, ElemIndex, EngineOrModuleTypeIndex, FactInlineIntrinsic, FrameStateSlotBuilder, FrameValType, FuncIndex, FuncKey, GlobalConstValue, GlobalIndex, - IndexType, KnownFunc, Memory, MemoryIndex, MemoryInit, MemorySegmentOffset, MemoryTunables, - Module, ModuleInternedTypeIndex, ModuleTranslation, ModuleTypesBuilder, + IndexType, KnownFunc, KnownGlobal, Memory, MemoryIndex, MemoryInit, MemorySegmentOffset, + MemoryTunables, Module, ModuleInternedTypeIndex, ModuleTranslation, ModuleTypesBuilder, NUM_COMPONENT_CONTEXT_SLOTS, PassiveElemIndex, PtrSize, RuntimeDataIndex, Table, TableIndex, TableInitialValue, TableSegment, TableSegmentElements, TagIndex, Tunables, TypeConvert, TypeIndex, VMOffsets, WasmCompositeInnerType, WasmFuncType, WasmHeapTopType, WasmHeapType, @@ -434,10 +434,14 @@ impl<'module_environment> FuncEnvironment<'module_environment> { } } None => match self.translation.known_imported_globals[global] { - Some(known) => { + Some(KnownGlobal::Defined(known)) => { self.alias_regions .defined_global_region(func, known.module, known.index) } + Some(KnownGlobal::ComponentInstanceFlags(instance)) => self + .alias_regions + .component_instance_flags_region(func, instance), + Some(KnownGlobal::TaskMayBlock) => self.alias_regions.task_may_block_region(func), None => self.alias_regions.public_global_region(func), }, } diff --git a/crates/environ/src/compile/module_environ.rs b/crates/environ/src/compile/module_environ.rs index 9a6e58b36958..384cde7a7317 100644 --- a/crates/environ/src/compile/module_environ.rs +++ b/crates/environ/src/compile/module_environ.rs @@ -75,7 +75,7 @@ impl From for KnownFunc { } /// A statically-known import of a core Wasm global, memory, or table. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct KnownEntity { /// The module that defines this entity. pub module: StaticModuleIndex, @@ -83,6 +83,23 @@ pub struct KnownEntity { pub index: T, } +/// A statically-known import of a core wasm global. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum KnownGlobal { + /// A global defined by a module in the same component. + Defined(KnownEntity), + + /// A component instance's runtime-managed flags, such as its `may-leave` + /// flag. + #[cfg(feature = "component-model")] + ComponentInstanceFlags(crate::component::RuntimeComponentInstanceIndex), + + /// The runtime-managed flag recording whether the currently-executing task + /// may perform blocking operations. + #[cfg(feature = "component-model")] + TaskMayBlock, +} + /// The result of translating via `ModuleEnvironment`. /// /// Function bodies are not yet translated, and data initializers have not yet @@ -133,7 +150,7 @@ pub struct ModuleTranslation<'data> { /// accesses the entity via the conservative region, could be inlined next /// to one of our accesses via the precise region, and accessing the same /// memory through two different alias regions is invalid. - pub known_imported_globals: SecondaryMap>>, + pub known_imported_globals: SecondaryMap>, /// Same as `known_imported_globals`, but for memories. pub known_imported_memories: SecondaryMap>>, diff --git a/crates/environ/src/component/translate.rs b/crates/environ/src/component/translate.rs index f355051647a4..5af75513e5f0 100644 --- a/crates/environ/src/component/translate.rs +++ b/crates/environ/src/component/translate.rs @@ -4,7 +4,7 @@ use crate::component::*; use crate::prelude::*; use crate::{ DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, EngineOrModuleTypeIndex, - EntityIndex, FactInlineIntrinsic, FuncKey, KnownEntity, ModuleEnvironment, + EntityIndex, FactInlineIntrinsic, FuncKey, KnownEntity, KnownGlobal, ModuleEnvironment, ModuleInternedTypeIndex, ModuleTranslation, ModuleTypesBuilder, PrimaryMap, ScopeVec, TagIndex, Tunables, TypeConvert, WasmHeapType, WasmResult, WasmValType, }; @@ -604,7 +604,10 @@ impl<'a, 'data> Translator<'a, 'data> { for i in 0..translation.module.num_defined_globals() { let index = DefinedGlobalIndex::new(i); let global = translation.module.global_index(index); - if !ambiguous.contains(&(module, EntityIndex::Global(global))) { + if !ambiguous + .entities + .contains(&(module, EntityIndex::Global(global))) + { translation .globals_known_to_importers .insert(index) @@ -614,7 +617,10 @@ impl<'a, 'data> Translator<'a, 'data> { for i in 0..translation.module.num_defined_memories() { let index = DefinedMemoryIndex::new(i); let memory = translation.module.memory_index(index); - if !ambiguous.contains(&(module, EntityIndex::Memory(memory))) { + if !ambiguous + .entities + .contains(&(module, EntityIndex::Memory(memory))) + { translation .memories_known_to_importers .insert(index) @@ -624,7 +630,10 @@ impl<'a, 'data> Translator<'a, 'data> { for i in 0..translation.module.num_defined_tables() { let index = DefinedTableIndex::new(i); let table = translation.module.table_index(index); - if !ambiguous.contains(&(module, EntityIndex::Table(table))) { + if !ambiguous + .entities + .contains(&(module, EntityIndex::Table(table))) + { translation .tables_known_to_importers .insert(index) @@ -650,9 +659,9 @@ impl<'a, 'data> Translator<'a, 'data> { // same defined entity, when we know that and when everything // else that imports that entity knows it too. macro_rules! record_known_entity { - ($variant:ident, $imported:expr, $defined_index:ident, $known:ident) => {{ + ($variant:ident, $imported:expr, $defined_index:ident, $known:ident, $wrap:expr) => {{ let Some((arg_module, EntityIndex::$variant(arg_entity))) = - unambiguous_entity(&instance_to_module, &ambiguous, arg) + unambiguous_entity(&instance_to_module, &ambiguous.entities, arg) else { continue; }; @@ -663,10 +672,10 @@ impl<'a, 'data> Translator<'a, 'data> { continue; }; assert!(self.static_modules[module].$known[$imported].is_none()); - self.static_modules[module].$known[$imported] = Some(KnownEntity { + self.static_modules[module].$known[$imported] = Some($wrap(KnownEntity { module: arg_module, index, - }); + })); }}; } @@ -769,27 +778,43 @@ impl<'a, 'data> Translator<'a, 'data> { // Note that a global import is not necessarily satisfied by a // wasm global: it can also be one of the component-model - // flags that live in the `VMComponentContext`, which have - // nothing to do with defined-global alias regions. - EntityIndex::Global(imported_global) => record_known_entity!( - Global, - imported_global, - defined_global_index, - known_imported_globals - ), + // flags that live in the `VMComponentContext`, and those get + // their own alias regions rather than a defined-global one. + EntityIndex::Global(imported_global) => match component_flags(arg) { + Some(flags) => { + if ambiguous.flags.contains(&flags) { + continue; + } + assert!( + self.static_modules[module].known_imported_globals[imported_global] + .is_none() + ); + self.static_modules[module].known_imported_globals[imported_global] = + Some(flags); + } + None => record_known_entity!( + Global, + imported_global, + defined_global_index, + known_imported_globals, + KnownGlobal::Defined + ), + }, EntityIndex::Memory(imported_memory) => record_known_entity!( Memory, imported_memory, defined_memory_index, - known_imported_memories + known_imported_memories, + core::convert::identity ), EntityIndex::Table(imported_table) => record_known_entity!( Table, imported_table, defined_table_index, - known_imported_tables + known_imported_tables, + core::convert::identity ), // Tags don't have alias regions of their own. @@ -1927,6 +1952,28 @@ mod pre_inlining { } use pre_inlining::PreInliningComponentTypes; +/// Every entity whose identity is not statically known to everything that can +/// access it. +#[derive(Default)] +struct Ambiguous { + /// Globals, memories, and tables defined by a static module in this + /// component. + entities: HashSet<(StaticModuleIndex, EntityIndex)>, + + /// Component-model flags living in the `VMComponentContext`. Only ever + /// contains the non-`KnownGlobal::Defined` variants. + flags: HashSet, +} + +/// Get the component-model flag that a `CoreDef` names, if it names one. +fn component_flags(def: &CoreDef) -> Option { + match def { + CoreDef::InstanceFlags(instance) => Some(KnownGlobal::ComponentInstanceFlags(*instance)), + CoreDef::TaskMayBlock => Some(KnownGlobal::TaskMayBlock), + CoreDef::Export(_) | CoreDef::Trampoline(_) | CoreDef::UnsafeIntrinsic(_) => None, + } +} + /// Resolve a `CoreExport` to the static module that defines it and the entity /// index it refers to within that module, when we can see through it statically. fn resolve_core_export( @@ -1985,22 +2032,21 @@ fn ambiguous_entities( translation: &ComponentTranslation, instantiations: &SecondaryMap>, instance_to_module: &PrimaryMap>, -) -> HashSet<(StaticModuleIndex, EntityIndex)> { - let mut ambiguous = HashSet::default(); +) -> Ambiguous { + let mut ambiguous = Ambiguous::default(); let mut mark = |def: &CoreDef| match def { CoreDef::Export(export) => { if let Some(entity) = resolve_core_export(instance_to_module, export) { - ambiguous.insert(entity); + ambiguous.entities.insert(entity); } } - // None of these are entities that get an alias region keyed by a - // defining module and index. - CoreDef::InstanceFlags(_) - | CoreDef::Trampoline(_) - | CoreDef::UnsafeIntrinsic(_) - | CoreDef::TaskMayBlock => {} + CoreDef::InstanceFlags(_) | CoreDef::TaskMayBlock => { + ambiguous.flags.insert(component_flags(def).unwrap()); + } + + CoreDef::Trampoline(_) | CoreDef::UnsafeIntrinsic(_) => {} }; for init in &translation.component.initializers { diff --git a/crates/environ/src/component/vmcomponent_offsets.rs b/crates/environ/src/component/vmcomponent_offsets.rs index 1b1c14834666..fc6e3769fcc7 100644 --- a/crates/environ/src/component/vmcomponent_offsets.rs +++ b/crates/environ/src/component/vmcomponent_offsets.rs @@ -4,8 +4,8 @@ // magic: u32, // builtins: &'static VMComponentBuiltins, // limits: *const VMStoreContext, -// may_leave: [VMGlobalDefinition; component.num_runtime_component_instances], // task_may_block: u32, +// may_leave: [VMGlobalDefinition; component.num_runtime_component_instances], // trampoline_func_refs: [VMFuncRef; component.num_trampolines], // unsafe_intrinsics: [VMFuncRef; component.num_unsafe_intrinsics], // lowerings: [VMLowering; component.num_lowerings], @@ -59,8 +59,8 @@ pub struct VMComponentOffsets

{ magic: u32, builtins: u32, vm_store_context: u32, - may_leave: u32, task_may_block: u32, + may_leave: u32, trampoline_func_refs: u32, intrinsic_func_refs: u32, lowerings: u32, @@ -128,8 +128,8 @@ impl VMComponentOffsets

{ magic: 0, builtins: 0, vm_store_context: 0, - may_leave: 0, task_may_block: 0, + may_leave: 0, trampoline_func_refs: 0, intrinsic_func_refs: 0, lowerings: 0, @@ -172,8 +172,8 @@ impl VMComponentOffsets

{ size(builtins) = ret.ptr.size(), size(vm_store_context) = ret.ptr.size(), align(16), - size(may_leave) = cmul(ret.num_runtime_component_instances, ret.ptr.vm_global_definition().size()), size(task_may_block) = ret.ptr.vm_global_definition().size(), + size(may_leave) = cmul(ret.num_runtime_component_instances, ret.ptr.vm_global_definition().size()), align(u32::from(ret.ptr.size())), size(trampoline_func_refs) = cmul(ret.num_trampolines, ret.ptr.vm_func_ref().size()), size(intrinsic_func_refs) = cmul(ret.num_unsafe_intrinsics, ret.ptr.vm_func_ref().size()), @@ -193,9 +193,30 @@ impl VMComponentOffsets

{ // location needs to be updated. assert_eq!(ret.magic, 0); + // The component-model flags must land where a compiler that only knows + // the pointer size can find them; see `Self::task_may_block_offset`. + debug_assert_eq!(ret.task_may_block, Self::task_may_block_offset(&ret.ptr)); + debug_assert_eq!( + ret.may_leave, + Self::may_leave_offset(&ret.ptr, RuntimeComponentInstanceIndex::from_u32(0)) + ); + return ret; } + /// The offset of the `task_may_block` field, given only the pointer size. + pub fn task_may_block_offset(ptr: &P) -> u32 { + let magic_and_pointers = align(4, u32::from(ptr.size())) + 2 * u32::from(ptr.size()); + align(magic_and_pointers, 16) + } + + /// The offset of the given component instance's `may_leave` flag, given + /// only the pointer size. + pub fn may_leave_offset(ptr: &P, index: RuntimeComponentInstanceIndex) -> u32 { + let global = u32::from(ptr.vm_global_definition().size()); + Self::task_may_block_offset(ptr) + global * (index.as_u32() + 1) + } + /// The size, in bytes, of the host pointer. #[inline] pub fn pointer_size(&self) -> u8 { @@ -218,7 +239,7 @@ impl VMComponentOffsets

{ #[inline] pub fn may_leave(&self, index: RuntimeComponentInstanceIndex) -> u32 { assert!(index.as_u32() < self.num_runtime_component_instances); - self.may_leave + index.as_u32() * u32::from(self.ptr.vm_global_definition().size()) + Self::may_leave_offset(&self.ptr, index) } /// The offset of the `task_may_block` field. @@ -391,3 +412,38 @@ impl VMComponentOffsets

{ self.size } } + +#[cfg(test)] +mod tests { + use super::*; + + /// The pointer-size-only flag offsets must match the real layout for every + /// pointer width and every number of component instances, since core wasm + /// compilation uses them to build alias regions that must agree with the + /// regions the component trampolines use. + #[test] + fn flag_offsets_match_layout() { + for ptr in [4u8, 8] { + for num_runtime_component_instances in 0..8 { + let component = Component { + num_runtime_component_instances, + ..Default::default() + }; + let offsets = VMComponentOffsets::new(ptr, &component); + + assert_eq!( + offsets.task_may_block(), + VMComponentOffsets::task_may_block_offset(&ptr) + ); + + for i in 0..num_runtime_component_instances { + let index = RuntimeComponentInstanceIndex::from_u32(i); + assert_eq!( + offsets.may_leave(index), + VMComponentOffsets::may_leave_offset(&ptr, index) + ); + } + } + } + } +} diff --git a/tests/disas/component-may-leave-without-signals-based-traps.wat b/tests/disas/component-may-leave-without-signals-based-traps.wat index d9834361eaea..bab1a5201a3d 100644 --- a/tests/disas/component-may-leave-without-signals-based-traps.wat +++ b/tests/disas/component-may-leave-without-signals-based-traps.wat @@ -24,7 +24,7 @@ ;; movq %rbp, %rcx ;; movq 8(%rcx), %rcx ;; movq %rcx, 0x38(%rax) -;; movl 0x20(%rdi), %eax +;; movl 0x30(%rdi), %eax ;; testl %eax, %eax ;; je 0x13b ;; fb: movq 8(%rdi), %rax diff --git a/tests/disas/component-model/direct-adapter-calls-inlining.wat b/tests/disas/component-model/direct-adapter-calls-inlining.wat index b69739b04ee6..bbce07409b13 100644 --- a/tests/disas/component-model/direct-adapter-calls-inlining.wat +++ b/tests/disas/component-model/direct-adapter-calls-inlining.wat @@ -59,7 +59,8 @@ ;; region1 = 67108888 "VMStoreContext+0x18" ;; region2 = 1207959576 "VMFunctionImport+0x18" ;; region3 = 1476395008 "VMGlobalImport+0x0" -;; region4 = 402653184 "PublicGlobal" +;; region4 = 738197584 "VMComponentContext+0x50" +;; region5 = 738197568 "VMComponentContext+0x40" ;; gv0 = vmctx ;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 ;; gv2 = load.i64 notrap aligned region1 gv1+24 @@ -91,7 +92,7 @@ ;; ;; block9: ;; v11 = load.i64 notrap aligned readonly can_move region3 v3+112 -;; v12 = load.i32 notrap aligned region4 v11 +;; v12 = load.i32 notrap aligned region5 v11 ;; jump block12 ;; ;; block12: @@ -101,7 +102,6 @@ ;; jump block11 ;; ;; block11: -;; store.i32 notrap aligned region4 v10, v9 ;; jump block7 ;; ;; block7: diff --git a/tests/disas/component-model/direct-adapter-calls.wat b/tests/disas/component-model/direct-adapter-calls.wat index ca4167811926..cdf17060efd2 100644 --- a/tests/disas/component-model/direct-adapter-calls.wat +++ b/tests/disas/component-model/direct-adapter-calls.wat @@ -99,8 +99,9 @@ ;; region0 = 8 "VMContext+0x8" ;; region1 = 67108888 "VMStoreContext+0x18" ;; region2 = 1476395008 "VMGlobalImport+0x0" -;; region3 = 402653184 "PublicGlobal" +;; region3 = 738197584 "VMComponentContext+0x50" ;; region4 = 1207959576 "VMFunctionImport+0x18" +;; region5 = 738197568 "VMComponentContext+0x40" ;; gv0 = vmctx ;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 ;; gv2 = load.i64 notrap aligned region1 gv1+24 @@ -123,7 +124,7 @@ ;; ;; block7: ;; @008e v10 = load.i64 notrap aligned readonly can_move region2 v0+112 -;; @008e v11 = load.i32 notrap aligned region3 v10 +;; @008e v11 = load.i32 notrap aligned region5 v10 ;; @009c v15 = load.i64 notrap aligned readonly can_move region4 v0+72 ;; @009c try_call fn0(v15, v0, v2), sig1, block9(ret0), [ context v0, default: block6(exn0) ] ;; diff --git a/tests/disas/component-model/known-imported-adapter-memory.wat b/tests/disas/component-model/known-imported-adapter-memory.wat index d1a8738fd197..bc1b0598fc7e 100644 --- a/tests/disas/component-model/known-imported-adapter-memory.wat +++ b/tests/disas/component-model/known-imported-adapter-memory.wat @@ -150,13 +150,14 @@ ;; region0 = 8 "VMContext+0x8" ;; region1 = 67108888 "VMStoreContext+0x18" ;; region2 = 1476395008 "VMGlobalImport+0x0" -;; region3 = 402653184 "PublicGlobal" +;; region3 = 738197584 "VMComponentContext+0x50" ;; region4 = 1207959576 "VMFunctionImport+0x18" -;; region5 = 1275068416 "VMMemoryImport+0x0" -;; region6 = 603979776 "VMMemoryDefinition+0x0" -;; region7 = 603979784 "VMMemoryDefinition+0x8" -;; region8 = 201326592 "DefinedMemory(StaticModuleIndex(0), DefinedMemoryIndex(0))" -;; region9 = 201588736 "DefinedMemory(StaticModuleIndex(1), DefinedMemoryIndex(0))" +;; region5 = 738197568 "VMComponentContext+0x40" +;; region6 = 1275068416 "VMMemoryImport+0x0" +;; region7 = 603979776 "VMMemoryDefinition+0x0" +;; region8 = 603979784 "VMMemoryDefinition+0x8" +;; region9 = 201326592 "DefinedMemory(StaticModuleIndex(0), DefinedMemoryIndex(0))" +;; region10 = 201588736 "DefinedMemory(StaticModuleIndex(1), DefinedMemoryIndex(0))" ;; gv0 = vmctx ;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 ;; gv2 = load.i64 notrap aligned region1 gv1+24 @@ -179,7 +180,7 @@ ;; ;; block7: ;; @00d1 v11 = load.i64 notrap aligned readonly can_move region2 v0+224 -;; @00d1 v12 = load.i32 notrap aligned region3 v11 +;; @00d1 v12 = load.i32 notrap aligned region5 v11 ;; @00df v16 = load.i64 notrap aligned readonly can_move region4 v0+184 ;; @00df try_call fn0(v16, v0, v2), sig1, block9(ret0), [ context v0, default: block6(exn0) ] ;; @@ -192,8 +193,8 @@ ;; @00ec jump block11 ;; ;; block11: -;; @00f8 v24 = load.i64 notrap aligned readonly can_move region5 v0+48 -;; @00f8 v25 = load.i64 notrap aligned region7 v24+8 +;; @00f8 v24 = load.i64 notrap aligned readonly can_move region6 v0+48 +;; @00f8 v25 = load.i64 notrap aligned region8 v24+8 ;; @00f8 v26 = iconst.i64 16 ;; @00f8 v27 = ushr v25, v26 ; v26 = 16 ;; @00f8 v28 = ireduce.i32 v27 @@ -218,8 +219,8 @@ ;; @0114 jump block16 ;; ;; block16: -;; @0120 v44 = load.i64 notrap aligned readonly can_move region5 v0+72 -;; @0120 v45 = load.i64 notrap aligned region7 v44+8 +;; @0120 v44 = load.i64 notrap aligned readonly can_move region6 v0+72 +;; @0120 v45 = load.i64 notrap aligned region8 v44+8 ;; v88 = iconst.i64 16 ;; v89 = ushr v45, v88 ; v88 = 16 ;; @0120 v48 = ireduce.i32 v89 @@ -238,17 +239,17 @@ ;; @0133 trap user4 ;; ;; block17: -;; @013b v62 = load.i64 notrap aligned readonly can_move region6 v24 +;; @013b v62 = load.i64 notrap aligned readonly can_move region7 v24 ;; @013b v63 = iadd v62, v32 -;; @013b v64 = load.i32 little region8 v63 -;; @013e v67 = load.i64 notrap aligned readonly can_move region6 v44 +;; @013b v64 = load.i32 little region9 v63 +;; @013e v67 = load.i64 notrap aligned readonly can_move region7 v44 ;; @013e v68 = iadd v67, v52 -;; @013e store little region9 v64, v68 +;; @013e store little region10 v64, v68 ;; @0146 v73 = iconst.i64 4 ;; @0146 v74 = iadd v63, v73 ; v73 = 4 -;; @0146 v75 = load.i32 little region8 v74 +;; @0146 v75 = load.i32 little region9 v74 ;; @0149 v81 = iadd v68, v73 ; v73 = 4 -;; @0149 store little region9 v75, v81 +;; @0149 store little region10 v75, v81 ;; @014f store.i32 notrap aligned region3 v8, v7 ;; @0151 jump block5 ;; diff --git a/tests/disas/component-model/sync-adapter-calls.wat b/tests/disas/component-model/sync-adapter-calls.wat index 024d7db65d88..e86e58f643f6 100644 --- a/tests/disas/component-model/sync-adapter-calls.wat +++ b/tests/disas/component-model/sync-adapter-calls.wat @@ -57,17 +57,19 @@ ;; region1 = 67108888 "VMStoreContext+0x18" ;; region2 = 1207959576 "VMFunctionImport+0x18" ;; region3 = 1476395008 "VMGlobalImport+0x0" -;; region4 = 402653184 "PublicGlobal" -;; region5 = 67109000 "VMStoreContext+0x88" -;; region6 = 1006632960 "VMDeferredThread+0x0" -;; region7 = 1006632968 "VMDeferredThread+0x8" -;; region8 = 1006632972 "VMDeferredThread+0xc" -;; region9 = 1006632976 "VMDeferredThread+0x10" -;; region10 = 67108992 "VMStoreContext+0x80" -;; region11 = 1006632980 "VMDeferredThread+0x14" -;; region12 = 67108996 "VMStoreContext+0x84" -;; region13 = 1006632984 "VMDeferredThread+0x18" -;; region14 = 1207959560 "VMFunctionImport+0x8" +;; region4 = 738197584 "VMComponentContext+0x50" +;; region5 = 738197536 "VMComponentContext+0x20" +;; region6 = 67109000 "VMStoreContext+0x88" +;; region7 = 1006632960 "VMDeferredThread+0x0" +;; region8 = 1006632968 "VMDeferredThread+0x8" +;; region9 = 1006632972 "VMDeferredThread+0xc" +;; region10 = 1006632976 "VMDeferredThread+0x10" +;; region11 = 67108992 "VMStoreContext+0x80" +;; region12 = 1006632980 "VMDeferredThread+0x14" +;; region13 = 67108996 "VMStoreContext+0x84" +;; region14 = 1006632984 "VMDeferredThread+0x18" +;; region15 = 738197568 "VMComponentContext+0x40" +;; region16 = 1207959560 "VMFunctionImport+0x8" ;; gv0 = vmctx ;; gv1 = load.i64 notrap aligned readonly can_move region0 gv0+8 ;; gv2 = load.i64 notrap aligned region1 gv1+24 @@ -101,27 +103,27 @@ ;; ;; block9: ;; v11 = load.i64 notrap aligned readonly can_move region3 v3+224 -;; v12 = load.i32 notrap aligned region4 v11 +;; v12 = load.i32 notrap aligned region5 v11 ;; v8 = iconst.i32 0 -;; store notrap aligned region4 v8, v11 ; v8 = 0 +;; store notrap aligned region5 v8, v11 ; v8 = 0 ;; v20 = load.i64 notrap aligned readonly can_move region0 v3+8 -;; v21 = load.i64 notrap aligned region5 v20+136 +;; v21 = load.i64 notrap aligned region6 v20+136 ;; v19 = stack_addr.i64 ss0 -;; store notrap aligned region6 v21, v19 +;; store notrap aligned region7 v21, v19 ;; v15 = iconst.i32 2 -;; store notrap aligned region7 v15, v19+8 ; v15 = 2 -;; store notrap aligned region8 v8, v19+12 ; v8 = 0 +;; store notrap aligned region8 v15, v19+8 ; v15 = 2 +;; store notrap aligned region9 v8, v19+12 ; v8 = 0 ;; v17 = iconst.i32 1 -;; store notrap aligned region9 v17, v19+16 ; v17 = 1 -;; v22 = load.i32 notrap aligned region10 v20+128 -;; store notrap aligned region11 v22, v19+20 -;; store notrap aligned region10 v8, v20+128 ; v8 = 0 -;; v24 = load.i32 notrap aligned region12 v20+132 -;; store notrap aligned region13 v24, v19+24 -;; store notrap aligned region12 v8, v20+132 ; v8 = 0 -;; store notrap aligned region5 v19, v20+136 +;; store notrap aligned region10 v17, v19+16 ; v17 = 1 +;; v22 = load.i32 notrap aligned region11 v20+128 +;; store notrap aligned region12 v22, v19+20 +;; store notrap aligned region11 v8, v20+128 ; v8 = 0 +;; v24 = load.i32 notrap aligned region13 v20+132 +;; store notrap aligned region14 v24, v19+24 +;; store notrap aligned region13 v8, v20+132 ; v8 = 0 +;; store notrap aligned region6 v19, v20+136 ;; v26 = load.i64 notrap aligned readonly can_move region3 v3+176 -;; v27 = load.i32 notrap aligned region4 v26 +;; v27 = load.i32 notrap aligned region15 v26 ;; jump block16 ;; ;; block16: @@ -134,14 +136,14 @@ ;; jump block12 ;; ;; block12: -;; store.i64 notrap aligned region5 v21, v20+136 -;; store.i32 notrap aligned region10 v22, v20+128 -;; store.i32 notrap aligned region12 v24, v20+132 +;; store.i64 notrap aligned region6 v21, v20+136 +;; store.i32 notrap aligned region11 v22, v20+128 +;; store.i32 notrap aligned region13 v24, v20+132 ;; jump block14 ;; ;; block14: ;; store.i32 notrap aligned region4 v10, v9 -;; store.i32 notrap aligned region4 v12, v11 +;; store.i32 notrap aligned region5 v12, v11 ;; jump block7 ;; ;; block7: diff --git a/tests/disas/riscv64-component-builtins-asm.wat b/tests/disas/riscv64-component-builtins-asm.wat index 9a71f7ea3047..b171c7dc830d 100644 --- a/tests/disas/riscv64-component-builtins-asm.wat +++ b/tests/disas/riscv64-component-builtins-asm.wat @@ -26,7 +26,7 @@ ;; sd a3, 0x30(a1) ;; ld a2, 8(s0) ;; sd a2, 0x38(a1) -;; lw a1, 0x20(a0) +;; lw a1, 0x30(a0) ;; sext.w a1, a1 ;; bnez a1, 8 ;; .byte 0x00, 0x00, 0x00, 0x00 diff --git a/tests/disas/riscv64-component-builtins.wat b/tests/disas/riscv64-component-builtins.wat index 03db5c955811..57d732beddbd 100644 --- a/tests/disas/riscv64-component-builtins.wat +++ b/tests/disas/riscv64-component-builtins.wat @@ -14,7 +14,7 @@ ;; region0 = 8 "VMContext+0x8" ;; region1 = 67108912 "VMStoreContext+0x30" ;; region2 = 67108920 "VMStoreContext+0x38" -;; region3 = 738197536 "VMComponentContext+0x20" +;; region3 = 738197552 "VMComponentContext+0x30" ;; region4 = 738197512 "VMComponentContext+0x8" ;; region5 = 1879048208 "ComponentBuiltinFunctionsArray+0x10" ;; region6 = 16 "VMContext+0x10" @@ -28,7 +28,7 @@ ;; store notrap aligned region1 v4, v3+48 ;; v5 = get_return_address.i64 ;; store notrap aligned region2 v5, v3+56 -;; v6 = load.i32 notrap aligned region3 v0+32 +;; v6 = load.i32 notrap aligned region3 v0+48 ;; trapz v6, user26 ;; v9 = load.i64 notrap aligned readonly region4 v0+8 ;; v10 = load.i64 notrap aligned readonly can_move region5 v9+16