Path: blob/main/crates/wizer/src/component/instrument.rs
2459 views
use crate::ModuleContext;1use crate::component::info::{Accessor, RawSection};2use crate::component::{ComponentContext, WIZER_INSTANCE};3use anyhow::{Result, bail};4use wasm_encoder::reencode::{Reencode, RoundtripReencoder};56/// Instrumentation phase of wizening a component.7///8/// This is similar to the core wasm wizening instrumentation but operates at9/// the component level. This notably handles multiple instances and multiple10/// globals/memories across these instances. The general idea of the11/// instrumented component is:12///13/// * All core modules are instrumented with typical wizer instrumentation (e.g.14/// `__wizer_*` exports of all internal state).15/// * A core module is then generated which accesses all of these exports in the16/// form of functions.17/// * This core module is instantiated and lifted in a component instance which18/// contains exported functions for accessing all of the various pieces of19/// state of the module.20/// * The lifted instance is exported under the `WIZER_INSTANCE` name.21///22/// The main goal is to reuse the core wasm instrumentation as much as possible23/// here, and then the only remaining question is how to plumb all the core wasm24/// state out of the component through WIT.25pub(crate) fn instrument(component: &mut ComponentContext<'_>) -> Result<Vec<u8>> {26let mut encoder = wasm_encoder::Component::new();2728// First pass through all sections as-is, ensuring that module sections are29// the instrumented version of the module.30for section in component.sections.iter_mut() {31match section {32RawSection::Raw(raw) => {33encoder.section(raw);34}35RawSection::Module(module) => {36let wasm = crate::instrument::instrument(module);37encoder.section(&wasm_encoder::RawSection {38id: wasm_encoder::ComponentSectionId::CoreModule as u8,39data: &wasm,40});41}42}43}4445// Build the accessor module and append it with this helper.46let mut builder = AccessorBuilder {47component,48accessor_types: Default::default(),49accessor_imports: Default::default(),50accessor_functions: Default::default(),51accessor_code: Default::default(),52accessor_exports: Default::default(),53accessor_nglobals: 0,54accessor_nmemories: 0,55instances_to_instantiate_with: Vec::new(),56accessors: Vec::new(),57extra_types: Default::default(),58extra_aliases: Default::default(),59extra_canonicals: Default::default(),60accessor_instance_export_items: Vec::new(),61extra_core_funcs: 0,62};63builder.build(&mut encoder)?;64component.accessors = Some(builder.accessors);6566Ok(encoder.finish())67}6869struct AccessorBuilder<'a> {70component: &'a ComponentContext<'a>,7172// Sections that are used to create the "accessor" module which is a bunch73// of functions that reads the internal state of all other instances in this74// component.75accessor_types: wasm_encoder::TypeSection,76accessor_imports: wasm_encoder::ImportSection,77accessor_functions: wasm_encoder::FunctionSection,78accessor_code: wasm_encoder::CodeSection,79accessor_exports: wasm_encoder::ExportSection,80accessor_nglobals: u32,81accessor_nmemories: u32,8283// Arguments to the instantiation of the accessor module.84instances_to_instantiate_with: Vec<(u32, String)>,8586// All accessor functions generated for all instance internal state.87accessors: Vec<Accessor>,8889// Sections that are appended to the component as part of the90// instrumentation. This is the implementation detail of lifting all the91// functions in the "accessor" module.92extra_types: wasm_encoder::ComponentTypeSection,93extra_aliases: wasm_encoder::ComponentAliasSection,94extra_core_funcs: u32,95extra_canonicals: wasm_encoder::CanonicalFunctionSection,96accessor_instance_export_items: Vec<(String, wasm_encoder::ComponentExportKind, u32)>,97}9899impl AccessorBuilder<'_> {100fn build(&mut self, encoder: &mut wasm_encoder::Component) -> Result<()> {101for (module_index, module) in self.component.core_modules() {102let instance_index = match self.component.core_instantiations.get(&module_index) {103Some(i) => *i,104None => continue,105};106self.add_core_instance(module_index, module, instance_index)?;107}108109self.finish(encoder);110Ok(())111}112113fn add_core_instance(114&mut self,115module_index: u32,116module: &ModuleContext<'_>,117instance_index: u32,118) -> Result<()> {119let instance_import_name = instance_index.to_string();120121for (_, ty, name) in module.defined_globals() {122let name = match name {123Some(n) => n,124None => continue,125};126127let accessor_export_name =128self.add_core_instance_global(&instance_import_name, name, ty)?;129self.accessors.push(Accessor::Global {130module_index,131accessor_export_name,132ty: ty.content_type,133core_export_name: name.to_string(),134});135self.accessor_nglobals += 1;136}137138let defined_memory_exports = module.defined_memory_exports.as_ref().unwrap();139for ((_, ty), name) in module.defined_memories().zip(defined_memory_exports) {140let accessor_export_name =141self.add_core_instance_memory(instance_index, &instance_import_name, name, ty);142self.accessors.push(Accessor::Memory {143module_index,144accessor_export_name,145core_export_name: name.to_string(),146});147self.accessor_nmemories += 1;148}149150self.instances_to_instantiate_with151.push((instance_index, instance_import_name));152153Ok(())154}155156fn add_core_instance_global(157&mut self,158instance_import_name: &str,159global_export_name: &str,160global_ty: wasmparser::GlobalType,161) -> Result<String> {162// Import the global and then define a function which returns the163// current value of the global.164self.accessor_imports.import(165&instance_import_name,166global_export_name,167RoundtripReencoder.global_type(global_ty).unwrap(),168);169let type_index = self.accessor_types.len();170self.accessor_types.ty().function(171[],172[RoundtripReencoder.val_type(global_ty.content_type).unwrap()],173);174self.accessor_functions.function(type_index);175176// Accessing a global is pretty easy, it's just `global.get`177let mut function = wasm_encoder::Function::new([]);178let mut ins = function.instructions();179ins.global_get(self.accessor_nglobals);180ins.end();181self.accessor_code.function(&function);182183let prim = match global_ty.content_type {184wasmparser::ValType::I32 => wasm_encoder::PrimitiveValType::S32,185wasmparser::ValType::I64 => wasm_encoder::PrimitiveValType::S64,186wasmparser::ValType::F32 => wasm_encoder::PrimitiveValType::F32,187wasmparser::ValType::F64 => wasm_encoder::PrimitiveValType::F64,188wasmparser::ValType::V128 => bail!("component wizening does not support v128 globals"),189wasmparser::ValType::Ref(_) => unreachable!(),190};191let lift_type_index = self.component.types + self.extra_types.len();192self.extra_types193.function()194.params::<_, wasm_encoder::ComponentValType>([])195.result(Some(wasm_encoder::ComponentValType::Primitive(prim)));196Ok(self.lift_accessor("global", lift_type_index, &[]))197}198199fn add_core_instance_memory(200&mut self,201instance_index: u32,202instance_import_name: &str,203memory_export_name: &str,204memory_ty: wasmparser::MemoryType,205) -> String {206self.accessor_imports.import(207&instance_import_name,208memory_export_name,209RoundtripReencoder.memory_type(memory_ty).unwrap(),210);211let type_index = self.accessor_types.len();212self.accessor_types213.ty()214.function([], [wasm_encoder::ValType::I32]);215self.accessor_functions.function(type_index);216217// Accessing a linear memory is more subtle than a global. We're218// returning a `list<u8>` in WIT but to do so we have to store the219// ptr/length in memory itself. To work around this the memory is grown220// by a single page to ensure we don't tamper with the original image,221// and then in this new page the ptr/len are stored. The base pointer is222// always 0 and the length is the size of memory prior to the growth.223let mut function = wasm_encoder::Function::new([(1, wasm_encoder::ValType::I32)]);224let mut ins = function.instructions();225226// Grow memory by 1 page, and trap if the growth failed.227let pages_to_grow_by = match memory_ty.page_size_log2 {228Some(0) => 8,229Some(16) | None => 1,230_ => unreachable!(),231};232ins.i32_const(pages_to_grow_by);233ins.memory_grow(self.accessor_nmemories);234ins.local_tee(0);235ins.i32_const(-1);236ins.i32_eq();237ins.if_(wasm_encoder::BlockType::Empty);238ins.unreachable();239ins.end();240241// Update our one local as the full byte length of memory.242ins.local_get(0);243ins.i32_const(memory_ty.page_size_log2.unwrap_or(16).cast_signed());244ins.i32_shl();245ins.local_set(0);246247let memarg = |offset| wasm_encoder::MemArg {248align: 2,249offset,250memory_index: self.accessor_nmemories,251};252// Store the ptr/len into the page that was just allocated253ins.local_get(0);254ins.i32_const(0);255ins.i32_store(memarg(0));256ins.local_get(0);257ins.local_get(0);258ins.i32_store(memarg(4));259260// and return the local as it's the pointer to the ptr/len combo261ins.local_get(0);262263ins.end();264self.accessor_code.function(&function);265266let list_ty = self.component.types + self.extra_types.len();267self.extra_types268.defined_type()269.list(wasm_encoder::ComponentValType::Primitive(270wasm_encoder::PrimitiveValType::U8,271));272let lift_type_index = self.component.types + self.extra_types.len();273self.extra_types274.function()275.params::<_, wasm_encoder::ComponentValType>([])276.result(Some(wasm_encoder::ComponentValType::Type(list_ty)));277self.extra_aliases278.alias(wasm_encoder::Alias::CoreInstanceExport {279instance: instance_index,280kind: wasm_encoder::ExportKind::Memory,281name: memory_export_name,282});283self.lift_accessor(284"memory",285lift_type_index,286&[wasm_encoder::CanonicalOption::Memory(287self.component.core_memories + self.accessor_nmemories,288)],289)290}291292fn lift_accessor(293&mut self,294item: &str,295lift_type_index: u32,296opts: &[wasm_encoder::CanonicalOption],297) -> String {298let accessor_core_export_name = self.accessors.len().to_string();299self.accessor_exports.export(300&accessor_core_export_name,301wasm_encoder::ExportKind::Func,302self.accessor_functions.len() - 1,303);304305self.extra_aliases306.alias(wasm_encoder::Alias::CoreInstanceExport {307instance: self.accessor_instance_index(),308kind: wasm_encoder::ExportKind::Func,309name: &accessor_core_export_name,310});311self.extra_core_funcs += 1;312self.extra_canonicals.lift(313self.component.core_funcs + self.extra_core_funcs - 1,314lift_type_index,315opts.iter().copied(),316);317318let accessor_export_name = format!("{item}{}", self.accessors.len());319self.accessor_instance_export_items.push((320accessor_export_name.clone(),321wasm_encoder::ComponentExportKind::Func,322self.component.funcs + self.extra_canonicals.len() - 1,323));324accessor_export_name325}326327fn finish(&mut self, encoder: &mut wasm_encoder::Component) {328// Build the `accessor_module` and add it to the component.329let mut accessor_module = wasm_encoder::Module::new();330accessor_module.section(&self.accessor_types);331accessor_module.section(&self.accessor_imports);332accessor_module.section(&self.accessor_functions);333accessor_module.section(&self.accessor_exports);334accessor_module.section(&self.accessor_code);335encoder.section(&wasm_encoder::ModuleSection(&accessor_module));336337// Instantiate the `accessor_module` with prior instantiations.338let mut extra_instances = wasm_encoder::InstanceSection::new();339extra_instances.instantiate(340self.component.num_core_modules(),341self.instances_to_instantiate_with342.iter()343.map(|(i, name)| (name.as_str(), wasm_encoder::ModuleArg::Instance(*i))),344);345encoder.section(&extra_instances);346347// Add instrumentation to the component which extracts names from the348// accessor instance, lifts things into the component model, and then349// export them.350encoder.section(&self.extra_aliases);351encoder.section(&self.extra_types);352encoder.section(&self.extra_canonicals);353let mut extra_component_instances = wasm_encoder::ComponentInstanceSection::new();354extra_component_instances.export_items(355self.accessor_instance_export_items356.iter()357.map(|(a, b, c)| (a.as_str(), *b, *c)),358);359encoder.section(&extra_component_instances);360361let mut extra_exports = wasm_encoder::ComponentExportSection::new();362extra_exports.export(363WIZER_INSTANCE,364wasm_encoder::ComponentExportKind::Instance,365self.component.instances,366None,367);368encoder.section(&extra_exports);369}370371fn accessor_instance_index(&self) -> u32 {372self.component.core_instances373}374}375376377