/src/wasm-tools/crates/wit-component/src/encoding/fixup.rs
Line | Count | Source |
1 | | use crate::ComponentEncoder; |
2 | | use crate::encoding::{CustomModule, EncodingState, Shim, ShimKind, Shims}; |
3 | | use crate::validation::Export; |
4 | | use anyhow::{Result, bail}; |
5 | | use indexmap::IndexMap; |
6 | | use std::collections::HashMap; |
7 | | use std::mem; |
8 | | use wasm_encoder::*; |
9 | | use wit_parser::WorldItem; |
10 | | use wit_parser::abi::{AbiVariant, WasmSignature}; |
11 | | |
12 | | #[derive(Default)] |
13 | | pub struct FixupModule { |
14 | | // Incrementally-built-up sections of the module-to-be. |
15 | | types: TypeSection, |
16 | | imports: ImportSection, |
17 | | function_names: NameMap, |
18 | | global_names: NameMap, |
19 | | elements: ElementSection, |
20 | | data: DataSection, |
21 | | |
22 | | /// Interning map for type signatures. |
23 | | type_map: HashMap<(Vec<ValType>, Vec<ValType>), u32>, |
24 | | |
25 | | /// The start function, if needed. This is split between "ABI details" and |
26 | | /// "user funcs" where ABI details must happen first and user funcs need to |
27 | | /// be wrapped in task intrinsics if provided. |
28 | | start_abi_details: Vec<StartAction>, |
29 | | start_user_funcs: Vec<StartAction>, |
30 | | |
31 | | /// Entries used to initialize the imported shim's table, if necessary. |
32 | | shims_in_table: Vec<ShimFill>, |
33 | | |
34 | | /// Entries to go into a declared element segment. |
35 | | declared_funcs: Vec<ShimFill>, |
36 | | |
37 | | /// The instance names that are imported into this module, and what those |
38 | | /// instances correspond to. |
39 | | imported_instances: IndexMap<String, ImportedInstance>, |
40 | | |
41 | | /// Interning map for imports. |
42 | | imported_items: HashMap<(String, String), (u32, EntityType)>, |
43 | | |
44 | | /// Functions that will be defined in this module itself. |
45 | | /// |
46 | | /// This is deferred to get emitted until the very end so all imports have |
47 | | /// been settled and then these import indices can be known. |
48 | | defined_functions: Vec<(u32, DefinedFunction)>, |
49 | | |
50 | | // Current counters for index spaces. |
51 | | funcs: u32, |
52 | | globals: u32, |
53 | | tables: u32, |
54 | | memories: u32, |
55 | | } |
56 | | |
57 | | /// Different instances that can be imported into the fixup module. |
58 | | #[derive(Clone, PartialEq, Eq, Hash, Debug)] |
59 | | pub enum ImportedInstance { |
60 | | /// The original "shim" module with stubs that need to be filled in. |
61 | | Shim, |
62 | | /// The "main" module. |
63 | | Main, |
64 | | /// A synthetic instance created by collecting actual implementations of |
65 | | /// shims from various locations. |
66 | | Actual, |
67 | | /// A named adapter module. |
68 | | Adapter(String), |
69 | | } |
70 | | |
71 | | pub enum StartAction { |
72 | | /// Sets `$global` to `ref.func $shim` |
73 | | InitGlobal { |
74 | | /// The index in `declared_funcs` of the function to fill in. |
75 | | shim: usize, |
76 | | /// The global being set. |
77 | | global: u32, |
78 | | }, |
79 | | /// Calls the specified function index. |
80 | | Call(u32), |
81 | | /// Used during the `link`-part of `wit-component` this adds the |
82 | | /// `memory_base` and `export` globals together and stores the result in |
83 | | /// `dest`. |
84 | | InitializeAddress { |
85 | | memory_base: u32, |
86 | | export: u32, |
87 | | dest: AddressDest, |
88 | | }, |
89 | | /// Invoke the `func` hook with the `hook` argument. |
90 | | CallHook { func: u32, hook: TaskHook }, |
91 | | } |
92 | | |
93 | | pub enum AddressDest { |
94 | | /// A linear memory address that's statically known. |
95 | | LinearMemory { address: u32, memory: u32 }, |
96 | | /// A global indexed by the argument here. |
97 | | Global(u32), |
98 | | } |
99 | | |
100 | | /// What can be used to fill in a shim that was originally defined in the shim |
101 | | /// module. |
102 | | /// |
103 | | /// Functions specified here either go into an element segment to fill in the |
104 | | /// original shim module's table or they're inserted into globals. |
105 | | enum ShimFill { |
106 | | /// An imported function. |
107 | | Import(u32), |
108 | | /// An imported function which is also known to be a resource destructor. |
109 | | /// Used to know when to insert task hooks. |
110 | | ImportedResourceDtor(u32), |
111 | | /// A `realloc` function which is exported from `instance` under the name |
112 | | /// `export` and imported here as `func`. |
113 | | Realloc { |
114 | | instance: ImportedInstance, |
115 | | export: String, |
116 | | func: u32, |
117 | | }, |
118 | | /// A function defined in this fixup module. |
119 | | DefinedFunction(usize), |
120 | | } |
121 | | |
122 | | /// Different functions that can be defined in the fixup module, apart from the |
123 | | /// `start` function. |
124 | | enum DefinedFunction { |
125 | | /// A function which wraps a core export to ensure that task state is set up |
126 | | /// before the core export is called. |
127 | | HookedCoreExport { |
128 | | /// The task hook function. |
129 | | task_hook: u32, |
130 | | /// The core function to call and delegate to. |
131 | | to_wrap: u32, |
132 | | /// The number of parameters to forward to `export`. |
133 | | params: usize, |
134 | | /// The debug-related core name of this function it's hooking. |
135 | | core_name: String, |
136 | | /// If `Some` this is the instance that is is exported for which will |
137 | | /// get inserted into `EncodingState`'s export wrapper map. |
138 | | export: Option<ImportedInstance>, |
139 | | /// The kind of hook to use at the start of this function. |
140 | | start: Option<TaskHook>, |
141 | | /// The kind of hook to use at the end of this function. |
142 | | end: EndTaskHook, |
143 | | }, |
144 | | } |
145 | | |
146 | | #[derive(Copy, Clone)] |
147 | | pub enum TaskHook { |
148 | | /// A synchronous task has started. |
149 | | SyncStart = 0, |
150 | | /// A synchronous task has finished. |
151 | | SyncFinish = 1, |
152 | | /// An async task has started. |
153 | | AsyncStart = 2, |
154 | | /// An async has resumed in its `callback` option. |
155 | | AsyncResume = 3, |
156 | | /// An async blocked, but not yet completed, and it's returning from either |
157 | | /// the main entrypoint or the `callback` option. |
158 | | AsyncBlock = 4, |
159 | | /// An async task has finished, returning from either the main entrypoint or |
160 | | /// the `callback` option. |
161 | | AsyncFinish = 5, |
162 | | /// The `_initialize` function and other ctors are being called. |
163 | | InitializeStart = 6, |
164 | | /// The `_initialize` function and other ctors are finished. |
165 | | InitializeFinish = 7, |
166 | | /// A resource destructor is starting. |
167 | | ResourceDtorStart = 8, |
168 | | /// A resource destructor is finished. |
169 | | ResourceDtorFinish = 9, |
170 | | // |
171 | | // 10/11 intentionally missing |
172 | | // |
173 | | /// A `realloc` function used as a canonical ABI option is being invoked. |
174 | | ReallocStart = 12, |
175 | | /// A `realloc` function used as a canonical ABI option has finished. |
176 | | ReallocFinish = 13, |
177 | | } |
178 | | |
179 | | enum EndTaskHook { |
180 | | None, |
181 | | AsyncCode, |
182 | | Normal(TaskHook), |
183 | | } |
184 | | |
185 | | impl FixupModule { |
186 | | /// Returns the 0-parameter 0-result function type. |
187 | 3.55k | pub fn type_thunk(&mut self) -> u32 { |
188 | 3.55k | self.type_intern(Vec::new(), Vec::new()) |
189 | 3.55k | } |
190 | | |
191 | | /// Interns `params`/`results` as a function type. |
192 | 5.83k | pub fn type_intern(&mut self, params: Vec<ValType>, results: Vec<ValType>) -> u32 { |
193 | 5.83k | *self |
194 | 5.83k | .type_map |
195 | 5.83k | .entry((params.clone(), results.clone())) |
196 | 5.83k | .or_insert_with(|| { |
197 | 4.25k | let index = self.types.len(); |
198 | 4.25k | self.types |
199 | 4.25k | .ty() |
200 | 4.25k | .function(params.iter().copied(), results.iter().copied()); |
201 | 4.25k | index |
202 | 4.25k | }) |
203 | 5.83k | } |
204 | | |
205 | 2.28k | fn type_index(&mut self, sig: &WasmSignature) -> u32 { |
206 | 2.28k | self.type_intern( |
207 | 2.28k | sig.params.iter().map(super::to_val_type).collect(), |
208 | 2.28k | sig.results.iter().map(super::to_val_type).collect(), |
209 | | ) |
210 | 2.28k | } |
211 | | |
212 | 7.10k | fn import_instance(&mut self, kind: &ImportedInstance) -> Result<String> { |
213 | 7.10k | let name = match kind { |
214 | 1.57k | ImportedInstance::Shim => "shim", |
215 | 3.24k | ImportedInstance::Main => "main", |
216 | 2.28k | ImportedInstance::Actual => "actual", |
217 | 0 | ImportedInstance::Adapter(name) => name.as_str(), |
218 | | }; |
219 | 7.10k | let name = name.to_string(); |
220 | 7.10k | if let Some(prev) = self.imported_instances.insert(name.clone(), kind.clone()) { |
221 | 2.20k | if prev != *kind { |
222 | 0 | bail!("instance `{name}` was already imported as a different kind: {prev:?}"); |
223 | 2.20k | } |
224 | 4.90k | } |
225 | 7.10k | Ok(name) |
226 | 7.10k | } |
227 | | |
228 | | /// Imports a new item into this fixup module to get used by later |
229 | | /// functions/etc. |
230 | | /// |
231 | | /// Returns the index that the item is imported as. |
232 | 7.10k | pub fn import( |
233 | 7.10k | &mut self, |
234 | 7.10k | instance: &ImportedInstance, |
235 | 7.10k | name: &str, |
236 | 7.10k | ty: EntityType, |
237 | 7.10k | ) -> Result<u32> { |
238 | 7.10k | let module = self.import_instance(instance)?; |
239 | 7.10k | let key = (module, name.to_string()); |
240 | 7.10k | if let Some((index, prev)) = self.imported_items.get(&key) { |
241 | 0 | if *prev != ty { |
242 | 0 | bail!( |
243 | | "`{}::{name}` was already imported with a different type", |
244 | | key.0 |
245 | | ); |
246 | 0 | } |
247 | 0 | return Ok(*index); |
248 | 7.10k | } |
249 | | // Note that debug names are recorded here, rather than in the helpers |
250 | | // below, so that a name is only recorded for a freshly created import. |
251 | 7.10k | let ret = match ty { |
252 | | EntityType::Function(_) => { |
253 | 5.52k | let index = inc(&mut self.funcs); |
254 | 5.52k | self.function_names.append(index, name); |
255 | 5.52k | index |
256 | | } |
257 | | EntityType::Global(_) => { |
258 | 1.06k | let index = inc(&mut self.globals); |
259 | 1.06k | self.global_names.append(index, name); |
260 | 1.06k | index |
261 | | } |
262 | 518 | EntityType::Table(_) => inc(&mut self.tables), |
263 | 0 | EntityType::Memory(_) => inc(&mut self.memories), |
264 | 0 | EntityType::Tag(_) | EntityType::FunctionExact(_) => unimplemented!(), |
265 | | }; |
266 | 7.10k | self.imports.import(&key.0, name, ty); |
267 | 7.10k | self.imported_items.insert(key, (ret, ty)); |
268 | 7.10k | Ok(ret) |
269 | 7.10k | } |
270 | | |
271 | | /// Helper over `self.import(...)` |
272 | 1.06k | pub fn import_global( |
273 | 1.06k | &mut self, |
274 | 1.06k | instance: &ImportedInstance, |
275 | 1.06k | name: &str, |
276 | 1.06k | ty: GlobalType, |
277 | 1.06k | ) -> Result<u32> { |
278 | 1.06k | self.import(instance, name, ty.into()) |
279 | 1.06k | } |
280 | | |
281 | | /// Helper over `self.import(...)` |
282 | 5.52k | pub fn import_func(&mut self, instance: &ImportedInstance, name: &str, ty: u32) -> Result<u32> { |
283 | 5.52k | self.import(instance, name, EntityType::Function(ty)) |
284 | 5.52k | } |
285 | | |
286 | | /// Appends to the `start` function that'll get generated. |
287 | 0 | pub fn add_start_abi_detail(&mut self, action: StartAction) { |
288 | 0 | self.start_abi_details.push(action); |
289 | 0 | } |
290 | | |
291 | | /// Appends to the `start` function that'll get generated. |
292 | 0 | pub fn add_start_user_func(&mut self, action: StartAction) { |
293 | 0 | self.start_user_funcs.push(action); |
294 | 0 | } |
295 | | |
296 | | /// Returns the underlying element section. |
297 | 0 | pub fn elements(&mut self) -> &mut ElementSection { |
298 | 0 | &mut self.elements |
299 | 0 | } |
300 | | |
301 | | /// Returns the underlying data section. |
302 | 0 | pub fn data(&mut self) -> &mut DataSection { |
303 | 0 | &mut self.data |
304 | 0 | } |
305 | | |
306 | | /// Adds `shim` to this fixup module to get filled in. |
307 | 2.28k | pub(super) fn add_shim(&mut self, opts: &ComponentEncoder, shim: &Shim) -> Result<()> { |
308 | 2.28k | let type_index = self.type_index(&shim.sig); |
309 | 2.28k | let func = self.import_func(&ImportedInstance::Actual, &shim.name, type_index)?; |
310 | | |
311 | 2.28k | let shim_fill = match &shim.kind { |
312 | 0 | ShimKind::Realloc { module, export } => ShimFill::Realloc { |
313 | 0 | instance: module.to_imported_instance(), |
314 | 0 | export: export.to_string(), |
315 | 0 | func, |
316 | 0 | }, |
317 | 234 | ShimKind::ResourceDtor { .. } => ShimFill::ImportedResourceDtor(func), |
318 | 2.05k | _ => ShimFill::Import(func), |
319 | | }; |
320 | | |
321 | 2.28k | if opts.shim_return_call_ref { |
322 | 1.06k | let global = self.import_global( |
323 | 1.06k | &ImportedInstance::Shim, |
324 | 1.06k | &format!("g{}", &shim.name), |
325 | 1.06k | GlobalType { |
326 | 1.06k | val_type: RefType { |
327 | 1.06k | heap_type: HeapType::Concrete(type_index), |
328 | 1.06k | nullable: false, |
329 | 1.06k | } |
330 | 1.06k | .into(), |
331 | 1.06k | mutable: true, |
332 | 1.06k | shared: false, |
333 | 1.06k | }, |
334 | 0 | )?; |
335 | | |
336 | 1.06k | self.start_abi_details.push(StartAction::InitGlobal { |
337 | 1.06k | shim: self.declared_funcs.len(), |
338 | 1.06k | global, |
339 | 1.06k | }); |
340 | 1.06k | self.declared_funcs.push(shim_fill); |
341 | 1.22k | } else { |
342 | 1.22k | self.shims_in_table.push(shim_fill); |
343 | 1.22k | } |
344 | 2.28k | Ok(()) |
345 | 2.28k | } |
346 | | |
347 | | /// Ensures there's a `start` function which invokes `initialize`. |
348 | 3.24k | pub fn add_initialize(&mut self, initialize: &str) -> Result<()> { |
349 | 3.24k | let ty = self.type_thunk(); |
350 | 3.24k | let func = self.import_func(&ImportedInstance::Main, initialize, ty)?; |
351 | 3.24k | self.start_user_funcs.push(StartAction::Call(func)); |
352 | 3.24k | Ok(()) |
353 | 3.24k | } |
354 | | |
355 | 3.24k | pub(super) fn instantiate( |
356 | 3.24k | &mut self, |
357 | 3.24k | shims: &Shims<'_>, |
358 | 3.24k | state: &mut EncodingState<'_>, |
359 | 3.24k | ) -> Result<()> { |
360 | | // First inject task hooks as necessary throughout the module which is |
361 | | // the final step before actually encoding the module. |
362 | 3.24k | self.prepare_task_hooks(state)?; |
363 | | |
364 | | // Generate the module, and this'll return `None` if the module isn't |
365 | | // necessary in which case we bail out. |
366 | 3.24k | let Some(module) = self.encode(state)? else { |
367 | 0 | return Ok(()); |
368 | | }; |
369 | | |
370 | | // Embed this fixup module in the component. |
371 | 3.24k | let module_index = state |
372 | 3.24k | .component |
373 | 3.24k | .core_module(Some("wit-component-fixup"), &module); |
374 | | |
375 | | // Prepare the arguments used to instantiate this component based on the |
376 | | // `imported_instances` map that's been generated. |
377 | 3.24k | let mut instance_args = Vec::new(); |
378 | 4.90k | for (name, instance) in self.imported_instances.iter() { |
379 | 4.90k | let index = match instance { |
380 | 3.24k | ImportedInstance::Main => state |
381 | 3.24k | .instance_index |
382 | 3.24k | .expect("must have an instantiated module"), |
383 | 0 | ImportedInstance::Adapter(name) => state.adapter_instances[name.as_str()], |
384 | 830 | ImportedInstance::Shim => state |
385 | 830 | .shim_instance_index |
386 | 830 | .expect("must have an instantiated shim"), |
387 | | ImportedInstance::Actual => { |
388 | 830 | let mut actual = Vec::new(); |
389 | 2.28k | for shim in shims.shims.values() { |
390 | 2.28k | let core_func_index = state.encode_shim(shims, shim)?; |
391 | 2.28k | actual.push((shim.name.as_str(), ExportKind::Func, core_func_index)); |
392 | | } |
393 | 830 | state |
394 | 830 | .component |
395 | 830 | .core_instantiate_exports(Some("actual"), actual) |
396 | | } |
397 | | }; |
398 | 4.90k | instance_args.push((name.as_str(), ModuleArg::Instance(index))); |
399 | | } |
400 | | |
401 | | // The side-effectful instantiation of the fixup instance. |
402 | 3.24k | let instance = state |
403 | 3.24k | .component |
404 | 3.24k | .core_instantiate(Some("fixup"), module_index, instance_args); |
405 | | |
406 | | // If there are hooked core exports in this module then register within |
407 | | // `state` that they should be preferred over their raw brethren. |
408 | 3.24k | for (i, (_ty, func)) in self.defined_functions.iter().enumerate() { |
409 | 0 | match func { |
410 | | DefinedFunction::HookedCoreExport { |
411 | 0 | core_name, |
412 | 0 | export: Some(from), |
413 | | .. |
414 | 0 | } => { |
415 | 0 | let name = format!("hook{i}"); |
416 | 0 | let wrapper = |
417 | 0 | state.core_alias_export(Some(&name), instance, &name, ExportKind::Func); |
418 | 0 | state |
419 | 0 | .export_task_initialization_wrappers |
420 | 0 | .insert((from.clone(), core_name.clone()), wrapper); |
421 | 0 | } |
422 | 0 | DefinedFunction::HookedCoreExport { export: None, .. } => {} |
423 | | } |
424 | | } |
425 | 3.24k | Ok(()) |
426 | 3.24k | } |
427 | | |
428 | 3.24k | fn encode(&mut self, state: &EncodingState<'_>) -> Result<Option<Module>> { |
429 | 3.24k | let opts = &state.info.encoder; |
430 | 3.24k | if self.start_abi_details.is_empty() |
431 | 2.92k | && self.start_user_funcs.is_empty() |
432 | 0 | && self.shims_in_table.is_empty() |
433 | 0 | && self.declared_funcs.is_empty() |
434 | 0 | && self.elements.is_empty() |
435 | 0 | && self.data.is_empty() |
436 | 0 | && self.defined_functions.is_empty() |
437 | | { |
438 | 0 | return Ok(None); |
439 | 3.24k | } |
440 | | |
441 | 3.24k | let mut functions = FunctionSection::new(); |
442 | 3.24k | let mut code = CodeSection::new(); |
443 | 3.24k | let mut exports = ExportSection::new(); |
444 | | |
445 | | // First define functions within this module now that the import space |
446 | | // for functions has settled. |
447 | 3.24k | let mut defined_func_indices = Vec::new(); |
448 | 3.24k | for (i, (ty, func)) in self.defined_functions.iter().enumerate() { |
449 | 0 | functions.function(*ty); |
450 | 0 | let index = inc(&mut self.funcs); |
451 | 0 | defined_func_indices.push(index); |
452 | 0 | let mut func = match func { |
453 | | DefinedFunction::HookedCoreExport { |
454 | 0 | task_hook, |
455 | 0 | to_wrap, |
456 | 0 | params, |
457 | 0 | core_name, |
458 | 0 | export, |
459 | 0 | start, |
460 | 0 | end, |
461 | | } => { |
462 | 0 | let mut locals = Vec::new(); |
463 | 0 | if let EndTaskHook::AsyncCode = end { |
464 | 0 | locals.push((1, ValType::I32)); |
465 | 0 | } |
466 | 0 | let mut f = Function::new(locals); |
467 | 0 | if let Some(start) = *start { |
468 | 0 | f.instructions().i32_const(start as i32).call(*task_hook); |
469 | 0 | } |
470 | 0 | for i in 0..*params { |
471 | 0 | f.instructions().local_get(i as u32); |
472 | 0 | } |
473 | 0 | f.instructions().call(*to_wrap); |
474 | 0 | match end { |
475 | 0 | EndTaskHook::AsyncCode => { |
476 | 0 | f.instructions() |
477 | 0 | .local_set(*params as u32) |
478 | 0 | // 0 == EXIT, 1/2 == yield/wait, so delegate to |
479 | 0 | // appropriate code. |
480 | 0 | // |
481 | 0 | // First thing pushed on the stack is "if true", |
482 | 0 | // which in this case nonzero means blocking. |
483 | 0 | // Second thing is "if false" meaning if 0 |
484 | 0 | // meaning EXIT meaning "done". Final thing is |
485 | 0 | // what to test, the return code. |
486 | 0 | .i32_const(TaskHook::AsyncBlock as i32) |
487 | 0 | .i32_const(TaskHook::AsyncFinish as i32) |
488 | 0 | .local_get(*params as u32) |
489 | 0 | .select() |
490 | 0 | .call(*task_hook) |
491 | 0 | // put the return code back on the stack to |
492 | 0 | // actually return |
493 | 0 | .local_get(*params as u32); |
494 | 0 | } |
495 | 0 | EndTaskHook::Normal(hook) => { |
496 | 0 | f.instructions().i32_const(*hook as i32).call(*task_hook); |
497 | 0 | } |
498 | 0 | EndTaskHook::None => {} |
499 | | } |
500 | 0 | if export.is_some() { |
501 | 0 | exports.export(&format!("hook{i}"), ExportKind::Func, index); |
502 | 0 | } |
503 | 0 | self.function_names |
504 | 0 | .append(index, &format!("hook-{core_name}")); |
505 | 0 | f |
506 | | } |
507 | | }; |
508 | 0 | func.instructions().end(); |
509 | 0 | code.function(&func); |
510 | | } |
511 | | |
512 | | // Using our defined functions above it's possible to implement |
513 | | // resolution of `ShimFill` items. |
514 | 3.34k | let resolve_shim = |shim: &ShimFill| match shim { |
515 | 3.05k | ShimFill::Import(func) => *func, |
516 | 287 | ShimFill::ImportedResourceDtor(func) => *func, |
517 | 0 | ShimFill::DefinedFunction(i) => defined_func_indices[*i], |
518 | 0 | ShimFill::Realloc { func, .. } => *func, |
519 | 3.34k | }; |
520 | | |
521 | | // If this fixup is filling in a table, then import the table and use an |
522 | | // element segment for its initialization. |
523 | 3.24k | if !self.shims_in_table.is_empty() { |
524 | 518 | let table_type = TableType { |
525 | 518 | element_type: RefType::FUNCREF, |
526 | 518 | minimum: self.shims_in_table.len() as u64, |
527 | 518 | maximum: Some(self.shims_in_table.len() as u64), |
528 | 518 | table64: false, |
529 | 518 | shared: false, |
530 | 518 | }; |
531 | 518 | let table = self.import( |
532 | 518 | &ImportedInstance::Shim, |
533 | 518 | super::INDIRECT_TABLE_NAME, |
534 | 518 | table_type.into(), |
535 | 0 | )?; |
536 | 518 | self.elements.active( |
537 | 518 | if table == 0 { None } else { Some(table) }, |
538 | 518 | &ConstExpr::i32_const(0), |
539 | 518 | Elements::Functions( |
540 | 518 | self.shims_in_table |
541 | 518 | .iter() |
542 | 518 | .map(resolve_shim) |
543 | 518 | .collect::<Vec<_>>() |
544 | 518 | .into(), |
545 | 518 | ), |
546 | | ); |
547 | 2.72k | } |
548 | | |
549 | | // Codegen the start function here. Special case an empty start function |
550 | | // or just a single function call as the start function, otherwise each |
551 | | // item needs to be handled individually. |
552 | 3.24k | let start = match (&self.start_abi_details[..], &self.start_user_funcs[..]) { |
553 | 3.24k | ([], []) => None, |
554 | 2.92k | ([], [StartAction::Call(func)]) => Some(*func), |
555 | 312 | (first, second) => { |
556 | 312 | let mut start = Function::new(Vec::new()); |
557 | 1.37k | for action in first.iter().chain(second.iter()) { |
558 | 1.37k | match action { |
559 | 1.06k | StartAction::InitGlobal { shim, global } => { |
560 | 1.06k | let func = resolve_shim(&self.declared_funcs[*shim]); |
561 | 1.06k | start.instructions().ref_func(func).global_set(*global); |
562 | 1.06k | } |
563 | 312 | StartAction::Call(func) => { |
564 | 312 | start.instructions().call(*func); |
565 | 312 | } |
566 | 0 | StartAction::CallHook { func, hook } => { |
567 | 0 | start.instructions().i32_const(*hook as i32).call(*func); |
568 | 0 | } |
569 | | StartAction::InitializeAddress { |
570 | 0 | memory_base, |
571 | 0 | export, |
572 | 0 | dest, |
573 | 0 | } => match dest { |
574 | 0 | AddressDest::LinearMemory { address, memory } => { |
575 | 0 | start |
576 | 0 | .instructions() |
577 | 0 | .i32_const(*address as i32) |
578 | 0 | .global_get(*memory_base) |
579 | 0 | .global_get(*export) |
580 | 0 | .i32_add() |
581 | 0 | .i32_store(MemArg { |
582 | 0 | offset: 0, |
583 | 0 | align: 2, |
584 | 0 | memory_index: *memory, |
585 | 0 | }); |
586 | 0 | } |
587 | 0 | AddressDest::Global(global) => { |
588 | 0 | start |
589 | 0 | .instructions() |
590 | 0 | .global_get(*memory_base) |
591 | 0 | .global_get(*export) |
592 | 0 | .i32_add() |
593 | 0 | .global_set(*global); |
594 | 0 | } |
595 | | }, |
596 | | } |
597 | | } |
598 | 312 | start.instructions().end(); |
599 | 312 | let index = inc(&mut self.funcs); |
600 | 312 | let ty = self.type_thunk(); |
601 | 312 | functions.function(ty); |
602 | 312 | code.function(&start); |
603 | 312 | self.function_names.append(index, "start"); |
604 | 312 | Some(index) |
605 | | } |
606 | | }; |
607 | | |
608 | | // Fill in the declared element segment, if necessary, used for |
609 | | // `ref.func` in the start function above. |
610 | 3.24k | if !self.declared_funcs.is_empty() { |
611 | 312 | self.elements.declared(Elements::Functions( |
612 | 312 | self.declared_funcs |
613 | 312 | .iter() |
614 | 312 | .map(resolve_shim) |
615 | 312 | .collect::<Vec<_>>() |
616 | 312 | .into(), |
617 | 312 | )); |
618 | 2.92k | } |
619 | | |
620 | | // Now that we've got all the pieces weave everything into a `Module`. |
621 | 3.24k | let mut module = Module::default(); |
622 | 3.24k | if !self.types.is_empty() { |
623 | 3.24k | module.section(&self.types); |
624 | 3.24k | } |
625 | 3.24k | if !self.imports.is_empty() { |
626 | 3.24k | module.section(&self.imports); |
627 | 3.24k | } |
628 | 3.24k | if !functions.is_empty() { |
629 | 312 | module.section(&functions); |
630 | 2.92k | } |
631 | 3.24k | if !exports.is_empty() { |
632 | 0 | module.section(&exports); |
633 | 3.24k | } |
634 | 3.24k | if let Some(start) = start { |
635 | 3.24k | module.section(&StartSection { |
636 | 3.24k | function_index: start, |
637 | 3.24k | }); |
638 | 3.24k | } |
639 | 3.24k | if !self.elements.is_empty() { |
640 | 830 | module.section(&self.elements); |
641 | 2.41k | } |
642 | 3.24k | if !code.is_empty() { |
643 | 312 | module.section(&code); |
644 | 2.92k | } |
645 | 3.24k | if !self.data.is_empty() { |
646 | 0 | module.section(&self.data); |
647 | 3.24k | } |
648 | 3.24k | module.section(&RawCustomSection( |
649 | 3.24k | &crate::base_producers().raw_custom_section(), |
650 | 3.24k | )); |
651 | | |
652 | 3.24k | if opts.debug_names { |
653 | 477 | let mut names = NameSection::new(); |
654 | 477 | names.module("wit-component:fixups"); |
655 | 477 | if !self.function_names.is_empty() { |
656 | 477 | names.functions(&self.function_names); |
657 | 477 | } |
658 | 477 | if !self.global_names.is_empty() { |
659 | 249 | names.globals(&self.global_names); |
660 | 249 | } |
661 | 477 | module.section(&names); |
662 | 2.76k | } |
663 | | |
664 | 3.24k | Ok(Some(module)) |
665 | 3.24k | } |
666 | | |
667 | | /// Prepares task hooks, like `__wasm_init_task`, to be configured in |
668 | | /// various locations throughout this fixup module. |
669 | | /// |
670 | | /// This handles: |
671 | | /// |
672 | | /// * The `_initialize` function (needs task setup first) |
673 | | /// * Hooks for all exports which are lifted. |
674 | 3.24k | fn prepare_task_hooks(&mut self, state: &mut EncodingState<'_>) -> Result<()> { |
675 | 3.24k | let info_main = state.info.exports_for(CustomModule::Main); |
676 | 3.24k | let Some(task_hook) = info_main.wasm_task_hook() else { |
677 | 3.24k | return Ok(()); |
678 | | }; |
679 | 0 | let ty = self.type_intern(vec![ValType::I32], Vec::new()); |
680 | 0 | let task_hook = self.import_func(&ImportedInstance::Main, task_hook, ty)?; |
681 | | |
682 | | // Handle the start function first where if there's something registered |
683 | | // in the "user function" area we need to configure that to work. |
684 | 0 | if !self.start_user_funcs.is_empty() { |
685 | 0 | self.start_user_funcs.insert( |
686 | 0 | 0, |
687 | 0 | StartAction::CallHook { |
688 | 0 | func: task_hook, |
689 | 0 | hook: TaskHook::InitializeStart, |
690 | 0 | }, |
691 | 0 | ); |
692 | 0 | self.start_user_funcs.push(StartAction::CallHook { |
693 | 0 | func: task_hook, |
694 | 0 | hook: TaskHook::InitializeFinish, |
695 | 0 | }); |
696 | 0 | } |
697 | | |
698 | | // Afterwards handle all exports for the main/adapter modules. |
699 | 0 | self.prepare_export_task_hooks_for(state, CustomModule::Main, task_hook)?; |
700 | 0 | for adapter in state.info.adapters.keys() { |
701 | 0 | self.prepare_export_task_hooks_for(state, CustomModule::Adapter(adapter), task_hook)?; |
702 | | } |
703 | | |
704 | | // Next up generate wrappers for all `realloc` functions which might be |
705 | | // used as a canonical option. |
706 | 0 | let mut reallocs = HashMap::new(); |
707 | 0 | self.prepare_realloc_task_hooks_for(state, CustomModule::Main, task_hook, &mut reallocs)?; |
708 | 0 | for adapter in state.info.adapters.keys() { |
709 | 0 | self.prepare_realloc_task_hooks_for( |
710 | 0 | state, |
711 | 0 | CustomModule::Adapter(adapter), |
712 | 0 | task_hook, |
713 | 0 | &mut reallocs, |
714 | 0 | )?; |
715 | | } |
716 | | |
717 | | // And finally redirect/fixup shims that need hooks to defined versions |
718 | | // locally in this module. |
719 | 0 | let mut shims_in_table = mem::take(&mut self.shims_in_table); |
720 | 0 | let mut declared_funcs = mem::take(&mut self.declared_funcs); |
721 | 0 | for shim in shims_in_table.iter_mut().chain(declared_funcs.iter_mut()) { |
722 | 0 | match shim { |
723 | | // Resource destructors are handled here directly inline. |
724 | 0 | ShimFill::ImportedResourceDtor(func) => { |
725 | 0 | let func = *func; |
726 | 0 | let ty = self.type_intern(vec![ValType::I32], Vec::new()); |
727 | 0 | *shim = ShimFill::DefinedFunction(self.defined_functions.len()); |
728 | 0 | self.defined_functions.push(( |
729 | 0 | ty, |
730 | 0 | DefinedFunction::HookedCoreExport { |
731 | 0 | task_hook, |
732 | 0 | to_wrap: func, |
733 | 0 | params: 1, |
734 | 0 | start: Some(TaskHook::ResourceDtorStart), |
735 | 0 | end: EndTaskHook::Normal(TaskHook::ResourceDtorFinish), |
736 | 0 | export: None, |
737 | 0 | core_name: format!("resource-dtor"), |
738 | 0 | }, |
739 | 0 | )); |
740 | 0 | } |
741 | | |
742 | | // All realloc functions were hooked above in |
743 | | // `prepare_realloc_task_hooks_for` so this just looks up in the |
744 | | // map built there to figure out which function to redirect to. |
745 | | ShimFill::Realloc { |
746 | 0 | instance, export, .. |
747 | 0 | } => { |
748 | 0 | let func = reallocs |
749 | 0 | .get(&(instance.clone(), export.clone())) |
750 | 0 | .copied() |
751 | 0 | .expect("realloc wrapper should have been generated"); |
752 | 0 | *shim = ShimFill::DefinedFunction(func); |
753 | 0 | } |
754 | | |
755 | 0 | ShimFill::Import(_) | ShimFill::DefinedFunction(_) => {} |
756 | | }; |
757 | | } |
758 | | |
759 | 0 | self.shims_in_table = shims_in_table; |
760 | 0 | self.declared_funcs = declared_funcs; |
761 | | |
762 | 0 | Ok(()) |
763 | 3.24k | } |
764 | | |
765 | | /// Generates a task hook wrapper for each `realloc` function exported by |
766 | | /// `for_module` which may be used as a canonical option. |
767 | | /// |
768 | | /// These wrappers are themselves imported from the fixup module for use in |
769 | | /// lifts-of-exports, and then additionally these are used to fill in |
770 | | /// `ShimFill::Realloc` later on. |
771 | 0 | fn prepare_realloc_task_hooks_for( |
772 | 0 | &mut self, |
773 | 0 | state: &mut EncodingState<'_>, |
774 | 0 | for_module: CustomModule<'_>, |
775 | 0 | task_hook: u32, |
776 | 0 | reallocs: &mut HashMap<(ImportedInstance, String), usize>, |
777 | 0 | ) -> Result<()> { |
778 | 0 | let instance = for_module.to_imported_instance(); |
779 | 0 | for name in state.info.exports_for(for_module).reallocs() { |
780 | 0 | let ty = self.type_intern(vec![ValType::I32; 4], vec![ValType::I32]); |
781 | 0 | let func = self.import_func(&instance, name, ty)?; |
782 | 0 | let index = self.defined_functions.len(); |
783 | 0 | self.defined_functions.push(( |
784 | 0 | ty, |
785 | 0 | DefinedFunction::HookedCoreExport { |
786 | 0 | task_hook, |
787 | 0 | to_wrap: func, |
788 | 0 | params: 4, |
789 | 0 | start: Some(TaskHook::ReallocStart), |
790 | 0 | end: EndTaskHook::Normal(TaskHook::ReallocFinish), |
791 | 0 | export: Some(instance.clone()), |
792 | 0 | core_name: name.to_string(), |
793 | 0 | }, |
794 | 0 | )); |
795 | 0 | reallocs.insert((instance.clone(), name.to_string()), index); |
796 | | } |
797 | 0 | Ok(()) |
798 | 0 | } |
799 | | |
800 | | /// Looks over the exports of `for_module` and adds any functions as |
801 | | /// necessary to `self.defined_functions` which hook the original export to |
802 | | /// ensure it's got task state set up when called first. |
803 | 0 | fn prepare_export_task_hooks_for( |
804 | 0 | &mut self, |
805 | 0 | state: &mut EncodingState<'_>, |
806 | 0 | for_module: CustomModule<'_>, |
807 | 0 | task_hook: u32, |
808 | 0 | ) -> Result<()> { |
809 | 0 | let resolve = &state.info.encoder.metadata.resolve; |
810 | 0 | let world = &resolve.worlds[state.info.encoder.metadata.world]; |
811 | 0 | let info = state.info.exports_for(for_module); |
812 | 0 | let imported_instance = for_module.to_imported_instance(); |
813 | | |
814 | 0 | for (core_name, export) in info.iter() { |
815 | 0 | let (key, f, abi) = match export { |
816 | 0 | Export::WorldFunc(key, _, abi) => match &world.exports[key] { |
817 | 0 | WorldItem::Function(f) => (key, f, abi), |
818 | 0 | _ => continue, |
819 | | }, |
820 | 0 | Export::InterfaceFunc(key, id, func_name, abi) => { |
821 | 0 | let func = &resolve.interfaces[*id].functions[func_name.as_str()]; |
822 | 0 | (key, func, abi) |
823 | | } |
824 | 0 | _ => continue, |
825 | | }; |
826 | | |
827 | 0 | let sig = resolve.wasm_signature(*abi, f); |
828 | 0 | let ty = self.type_index(&sig); |
829 | 0 | let func = self.import_func(&imported_instance, core_name, ty)?; |
830 | 0 | let post_return = info.post_return(key, f); |
831 | 0 | self.defined_functions.push(( |
832 | 0 | ty, |
833 | | DefinedFunction::HookedCoreExport { |
834 | 0 | task_hook, |
835 | 0 | to_wrap: func, |
836 | 0 | export: Some(imported_instance.clone()), |
837 | 0 | params: sig.params.len(), |
838 | 0 | core_name: core_name.to_string(), |
839 | 0 | start: Some(if abi.is_async() { |
840 | 0 | TaskHook::AsyncStart |
841 | | } else { |
842 | 0 | TaskHook::SyncStart |
843 | | }), |
844 | 0 | end: if abi.is_async() { |
845 | 0 | assert!(post_return.is_none()); |
846 | 0 | if *abi == AbiVariant::GuestExportAsyncStackful { |
847 | 0 | EndTaskHook::Normal(TaskHook::AsyncFinish) |
848 | | } else { |
849 | 0 | EndTaskHook::AsyncCode |
850 | | } |
851 | | } else { |
852 | 0 | if post_return.is_some() { |
853 | 0 | EndTaskHook::None |
854 | | } else { |
855 | 0 | EndTaskHook::Normal(TaskHook::SyncFinish) |
856 | | } |
857 | | }, |
858 | | }, |
859 | | )); |
860 | | |
861 | 0 | if let Some(post_return) = info.post_return(key, f) { |
862 | 0 | let mut post_return_sig = sig.clone(); |
863 | 0 | post_return_sig.params = mem::take(&mut post_return_sig.results); |
864 | 0 | let ty = self.type_index(&post_return_sig); |
865 | 0 | let func = self.import_func(&imported_instance, post_return, ty)?; |
866 | 0 | self.defined_functions.push(( |
867 | 0 | ty, |
868 | 0 | DefinedFunction::HookedCoreExport { |
869 | 0 | task_hook, |
870 | 0 | to_wrap: func, |
871 | 0 | export: Some(imported_instance.clone()), |
872 | 0 | params: post_return_sig.params.len(), |
873 | 0 | core_name: post_return.to_string(), |
874 | 0 | start: None, |
875 | 0 | end: EndTaskHook::Normal(TaskHook::SyncFinish), |
876 | 0 | }, |
877 | 0 | )); |
878 | 0 | } |
879 | | |
880 | 0 | if let Some(callback) = info.callback(key, f) { |
881 | 0 | let ty = self.type_intern(vec![ValType::I32; 3], vec![ValType::I32]); |
882 | 0 | let func = self.import_func(&imported_instance, callback, ty)?; |
883 | 0 | self.defined_functions.push(( |
884 | 0 | ty, |
885 | 0 | DefinedFunction::HookedCoreExport { |
886 | 0 | task_hook, |
887 | 0 | to_wrap: func, |
888 | 0 | export: Some(imported_instance.clone()), |
889 | 0 | params: 3, |
890 | 0 | core_name: callback.to_string(), |
891 | 0 | start: Some(TaskHook::AsyncResume), |
892 | 0 | end: EndTaskHook::AsyncCode, |
893 | 0 | }, |
894 | 0 | )); |
895 | 0 | } |
896 | | } |
897 | | |
898 | 0 | Ok(()) |
899 | 0 | } |
900 | | } |
901 | | |
902 | 7.41k | fn inc(i: &mut u32) -> u32 { |
903 | 7.41k | let ret = *i; |
904 | 7.41k | *i += 1; |
905 | 7.41k | ret |
906 | 7.41k | } |