| use self::bitvec::BitVec; |
| use anyhow::{bail, Result}; |
| use indexmap::{IndexMap, IndexSet}; |
| use std::{ |
| borrow::Cow, |
| collections::{HashMap, HashSet}, |
| convert::Infallible, |
| mem, |
| ops::Deref, |
| }; |
| use wasm_encoder::reencode::Reencode; |
| use wasm_encoder::{Encode, EntityType, Instruction, RawCustomSection}; |
| use wasmparser::*; |
| |
| const PAGE_SIZE: i32 = 64 * 1024; |
| |
| /// This function will reduce the input core `wasm` module to only the set of |
| /// exports `required`. |
| /// |
| /// This internally performs a "gc" pass after removing exports to ensure that |
| /// the resulting module imports the minimal set of functions necessary. |
| pub fn run( |
| wasm: &[u8], |
| required: &IndexSet<String>, |
| main_module_realloc: Option<&str>, |
| ) -> Result<Vec<u8>> { |
| assert!(!required.is_empty()); |
| |
| let mut module = Module::default(); |
| module.parse(wasm)?; |
| |
| // Make sure that all required names are present in the module, and then |
| // remove all names that are not required. |
| for name in required { |
| if !module.exports.contains_key(name.as_str()) { |
| bail!("adapter module does not have export `{name}`") |
| } |
| } |
| let mut not_required = IndexSet::new(); |
| for name in module.exports.keys().copied() { |
| if !required.contains(name) { |
| not_required.insert(name); |
| } |
| } |
| for name in not_required { |
| module.exports.swap_remove(name); |
| } |
| assert!(!module.exports.is_empty()); |
| module.liveness()?; |
| module.encode(main_module_realloc) |
| } |
| |
| /// This function generates a Wasm function body which implements `cabi_realloc` in terms of `memory.grow`. It |
| /// only accepts new, page-sized allocations. |
| fn realloc_via_memory_grow() -> wasm_encoder::Function { |
| use wasm_encoder::Instruction::*; |
| |
| let mut func = wasm_encoder::Function::new([(1, wasm_encoder::ValType::I32)]); |
| |
| // Assert `old_ptr` is null. |
| func.instruction(&I32Const(0)); |
| func.instruction(&LocalGet(0)); |
| func.instruction(&I32Ne); |
| func.instruction(&If(wasm_encoder::BlockType::Empty)); |
| func.instruction(&Unreachable); |
| func.instruction(&End); |
| |
| // Assert `old_len` is zero. |
| func.instruction(&I32Const(0)); |
| func.instruction(&LocalGet(1)); |
| func.instruction(&I32Ne); |
| func.instruction(&If(wasm_encoder::BlockType::Empty)); |
| func.instruction(&Unreachable); |
| func.instruction(&End); |
| |
| // Assert `new_len` is equal to the page size (which is the only value we currently support) |
| // Note: we could easily support arbitrary multiples of PAGE_SIZE here if the need arises. |
| func.instruction(&I32Const(PAGE_SIZE)); |
| func.instruction(&LocalGet(3)); |
| func.instruction(&I32Ne); |
| func.instruction(&If(wasm_encoder::BlockType::Empty)); |
| func.instruction(&Unreachable); |
| func.instruction(&End); |
| |
| // Grow the memory by 1 page. |
| func.instruction(&I32Const(1)); |
| func.instruction(&MemoryGrow(0)); |
| func.instruction(&LocalTee(4)); |
| |
| // Test if the return value of the growth was -1 and, if so, trap due to a failed allocation. |
| func.instruction(&I32Const(-1)); |
| func.instruction(&I32Eq); |
| func.instruction(&If(wasm_encoder::BlockType::Empty)); |
| func.instruction(&Unreachable); |
| func.instruction(&End); |
| |
| func.instruction(&LocalGet(4)); |
| func.instruction(&I32Const(16)); |
| func.instruction(&I32Shl); |
| func.instruction(&End); |
| |
| func |
| } |
| |
| #[repr(i32)] |
| #[non_exhaustive] |
| enum StackAllocationState { |
| Unallocated, |
| Allocating, |
| Allocated, |
| } |
| |
| fn allocate_stack_via_realloc( |
| realloc_index: u32, |
| sp: u32, |
| allocation_state: Option<u32>, |
| ) -> wasm_encoder::Function { |
| use wasm_encoder::Instruction::*; |
| |
| let mut func = wasm_encoder::Function::new([]); |
| |
| if let Some(allocation_state) = allocation_state { |
| // This means we're lazily allocating the stack, keeping track of state via `$allocation_state` |
| func.instruction(&GlobalGet(allocation_state)); |
| func.instruction(&I32Const(StackAllocationState::Unallocated as _)); |
| func.instruction(&I32Eq); |
| func.instruction(&If(wasm_encoder::BlockType::Empty)); |
| func.instruction(&I32Const(StackAllocationState::Allocating as _)); |
| func.instruction(&GlobalSet(allocation_state)); |
| // We could also set `sp` to zero here to ensure the yet-to-be-allocated stack is empty. However, we |
| // assume it defaults to zero anyway, in which case setting it would be redundant. |
| } |
| |
| func.instruction(&I32Const(0)); |
| func.instruction(&I32Const(0)); |
| func.instruction(&I32Const(8)); |
| func.instruction(&I32Const(PAGE_SIZE)); |
| func.instruction(&Call(realloc_index)); |
| func.instruction(&I32Const(PAGE_SIZE)); |
| func.instruction(&I32Add); |
| func.instruction(&GlobalSet(sp)); |
| |
| if let Some(allocation_state) = allocation_state { |
| func.instruction(&I32Const(StackAllocationState::Allocated as _)); |
| func.instruction(&GlobalSet(allocation_state)); |
| func.instruction(&End); |
| } |
| |
| func.instruction(&End); |
| |
| func |
| } |
| |
| // Represents a function called while processing a module work list. |
| type WorklistFunc<'a> = fn(&mut Module<'a>, u32) -> Result<()>; |
| |
| // Representation of a wasm module which is used to GC a module to its minimal |
| // set of required items necessary to implement the `exports` |
| // |
| // Note that this is not a complete representation of a wasm module since it |
| // doesn't represent everything such as data and element segments. This is only |
| // used for adapter modules which otherwise have these restrictions and makes |
| // this gc pass a bit easier to write. |
| #[derive(Default)] |
| struct Module<'a> { |
| // Definitions found when parsing a module |
| types: Vec<FuncType>, |
| tables: Vec<Table<'a>>, |
| globals: Vec<Global<'a>>, |
| memories: Vec<Memory<'a>>, |
| funcs: Vec<Func<'a>>, |
| exports: IndexMap<&'a str, Export<'a>>, |
| func_names: HashMap<u32, &'a str>, |
| global_names: HashMap<u32, &'a str>, |
| producers: Option<wasm_metadata::Producers>, |
| |
| // Known-live sets of indices after the `liveness` pass has run. |
| live_types: BitVec, |
| live_tables: BitVec, |
| live_globals: BitVec, |
| live_memories: BitVec, |
| live_funcs: BitVec, |
| |
| // Helper data structure used during the `liveness` path to avoid recursion. |
| // When calculating the liveness of an item this `worklist` is pushed to and |
| // then processed until it's empty. An item pushed onto this list represents |
| // a new index that has been discovered to be live and the function is what |
| // walks the item's definition to find other items that it references. |
| worklist: Vec<(u32, WorklistFunc<'a>)>, |
| } |
| |
| struct Table<'a> { |
| def: Definition<'a, ()>, |
| ty: TableType, |
| } |
| |
| struct Memory<'a> { |
| def: Definition<'a, ()>, |
| ty: MemoryType, |
| } |
| |
| struct Global<'a> { |
| def: Definition<'a, ConstExpr<'a>>, |
| ty: GlobalType, |
| } |
| |
| #[derive(Clone)] |
| struct Func<'a> { |
| def: Definition<'a, FunctionBody<'a>>, |
| ty: u32, |
| } |
| |
| #[derive(Clone)] |
| enum Definition<'a, T> { |
| Import(&'a str, &'a str), |
| Local(T), |
| } |
| |
| impl<'a> Module<'a> { |
| fn parse(&mut self, wasm: &'a [u8]) -> Result<()> { |
| let mut next_code_index = 0; |
| let mut validator = Validator::new(); |
| for payload in Parser::new(0).parse_all(wasm) { |
| let payload = payload?; |
| validator.payload(&payload)?; |
| match payload { |
| Payload::Version { encoding, .. } => { |
| if encoding != Encoding::Module { |
| bail!("adapter must be a core wasm module, not a component"); |
| } |
| } |
| Payload::End(_) => {} |
| Payload::TypeSection(s) => { |
| for ty in s.into_iter_err_on_gc_types() { |
| self.types.push(ty?); |
| } |
| } |
| Payload::ImportSection(s) => { |
| for i in s { |
| let i = i?; |
| match i.ty { |
| TypeRef::Func(ty) => self.funcs.push(Func { |
| def: Definition::Import(i.module, i.name), |
| ty, |
| }), |
| TypeRef::Table(ty) => self.tables.push(Table { |
| def: Definition::Import(i.module, i.name), |
| ty, |
| }), |
| TypeRef::Global(ty) => self.globals.push(Global { |
| def: Definition::Import(i.module, i.name), |
| ty, |
| }), |
| TypeRef::Memory(ty) => self.memories.push(Memory { |
| def: Definition::Import(i.module, i.name), |
| ty, |
| }), |
| TypeRef::Tag(_) => bail!("unsupported `tag` type"), |
| } |
| } |
| } |
| Payload::TableSection(s) => { |
| for table in s { |
| let table = table?; |
| self.tables.push(Table { |
| def: Definition::Local(()), |
| ty: table.ty, |
| }); |
| } |
| } |
| Payload::MemorySection(s) => { |
| for ty in s { |
| let ty = ty?; |
| self.memories.push(Memory { |
| def: Definition::Local(()), |
| ty, |
| }); |
| } |
| } |
| Payload::GlobalSection(s) => { |
| for g in s { |
| let g = g?; |
| self.globals.push(Global { |
| def: Definition::Local(g.init_expr), |
| ty: g.ty, |
| }); |
| } |
| } |
| |
| Payload::ExportSection(s) => { |
| for e in s { |
| let e = e?; |
| self.exports.insert(e.name, e); |
| } |
| } |
| |
| Payload::FunctionSection(s) => { |
| next_code_index = self.funcs.len(); |
| for ty in s { |
| let ty = ty?; |
| self.funcs.push(Func { |
| // Specify a dummy definition to get filled in later |
| // when parsing the code section. |
| def: Definition::Local(FunctionBody::new(BinaryReader::new(&[], 0))), |
| ty, |
| }); |
| } |
| } |
| |
| Payload::CodeSectionStart { .. } => {} |
| Payload::CodeSectionEntry(body) => { |
| self.funcs[next_code_index].def = Definition::Local(body); |
| next_code_index += 1; |
| } |
| |
| // Ignore all custom sections except for the `name` and |
| // `producers` sections which we parse, but ignore errors within. |
| Payload::CustomSection(s) => match s.as_known() { |
| KnownCustom::Name(s) => drop(self.parse_name_section(s)), |
| KnownCustom::Producers(_) => drop(self.parse_producers_section(&s)), |
| _ => {} |
| }, |
| |
| // sections that shouldn't appear in the specially-crafted core |
| // wasm adapter self we're processing |
| other => match other.as_section() { |
| Some((id, _)) => bail!("unsupported section `{}` in adapter", id), |
| None => bail!("unsupported payload in adapter"), |
| }, |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| fn parse_name_section(&mut self, section: NameSectionReader<'a>) -> Result<()> { |
| for s in section { |
| match s? { |
| Name::Function(map) => { |
| for naming in map { |
| let naming = naming?; |
| self.func_names.insert(naming.index, naming.name); |
| } |
| } |
| Name::Global(map) => { |
| for naming in map { |
| let naming = naming?; |
| self.global_names.insert(naming.index, naming.name); |
| } |
| } |
| _ => {} |
| } |
| } |
| Ok(()) |
| } |
| |
| fn parse_producers_section(&mut self, section: &CustomSectionReader<'a>) -> Result<()> { |
| let producers = |
| wasm_metadata::Producers::from_bytes(section.data(), section.data_offset())?; |
| self.producers = Some(producers); |
| Ok(()) |
| } |
| |
| /// Iteratively calculates the set of live items within this module |
| /// considering all exports as the root of live functions. |
| fn liveness(&mut self) -> Result<()> { |
| let exports = mem::take(&mut self.exports); |
| for (_, e) in exports.iter() { |
| match e.kind { |
| ExternalKind::Func => self.func(e.index), |
| ExternalKind::Global => self.global(e.index), |
| ExternalKind::Table => self.table(e.index), |
| ExternalKind::Memory => self.memory(e.index), |
| ExternalKind::Tag => bail!("unsupported exported tag"), |
| } |
| } |
| self.exports = exports; |
| |
| while let Some((idx, func)) = self.worklist.pop() { |
| func(self, idx)?; |
| } |
| Ok(()) |
| } |
| |
| fn func(&mut self, func: u32) { |
| if !self.live_funcs.insert(func) { |
| return; |
| } |
| self.worklist.push((func, |me, func| { |
| let func = me.funcs[func as usize].clone(); |
| me.ty(func.ty); |
| let mut body = match &func.def { |
| Definition::Import(..) => return Ok(()), |
| Definition::Local(e) => e.get_binary_reader(), |
| }; |
| let local_count = body.read_var_u32()?; |
| for _ in 0..local_count { |
| body.read_var_u32()?; |
| body.read::<ValType>()?; |
| } |
| me.operators(body) |
| })); |
| } |
| |
| fn global(&mut self, global: u32) { |
| if !self.live_globals.insert(global) { |
| return; |
| } |
| self.worklist.push((global, |me, global| { |
| let init = match &me.globals[global as usize].def { |
| Definition::Import(..) => return Ok(()), |
| Definition::Local(e) => e, |
| }; |
| me.operators(init.get_binary_reader()) |
| })); |
| } |
| |
| fn table(&mut self, table: u32) { |
| if !self.live_tables.insert(table) { |
| return; |
| } |
| self.worklist.push((table, |me, table| { |
| let ty = me.tables[table as usize].ty.element_type; |
| me.valty(ty.into()); |
| Ok(()) |
| })); |
| } |
| |
| fn memory(&mut self, memory: u32) { |
| self.live_memories.insert(memory); |
| } |
| |
| fn blockty(&mut self, ty: BlockType) { |
| if let BlockType::FuncType(ty) = ty { |
| self.ty(ty); |
| } |
| } |
| |
| fn valty(&mut self, ty: ValType) { |
| match ty { |
| ValType::Ref(r) => self.refty(r), |
| ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => {} |
| } |
| } |
| |
| fn refty(&mut self, ty: RefType) { |
| self.heapty(ty.heap_type()) |
| } |
| |
| fn heapty(&mut self, ty: HeapType) { |
| match ty { |
| HeapType::Abstract { .. } => {} |
| HeapType::Concrete(i) => self.ty(i.as_module_index().unwrap()), |
| } |
| } |
| |
| fn ty(&mut self, ty: u32) { |
| if !self.live_types.insert(ty) { |
| return; |
| } |
| self.worklist.push((ty, |me, ty| { |
| let ty = me.types[ty as usize].clone(); |
| for param in ty.params().iter().chain(ty.results()) { |
| me.valty(*param); |
| } |
| Ok(()) |
| })); |
| } |
| |
| fn operators(&mut self, mut reader: BinaryReader<'a>) -> Result<()> { |
| while !reader.eof() { |
| reader.visit_operator(self)?; |
| } |
| Ok(()) |
| } |
| |
| fn live_types(&self) -> impl Iterator<Item = (u32, &FuncType)> + '_ { |
| live_iter(&self.live_types, self.types.iter()) |
| } |
| |
| fn live_funcs(&self) -> impl Iterator<Item = (u32, &Func<'a>)> + '_ { |
| live_iter(&self.live_funcs, self.funcs.iter()) |
| } |
| |
| fn live_memories(&self) -> impl Iterator<Item = (u32, &Memory<'a>)> + '_ { |
| live_iter(&self.live_memories, self.memories.iter()) |
| } |
| |
| fn live_globals(&self) -> impl Iterator<Item = (u32, &Global<'a>)> + '_ { |
| live_iter(&self.live_globals, self.globals.iter()) |
| } |
| |
| fn live_tables(&self) -> impl Iterator<Item = (u32, &Table<'a>)> + '_ { |
| live_iter(&self.live_tables, self.tables.iter()) |
| } |
| |
| /// Encodes this `Module` to a new wasm module which is gc'd and only |
| /// contains the items that are live as calculated by the `liveness` pass. |
| fn encode(&mut self, main_module_realloc: Option<&str>) -> Result<Vec<u8>> { |
| // Data structure used to track the mapping of old index to new index |
| // for all live items. |
| let mut map = Encoder::default(); |
| |
| // Sections that will be assembled into the final module at the end of |
| // this function. |
| let mut types = wasm_encoder::TypeSection::new(); |
| let mut imports = wasm_encoder::ImportSection::new(); |
| let mut funcs = wasm_encoder::FunctionSection::new(); |
| let mut tables = wasm_encoder::TableSection::new(); |
| let mut memories = wasm_encoder::MemorySection::new(); |
| let mut globals = wasm_encoder::GlobalSection::new(); |
| let mut code = wasm_encoder::CodeSection::new(); |
| |
| let mut empty_type = None; |
| for (i, ty) in self.live_types() { |
| map.types.push(i); |
| |
| let ty = map.func_type(ty.clone())?; |
| types.ty().func_type(&ty); |
| |
| // Keep track of the "empty type" to see if we can reuse an |
| // existing one or one needs to be injected if a `start` |
| // function is calculated at the end. |
| if ty.params().is_empty() && ty.results().is_empty() { |
| empty_type = Some(map.types.remap(i)); |
| } |
| } |
| |
| let mut num_memories = 0; |
| for (i, mem) in self.live_memories() { |
| map.memories.push(i); |
| let ty = map.memory_type(mem.ty); |
| match &mem.def { |
| Definition::Import(m, n) => { |
| imports.import(m, n, ty); |
| } |
| Definition::Local(()) => { |
| memories.memory(ty); |
| } |
| } |
| num_memories += 1; |
| } |
| |
| for (i, table) in self.live_tables() { |
| map.tables.push(i); |
| let ty = map.table_type(table.ty)?; |
| match &table.def { |
| Definition::Import(m, n) => { |
| imports.import(m, n, ty); |
| } |
| Definition::Local(()) => { |
| tables.table(ty); |
| } |
| } |
| } |
| |
| for (i, global) in self.live_globals() { |
| map.globals.push(i); |
| let ty = map.global_type(global.ty)?; |
| match &global.def { |
| Definition::Import(m, n) => { |
| imports.import(m, n, ty); |
| } |
| Definition::Local(init) => { |
| let init = &map.const_expr(init.clone())?; |
| globals.global(ty, &init); |
| } |
| } |
| } |
| |
| let mut realloc_index = None; |
| let mut num_func_imports = 0; |
| |
| // For functions first assign a new index to all functions and then |
| // afterwards actually map the body of all functions so the `map` of all |
| // index mappings is fully populated before instructions are mapped. |
| |
| let is_realloc = |
| |m, n| m == "__main_module__" && matches!(n, "canonical_abi_realloc" | "cabi_realloc"); |
| |
| let (imported, local) = |
| self.live_funcs() |
| .partition::<Vec<_>, _>(|(_, func)| match &func.def { |
| Definition::Import(m, n) => { |
| !is_realloc(*m, *n) || main_module_realloc.is_some() |
| } |
| Definition::Local(_) => false, |
| }); |
| |
| for (i, func) in imported { |
| map.funcs.push(i); |
| let ty = map.types.remap(func.ty); |
| match &func.def { |
| Definition::Import(m, n) => { |
| let name = if is_realloc(*m, *n) { |
| // The adapter is importing `cabi_realloc` from the main module, and the main module |
| // exports that function, but possibly using a different name |
| // (e.g. `canonical_abi_realloc`). Update the name to match if necessary. |
| realloc_index = Some(num_func_imports); |
| main_module_realloc.unwrap_or(n) |
| } else { |
| n |
| }; |
| imports.import(m, name, EntityType::Function(ty)); |
| num_func_imports += 1; |
| } |
| Definition::Local(_) => unreachable!(), |
| } |
| } |
| |
| let add_realloc_type = |types: &mut wasm_encoder::TypeSection| { |
| let type_index = types.len(); |
| types.ty().function( |
| [ |
| wasm_encoder::ValType::I32, |
| wasm_encoder::ValType::I32, |
| wasm_encoder::ValType::I32, |
| wasm_encoder::ValType::I32, |
| ], |
| [wasm_encoder::ValType::I32], |
| ); |
| type_index |
| }; |
| |
| let add_empty_type = |types: &mut wasm_encoder::TypeSection| { |
| let type_index = types.len(); |
| types.ty().function([], []); |
| type_index |
| }; |
| |
| let sp = self.find_mut_i32_global("__stack_pointer")?; |
| let allocation_state = self.find_mut_i32_global("allocation_state")?; |
| |
| let mut func_names = Vec::new(); |
| |
| if let (Some(realloc), Some(_), None) = (main_module_realloc, sp, realloc_index) { |
| // The main module exports a realloc function, and although the adapter doesn't import it, we're going |
| // to add a function which calls it to allocate some stack space, so let's add an import now. |
| |
| // Tell the function remapper we're reserving a slot for our extra import: |
| map.funcs.next += 1; |
| |
| realloc_index = Some(num_func_imports); |
| imports.import( |
| "__main_module__", |
| realloc, |
| EntityType::Function(add_realloc_type(&mut types)), |
| ); |
| func_names.push((num_func_imports, realloc)); |
| num_func_imports += 1; |
| } |
| |
| for (i, func) in local { |
| map.funcs.push(i); |
| let ty = map.types.remap(func.ty); |
| match &func.def { |
| Definition::Import(_, _) => { |
| // The adapter is importing `cabi_realloc` from the main module, but the main module isn't |
| // exporting it. In this case, we need to define a local function it can call instead. |
| realloc_index = Some(num_func_imports + funcs.len()); |
| funcs.function(ty); |
| code.function(&realloc_via_memory_grow()); |
| } |
| Definition::Local(_) => { |
| funcs.function(ty); |
| } |
| } |
| } |
| |
| let lazy_stack_init_index = |
| if sp.is_some() && allocation_state.is_some() && main_module_realloc.is_some() { |
| // We have a stack pointer, a `cabi_realloc` function from the main module, and a global variable for |
| // keeping track of (and short-circuiting) reentrance. That means we can (and should) do lazy stack |
| // allocation. |
| let index = num_func_imports + funcs.len(); |
| |
| // Tell the function remapper we're reserving a slot for our extra function: |
| map.funcs.next += 1; |
| |
| funcs.function(add_empty_type(&mut types)); |
| |
| Some(index) |
| } else { |
| None |
| }; |
| |
| let exported_funcs = self |
| .exports |
| .values() |
| .filter_map(|export| match export.kind { |
| ExternalKind::Func => Some(export.index), |
| _ => None, |
| }) |
| .collect::<HashSet<_>>(); |
| |
| for (i, func) in self.live_funcs() { |
| let body = match &func.def { |
| Definition::Import(..) => continue, |
| Definition::Local(body) => body, |
| }; |
| |
| match (lazy_stack_init_index, exported_funcs.contains(&i)) { |
| // Prepend an `allocate_stack` call to all exports if we're |
| // lazily allocating the stack. |
| (Some(lazy_stack_init_index), true) => { |
| let mut func = map.new_function_with_parsed_locals(&body)?; |
| func.instruction(&Instruction::Call(lazy_stack_init_index)); |
| let mut reader = body.get_operators_reader()?; |
| while !reader.eof() { |
| func.instruction(&map.parse_instruction(&mut reader)?); |
| } |
| code.function(&func); |
| } |
| _ => { |
| map.parse_function_body(&mut code, body.clone())?; |
| } |
| } |
| } |
| |
| if lazy_stack_init_index.is_some() { |
| code.function(&allocate_stack_via_realloc( |
| realloc_index.unwrap(), |
| sp.unwrap(), |
| allocation_state, |
| )); |
| } |
| |
| if sp.is_some() && (realloc_index.is_none() || allocation_state.is_none()) { |
| // Either the main module does _not_ export a realloc function, or it is not safe to use for stack |
| // allocation because we have no way to short-circuit reentrance, so we'll use `memory.grow` instead. |
| realloc_index = Some(num_func_imports + funcs.len()); |
| funcs.function(add_realloc_type(&mut types)); |
| code.function(&realloc_via_memory_grow()); |
| } |
| |
| // Inject a start function to initialize the stack pointer which will be local to this module. This only |
| // happens if a memory is preserved, a stack pointer global is found, and we're not doing lazy stack |
| // allocation. |
| let mut start = None; |
| if let (Some(sp), None) = (sp, lazy_stack_init_index) { |
| if num_memories > 0 { |
| // If there are any memories or any mutable globals there must be |
| // precisely one of each as otherwise we don't know how to filter |
| // down to the right one. |
| if num_memories != 1 { |
| bail!("adapter modules don't support multi-memory"); |
| } |
| |
| let sp = map.globals.remap(sp); |
| |
| let function_index = num_func_imports + funcs.len(); |
| |
| // Generate a function type for this start function, adding a new |
| // function type to the module if necessary. |
| let empty_type = empty_type.unwrap_or_else(|| { |
| types.ty().function([], []); |
| types.len() - 1 |
| }); |
| funcs.function(empty_type); |
| func_names.push((function_index, "allocate_stack")); |
| code.function(&allocate_stack_via_realloc( |
| realloc_index.unwrap(), |
| sp, |
| allocation_state, |
| )); |
| |
| start = Some(wasm_encoder::StartSection { function_index }); |
| } |
| } |
| |
| // Sanity-check the shape of the module since some parts won't work if |
| // this fails. Note that during parsing we've already validated there |
| // are no data segments or element segments. |
| |
| // Shouldn't have any tables if there are no element segments since |
| // otherwise there's no meaning to a defined or imported table. |
| if self.live_tables().count() != 0 { |
| bail!("tables should not be present in the final adapter module"); |
| } |
| |
| // multi-memory should not be enabled and if any memory it should be |
| // imported. |
| if self.live_memories().count() > 1 { |
| bail!("the adapter module should not use multi-memory"); |
| } |
| if !memories.is_empty() { |
| bail!("locally-defined memories are not allowed define a local memory"); |
| } |
| |
| let mut ret = wasm_encoder::Module::default(); |
| if !types.is_empty() { |
| ret.section(&types); |
| } |
| if !imports.is_empty() { |
| ret.section(&imports); |
| } |
| if !funcs.is_empty() { |
| ret.section(&funcs); |
| } |
| if !tables.is_empty() { |
| ret.section(&tables); |
| } |
| if !memories.is_empty() { |
| ret.section(&memories); |
| } |
| if !globals.is_empty() { |
| ret.section(&globals); |
| } |
| |
| if !self.exports.is_empty() { |
| let mut exports = wasm_encoder::ExportSection::new(); |
| for (_, export) in self.exports.iter() { |
| let (kind, index) = match export.kind { |
| ExternalKind::Func => ( |
| wasm_encoder::ExportKind::Func, |
| map.funcs.remap(export.index), |
| ), |
| ExternalKind::Table => ( |
| wasm_encoder::ExportKind::Table, |
| map.tables.remap(export.index), |
| ), |
| ExternalKind::Memory => ( |
| wasm_encoder::ExportKind::Memory, |
| map.memories.remap(export.index), |
| ), |
| ExternalKind::Global => ( |
| wasm_encoder::ExportKind::Global, |
| map.globals.remap(export.index), |
| ), |
| kind => bail!("unsupported export kind {kind:?}"), |
| }; |
| exports.export(export.name, kind, index); |
| } |
| ret.section(&exports); |
| } |
| |
| if let Some(start) = &start { |
| ret.section(start); |
| } |
| |
| if !code.is_empty() { |
| ret.section(&code); |
| } |
| |
| // Append a custom `name` section using the names of the functions that |
| // were found prior to the GC pass in the original module. |
| let mut global_names = Vec::new(); |
| for (i, _func) in self.live_funcs() { |
| let name = match self.func_names.get(&i) { |
| Some(name) => name, |
| None => continue, |
| }; |
| func_names.push((map.funcs.remap(i), *name)); |
| } |
| for (i, _global) in self.live_globals() { |
| let name = match self.global_names.get(&i) { |
| Some(name) => name, |
| None => continue, |
| }; |
| global_names.push((map.globals.remap(i), *name)); |
| } |
| let mut section = Vec::new(); |
| let mut encode_subsection = |code: u8, names: &[(u32, &str)]| { |
| if names.is_empty() { |
| return; |
| } |
| let mut subsection = Vec::new(); |
| names.len().encode(&mut subsection); |
| for (i, name) in names { |
| i.encode(&mut subsection); |
| name.encode(&mut subsection); |
| } |
| section.push(code); |
| subsection.encode(&mut section); |
| }; |
| if let (Some(realloc_index), true) = ( |
| realloc_index, |
| main_module_realloc.is_none() || allocation_state.is_none(), |
| ) { |
| func_names.push((realloc_index, "realloc_via_memory_grow")); |
| } |
| if let Some(lazy_stack_init_index) = lazy_stack_init_index { |
| func_names.push((lazy_stack_init_index, "allocate_stack")); |
| } |
| encode_subsection(0x01, &func_names); |
| encode_subsection(0x07, &global_names); |
| if !section.is_empty() { |
| ret.section(&wasm_encoder::CustomSection { |
| name: "name".into(), |
| data: Cow::Borrowed(§ion), |
| }); |
| } |
| if let Some(producers) = &self.producers { |
| ret.section(&RawCustomSection(&producers.raw_custom_section())); |
| } |
| |
| Ok(ret.finish()) |
| } |
| |
| fn find_mut_i32_global(&self, name: &str) -> Result<Option<u32>> { |
| let matches = &self |
| .live_globals() |
| .filter_map(|(i, g)| { |
| if g.ty.mutable |
| && g.ty.content_type == ValType::I32 |
| && *self.global_names.get(&i)? == name |
| { |
| Some(i) |
| } else { |
| None |
| } |
| }) |
| .collect::<Vec<_>>(); |
| |
| match matches.deref() { |
| [] => Ok(None), |
| [i] => Ok(Some(*i)), |
| _ => bail!( |
| "found {} mutable i32 globals with name {name}", |
| matches.len() |
| ), |
| } |
| } |
| } |
| |
| // This helper macro is used to define a visitor of all instructions with |
| // special handling for all payloads of instructions to mark any referenced |
| // items live. |
| // |
| // Currently item identification happens through the field name of the payload. |
| // While not exactly the most robust solution this should work well enough for |
| // now. |
| macro_rules! define_visit { |
| ($(@$p:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => { |
| $( |
| fn $visit(&mut self $(, $($arg: $argty),*)?) { |
| $( |
| $( |
| define_visit!(mark_live self $arg $arg); |
| )* |
| )? |
| } |
| )* |
| }; |
| |
| (mark_live $self:ident $arg:ident type_index) => {$self.ty($arg);}; |
| (mark_live $self:ident $arg:ident array_type_index) => {$self.ty($arg);}; |
| (mark_live $self:ident $arg:ident array_type_index_dst) => {$self.ty($arg);}; |
| (mark_live $self:ident $arg:ident array_type_index_src) => {$self.ty($arg);}; |
| (mark_live $self:ident $arg:ident struct_type_index) => {$self.ty($arg);}; |
| (mark_live $self:ident $arg:ident src_table) => {$self.table($arg);}; |
| (mark_live $self:ident $arg:ident dst_table) => {$self.table($arg);}; |
| (mark_live $self:ident $arg:ident table_index) => {$self.table($arg);}; |
| (mark_live $self:ident $arg:ident table) => {$self.table($arg);}; |
| (mark_live $self:ident $arg:ident table_index) => {$self.table($arg);}; |
| (mark_live $self:ident $arg:ident global_index) => {$self.global($arg);}; |
| (mark_live $self:ident $arg:ident function_index) => {$self.func($arg);}; |
| (mark_live $self:ident $arg:ident mem) => {$self.memory($arg);}; |
| (mark_live $self:ident $arg:ident src_mem) => {$self.memory($arg);}; |
| (mark_live $self:ident $arg:ident dst_mem) => {$self.memory($arg);}; |
| (mark_live $self:ident $arg:ident memarg) => {$self.memory($arg.memory);}; |
| (mark_live $self:ident $arg:ident blockty) => {$self.blockty($arg);}; |
| (mark_live $self:ident $arg:ident ty) => {$self.valty($arg)}; |
| (mark_live $self:ident $arg:ident hty) => {$self.heapty($arg)}; |
| (mark_live $self:ident $arg:ident from_ref_type) => {$self.refty($arg);}; |
| (mark_live $self:ident $arg:ident to_ref_type) => {$self.refty($arg);}; |
| (mark_live $self:ident $arg:ident lane) => {}; |
| (mark_live $self:ident $arg:ident lanes) => {}; |
| (mark_live $self:ident $arg:ident flags) => {}; |
| (mark_live $self:ident $arg:ident value) => {}; |
| (mark_live $self:ident $arg:ident local_index) => {}; |
| (mark_live $self:ident $arg:ident relative_depth) => {}; |
| (mark_live $self:ident $arg:ident tag_index) => {}; |
| (mark_live $self:ident $arg:ident targets) => {}; |
| (mark_live $self:ident $arg:ident data_index) => {}; |
| (mark_live $self:ident $arg:ident array_data_index) => {}; |
| (mark_live $self:ident $arg:ident elem_index) => {}; |
| (mark_live $self:ident $arg:ident array_elem_index) => {}; |
| (mark_live $self:ident $arg:ident array_size) => {}; |
| (mark_live $self:ident $arg:ident field_index) => {}; |
| (mark_live $self:ident $arg:ident from_type_nullable) => {}; |
| (mark_live $self:ident $arg:ident to_type_nullable) => {}; |
| (mark_live $self:ident $arg:ident ordering) => {}; |
| (mark_live $self:ident $arg:ident try_table) => {unimplemented!();}; |
| (mark_live $self:ident $arg:ident argument_index) => {}; |
| (mark_live $self:ident $arg:ident result_index) => {}; |
| (mark_live $self:ident $arg:ident cont_type_index) => {}; |
| (mark_live $self:ident $arg:ident resume_table) => {unimplemented!();}; |
| } |
| |
| impl<'a> VisitOperator<'a> for Module<'a> { |
| type Output = (); |
| |
| wasmparser::for_each_operator!(define_visit); |
| } |
| |
| /// Helper function to filter `iter` based on the `live` set, yielding an |
| /// iterator over the index of the item that's live as well as the item itself. |
| fn live_iter<'a, T>( |
| live: &'a BitVec, |
| iter: impl Iterator<Item = T> + 'a, |
| ) -> impl Iterator<Item = (u32, T)> + 'a { |
| iter.enumerate().filter_map(|(i, t)| { |
| let i = i as u32; |
| if live.contains(i) { |
| Some((i, t)) |
| } else { |
| None |
| } |
| }) |
| } |
| |
| #[derive(Default)] |
| struct Encoder { |
| types: Remap, |
| funcs: Remap, |
| memories: Remap, |
| globals: Remap, |
| tables: Remap, |
| } |
| |
| impl Reencode for Encoder { |
| type Error = Infallible; |
| |
| fn type_index(&mut self, i: u32) -> u32 { |
| self.types.remap(i) |
| } |
| fn function_index(&mut self, i: u32) -> u32 { |
| self.funcs.remap(i) |
| } |
| fn memory_index(&mut self, i: u32) -> u32 { |
| self.memories.remap(i) |
| } |
| fn global_index(&mut self, i: u32) -> u32 { |
| self.globals.remap(i) |
| } |
| fn table_index(&mut self, i: u32) -> u32 { |
| self.tables.remap(i) |
| } |
| } |
| |
| // Minimal definition of a bit vector necessary for the liveness calculations |
| // above. |
| mod bitvec { |
| use std::mem; |
| |
| type T = u64; |
| |
| #[derive(Default)] |
| pub struct BitVec { |
| bits: Vec<T>, |
| } |
| |
| impl BitVec { |
| /// Inserts `idx` into this bit vector, returning whether it was not |
| /// previously present. |
| pub fn insert(&mut self, idx: u32) -> bool { |
| let (idx, bit) = idx_bit(idx); |
| match self.bits.get_mut(idx) { |
| Some(bits) => { |
| if *bits & bit != 0 { |
| return false; |
| } |
| *bits |= bit; |
| } |
| None => { |
| self.bits.resize(idx + 1, 0); |
| self.bits[idx] = bit; |
| } |
| } |
| true |
| } |
| |
| /// Returns whether this bit vector contains the specified `idx`th bit. |
| pub fn contains(&self, idx: u32) -> bool { |
| let (idx, bit) = idx_bit(idx); |
| match self.bits.get(idx) { |
| Some(bits) => (*bits & bit) != 0, |
| None => false, |
| } |
| } |
| } |
| |
| fn idx_bit(idx: u32) -> (usize, T) { |
| let idx = idx as usize; |
| let size = mem::size_of::<T>() * 8; |
| let index = idx / size; |
| let bit = 1 << (idx % size); |
| (index, bit) |
| } |
| } |
| |
| /// Small data structure used to track index mappings from an old index space to |
| /// a new. |
| #[derive(Default)] |
| struct Remap { |
| /// Map, indexed by the old index set, to the new index set. |
| map: HashMap<u32, u32>, |
| /// The next available index in the new index space. |
| next: u32, |
| } |
| |
| impl Remap { |
| /// Appends a new live "old index" into this remapping structure. |
| /// |
| /// This will assign a new index for the old index provided. |
| fn push(&mut self, old: u32) { |
| self.map.insert(old, self.next); |
| self.next += 1; |
| } |
| |
| /// Returns the new index corresponding to an old index. |
| /// |
| /// Panics if the `old` index was not added via `push` above. |
| fn remap(&self, old: u32) -> u32 { |
| *self |
| .map |
| .get(&old) |
| .unwrap_or_else(|| panic!("can't map {old} to a new index")) |
| } |
| } |