From 6a241a28bc130b445148f88feb771cac5d869c3a Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Wed, 5 Aug 2026 06:36:41 -0700 Subject: [PATCH 1/2] 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 | 38 +++++++ crates/cranelift/src/func_environ.rs | 10 +- crates/environ/src/compile/module_environ.rs | 21 +++- crates/environ/src/component/translate.rs | 105 +++++++++++++----- .../src/component/vmcomponent_offsets.rs | 70 +++++++++++- crates/environ/src/vmctxtypes.rs | 13 ++- ...-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 +- 13 files changed, 282 insertions(+), 91 deletions(-) diff --git a/crates/cranelift/src/alias_region.rs b/crates/cranelift/src/alias_region.rs index 24d1dd1d0254..33f0d0207d0e 100644 --- a/crates/cranelift/src/alias_region.rs +++ b/crates/cranelift/src/alias_region.rs @@ -986,6 +986,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) + } +} + /// `VMStoreContext`-related methods. impl AliasRegions where diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index 94693c965ce6..d6bfbbc1b0eb 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, @@ -446,10 +446,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 1df5a9e2eb78..ed18770b73f8 100644 --- a/crates/environ/src/compile/module_environ.rs +++ b/crates/environ/src/compile/module_environ.rs @@ -74,7 +74,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, @@ -82,6 +82,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 @@ -154,7 +171,7 @@ pub struct ModuleTranslation<'data> { /// TODO(#14164): Actually record (1) and (2) in separate maps, enabling /// optimizations that rely on just (1) but not (2), instead of folding them /// into this same map. - 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 b81089d14677..1a8c3136270f 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, }; @@ -606,21 +606,30 @@ 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); } } 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); } } 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); } } @@ -643,9 +652,14 @@ 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(&self.static_modules, &instances, &ambiguous, arg) + unambiguous_entity( + &self.static_modules, + &instances, + &ambiguous.entities, + arg, + ) else { continue; }; @@ -657,10 +671,10 @@ impl<'a, 'data> Translator<'a, 'data> { defines", ); 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, - }); + })); }}; } @@ -761,27 +775,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 +1957,28 @@ use pre_inlining::PreInliningComponentTypes; type StaticInstances<'a> = PrimaryMap>; +/// 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. /// @@ -2047,22 +2099,23 @@ fn ambiguous_entities( translation: &ComponentTranslation, instantiations: &SecondaryMap>, instances: &StaticInstances<'_>, -) -> 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(static_modules, instances, export) { - ambiguous.insert(entity); + ambiguous.entities.insert(entity); } } - // None of these are entities that get an alias region keyed by a + CoreDef::InstanceFlags(_) | CoreDef::TaskMayBlock => { + ambiguous.flags.insert(component_flags(def).unwrap()); + } + + // Neither of these is an entity that gets an alias region keyed by a // defining module and index. - CoreDef::InstanceFlags(_) - | CoreDef::Trampoline(_) - | CoreDef::UnsafeIntrinsic(_) - | CoreDef::TaskMayBlock => {} + 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 937b7445278c..156141a44808 100644 --- a/crates/environ/src/component/vmcomponent_offsets.rs +++ b/crates/environ/src/component/vmcomponent_offsets.rs @@ -49,8 +49,8 @@ pub struct VMComponentOffsets

{ // plus this `VMComponentContext`'s total size. These are all computed by the // generated `compute_field_offsets` and read by the generated accessors of // the same names. - may_leave: u32, task_may_block: u32, + may_leave: u32, trampoline_func_refs: u32, intrinsic_func_refs: u32, lowerings: u32, @@ -165,8 +165,8 @@ impl VMComponentOffsets

{ 0 }, num_resources: component.num_resources, - may_leave: 0, task_may_block: 0, + may_leave: 0, trampoline_func_refs: 0, intrinsic_func_refs: 0, lowerings: 0, @@ -181,9 +181,40 @@ impl VMComponentOffsets

{ ret.compute_field_offsets(); + // The component-model flags must land where a compiler that only knows + // the pointer size can find them; see `Self::task_may_block_offset` and + // `Self::may_leave_offset`. + debug_assert_eq!(ret.task_may_block(), Self::task_may_block_offset(&ret.ptr)); + debug_assert!( + (0..ret.num_runtime_component_instances) + .map(RuntimeComponentInstanceIndex::from_u32) + .all(|i| ret.may_leave().at(i) == Self::may_leave_offset(&ret.ptr, i)) + ); + ret } + /// The offset of the `task_may_block` flag, given only the pointer size. + /// + /// Core Wasm compilation does not have a component's `VMComponentOffsets` on + /// hand, but it must still be able to name this flag's location to build the + /// alias region for accessing it. This is only possible because the flags + /// are laid out before every field whose offset depends on the component's + /// shape; see `for_each_vmctx_type!`. + pub fn task_may_block_offset(ptr: &P) -> u32 { + crate::vmctxtypes::align_up(u32::from(ptr.vmcomponent().end_of_static_fields()), 16) + } + + /// The offset of the given component instance's `may_leave` flag, given only + /// the pointer size. + /// + /// See [`Self::task_may_block_offset`] for why this is computable without + /// the full offsets. + 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 { @@ -220,3 +251,38 @@ impl VMComponentOffsets

{ u32::from(self.ptr.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().at(index), + VMComponentOffsets::may_leave_offset(&ptr, index) + ); + } + } + } + } +} diff --git a/crates/environ/src/vmctxtypes.rs b/crates/environ/src/vmctxtypes.rs index ddac95c64e0b..83965d05fe14 100644 --- a/crates/environ/src/vmctxtypes.rs +++ b/crates/environ/src/vmctxtypes.rs @@ -227,13 +227,22 @@ macro_rules! for_each_vmctx_type { // Each of these flags gets a whole `VMGlobalDefinition`'s // worth of space, but only its first four bytes are ever // accessed. + // + // NB: these flags come first, before any field whose offset + // depends on the component's shape, so that their offsets + // are a function of the target pointer size alone. Core Wasm + // compilation does not have the enclosing + // `VMComponentContext`'s offsets on hand, but must still be + // able to compute these flags' offsets to build the alias + // regions for accessing them; see + // `VMComponentOffsets::{task_may_block,may_leave}_offset`. + field { #[access_as = u32] task_may_block: VMGlobalDefinition } + array { #[access_as = u32] may_leave[num_runtime_component_instances; RuntimeComponentInstanceIndex]: VMGlobalDefinition } - field { #[access_as = u32] task_may_block: VMGlobalDefinition } - align { ptr } array { 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 f266e9f9a4fb..a70e875e55c5 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+144 -;; 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 32963874224f..5c6865a46863 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: ;; @009e v9 = load.i64 notrap aligned readonly can_move region2 v0+144 -;; @009e v10 = load.i32 notrap aligned region3 v9 +;; @009e v10 = load.i32 notrap aligned region5 v9 ;; @00ac v14 = load.i64 notrap aligned readonly can_move region4 v0+72 ;; @00ac try_call fn0(v14, 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 f0e2c91dac3a..b427a46e580c 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: ;; @0103 v10 = load.i64 notrap aligned readonly can_move region2 v0+320 -;; @0103 v11 = load.i32 notrap aligned region3 v10 +;; @0103 v11 = load.i32 notrap aligned region5 v10 ;; @0111 v15 = load.i64 notrap aligned readonly can_move region4 v0+184 ;; @0111 try_call fn0(v15, v0, v2), sig1, block9(ret0), [ context v0, default: block6(exn0) ] ;; @@ -192,8 +193,8 @@ ;; @011e jump block11 ;; ;; block11: -;; @0128 v22 = load.i64 notrap aligned readonly can_move region5 v0+48 -;; @0128 v23 = load.i64 notrap aligned region7 v22+8 +;; @0128 v22 = load.i64 notrap aligned readonly can_move region6 v0+48 +;; @0128 v23 = load.i64 notrap aligned region8 v22+8 ;; @0128 v24 = iconst.i64 16 ;; @0128 v25 = ushr v23, v24 ; v24 = 16 ;; @0128 v26 = ireduce.i32 v25 @@ -218,8 +219,8 @@ ;; @0142 jump block16 ;; ;; block16: -;; @014c v40 = load.i64 notrap aligned readonly can_move region5 v0+72 -;; @014c v41 = load.i64 notrap aligned region7 v40+8 +;; @014c v40 = load.i64 notrap aligned readonly can_move region6 v0+72 +;; @014c v41 = load.i64 notrap aligned region8 v40+8 ;; v82 = iconst.i64 16 ;; v83 = ushr v41, v82 ; v82 = 16 ;; @014c v44 = ireduce.i32 v83 @@ -238,17 +239,17 @@ ;; @015d trap user4 ;; ;; block17: -;; @0165 v57 = load.i64 notrap aligned readonly can_move region6 v22 +;; @0165 v57 = load.i64 notrap aligned readonly can_move region7 v22 ;; @0165 v58 = iadd v57, v30 -;; @0165 v59 = load.i32 little region8 v58 -;; @0168 v62 = load.i64 notrap aligned readonly can_move region6 v40 +;; @0165 v59 = load.i32 little region9 v58 +;; @0168 v62 = load.i64 notrap aligned readonly can_move region7 v40 ;; @0168 v63 = iadd v62, v48 -;; @0168 store little region9 v59, v63 +;; @0168 store little region10 v59, v63 ;; @0170 v68 = iconst.i64 4 ;; @0170 v69 = iadd v58, v68 ; v68 = 4 -;; @0170 v70 = load.i32 little region8 v69 +;; @0170 v70 = load.i32 little region9 v69 ;; @0173 v76 = iadd v63, v68 ; v68 = 4 -;; @0173 store little region9 v70, v76 +;; @0173 store little region10 v70, v76 ;; @0179 store.i32 notrap aligned region3 v8, v7 ;; @017b jump block5 ;; diff --git a/tests/disas/component-model/sync-adapter-calls.wat b/tests/disas/component-model/sync-adapter-calls.wat index b89550743fdf..91ee39342691 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 @@ -100,27 +102,27 @@ ;; ;; block9: ;; v11 = load.i64 notrap aligned readonly can_move region3 v3+256 -;; 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+208 -;; v27 = load.i32 notrap aligned region4 v26 +;; v27 = load.i32 notrap aligned region15 v26 ;; jump block16 ;; ;; block16: @@ -133,14 +135,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 From 1831d57464b3dd26534c5ad6ccf7dc1e357ed8d9 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 20 Aug 2026 12:48:42 -0700 Subject: [PATCH 2/2] Cleanups after rebasing on top of the vmctx macros Avoid defining new `AliasRegions` helpers and use macro-generated helpers instead. --- crates/cranelift/src/alias_region.rs | 101 ++++++++++-------- crates/cranelift/src/func_environ.rs | 10 +- .../src/component/vmcomponent_offsets.rs | 35 +----- crates/environ/src/vmctxtypes.rs | 20 ++-- crates/environ/src/vmoffsets.rs | 74 ++++++++++++- 5 files changed, 154 insertions(+), 86 deletions(-) diff --git a/crates/cranelift/src/alias_region.rs b/crates/cranelift/src/alias_region.rs index 33f0d0207d0e..fda5de40830a 100644 --- a/crates/cranelift/src/alias_region.rs +++ b/crates/cranelift/src/alias_region.rs @@ -456,10 +456,15 @@ impl<'a, Offsets> Field<'a, Offsets> { self } + /// Get-or-create this field's alias region. + pub fn region(&mut self, func: &mut ir::Function) -> ir::AliasRegion { + self.regions.region(func, self.key) + } + /// Get-or-create this field's alias region and mix it into this field's /// base flags. fn flags_with_region(&mut self, func: &mut ir::Function) -> ir::MemFlagsData { - let region = self.regions.region(func, self.key); + let region = self.region(func); self.flags.with_alias_region(Some(region)) } @@ -710,7 +715,10 @@ wasmtime_environ::for_each_vm_type!(define_vm_type_alias_region_helpers); /// pointer size, so their accessors are available for any `Offsets: GetPtrSize`. /// Its `dynamic` fields sit at offsets that additionally depend on the module or /// component being compiled, so their accessors are only available when the -/// `AliasRegions` carries that vmctx's own fully-computed offsets. +/// `AliasRegions` carries that vmctx's own fully-computed offsets. The exception +/// is a `dynamic` field marked `#[ptr_size_offset]`, whose offsets are derived +/// only from the pointer size; these live in the `Offsets: GetPtrSize` block as +/// well. /// /// A field marked `#[aggregate]` gets no accessor, for the same reason it gets /// none in [`define_vm_type_alias_region_helpers!`]: it has no single Cranelift @@ -748,6 +756,7 @@ macro_rules! define_vmctx_alias_region_helpers { (@apply_attr $flags:expr, [readonly]) => { $flags.with_readonly() }; (@apply_attr $flags:expr, [can_move]) => { $flags.with_can_move() }; (@apply_attr $flags:expr, [access_as = $($t:tt)*]) => { $flags }; + (@apply_attr $flags:expr, [ptr_size_offset]) => { $flags }; // Compute a field's access flags and Cranelift type from its declared type // and marker attributes, and build the `Field` for it at `$offset`. @@ -784,8 +793,53 @@ macro_rules! define_vmctx_alias_region_helpers { } }; + // ### `dynamic` Section Entries Marked `#[ptr_size_offset]` + // + // These get their offsets from the pointer-size-only `offsets::VMFoo

` + // wrapper, and don't require a full `VMOffsets` parameterization. + + (@ptr_size_entry $Name:ident $snake:ident field { + #[ptr_size_offset] $(# $fattr:tt)* $fname:ident : $($fty:tt)* + }) => { + #[doc = concat!( + "Get the [`Field`] for the `", stringify!($fname), "` field of `", + stringify!($Name), "`." + )] + pub fn $fname(self) -> Field<'a, Offsets> { + let offset = self.regions.offsets.get_ptr_size().$snake().$fname(); + define_vmctx_alias_region_helpers!( + @field $Name (self, offset) [ $($fty)* ] [ $(# $fattr)* ] + ) + } + }; + + (@ptr_size_entry $Name:ident $snake:ident array { + #[ptr_size_offset] $(# $fattr:tt)* $fname:ident [ $count:ident ; $Index:ident ] : $($fty:tt)* + }) => { + #[doc = concat!( + "Get the [`Field`] for the `index`th element of `", stringify!($Name), + "`'s `", stringify!($fname), "` array.\n\nThis is not bounds checked: \ + the array's length depends on the module or component being compiled, \ + which is precisely what this accessor does not require knowing." + )] + pub fn $fname(self, index: $Index) -> Field<'a, Offsets> { + let offset = self.regions.offsets.get_ptr_size().$snake().$fname(index); + define_vmctx_alias_region_helpers!( + @field $Name (self, offset) [ $($fty)* ] [ $(# $fattr)* ] + ) + } + }; + + (@ptr_size_entry $Name:ident $snake:ident $kind:ident $entry:tt) => {}; + // ### `dynamic` Section Entries + // Entries marked `#[ptr_size_offset]` were already handled above; emitting + // them here too would be a duplicate definition. + (@dynamic_entry $Name:ident $Offsets:tt $kind:ident { + #[ptr_size_offset] $($rest:tt)* + }) => {}; + (@dynamic_entry $Name:ident $Offsets:tt align { $al:tt }) => {}; // Aggregates get no accessor. @@ -848,7 +902,7 @@ macro_rules! define_vmctx_alias_region_helpers { // Emit the accessor `struct` and both `impl` blocks for one vmctx type. (@emit $Name:ident $snake:ident $Offsets:tt static { $($skind:ident $sentry:tt)* } - dynamic { $($dyn:tt)* } + dynamic { $($dkind:ident $dentry:tt)* } ) => { #[doc = concat!( "An [`AliasRegions`] accessor for the fields of a `", stringify!($Name), @@ -879,13 +933,14 @@ macro_rules! define_vmctx_alias_region_helpers { Offsets: GetPtrSize, { $( define_vmctx_alias_region_helpers!(@static_entry $Name $snake $skind $sentry); )* + $( define_vmctx_alias_region_helpers!(@ptr_size_entry $Name $snake $dkind $dentry); )* } // A dynamically-positioned field's offset depends on the module or // component being compiled, so these accessors require this vmctx's own // fully-computed offsets. define_vmctx_alias_region_helpers!( - @dynamic_impl $Name $Offsets $Offsets { $($dyn)* } + @dynamic_impl $Name $Offsets $Offsets { $($dkind $dentry)* } ); }; @@ -986,44 +1041,6 @@ 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) - } -} - /// `VMStoreContext`-related methods. impl AliasRegions where diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index d6bfbbc1b0eb..7950d480bc79 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -452,8 +452,14 @@ impl<'module_environment> FuncEnvironment<'module_environment> { } Some(KnownGlobal::ComponentInstanceFlags(instance)) => self .alias_regions - .component_instance_flags_region(func, instance), - Some(KnownGlobal::TaskMayBlock) => self.alias_regions.task_may_block_region(func), + .vmcomponent() + .may_leave(instance) + .region(func), + Some(KnownGlobal::TaskMayBlock) => self + .alias_regions + .vmcomponent() + .task_may_block() + .region(func), None => self.alias_regions.public_global_region(func), }, } diff --git a/crates/environ/src/component/vmcomponent_offsets.rs b/crates/environ/src/component/vmcomponent_offsets.rs index 156141a44808..6a2f8ced008c 100644 --- a/crates/environ/src/component/vmcomponent_offsets.rs +++ b/crates/environ/src/component/vmcomponent_offsets.rs @@ -182,39 +182,17 @@ impl VMComponentOffsets

{ ret.compute_field_offsets(); // The component-model flags must land where a compiler that only knows - // the pointer size can find them; see `Self::task_may_block_offset` and - // `Self::may_leave_offset`. - debug_assert_eq!(ret.task_may_block(), Self::task_may_block_offset(&ret.ptr)); + // the pointer size can find them. + debug_assert_eq!(ret.task_may_block(), ret.ptr.vmcomponent().task_may_block()); debug_assert!( (0..ret.num_runtime_component_instances) .map(RuntimeComponentInstanceIndex::from_u32) - .all(|i| ret.may_leave().at(i) == Self::may_leave_offset(&ret.ptr, i)) + .all(|i| ret.may_leave().at(i) == ret.ptr.vmcomponent().may_leave(i)) ); ret } - /// The offset of the `task_may_block` flag, given only the pointer size. - /// - /// Core Wasm compilation does not have a component's `VMComponentOffsets` on - /// hand, but it must still be able to name this flag's location to build the - /// alias region for accessing it. This is only possible because the flags - /// are laid out before every field whose offset depends on the component's - /// shape; see `for_each_vmctx_type!`. - pub fn task_may_block_offset(ptr: &P) -> u32 { - crate::vmctxtypes::align_up(u32::from(ptr.vmcomponent().end_of_static_fields()), 16) - } - - /// The offset of the given component instance's `may_leave` flag, given only - /// the pointer size. - /// - /// See [`Self::task_may_block_offset`] for why this is computable without - /// the full offsets. - 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 { @@ -270,16 +248,13 @@ mod tests { }; let offsets = VMComponentOffsets::new(ptr, &component); - assert_eq!( - offsets.task_may_block(), - VMComponentOffsets::task_may_block_offset(&ptr) - ); + assert_eq!(offsets.task_may_block(), ptr.vmcomponent().task_may_block()); for i in 0..num_runtime_component_instances { let index = RuntimeComponentInstanceIndex::from_u32(i); assert_eq!( offsets.may_leave().at(index), - VMComponentOffsets::may_leave_offset(&ptr, index) + ptr.vmcomponent().may_leave(index) ); } } diff --git a/crates/environ/src/vmctxtypes.rs b/crates/environ/src/vmctxtypes.rs index 83965d05fe14..428f04c76cdb 100644 --- a/crates/environ/src/vmctxtypes.rs +++ b/crates/environ/src/vmctxtypes.rs @@ -85,6 +85,12 @@ /// example, the component context's `may_leave` flags are each stored in a /// whole `VMGlobalDefinition` but only ever accessed as a `u32`. /// +/// * `#[ptr_size_offset]` (`dynamic` only): this field's offset is a function +/// of the target pointer size alone, even though it lives in the `dynamic` +/// section, because it and everything before it have sizes that do not depend +/// on the vmctx's shape. These fields get generated accessors that do not +/// need to be parameterized over `VMOffsets`, only `GetPtrSize`. +/// /// Doc comments are deliberately *not* accepted on fields; use `//` comments for /// prose about the layout. Accessor documentation is synthesized from the field /// names instead, so that there is only one place a field can be described. @@ -230,15 +236,15 @@ macro_rules! for_each_vmctx_type { // // NB: these flags come first, before any field whose offset // depends on the component's shape, so that their offsets - // are a function of the target pointer size alone. Core Wasm - // compilation does not have the enclosing - // `VMComponentContext`'s offsets on hand, but must still be - // able to compute these flags' offsets to build the alias - // regions for accessing them; see - // `VMComponentOffsets::{task_may_block,may_leave}_offset`. - field { #[access_as = u32] task_may_block: VMGlobalDefinition } + // are a function of the target pointer size alone, as marked + // by `#[ptr_size_offset]`. Core Wasm compilation does not + // have the enclosing `VMComponentContext`'s offsets on hand, + // but must still be able to compute these flags' offsets to + // build the alias regions for accessing them. + field { #[ptr_size_offset] #[access_as = u32] task_may_block: VMGlobalDefinition } array { + #[ptr_size_offset] #[access_as = u32] may_leave[num_runtime_component_instances; RuntimeComponentInstanceIndex]: VMGlobalDefinition } diff --git a/crates/environ/src/vmoffsets.rs b/crates/environ/src/vmoffsets.rs index f4f6b33b14cc..3293e9745dfa 100644 --- a/crates/environ/src/vmoffsets.rs +++ b/crates/environ/src/vmoffsets.rs @@ -224,10 +224,16 @@ macro_rules! define_vm_type_offsets { /// Generate a `struct VMContext(P)`-style wrapper for each vmctx /// type, with a method per statically-positioned field returning that field's /// offset. +#[allow( + unused_macro_rules, + reason = "only `VMComponentContext` has a `#[ptr_size_offset]` prefix in its \ + `dynamic` section, so those arms go unused for `VMContext`" +)] macro_rules! define_vmctx_static_offsets { // Munch the `static` section, threading a closed-form expression for the - // running offset. - (@chain $p:ident [ $($prev:tt)* ] []) => { + // running offset. Once it is exhausted, keep going into the `dynamic` + // section's `#[ptr_size_offset]` prefix, whose offsets are closed-form too. + (@chain $p:ident [ $($prev:tt)* ] [] [ $($dyn:tt)* ]) => { /// The offset just past this type's last statically-positioned field. /// /// Everything after this point is dynamically sized. @@ -237,17 +243,20 @@ macro_rules! define_vmctx_static_offsets { let _ = $p; u8::try_from($($prev)*).unwrap() } + + define_vmctx_static_offsets!(@dyn_chain $p [ $($prev)* ] [ $($dyn)* ]); }; - (@chain $p:ident [ $($prev:tt)* ] [ align { $al:tt } $($rest:tt)* ]) => { + (@chain $p:ident [ $($prev:tt)* ] [ align { $al:tt } $($rest:tt)* ] $dyn:tt) => { define_vmctx_static_offsets!( @chain $p [ crate::vmctxtypes::align_up($($prev)*, vmctx_align_value!(($p) $al)) ] [ $($rest)* ] + $dyn ); }; (@chain $p:ident [ $($prev:tt)* ] [ field { $(# $fattr:tt)* $fname:ident : $($fty:tt)* } $($rest:tt)* - ]) => { + ] $dyn:tt) => { #[doc = concat!("The offset of the `", stringify!($fname), "` field.")] #[inline] pub fn $fname(&self) -> u8 { @@ -259,8 +268,63 @@ macro_rules! define_vmctx_static_offsets { @chain $p [ $($prev)* + vmctx_field_size!(($p) $($fty)*) ] [ $($rest)* ] + $dyn + ); + }; + + // Munch the `dynamic` section's leading run of `#[ptr_size_offset]` entries, + // continuing to thread the closed-form running offset. + (@dyn_chain $p:ident [ $($prev:tt)* ] [ align { $al:tt } $($rest:tt)* ]) => { + define_vmctx_static_offsets!( + @dyn_chain $p + [ crate::vmctxtypes::align_up($($prev)*, vmctx_align_value!(($p) $al)) ] + [ $($rest)* ] ); }; + (@dyn_chain $p:ident [ $($prev:tt)* ] [ + field { #[ptr_size_offset] $(# $fattr:tt)* $fname:ident : $($fty:tt)* } $($rest:tt)* + ]) => { + #[doc = concat!("The offset of the `", stringify!($fname), "` field.")] + #[inline] + pub fn $fname(&self) -> u32 { + let $p = self.0.size(); + let _ = $p; + $($prev)* + } + define_vmctx_static_offsets!( + @dyn_chain $p + [ $($prev)* + vmctx_field_size!(($p) $($fty)*) ] + [ $($rest)* ] + ); + }; + (@dyn_chain $p:ident [ $($prev:tt)* ] [ + array { + #[ptr_size_offset] $(# $fattr:tt)* + $fname:ident [ $count:ident ; $Index:ident ] : $($fty:tt)* + } $($rest:tt)* + ]) => { + // NB: this takes `impl VmctxArrayIndex` rather than the declared + // `$Index` because these wrappers are compiled even when the + // `component-model` feature is off, and some of the index types are not. + #[doc = concat!( + "The offset of the `index`th element of the `", stringify!($fname), + "` array.\n\nThis is not bounds checked: the array's length is a \ + property of the particular module or component being compiled, which \ + is exactly what this wrapper does not know." + )] + #[inline] + pub fn $fname(&self, index: impl crate::VmctxArrayIndex) -> u32 { + let $p = self.0.size(); + let _ = $p; + let index = index.vmctx_array_index(); + ($($prev)*) + vmctx_field_size!(($p) $($fty)*) * index + } + // An array's size depends on how many elements this particular module or + // component needs, so nothing after it has a closed-form offset and the + // chain necessarily stops here. + }; + // Anything else ends the closed-form prefix. + (@dyn_chain $p:ident [ $($prev:tt)* ] [ $($rest:tt)* ]) => {}; ( $( { @@ -275,7 +339,7 @@ macro_rules! define_vmctx_static_offsets { pub struct $Name(pub P); impl $Name

{ - define_vmctx_static_offsets!(@chain ptr [ 0u32 ] [ $($stat)* ]); + define_vmctx_static_offsets!(@chain ptr [ 0u32 ] [ $($stat)* ] [ $($dyn)* ]); } )* };