diff --git a/crates/cranelift/src/alias_region.rs b/crates/cranelift/src/alias_region.rs index 9963f5ef22d9..24d1dd1d0254 100644 --- a/crates/cranelift/src/alias_region.rs +++ b/crates/cranelift/src/alias_region.rs @@ -75,8 +75,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. @@ -87,8 +87,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. @@ -99,8 +99,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. @@ -937,14 +937,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, @@ -954,14 +953,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, @@ -971,14 +969,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 71b07ef66604..94693c965ce6 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -342,70 +342,116 @@ impl<'module_environment> FuncEnvironment<'module_environment> { self.isa.pointer_type() } + /// Get the alias region to use for accesses of the given memory. + /// + /// XXX: Keep the `{memory,global,table}_alias_region` methods in sync with + /// each other. pub(crate) fn memory_alias_region( &mut self, 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), + }, } } + /// Get the alias region to use for accesses of the given table. + /// + /// XXX: Keep the `{memory,global,table}_alias_region` methods in sync with + /// each other. pub(crate) fn table_alias_region( &mut self, 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), + }, } } + /// Get the alias region to use for accesses of the given global. + /// + /// XXX: Keep the `{memory,global,table}_alias_region` methods in sync with + /// each other. pub(crate) fn global_alias_region( &mut self, 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 22d0f842b7ac..1df5a9e2eb78 100644 --- a/crates/environ/src/compile/module_environ.rs +++ b/crates/environ/src/compile/module_environ.rs @@ -4,16 +4,17 @@ 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, Trap, 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, Trap, Tunables, TypeConvert, TypeIndex, WasmHeapTopType, WasmHeapType, WasmResult, + WasmValType, WasmparserTypeConverter, }; use alloc::borrow::Cow; -use cranelift_entity::SecondaryMap; use cranelift_entity::packed_option::ReservedValue; +use cranelift_entity::{EntitySet, SecondaryMap}; use std::collections::HashMap; use std::mem; use std::path::PathBuf; @@ -72,6 +73,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 @@ -107,6 +117,69 @@ 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. + /// + /// This extra condition is an artifact of this implementation, and how we + /// consume this data to choose the alias region for loads and stores to a + /// global/memory/table, not something inherent to knowing exactly which + /// entity satisfies a particular import. Really, there are two independent + /// axes here: + /// + /// 1. Is this import always satisfied by the same defined entity? + /// + /// 2. Is that entity's identity additionally known to *every* other module + /// that may import it? + /// + /// Only alias regions need (2), but other theoretical optimizations could + /// be perfectly happy with just (1). For example, if we know that an import + /// of an immutable global is always a particular defined global, then we + /// could inline that global's value at each `global.get` of the import, + /// regardless what any other module does or does not know about that + /// global. + /// + /// 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>>, + + /// 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: EntitySet, + + /// Same as [`Self::globals_known_to_importers`], but for memories. + pub memories_known_to_importers: EntitySet, + + /// Same as [`Self::globals_known_to_importers`], but for tables. + pub tables_known_to_importers: EntitySet, + /// 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. @@ -228,6 +301,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: EntitySet::new(), + memories_known_to_importers: EntitySet::new(), + tables_known_to_importers: EntitySet::new(), 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 4a80c5f927b2..b81089d14677 100644 --- a/crates/environ/src/component/translate.rs +++ b/crates/environ/src/component/translate.rs @@ -3,15 +3,15 @@ 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, @@ -549,26 +549,30 @@ 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(); - let mut instance_to_module = - PrimaryMap::>::new(); + let mut instances = StaticInstances::new(); for init in &translation.component.initializers { match init { GlobalInitializer::InstantiateModule(instantiation, _) => match instantiation { InstantiateModule::Static(module, args) => { instantiations[*module].join(AbstractInstantiations::One(&*args)); - instance_to_module.push(Some(*module).into()); + instances.push(Some((*module, &args[..]))); } _ => { - instance_to_module.push(None.into()); + instances.push(None); } }, _ => continue, @@ -583,106 +587,206 @@ 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( + &self.static_modules, + translation, + &instantiations, + &instances, + ); + + // 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); + } + } + 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); + } + } + 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); + } + } + } + // 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(trap) => FactInlineIntrinsic::Trap(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. + // 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(&self.static_modules, &instances, &ambiguous, arg) + else { continue; }; + let index = self.static_modules[arg_module] + .module + .$defined_index(arg_entity) + .expect( + "`resolve_core_export` only returns entities that their module \ + defines", + ); + assert!(self.static_modules[module].$known[$imported].is_none()); + self.static_modules[module].$known[$imported] = Some(KnownEntity { + module: arg_module, + index, + }); + }}; + } - let ExportItem::Index(EntityIndex::Function(arg_func)) = &export.item - else { - unreachable!("function imports must be functions") - }; + 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") + } - 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; + // 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(trap) => FactInlineIntrinsic::Trap(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(&self.static_modules, &instances, export) + else { + // Either an instance of a dynamic module that + // is not part of this component, or a + // re-export chain that bottoms out in + // something that isn't a defined function + // (for example a re-export of a trampoline; + // note that we only match trampolines and + // intrinsics as *direct* arguments above). + // Either way we have to do an indirect call. + continue; + }; + + let EntityIndex::Function(arg_func) = arg_entity else { + unreachable!("function imports must be functions") + }; + + let arg_module_def_func = self.static_modules[arg_module] + .module + .defined_func_index(arg_func) + .expect( + "`resolve_core_export` only returns entities that their \ + module defines", + ); + + 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 +1918,191 @@ mod pre_inlining { } } use pre_inlining::PreInliningComponentTypes; + +/// A map from each runtime instance to the static module it is an instance of +/// and the arguments it was instantiated with, when we statically know them. +/// +/// `None` for instances of modules that are not part of this component, and +/// whose shape we therefore cannot see into. +type StaticInstances<'a> = + PrimaryMap>; + +/// 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. +/// +/// A module may import an entity and then re-export it, in which case the +/// export names an index in the re-exporting module's *imported* index space. +/// We follow those chains all the way back to the module that actually defines +/// the entity, so that the returned pair is a canonical identity for it: every +/// reference to the same entity resolves to the same `(module, entity)` pair, no +/// matter how many modules it was laundered through along the way. That is +/// load-bearing for alias regions, where naming the same bytes with two +/// different keys is a miscompile. +/// +/// Therefore a returned `Some((module, entity))` always satisfies +/// `!static_modules[module].module.is_imported(entity)`. +fn resolve_core_export( + static_modules: &PrimaryMap>, + instances: &StaticInstances<'_>, + export: &CoreExport, +) -> Option<(StaticModuleIndex, EntityIndex)> { + let mut instance = export.instance; + let mut item = &export.item; + + loop { + // 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, args) = instances[instance]?; + + let index = match item { + ExportItem::Index(index) => *index, + // Names are only used for instances of modules whose shape we don't + // statically know, which we already filtered out. + ExportItem::Name(_) => return None, + }; + + // The common case: this instance's module defines the entity itself, so + // we've bottomed out at its canonical identity. + if !static_modules[module].module.is_imported(index) { + return Some((module, index)); + } + + // Otherwise this is a re-export of one of the module's imports, so keep + // walking through whichever argument satisfied that import. + let position = static_modules[module] + .module + .import_position(index) + .expect("imported entities always have an associated import initializer"); + match &args[position] { + CoreDef::Export(next) => { + // An instantiation's arguments are always exports of instances + // created before the instance being instantiated: `LinearizeDfg` + // builds the argument `CoreDef`s before assigning the new + // instance's `RuntimeInstanceIndex`, and would panic building an + // export of an instance it had not linearized yet. So this walk + // strictly decreases and must terminate. + assert!(next.instance < instance); + instance = next.instance; + item = &next.item; + } + + // The chain bottoms out in something that is not an export of + // another instance in this component, so there is no defining module + // for us to name. + CoreDef::InstanceFlags(_) + | CoreDef::Trampoline(_) + | CoreDef::UnsafeIntrinsic(_) + | CoreDef::TaskMayBlock => return None, + } + } +} + +/// Same as `resolve_core_export`, but for a `CoreDef` that must additionally be +/// unambiguous. +fn unambiguous_entity( + static_modules: &PrimaryMap>, + instances: &StaticInstances<'_>, + ambiguous: &HashSet<(StaticModuleIndex, EntityIndex)>, + def: &CoreDef, +) -> Option<(StaticModuleIndex, EntityIndex)> { + let CoreDef::Export(export) = def else { + return None; + }; + let entity = resolve_core_export(static_modules, instances, 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. +/// +/// Entities in the returned set are identified by the module that *defines* +/// them, as resolved by `resolve_core_export`. That is what makes the previous +/// paragraph work through re-exports: marking a module's re-export of an import +/// as ambiguous poisons the definition it ultimately refers to, and therefore +/// every other module that can reach that definition, and not just the +/// re-exporter. +fn ambiguous_entities( + static_modules: &PrimaryMap>, + translation: &ComponentTranslation, + instantiations: &SecondaryMap>, + instances: &StaticInstances<'_>, +) -> 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(static_modules, instances, 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..9db128df5be4 100644 --- a/crates/environ/src/module.rs +++ b/crates/environ/src/module.rs @@ -511,6 +511,43 @@ 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), + } + } + + /// Test whether the given entity index refers to one of this module's + /// imports, rather than to something it defines itself. + pub fn is_imported(&self, entity: EntityIndex) -> bool { + match entity { + EntityIndex::Function(i) => self.is_imported_function(i), + EntityIndex::Table(i) => self.is_imported_table(i), + EntityIndex::Memory(i) => self.is_imported_memory(i), + EntityIndex::Global(i) => self.is_imported_global(i), + EntityIndex::Tag(i) => self.is_imported_tag(i), + } + } + + /// Get the position of the import that defines the given entity, suitable + /// for indexing an instantiation's argument list. + /// + /// Returns `None` when this module defines the entity itself, rather than + /// importing it. + /// + /// Note that this has to scan the initializers: imports of different kinds + /// are interleaved in declaration order, so an import's position is not + /// recoverable from the `num_imported_*` counts alone. + pub fn import_position(&self, entity: EntityIndex) -> Option { + if !self.is_imported(entity) { + return None; + } + self.initializers.iter().position(|i| match i { + Initializer::Import { index, .. } => *index == entity, + }) + } + /// 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..f0e2c91dac3a --- /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) 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): +;; @00f2 jump block4 +;; +;; block6(v5: i64): +;; @00f2 jump block3 +;; +;; block4: +;; @00f9 v7 = load.i64 notrap aligned readonly can_move region2 v0+344 +;; @00f9 v8 = load.i32 notrap aligned region3 v7 +;; @00fd trapz v8, user26 +;; @00fd jump block7 +;; +;; block7: +;; @0103 v10 = load.i64 notrap aligned readonly can_move region2 v0+320 +;; @0103 v11 = load.i32 notrap aligned region3 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) ] +;; +;; block9(v16: i32): +;; @00ec v4 = iconst.i32 0 +;; @0117 store notrap aligned region3 v4, v7 ; v4 = 0 +;; @011b v19 = iconst.i32 3 +;; @011d v20 = band v16, v19 ; v19 = 3 +;; @011e trapnz v20, user36 +;; @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 v24 = iconst.i64 16 +;; @0128 v25 = ushr v23, v24 ; v24 = 16 +;; @0128 v26 = ireduce.i32 v25 +;; @012a v27 = uextend.i64 v26 +;; @012d v29 = ishl v27, v24 ; v24 = 16 +;; @0130 v30 = uextend.i64 v16 +;; v79 = iconst.i64 8 +;; @0134 v33 = iadd v30, v79 ; v79 = 8 +;; @0135 v34 = icmp uge v29, v33 +;; @0136 brif v34, block12, block14 +;; +;; block14: +;; @0138 jump block13 +;; +;; block13: +;; @0139 trap user4 +;; +;; block12: +;; v80 = iconst.i32 3 +;; v81 = band.i32 v3, v80 ; v80 = 3 +;; @0142 trapnz v81, user36 +;; @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 +;; v82 = iconst.i64 16 +;; v83 = ushr v41, v82 ; v82 = 16 +;; @014c v44 = ireduce.i32 v83 +;; @014e v45 = uextend.i64 v44 +;; v84 = ishl v45, v82 ; v82 = 16 +;; @0154 v48 = uextend.i64 v3 +;; v85 = iconst.i64 8 +;; v86 = iadd v48, v85 ; v85 = 8 +;; @0159 v52 = icmp uge v84, v86 +;; @015a brif v52, block17, block19 +;; +;; block19: +;; @015c jump block18 +;; +;; block18: +;; @015d trap user4 +;; +;; block17: +;; @0165 v57 = load.i64 notrap aligned readonly can_move region6 v22 +;; @0165 v58 = iadd v57, v30 +;; @0165 v59 = load.i32 little region8 v58 +;; @0168 v62 = load.i64 notrap aligned readonly can_move region6 v40 +;; @0168 v63 = iadd v62, v48 +;; @0168 store little region9 v59, v63 +;; @0170 v68 = iconst.i64 4 +;; @0170 v69 = iadd v58, v68 ; v68 = 4 +;; @0170 v70 = load.i32 little region8 v69 +;; @0173 v76 = iadd v63, v68 ; v68 = 4 +;; @0173 store little region9 v70, v76 +;; @0179 store.i32 notrap aligned region3 v8, v7 +;; @017b jump block5 +;; +;; block5: +;; @017c jump block2 +;; +;; block3: +;; @017f trap user52 +;; +;; block2: +;; @0183 jump block1 +;; +;; block1: +;; @0183 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 +;; } diff --git a/tests/disas/component-model/reexported-ambiguous-entities.wat b/tests/disas/component-model/reexported-ambiguous-entities.wat new file mode 100644 index 000000000000..72f93efe2f5e --- /dev/null +++ b/tests/disas/component-model/reexported-ambiguous-entities.wat @@ -0,0 +1,405 @@ +;;! target = "x86_64" +;;! test = "optimize" +;;! filter = "function" +;;! flags = "-C inlining=n -Wconcurrency-support=n" + +;; Same as `reexported-known-entities.wat` except that `$P` is instantiated +;; twice: once with `$N`'s re-exports (which are really `$M1`'s definitions) and +;; once with `$M2`'s definitions directly. +;; +;; `$N` itself is still instantiated exactly once, so looking only at `$N`'s +;; instantiation its imports appear unambiguous. But the entities it re-exports +;; flow onwards into `$P`, which cannot statically know which of `$M1`'s or +;; `$M2`'s entities it was handed. That ambiguity has to propagate back through +;; `$N`'s re-export to `$M1`, so every access of `$M1`'s memory, global, and +;; table -- in `$M1`, in `$N`, and in `$P` -- must use the conservative +;; `PublicMemory`/`PublicGlobal`/`PublicTable` regions, as must every access of +;; `$M2`'s. If `$M1` and `$N` kept the precise `DefinedMemory`/`DefinedGlobal`/ +;; `DefinedTable` regions while `$P` used the public ones, then inlining one of +;; `$M1`'s or `$N`'s functions into one of `$P`'s would access 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)) + + ;; Re-export our imports. + (export "mem" (memory 0)) + (export "g" (global 0)) + (export "t" (table 0)) + + (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 $m1)))) + + (core module $P + (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))) + ) + + ;; `$P` gets `$M1`'s entities via `$N`'s re-export here... + (core instance $p1 (instantiate $P (with "" (instance $n)))) + ;; ...and `$M2`'s entities here. + (core instance $p2 (instantiate $P (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): +;; @0192 v4 = load.i64 notrap aligned readonly can_move region2 v0+48 +;; @0192 v5 = load.i64 notrap aligned readonly can_move region3 v4 +;; @0192 v7 = load.i32 little region5 v5 +;; @0195 jump block1 +;; +;; block1: +;; @0195 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): +;; @0198 v2 = load.i64 notrap aligned readonly can_move region2 v0+96 +;; @0198 v3 = load.i32 notrap aligned region3 v2 +;; @019a jump block1 +;; +;; block1: +;; @019a 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): +;; @019f v3 = load.i64 notrap aligned readonly can_move region2 v0+72 +;; @019f v4 = load.i64 notrap aligned region4 v3+8 +;; @019f v9 = load.i64 notrap aligned region3 v3 +;; @019f v5 = ireduce.i32 v4 +;; @019d v2 = iconst.i32 0 +;; v23 = icmp eq v5, v2 ; v2 = 0 +;; v26 = iconst.i64 0 +;; @019f v14 = select_spectre_guard v23, v26, v9 ; v26 = 0 +;; @019f v15 = load.i64 user6 aligned region5 v14 +;; @019f v16 = iconst.i64 -2 +;; @019f v17 = band v15, v16 ; v16 = -2 +;; @019f brif v15, block3(v17), block2 +;; +;; block2 cold: +;; v27 = iconst.i32 0 +;; v28 = iconst.i64 0 +;; @019f v21 = call fn0(v0, v27, v28) ; v27 = 0, v28 = 0 +;; @019f jump block3(v21) +;; +;; block3(v18: i64): +;; @01a1 jump block1 +;; +;; block1: +;; @01a1 return v18 +;; } +;; +;; function u3: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): +;; @0217 v4 = load.i64 notrap aligned readonly can_move region2 v0+48 +;; @0217 v5 = load.i64 notrap aligned readonly can_move region3 v4 +;; @0217 v7 = load.i32 little region5 v5 +;; @021a jump block1 +;; +;; block1: +;; @021a return v7 +;; } +;; +;; function u3: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): +;; @021d v2 = load.i64 notrap aligned readonly can_move region2 v0+96 +;; @021d v3 = load.i32 notrap aligned region3 v2 +;; @021f jump block1 +;; +;; block1: +;; @021f return v3 +;; } +;; +;; function u3: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): +;; @0224 v3 = load.i64 notrap aligned readonly can_move region2 v0+72 +;; @0224 v4 = load.i64 notrap aligned region4 v3+8 +;; @0224 v9 = load.i64 notrap aligned region3 v3 +;; @0224 v5 = ireduce.i32 v4 +;; @0222 v2 = iconst.i32 0 +;; v23 = icmp eq v5, v2 ; v2 = 0 +;; v26 = iconst.i64 0 +;; @0224 v14 = select_spectre_guard v23, v26, v9 ; v26 = 0 +;; @0224 v15 = load.i64 user6 aligned region5 v14 +;; @0224 v16 = iconst.i64 -2 +;; @0224 v17 = band v15, v16 ; v16 = -2 +;; @0224 brif v15, block3(v17), block2 +;; +;; block2 cold: +;; v27 = iconst.i32 0 +;; v28 = iconst.i64 0 +;; @0224 v21 = call fn0(v0, v27, v28) ; v27 = 0, v28 = 0 +;; @0224 jump block3(v21) +;; +;; block3(v18: i64): +;; @0226 jump block1 +;; +;; block1: +;; @0226 return v18 +;; } diff --git a/tests/disas/component-model/reexported-entities-to-imported-module.wat b/tests/disas/component-model/reexported-entities-to-imported-module.wat new file mode 100644 index 000000000000..911f73ac79be --- /dev/null +++ b/tests/disas/component-model/reexported-entities-to-imported-module.wat @@ -0,0 +1,220 @@ +;;! target = "x86_64" +;;! test = "optimize" +;;! filter = "function" +;;! flags = "-C inlining=n -Wconcurrency-support=n" + +;; Same as `reexported-known-entities.wat` except that instead of a third module +;; defined inside this component, it is a core module *imported* by this +;; component that receives `$N`'s re-exports. +;; +;; Both `$M` and `$N` are still instantiated exactly once, but the imported +;; module is compiled separately from this component and so always accesses the +;; memory, global, and table it is given via the conservative `PublicMemory` / +;; `PublicGlobal`/`PublicTable` regions. Therefore `$M` and `$N` must use those +;; same conservative regions as well; using the precise `DefinedMemory` / +;; `DefinedGlobal`/`DefinedTable` regions here would mean the same entity is +;; accessed through two different alias regions, which is invalid. + +(component + (import "dyn" (core module $Dyn + (import "" "mem" (memory 1)) + (import "" "g" (global (mut i32))) + (import "" "t" (table 1 funcref)) + )) + + (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)) + + ;; Re-export our imports. + (export "mem" (memory 0)) + (export "g" (global 0)) + (export "t" (table 0)) + + (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)))) + + ;; Hand `$M`'s entities, laundered through `$N`'s re-export, to a module we + ;; cannot see inside of. + (core instance $d (instantiate $Dyn (with "" (instance $n)))) +) +;; 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): +;; @009b v4 = load.i64 notrap aligned readonly can_move region2 v0+56 +;; @009b v6 = load.i32 little region4 v4 +;; @009e jump block1 +;; +;; block1: +;; @009e 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): +;; @00a1 v2 = load.i32 notrap aligned region2 v0+96 +;; @00a3 jump block1 +;; +;; block1: +;; @00a3 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): +;; @00a8 v3 = load.i64 notrap aligned region3 v0+80 +;; @00a8 v7 = load.i64 notrap aligned region2 v0+72 +;; @00a8 v4 = ireduce.i32 v3 +;; @00a6 v2 = iconst.i32 0 +;; v21 = icmp eq v4, v2 ; v2 = 0 +;; v24 = iconst.i64 0 +;; @00a8 v12 = select_spectre_guard v21, v24, v7 ; v24 = 0 +;; @00a8 v13 = load.i64 user6 aligned region4 v12 +;; @00a8 v14 = iconst.i64 -2 +;; @00a8 v15 = band v13, v14 ; v14 = -2 +;; @00a8 brif v13, block3(v15), block2 +;; +;; block2 cold: +;; v25 = iconst.i32 0 +;; v26 = iconst.i64 0 +;; @00a8 v19 = call fn0(v0, v25, v26) ; v25 = 0, v26 = 0 +;; @00a8 jump block3(v19) +;; +;; block3(v16: i64): +;; @00aa jump block1 +;; +;; block1: +;; @00aa 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 = 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): +;; @012c v4 = load.i64 notrap aligned readonly can_move region2 v0+48 +;; @012c v5 = load.i64 notrap aligned readonly can_move region3 v4 +;; @012c v7 = load.i32 little region5 v5 +;; @012f jump block1 +;; +;; block1: +;; @012f return v7 +;; } +;; +;; function u1: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): +;; @0132 v2 = load.i64 notrap aligned readonly can_move region2 v0+96 +;; @0132 v3 = load.i32 notrap aligned region3 v2 +;; @0134 jump block1 +;; +;; block1: +;; @0134 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 = 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): +;; @0139 v3 = load.i64 notrap aligned readonly can_move region2 v0+72 +;; @0139 v4 = load.i64 notrap aligned region4 v3+8 +;; @0139 v9 = load.i64 notrap aligned region3 v3 +;; @0139 v5 = ireduce.i32 v4 +;; @0137 v2 = iconst.i32 0 +;; v23 = icmp eq v5, v2 ; v2 = 0 +;; v26 = iconst.i64 0 +;; @0139 v14 = select_spectre_guard v23, v26, v9 ; v26 = 0 +;; @0139 v15 = load.i64 user6 aligned region5 v14 +;; @0139 v16 = iconst.i64 -2 +;; @0139 v17 = band v15, v16 ; v16 = -2 +;; @0139 brif v15, block3(v17), block2 +;; +;; block2 cold: +;; v27 = iconst.i32 0 +;; v28 = iconst.i64 0 +;; @0139 v21 = call fn0(v0, v27, v28) ; v27 = 0, v28 = 0 +;; @0139 jump block3(v21) +;; +;; block3(v18: i64): +;; @013b jump block1 +;; +;; block1: +;; @013b return v18 +;; } diff --git a/tests/disas/component-model/reexported-known-entities.wat b/tests/disas/component-model/reexported-known-entities.wat new file mode 100644 index 000000000000..54b99dc4ac39 --- /dev/null +++ b/tests/disas/component-model/reexported-known-entities.wat @@ -0,0 +1,308 @@ +;;! target = "x86_64" +;;! test = "optimize" +;;! filter = "function" +;;! flags = "-C inlining=n -Wconcurrency-support=n" + +;; Same as `known-imported-entities.wat` except that `$N` additionally +;; re-exports the memory, global, and table that it imports, and `$P` imports +;; them from `$N` rather than directly from `$M`. +;; +;; Every module is still instantiated exactly once and nothing else in the +;; component can get its hands on these entities, so the transitive import +;; through `$N`'s re-export is unambiguous: all three of `$M`, `$N`, and `$P` +;; must use the same precise `DefinedMemory`/`DefinedGlobal`/`DefinedTable` +;; region for `$M`'s definitions. If `$P` fell back to the conservative +;; `PublicMemory`/`PublicGlobal`/`PublicTable` regions while `$M` and `$N` used +;; the precise ones, then inlining across those modules would access the same +;; entity through two different alias regions, which is invalid. + +(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)) + + ;; Re-export our imports. + (export "mem" (memory 0)) + (export "g" (global 0)) + (export "t" (table 0)) + + (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)))) + + (core module $P + (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 $p (instantiate $P (with "" (instance $n)))) +) +;; 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): +;; @0104 v4 = load.i64 notrap aligned readonly can_move region2 v0+48 +;; @0104 v5 = load.i64 notrap aligned readonly can_move region3 v4 +;; @0104 v7 = load.i32 little region5 v5 +;; @0107 jump block1 +;; +;; block1: +;; @0107 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): +;; @010a v2 = load.i64 notrap aligned readonly can_move region2 v0+96 +;; @010a v3 = load.i32 notrap aligned region3 v2 +;; @010c jump block1 +;; +;; block1: +;; @010c 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): +;; @0111 v3 = load.i64 notrap aligned readonly can_move region2 v0+72 +;; @0111 v4 = load.i64 notrap aligned region4 v3+8 +;; @0111 v9 = load.i64 notrap aligned region3 v3 +;; @0111 v5 = ireduce.i32 v4 +;; @010f v2 = iconst.i32 0 +;; v23 = icmp eq v5, v2 ; v2 = 0 +;; v26 = iconst.i64 0 +;; @0111 v14 = select_spectre_guard v23, v26, v9 ; v26 = 0 +;; @0111 v15 = load.i64 user6 aligned region5 v14 +;; @0111 v16 = iconst.i64 -2 +;; @0111 v17 = band v15, v16 ; v16 = -2 +;; @0111 brif v15, block3(v17), block2 +;; +;; block2 cold: +;; v27 = iconst.i32 0 +;; v28 = iconst.i64 0 +;; @0111 v21 = call fn0(v0, v27, v28) ; v27 = 0, v28 = 0 +;; @0111 jump block3(v21) +;; +;; block3(v18: i64): +;; @0113 jump block1 +;; +;; block1: +;; @0113 return v18 +;; } +;; +;; 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 = 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): +;; @0189 v4 = load.i64 notrap aligned readonly can_move region2 v0+48 +;; @0189 v5 = load.i64 notrap aligned readonly can_move region3 v4 +;; @0189 v7 = load.i32 little region5 v5 +;; @018c jump block1 +;; +;; block1: +;; @018c return v7 +;; } +;; +;; function u2: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): +;; @018f v2 = load.i64 notrap aligned readonly can_move region2 v0+96 +;; @018f v3 = load.i32 notrap aligned region3 v2 +;; @0191 jump block1 +;; +;; block1: +;; @0191 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 = 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): +;; @0196 v3 = load.i64 notrap aligned readonly can_move region2 v0+72 +;; @0196 v4 = load.i64 notrap aligned region4 v3+8 +;; @0196 v9 = load.i64 notrap aligned region3 v3 +;; @0196 v5 = ireduce.i32 v4 +;; @0194 v2 = iconst.i32 0 +;; v23 = icmp eq v5, v2 ; v2 = 0 +;; v26 = iconst.i64 0 +;; @0196 v14 = select_spectre_guard v23, v26, v9 ; v26 = 0 +;; @0196 v15 = load.i64 user6 aligned region5 v14 +;; @0196 v16 = iconst.i64 -2 +;; @0196 v17 = band v15, v16 ; v16 = -2 +;; @0196 brif v15, block3(v17), block2 +;; +;; block2 cold: +;; v27 = iconst.i32 0 +;; v28 = iconst.i64 0 +;; @0196 v21 = call fn0(v0, v27, v28) ; v27 = 0, v28 = 0 +;; @0196 jump block3(v21) +;; +;; block3(v18: i64): +;; @0198 jump block1 +;; +;; block1: +;; @0198 return v18 +;; } diff --git a/tests/misc_testsuite/component-model/alias-region-known-imported-adapter-memory.wast b/tests/misc_testsuite/component-model/alias-region-known-imported-adapter-memory.wast new file mode 100644 index 000000000000..80d85dde9ca7 --- /dev/null +++ b/tests/misc_testsuite/component-model/alias-region-known-imported-adapter-memory.wast @@ -0,0 +1,86 @@ +;;! multi_memory = true + +;; Component `$A` exports a function returning a tuple, and component `$B` +;; imports it and lowers it with `$Mem`'s memory and realloc. The fused adapter +;; copies the returned tuple into `$Mem`'s memory, and `$N` -- which imports +;; that same memory -- reads the result back out. Every access of `$Mem`'s +;; memory (in `$Mem` itself, in `$N`, and in the adapter) must use the same +;; `DefinedMemory` alias region. +;; +;; When inlining is enabled (randomly by the wast file fuzzer) we shouldn't get +;; any stale values from alias analysis forwarding stored values to loads across +;; mismatched alias regions. + +(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)) + (func (export "set-mem") + (i32.store (i32.const 0) (i32.const 0x1234))) + ) + (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))) + (import "" "set-mem" (func $set-mem)) + + (func (export "g") (result i32) + (call $f' (i32.const 42) (i32.const 0)) + (i32.add (i32.load (i32.const 0)) + (i32.load offset=4 (i32.const 0)))) + + (func (export "probe-mem") (result i32) + (i32.store (i32.const 0) (i32.const 1)) + (call $set-mem) + (i32.load (i32.const 0))) + ) + (core instance $n (instantiate $N + (with "" (instance + (export "mem" (memory $mem "mem")) + (export "f'" (func $f')) + (export "set-mem" (func $mem "set-mem")) + )) + )) + + (func (export "g") (result u32) + (canon lift (core func $n "g"))) + (func (export "probe-mem") (result u32) + (canon lift (core func $n "probe-mem"))) + ) + + (instance $b (instantiate $B (with "f" (func $a "f")))) + + (export "g" (func $b "g")) + (export "probe-mem" (func $b "probe-mem")) +) + +(assert_return (invoke "g") (u32.const 84)) +(assert_return (invoke "probe-mem") (u32.const 0x1234)) diff --git a/tests/misc_testsuite/component-model/alias-region-known-imported-canonical-abi-memory.wast b/tests/misc_testsuite/component-model/alias-region-known-imported-canonical-abi-memory.wast new file mode 100644 index 000000000000..b61c1c8445d0 --- /dev/null +++ b/tests/misc_testsuite/component-model/alias-region-known-imported-canonical-abi-memory.wast @@ -0,0 +1,59 @@ +;; `$M`'s memory is exported and handed to the canonical ABI (it is the memory +;; and realloc backing a `canon lift` with a `string` parameter, so the string +;; transcoding intrinsics write into it), but it is still unambiguous: every +;; core module that can reach it knows exactly which memory it is. `$M` and the +;; importing module `$Q` must therefore agree on the precise `DefinedMemory` +;; alias region for it. +;; +;; When inlining is enabled (randomly by the wast file fuzzer) we shouldn't get +;; any stale values from alias analysis forwarding stored values to loads across +;; mismatched alias regions. + +(component + (core module $M + (memory (export "mem") 1) + + (func (export "realloc") (param i32 i32 i32 i32) (result i32) + (i32.const 16)) + + ;; The core function behind the lifted `f` below: record the length of the + ;; string the canonical ABI transcoded into our memory. + (func (export "f") (param i32 i32) + (i32.store (i32.const 8) (local.get 1))) + + (func (export "set-mem") + (i32.store (i32.const 0) (i32.const 0x1234))) + + (func (export "get-len") (result i32) + (i32.load (i32.const 8))) + ) + + (core instance $m (instantiate $M)) + + (core module $Q + (import "" "mem" (memory 1)) + (import "" "set-mem" (func $set-mem)) + + (func (export "probe-mem") (result i32) + (i32.store (i32.const 0) (i32.const 1)) + (call $set-mem) + (i32.load (i32.const 0))) + ) + + (core instance $q (instantiate $Q (with "" (instance $m)))) + + (func (export "f") (param "s" string) + (canon lift (core func $m "f") + (memory $m "mem") + (realloc (func $m "realloc")))) + + (func (export "get-len") (result u32) + (canon lift (core func $m "get-len"))) + + (func (export "probe-mem") (result u32) + (canon lift (core func $q "probe-mem"))) +) + +(assert_return (invoke "f" (str.const "hello"))) +(assert_return (invoke "get-len") (u32.const 5)) +(assert_return (invoke "probe-mem") (u32.const 0x1234)) diff --git a/tests/misc_testsuite/component-model/alias-region-known-imported-entities.wast b/tests/misc_testsuite/component-model/alias-region-known-imported-entities.wast new file mode 100644 index 000000000000..763b9cb48fd8 --- /dev/null +++ b/tests/misc_testsuite/component-model/alias-region-known-imported-entities.wast @@ -0,0 +1,66 @@ +;;! gc = true + +;; `$M` defines a memory, global, and table, and `$Q` imports them along with +;; the tiny setter functions that write to them. Every module here is +;; instantiated exactly once and nothing else can get its hands on these +;; entities, so both modules must use the same precise +;; `DefinedMemory`/`DefinedGlobal`/`DefinedTable` alias region for each of them. +;; +;; When inlining is enabled (randomly by the wast file fuzzer) we shouldn't get +;; any stale values from alias analysis forwarding stored values to loads across +;; mismatched alias regions. + +(component + (core module $M + (memory (export "mem") 1) + (global (export "g") (mut i32) (i32.const 0)) + (table (export "t") 1 (ref i31) (ref.i31 (i32.const 0))) + + (func (export "set-mem") + (i32.store (i32.const 0) (i32.const 0x1234))) + (func (export "set-global") + (global.set 0 (i32.const 0x1234))) + (func (export "set-table") + (table.set 0 (i32.const 0) (ref.i31 (i32.const 0x1234)))) + ) + + (core instance $m (instantiate $M)) + + (core module $Q + (import "" "mem" (memory 1)) + (import "" "g" (global $g (mut i32))) + (import "" "t" (table 1 (ref i31))) + (import "" "set-mem" (func $set-mem)) + (import "" "set-global" (func $set-global)) + (import "" "set-table" (func $set-table)) + + (func (export "probe-mem") (result i32) + (i32.store (i32.const 0) (i32.const 1)) + (call $set-mem) + (i32.load (i32.const 0))) + + (func (export "probe-global") (result i32) + (global.set $g (i32.const 1)) + (call $set-global) + (global.get $g)) + + (func (export "probe-table") (result i32) + (local $before i32) + (local.set $before (i31.get_u (table.get 0 (i32.const 0)))) + (call $set-table) + (i32.sub (i31.get_u (table.get 0 (i32.const 0))) (local.get $before))) + ) + + (core instance $q (instantiate $Q (with "" (instance $m)))) + + (func (export "probe-mem") (result u32) + (canon lift (core func $q "probe-mem"))) + (func (export "probe-global") (result u32) + (canon lift (core func $q "probe-global"))) + (func (export "probe-table") (result u32) + (canon lift (core func $q "probe-table"))) +) + +(assert_return (invoke "probe-mem") (u32.const 0x1234)) +(assert_return (invoke "probe-global") (u32.const 0x1234)) +(assert_return (invoke "probe-table") (u32.const 0x1234)) diff --git a/tests/misc_testsuite/component-model/alias-region-multiple-instantiations.wast b/tests/misc_testsuite/component-model/alias-region-multiple-instantiations.wast new file mode 100644 index 000000000000..dae292ceacc0 --- /dev/null +++ b/tests/misc_testsuite/component-model/alias-region-multiple-instantiations.wast @@ -0,0 +1,91 @@ +;;! gc = true + +;; `$N` is instantiated twice, once with `$M1`'s entities and once with `$M2`'s, +;; so nothing can statically know which memory, global, or table `$N` is looking +;; at. That ambiguity is a property of the entities themselves, not just of +;; `$N`: `$M1` and `$M2` must fall back to the conservative +;; `PublicMemory`/`PublicGlobal`/`PublicTable` regions for their own definitions +;; too, because otherwise inlining one of their functions into one of `$N`'s +;; would access the same entity through two different alias regions. +;; +;; When inlining is enabled (randomly by the wast file fuzzer) we shouldn't get +;; any stale values from alias analysis forwarding stored values to loads across +;; mismatched alias regions. + +(component + (core module $M1 + (memory (export "mem") 1) + (global (export "g") (mut i32) (i32.const 0)) + (table (export "t") 1 (ref i31) (ref.i31 (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 (ref i31) (ref.i31 (i32.const 0))) + ) + (core instance $m2 (instantiate $M2)) + + (core module $N + (import "" "mem" (memory 1)) + (import "" "g" (global $g (mut i32))) + (import "" "t" (table 1 (ref i31))) + + (func (export "set-mem") + (i32.store (i32.const 0) (i32.const 0x1234))) + (func (export "set-global") + (global.set $g (i32.const 0x1234))) + (func (export "set-table") + (table.set 0 (i32.const 0) (ref.i31 (i32.const 0x1234)))) + ) + (core instance $n1 (instantiate $N (with "" (instance $m1)))) + (core instance $n2 (instantiate $N (with "" (instance $m2)))) + + ;; The observer: entities straight from `$M1`, setters from `$N`. + (core module $Q + (import "" "mem" (memory 1)) + (import "" "g" (global $g (mut i32))) + (import "" "t" (table 1 (ref i31))) + (import "" "set-mem" (func $set-mem)) + (import "" "set-global" (func $set-global)) + (import "" "set-table" (func $set-table)) + + (func (export "probe-mem") (result i32) + (i32.store (i32.const 0) (i32.const 1)) + (call $set-mem) + (i32.load (i32.const 0))) + + (func (export "probe-global") (result i32) + (global.set $g (i32.const 1)) + (call $set-global) + (global.get $g)) + + (func (export "probe-table") (result i32) + (local $before i32) + (local.set $before (i31.get_u (table.get 0 (i32.const 0)))) + (call $set-table) + (i32.sub (i31.get_u (table.get 0 (i32.const 0))) (local.get $before))) + ) + (core instance $q (instantiate $Q + (with "" (instance + (export "mem" (memory $m1 "mem")) + (export "g" (global $m1 "g")) + (export "t" (table $m1 "t")) + (export "set-mem" (func $n1 "set-mem")) + (export "set-global" (func $n1 "set-global")) + (export "set-table" (func $n1 "set-table")) + )) + )) + + (func (export "probe-mem") (result u32) + (canon lift (core func $q "probe-mem"))) + (func (export "probe-global") (result u32) + (canon lift (core func $q "probe-global"))) + (func (export "probe-table") (result u32) + (canon lift (core func $q "probe-table"))) +) + +(assert_return (invoke "probe-mem") (u32.const 0x1234)) +(assert_return (invoke "probe-global") (u32.const 0x1234)) +(assert_return (invoke "probe-table") (u32.const 0x1234)) diff --git a/tests/misc_testsuite/component-model/alias-region-reexported-ambiguous-entities.wast b/tests/misc_testsuite/component-model/alias-region-reexported-ambiguous-entities.wast new file mode 100644 index 000000000000..c2e878abfe74 --- /dev/null +++ b/tests/misc_testsuite/component-model/alias-region-reexported-ambiguous-entities.wast @@ -0,0 +1,140 @@ +;;! gc = true + +;; `$M1` defines a memory, global, and table; `$N` imports and re-exports them; +;; and `$P` is instantiated twice, once with `$N`'s re-exports (really `$M1`'s +;; definitions) and once with `$M2`'s definitions directly. `$P` therefore +;; cannot statically know which entities it was handed, and that ambiguity has +;; to propagate back through `$N`'s re-export to `$M1` itself: every access of +;; `$M1`'s entities, in any module, must use the conservative +;; `PublicMemory`/`PublicGlobal`/`PublicTable` regions. +;; +;; When inlining is enabled (randomly by the wast file fuzzer) we shouldn't get +;; any stale values from alias analysis forwarding stored values to loads across +;; mismatched alias regions. + +(component + (core module $M1 + (memory (export "mem") 1) + (global (export "g") (mut i32) (i32.const 0)) + (table (export "t") 1 (ref i31) (ref.i31 (i32.const 0))) + + (func (export "set-mem") + (i32.store (i32.const 0) (i32.const 0x1234))) + (func (export "set-global") + (global.set 0 (i32.const 0x1234))) + (func (export "set-table") + (table.set 0 (i32.const 0) (ref.i31 (i32.const 0x1234)))) + ) + (core instance $m1 (instantiate $M1)) + + (core module $M2 + (memory (export "mem") 1) + (global (export "g") (mut i32) (i32.const 0)) + (table (export "t") 1 (ref i31) (ref.i31 (i32.const 0))) + ) + (core instance $m2 (instantiate $M2)) + + (core module $N + (import "" "mem" (memory 1)) + (import "" "g" (global (mut i32))) + (import "" "t" (table 1 (ref i31))) + + ;; Re-export our imports. + (export "mem" (memory 0)) + (export "g" (global 0)) + (export "t" (table 0)) + ) + (core instance $n (instantiate $N (with "" (instance $m1)))) + + ;; Instantiated twice, so its imports are not statically known and the + ;; entities that flow into it -- including `$M1`'s, via `$N`'s re-export -- + ;; are ambiguous. + (core module $P + (import "" "mem" (memory 1)) + (import "" "g" (global $g (mut i32))) + (import "" "t" (table 1 (ref i31))) + + (func (export "set-mem") + (i32.store (i32.const 0) (i32.const 0x1234))) + (func (export "set-global") + (global.set $g (i32.const 0x1234))) + (func (export "set-table") + (table.set 0 (i32.const 0) (ref.i31 (i32.const 0x1234)))) + ) + (core instance $p1 (instantiate $P (with "" (instance $n)))) + (core instance $p2 (instantiate $P (with "" (instance $m2)))) + + (core module $Q + (import "" "mem" (memory 1)) + (import "" "g" (global $g (mut i32))) + (import "" "t" (table 1 (ref i31))) + (import "" "m1-set-mem" (func $m1-set-mem)) + (import "" "m1-set-global" (func $m1-set-global)) + (import "" "m1-set-table" (func $m1-set-table)) + (import "" "p-set-mem" (func $p-set-mem)) + (import "" "p-set-global" (func $p-set-global)) + (import "" "p-set-table" (func $p-set-table)) + + (func (export "probe-mem-via-m1") (result i32) + (i32.store (i32.const 0) (i32.const 1)) + (call $m1-set-mem) + (i32.load (i32.const 0))) + (func (export "probe-global-via-m1") (result i32) + (global.set $g (i32.const 1)) + (call $m1-set-global) + (global.get $g)) + (func (export "probe-table-via-m1") (result i32) + (local $before i32) + (local.set $before (i31.get_u (table.get 0 (i32.const 0)))) + (call $m1-set-table) + (i32.sub (i31.get_u (table.get 0 (i32.const 0))) (local.get $before))) + + (func (export "probe-mem-via-p") (result i32) + (i32.store (i32.const 0) (i32.const 1)) + (call $p-set-mem) + (i32.load (i32.const 0))) + (func (export "probe-global-via-p") (result i32) + (global.set $g (i32.const 1)) + (call $p-set-global) + (global.get $g)) + (func (export "probe-table-via-p") (result i32) + (local $before i32) + (table.set 0 (i32.const 0) (ref.i31 (i32.const 0))) + (local.set $before (i31.get_u (table.get 0 (i32.const 0)))) + (call $p-set-table) + (i32.sub (i31.get_u (table.get 0 (i32.const 0))) (local.get $before))) + ) + (core instance $q (instantiate $Q + (with "" (instance + (export "mem" (memory $n "mem")) + (export "g" (global $n "g")) + (export "t" (table $n "t")) + (export "m1-set-mem" (func $m1 "set-mem")) + (export "m1-set-global" (func $m1 "set-global")) + (export "m1-set-table" (func $m1 "set-table")) + (export "p-set-mem" (func $p1 "set-mem")) + (export "p-set-global" (func $p1 "set-global")) + (export "p-set-table" (func $p1 "set-table")) + )) + )) + + (func (export "probe-mem-via-m1") (result u32) + (canon lift (core func $q "probe-mem-via-m1"))) + (func (export "probe-global-via-m1") (result u32) + (canon lift (core func $q "probe-global-via-m1"))) + (func (export "probe-table-via-m1") (result u32) + (canon lift (core func $q "probe-table-via-m1"))) + (func (export "probe-mem-via-p") (result u32) + (canon lift (core func $q "probe-mem-via-p"))) + (func (export "probe-global-via-p") (result u32) + (canon lift (core func $q "probe-global-via-p"))) + (func (export "probe-table-via-p") (result u32) + (canon lift (core func $q "probe-table-via-p"))) +) + +(assert_return (invoke "probe-mem-via-m1") (u32.const 0x1234)) +(assert_return (invoke "probe-global-via-m1") (u32.const 0x1234)) +(assert_return (invoke "probe-table-via-m1") (u32.const 0x1234)) +(assert_return (invoke "probe-mem-via-p") (u32.const 0x1234)) +(assert_return (invoke "probe-global-via-p") (u32.const 0x1234)) +(assert_return (invoke "probe-table-via-p") (u32.const 0x1234)) diff --git a/tests/misc_testsuite/component-model/alias-region-reexported-entities-to-imported-module.wast b/tests/misc_testsuite/component-model/alias-region-reexported-entities-to-imported-module.wast new file mode 100644 index 000000000000..fb1ca698ef60 --- /dev/null +++ b/tests/misc_testsuite/component-model/alias-region-reexported-entities-to-imported-module.wast @@ -0,0 +1,103 @@ +;;! gc = true + +;; `$M` defines a memory, global, and table; `$N` imports and re-exports them; +;; and those re-exports are then handed to a core module that this component +;; *imports* rather than defines. That module is compiled separately from this +;; component and always accesses whatever it is given through the conservative +;; `PublicMemory`/`PublicGlobal`/`PublicTable` regions, so `$M` and `$N` must use +;; those conservative regions as well. +;; +;; When inlining is enabled (randomly by the wast file fuzzer) we shouldn't get +;; any stale values from alias analysis forwarding stored values to loads across +;; mismatched alias regions. + +(component + (import "host" (instance $host + (export "simple-module" (core module + (import "" "mem" (memory 1)) + (import "" "g" (global (mut i32))) + (import "" "t" (table 1 (ref i31))) + )) + )) + (alias export $host "simple-module" (core module $Dyn)) + + (core module $M + (memory (export "mem") 1) + (global (export "g") (mut i32) (i32.const 0)) + (table (export "t") 1 (ref i31) (ref.i31 (i32.const 0))) + + (func (export "set-mem") + (i32.store (i32.const 0) (i32.const 0x1234))) + (func (export "set-global") + (global.set 0 (i32.const 0x1234))) + (func (export "set-table") + (table.set 0 (i32.const 0) (ref.i31 (i32.const 0x1234)))) + ) + + (core instance $m (instantiate $M)) + + (core module $N + (import "" "mem" (memory 1)) + (import "" "g" (global (mut i32))) + (import "" "t" (table 1 (ref i31))) + + ;; Re-export our imports. + (export "mem" (memory 0)) + (export "g" (global 0)) + (export "t" (table 0)) + ) + + (core instance $n (instantiate $N (with "" (instance $m)))) + + ;; Hand `$M`'s entities, laundered through `$N`'s re-export, to a module we + ;; cannot see inside of. + (core instance $dyn (instantiate $Dyn (with "" (instance $n)))) + + ;; The observer: entities through `$N`'s re-export, setters straight from `$M`. + (core module $Q + (import "" "mem" (memory 1)) + (import "" "g" (global $g (mut i32))) + (import "" "t" (table 1 (ref i31))) + (import "" "set-mem" (func $set-mem)) + (import "" "set-global" (func $set-global)) + (import "" "set-table" (func $set-table)) + + (func (export "probe-mem") (result i32) + (i32.store (i32.const 0) (i32.const 1)) + (call $set-mem) + (i32.load (i32.const 0))) + + (func (export "probe-global") (result i32) + (global.set $g (i32.const 1)) + (call $set-global) + (global.get $g)) + + (func (export "probe-table") (result i32) + (local $before i32) + (local.set $before (i31.get_u (table.get 0 (i32.const 0)))) + (call $set-table) + (i32.sub (i31.get_u (table.get 0 (i32.const 0))) (local.get $before))) + ) + + (core instance $q (instantiate $Q + (with "" (instance + (export "mem" (memory $n "mem")) + (export "g" (global $n "g")) + (export "t" (table $n "t")) + (export "set-mem" (func $m "set-mem")) + (export "set-global" (func $m "set-global")) + (export "set-table" (func $m "set-table")) + )) + )) + + (func (export "probe-mem") (result u32) + (canon lift (core func $q "probe-mem"))) + (func (export "probe-global") (result u32) + (canon lift (core func $q "probe-global"))) + (func (export "probe-table") (result u32) + (canon lift (core func $q "probe-table"))) +) + +(assert_return (invoke "probe-mem") (u32.const 0x1234)) +(assert_return (invoke "probe-global") (u32.const 0x1234)) +(assert_return (invoke "probe-table") (u32.const 0x1234)) diff --git a/tests/misc_testsuite/component-model/alias-region-reexported-known-entities.wast b/tests/misc_testsuite/component-model/alias-region-reexported-known-entities.wast new file mode 100644 index 000000000000..97a1715a6614 --- /dev/null +++ b/tests/misc_testsuite/component-model/alias-region-reexported-known-entities.wast @@ -0,0 +1,91 @@ +;;! gc = true + +;; `$M` defines a memory, global, and table; `$N` imports them and re-exports +;; them under the same names; and `$Q` imports them transitively, through `$N`'s +;; re-export. Every module is instantiated exactly once and nothing else can get +;; its hands on these entities, so the transitive import is still unambiguous +;; and all three modules must use the same precise +;; `DefinedMemory`/`DefinedGlobal`/`DefinedTable` region. +;; +;; When inlining is enabled (randomly by the wast file fuzzer) we shouldn't get +;; any stale values from alias analysis forwarding stored values to loads across +;; mismatched alias regions. + +(component + (core module $M + (memory (export "mem") 1) + (global (export "g") (mut i32) (i32.const 0)) + (table (export "t") 1 (ref i31) (ref.i31 (i32.const 0))) + + (func (export "set-mem") + (i32.store (i32.const 0) (i32.const 0x1234))) + (func (export "set-global") + (global.set 0 (i32.const 0x1234))) + (func (export "set-table") + (table.set 0 (i32.const 0) (ref.i31 (i32.const 0x1234)))) + ) + + (core instance $m (instantiate $M)) + + (core module $N + (import "" "mem" (memory 1)) + (import "" "g" (global (mut i32))) + (import "" "t" (table 1 (ref i31))) + + ;; Re-export our imports. + (export "mem" (memory 0)) + (export "g" (global 0)) + (export "t" (table 0)) + ) + + (core instance $n (instantiate $N (with "" (instance $m)))) + + ;; The observer: entities laundered through `$N`'s re-export, setters straight + ;; from `$M`. + (core module $Q + (import "" "mem" (memory 1)) + (import "" "g" (global $g (mut i32))) + (import "" "t" (table 1 (ref i31))) + (import "" "set-mem" (func $set-mem)) + (import "" "set-global" (func $set-global)) + (import "" "set-table" (func $set-table)) + + (func (export "probe-mem") (result i32) + (i32.store (i32.const 0) (i32.const 1)) + (call $set-mem) + (i32.load (i32.const 0))) + + (func (export "probe-global") (result i32) + (global.set $g (i32.const 1)) + (call $set-global) + (global.get $g)) + + (func (export "probe-table") (result i32) + (local $before i32) + (local.set $before (i31.get_u (table.get 0 (i32.const 0)))) + (call $set-table) + (i32.sub (i31.get_u (table.get 0 (i32.const 0))) (local.get $before))) + ) + + (core instance $q (instantiate $Q + (with "" (instance + (export "mem" (memory $n "mem")) + (export "g" (global $n "g")) + (export "t" (table $n "t")) + (export "set-mem" (func $m "set-mem")) + (export "set-global" (func $m "set-global")) + (export "set-table" (func $m "set-table")) + )) + )) + + (func (export "probe-mem") (result u32) + (canon lift (core func $q "probe-mem"))) + (func (export "probe-global") (result u32) + (canon lift (core func $q "probe-global"))) + (func (export "probe-table") (result u32) + (canon lift (core func $q "probe-table"))) +) + +(assert_return (invoke "probe-mem") (u32.const 0x1234)) +(assert_return (invoke "probe-global") (u32.const 0x1234)) +(assert_return (invoke "probe-table") (u32.const 0x1234))