wasmtime_environ/fact.rs
1//! Wasmtime's Fused Adapter Compiler of Trampolines (FACT)
2//!
3//! This module contains a compiler which emits trampolines to implement fused
4//! adapters for the component model. A fused adapter is when a core wasm
5//! function is lifted from one component instance and then lowered into another
6//! component instance. This communication between components is well-defined by
7//! the spec and ends up creating what's called a "fused adapter".
8//!
9//! Adapters are currently implemented with WebAssembly modules. This submodule
10//! will generate a core wasm binary which contains the adapters specified
11//! during compilation. The actual wasm is then later processed by standard
12//! paths in Wasmtime to create native machine code and runtime representations
13//! of modules.
14//!
15//! Note that identification of precisely what goes into an adapter module is
16//! not handled in this file, instead that's all done in `translate/adapt.rs`.
17//! Otherwise this module is only responsible for taking a set of adapters and
18//! their imports and then generating a core wasm module to implement all of
19//! that.
20
21use crate::component::dfg::CoreDef;
22use crate::component::{
23 Adapter, AdapterOptions as AdapterOptionsDfg, ComponentTypesBuilder, FlatType, InterfaceType,
24 StringEncoding, Transcode, TypeFuncIndex,
25};
26use crate::fact::transcode::Transcoder;
27use crate::prelude::*;
28use crate::{EntityRef, FuncIndex, GlobalIndex, MemoryIndex, PrimaryMap};
29use std::borrow::Cow;
30use std::collections::HashMap;
31use wasm_encoder::*;
32
33mod core_types;
34mod signature;
35mod trampoline;
36mod transcode;
37mod traps;
38
39/// Representation of an adapter module.
40pub struct Module<'a> {
41 /// Whether or not debug code is inserted into the adapters themselves.
42 debug: bool,
43 /// Type information from the creator of this `Module`
44 types: &'a ComponentTypesBuilder,
45
46 /// Core wasm type section that's incrementally built
47 core_types: core_types::CoreTypes,
48
49 /// Core wasm import section which is built as adapters are inserted. Note
50 /// that imports here are intern'd to avoid duplicate imports of the same
51 /// item.
52 core_imports: ImportSection,
53 /// Final list of imports that this module ended up using, in the same order
54 /// as the imports in the import section.
55 imports: Vec<Import>,
56 /// Intern'd imports and what index they were assigned. Note that this map
57 /// covers all the index spaces for imports, not just one.
58 imported: HashMap<CoreDef, usize>,
59 /// Intern'd transcoders and what index they were assigned.
60 imported_transcoders: HashMap<Transcoder, FuncIndex>,
61
62 /// Cached versions of imported trampolines for working with resources.
63 imported_resource_transfer_own: Option<FuncIndex>,
64 imported_resource_transfer_borrow: Option<FuncIndex>,
65 imported_resource_enter_call: Option<FuncIndex>,
66 imported_resource_exit_call: Option<FuncIndex>,
67
68 // Current status of index spaces from the imports generated so far.
69 imported_funcs: PrimaryMap<FuncIndex, Option<CoreDef>>,
70 imported_memories: PrimaryMap<MemoryIndex, CoreDef>,
71 imported_globals: PrimaryMap<GlobalIndex, CoreDef>,
72
73 funcs: PrimaryMap<FunctionId, Function>,
74 helper_funcs: HashMap<Helper, FunctionId>,
75 helper_worklist: Vec<(FunctionId, Helper)>,
76}
77
78struct AdapterData {
79 /// Export name of this adapter
80 name: String,
81 /// Options specified during the `canon lift` operation
82 lift: AdapterOptions,
83 /// Options specified during the `canon lower` operation
84 lower: AdapterOptions,
85 /// The core wasm function that this adapter will be calling (the original
86 /// function that was `canon lift`'d)
87 callee: FuncIndex,
88 /// FIXME(#4185) should be plumbed and handled as part of the new reentrance
89 /// rules not yet implemented here.
90 called_as_export: bool,
91}
92
93/// Configuration options which apply at the "global adapter" level.
94///
95/// These options are typically unique per-adapter and generally aren't needed
96/// when translating recursive types within an adapter.
97struct AdapterOptions {
98 /// The ascribed type of this adapter.
99 ty: TypeFuncIndex,
100 /// The global that represents the instance flags for where this adapter
101 /// came from.
102 flags: GlobalIndex,
103 /// The configured post-return function, if any.
104 post_return: Option<FuncIndex>,
105 /// Other, more general, options configured.
106 options: Options,
107}
108
109/// This type is split out of `AdapterOptions` and is specifically used to
110/// deduplicate translation functions within a module. Consequently this has
111/// as few fields as possible to minimize the number of functions generated
112/// within an adapter module.
113#[derive(PartialEq, Eq, Hash, Copy, Clone)]
114struct Options {
115 /// The encoding that strings use from this adapter.
116 string_encoding: StringEncoding,
117 /// Whether or not the `memory` field, if present, is a 64-bit memory.
118 memory64: bool,
119 /// An optionally-specified memory where values may travel through for
120 /// types like lists.
121 memory: Option<MemoryIndex>,
122 /// An optionally-specified function to be used to allocate space for
123 /// types such as strings as they go into a module.
124 realloc: Option<FuncIndex>,
125}
126
127enum Context {
128 Lift,
129 Lower,
130}
131
132/// Representation of a "helper function" which may be generated as part of
133/// generating an adapter trampoline.
134///
135/// Helper functions are created when inlining the translation for a type in its
136/// entirety would make a function excessively large. This is currently done via
137/// a simple fuel/cost heuristic based on the type being translated but may get
138/// fancier over time.
139#[derive(Copy, Clone, PartialEq, Eq, Hash)]
140struct Helper {
141 /// Metadata about the source type of what's being translated.
142 src: HelperType,
143 /// Metadata about the destination type which is being translated to.
144 dst: HelperType,
145}
146
147/// Information about a source or destination type in a `Helper` which is
148/// generated.
149#[derive(Copy, Clone, PartialEq, Eq, Hash)]
150struct HelperType {
151 /// The concrete type being translated.
152 ty: InterfaceType,
153 /// The configuration options (memory, etc) for the adapter.
154 opts: Options,
155 /// Where the type is located (either the stack or in memory)
156 loc: HelperLocation,
157}
158
159/// Where a `HelperType` is located, dictating the signature of the helper
160/// function.
161#[derive(Copy, Clone, PartialEq, Eq, Hash)]
162enum HelperLocation {
163 /// Located on the stack in wasm locals.
164 Stack,
165 /// Located in linear memory as configured by `opts`.
166 Memory,
167}
168
169impl<'a> Module<'a> {
170 /// Creates an empty module.
171 pub fn new(types: &'a ComponentTypesBuilder, debug: bool) -> Module<'a> {
172 Module {
173 debug,
174 types,
175 core_types: Default::default(),
176 core_imports: Default::default(),
177 imported: Default::default(),
178 imports: Default::default(),
179 imported_transcoders: Default::default(),
180 imported_funcs: PrimaryMap::new(),
181 imported_memories: PrimaryMap::new(),
182 imported_globals: PrimaryMap::new(),
183 funcs: PrimaryMap::new(),
184 helper_funcs: HashMap::new(),
185 helper_worklist: Vec::new(),
186 imported_resource_transfer_own: None,
187 imported_resource_transfer_borrow: None,
188 imported_resource_enter_call: None,
189 imported_resource_exit_call: None,
190 }
191 }
192
193 /// Registers a new adapter within this adapter module.
194 ///
195 /// The `name` provided is the export name of the adapter from the final
196 /// module, and `adapter` contains all metadata necessary for compilation.
197 pub fn adapt(&mut self, name: &str, adapter: &Adapter) {
198 // Import any items required by the various canonical options
199 // (memories, reallocs, etc)
200 let mut lift = self.import_options(adapter.lift_ty, &adapter.lift_options);
201 let lower = self.import_options(adapter.lower_ty, &adapter.lower_options);
202
203 // Lowering options are not allowed to specify post-return as per the
204 // current canonical abi specification.
205 assert!(adapter.lower_options.post_return.is_none());
206
207 // Import the core wasm function which was lifted using its appropriate
208 // signature since the exported function this adapter generates will
209 // call the lifted function.
210 let signature = self.types.signature(&lift, Context::Lift);
211 let ty = self
212 .core_types
213 .function(&signature.params, &signature.results);
214 let callee = self.import_func("callee", name, ty, adapter.func.clone());
215
216 // Handle post-return specifically here where we have `core_ty` and the
217 // results of `core_ty` are the parameters to the post-return function.
218 lift.post_return = adapter.lift_options.post_return.as_ref().map(|func| {
219 let ty = self.core_types.function(&signature.results, &[]);
220 self.import_func("post_return", name, ty, func.clone())
221 });
222
223 // This will internally create the adapter as specified and append
224 // anything necessary to `self.funcs`.
225 trampoline::compile(
226 self,
227 &AdapterData {
228 name: name.to_string(),
229 lift,
230 lower,
231 callee,
232 // FIXME(#4185) should be plumbed and handled as part of the new
233 // reentrance rules not yet implemented here.
234 called_as_export: true,
235 },
236 );
237
238 while let Some((result, helper)) = self.helper_worklist.pop() {
239 trampoline::compile_helper(self, result, helper);
240 }
241 }
242
243 fn import_options(&mut self, ty: TypeFuncIndex, options: &AdapterOptionsDfg) -> AdapterOptions {
244 let AdapterOptionsDfg {
245 instance,
246 string_encoding,
247 memory,
248 memory64,
249 realloc,
250 post_return: _, // handled above
251 } = options;
252 let flags = self.import_global(
253 "flags",
254 &format!("instance{}", instance.as_u32()),
255 GlobalType {
256 val_type: ValType::I32,
257 mutable: true,
258 shared: false,
259 },
260 CoreDef::InstanceFlags(*instance),
261 );
262 let memory = memory.as_ref().map(|memory| {
263 self.import_memory(
264 "memory",
265 &format!("m{}", self.imported_memories.len()),
266 MemoryType {
267 minimum: 0,
268 maximum: None,
269 shared: false,
270 memory64: *memory64,
271 page_size_log2: None,
272 },
273 memory.clone().into(),
274 )
275 });
276 let realloc = realloc.as_ref().map(|func| {
277 let ptr = if *memory64 {
278 ValType::I64
279 } else {
280 ValType::I32
281 };
282 let ty = self.core_types.function(&[ptr, ptr, ptr, ptr], &[ptr]);
283 self.import_func(
284 "realloc",
285 &format!("f{}", self.imported_funcs.len()),
286 ty,
287 func.clone(),
288 )
289 });
290
291 AdapterOptions {
292 ty,
293 flags,
294 post_return: None,
295 options: Options {
296 string_encoding: *string_encoding,
297 memory64: *memory64,
298 memory,
299 realloc,
300 },
301 }
302 }
303
304 fn import_func(&mut self, module: &str, name: &str, ty: u32, def: CoreDef) -> FuncIndex {
305 self.import(module, name, EntityType::Function(ty), def, |m| {
306 &mut m.imported_funcs
307 })
308 }
309
310 fn import_global(
311 &mut self,
312 module: &str,
313 name: &str,
314 ty: GlobalType,
315 def: CoreDef,
316 ) -> GlobalIndex {
317 self.import(module, name, EntityType::Global(ty), def, |m| {
318 &mut m.imported_globals
319 })
320 }
321
322 fn import_memory(
323 &mut self,
324 module: &str,
325 name: &str,
326 ty: MemoryType,
327 def: CoreDef,
328 ) -> MemoryIndex {
329 self.import(module, name, EntityType::Memory(ty), def, |m| {
330 &mut m.imported_memories
331 })
332 }
333
334 fn import<K: EntityRef, V: From<CoreDef>>(
335 &mut self,
336 module: &str,
337 name: &str,
338 ty: EntityType,
339 def: CoreDef,
340 map: impl FnOnce(&mut Self) -> &mut PrimaryMap<K, V>,
341 ) -> K {
342 if let Some(prev) = self.imported.get(&def) {
343 return K::new(*prev);
344 }
345 let idx = map(self).push(def.clone().into());
346 self.core_imports.import(module, name, ty);
347 self.imported.insert(def.clone(), idx.index());
348 self.imports.push(Import::CoreDef(def));
349 idx
350 }
351
352 fn import_transcoder(&mut self, transcoder: transcode::Transcoder) -> FuncIndex {
353 *self
354 .imported_transcoders
355 .entry(transcoder)
356 .or_insert_with(|| {
357 // Add the import to the core wasm import section...
358 let name = transcoder.name();
359 let ty = transcoder.ty(&mut self.core_types);
360 self.core_imports.import("transcode", &name, ty);
361
362 // ... and also record the metadata for what this import
363 // corresponds to.
364 let from = self.imported_memories[transcoder.from_memory].clone();
365 let to = self.imported_memories[transcoder.to_memory].clone();
366 self.imports.push(Import::Transcode {
367 op: transcoder.op,
368 from,
369 from64: transcoder.from_memory64,
370 to,
371 to64: transcoder.to_memory64,
372 });
373
374 self.imported_funcs.push(None)
375 })
376 }
377
378 fn import_simple(
379 &mut self,
380 module: &str,
381 name: &str,
382 params: &[ValType],
383 results: &[ValType],
384 import: Import,
385 get: impl Fn(&mut Self) -> &mut Option<FuncIndex>,
386 ) -> FuncIndex {
387 if let Some(idx) = get(self) {
388 return *idx;
389 }
390 let ty = self.core_types.function(params, results);
391 let ty = EntityType::Function(ty);
392 self.core_imports.import(module, name, ty);
393
394 self.imports.push(import);
395 let idx = self.imported_funcs.push(None);
396 *get(self) = Some(idx);
397 idx
398 }
399
400 fn import_resource_transfer_own(&mut self) -> FuncIndex {
401 self.import_simple(
402 "resource",
403 "transfer-own",
404 &[ValType::I32, ValType::I32, ValType::I32],
405 &[ValType::I32],
406 Import::ResourceTransferOwn,
407 |me| &mut me.imported_resource_transfer_own,
408 )
409 }
410
411 fn import_resource_transfer_borrow(&mut self) -> FuncIndex {
412 self.import_simple(
413 "resource",
414 "transfer-borrow",
415 &[ValType::I32, ValType::I32, ValType::I32],
416 &[ValType::I32],
417 Import::ResourceTransferBorrow,
418 |me| &mut me.imported_resource_transfer_borrow,
419 )
420 }
421
422 fn import_resource_enter_call(&mut self) -> FuncIndex {
423 self.import_simple(
424 "resource",
425 "enter-call",
426 &[],
427 &[],
428 Import::ResourceEnterCall,
429 |me| &mut me.imported_resource_enter_call,
430 )
431 }
432
433 fn import_resource_exit_call(&mut self) -> FuncIndex {
434 self.import_simple(
435 "resource",
436 "exit-call",
437 &[],
438 &[],
439 Import::ResourceExitCall,
440 |me| &mut me.imported_resource_exit_call,
441 )
442 }
443
444 fn translate_helper(&mut self, helper: Helper) -> FunctionId {
445 *self.helper_funcs.entry(helper).or_insert_with(|| {
446 // Generate a fresh `Function` with a unique id for what we're about to
447 // generate.
448 let ty = helper.core_type(self.types, &mut self.core_types);
449 let id = self.funcs.push(Function::new(None, ty));
450 self.helper_worklist.push((id, helper));
451 id
452 })
453 }
454
455 /// Encodes this module into a WebAssembly binary.
456 pub fn encode(&mut self) -> Vec<u8> {
457 // Build the function/export sections of the wasm module in a first pass
458 // which will assign a final `FuncIndex` to all functions defined in
459 // `self.funcs`.
460 let mut funcs = FunctionSection::new();
461 let mut exports = ExportSection::new();
462 let mut id_to_index = PrimaryMap::<FunctionId, FuncIndex>::new();
463 for (id, func) in self.funcs.iter() {
464 assert!(func.filled_in);
465 let idx = FuncIndex::from_u32(self.imported_funcs.next_key().as_u32() + id.as_u32());
466 let id2 = id_to_index.push(idx);
467 assert_eq!(id2, id);
468
469 funcs.function(func.ty);
470
471 if let Some(name) = &func.export {
472 exports.export(name, ExportKind::Func, idx.as_u32());
473 }
474 }
475
476 // With all functions numbered the fragments of the body of each
477 // function can be assigned into one final adapter function.
478 let mut code = CodeSection::new();
479 let mut traps = traps::TrapSection::default();
480 for (id, func) in self.funcs.iter() {
481 let mut func_traps = Vec::new();
482 let mut body = Vec::new();
483
484 // Encode all locals used for this function
485 func.locals.len().encode(&mut body);
486 for (count, ty) in func.locals.iter() {
487 count.encode(&mut body);
488 ty.encode(&mut body);
489 }
490
491 // Then encode each "chunk" of a body which may have optional traps
492 // specified within it. Traps get offset by the current length of
493 // the body and otherwise our `Call` instructions are "relocated"
494 // here to the final function index.
495 for chunk in func.body.iter() {
496 match chunk {
497 Body::Raw(code, traps) => {
498 let start = body.len();
499 body.extend_from_slice(code);
500 for (offset, trap) in traps {
501 func_traps.push((start + offset, *trap));
502 }
503 }
504 Body::Call(id) => {
505 Instruction::Call(id_to_index[*id].as_u32()).encode(&mut body);
506 }
507 }
508 }
509 code.raw(&body);
510 traps.append(id_to_index[id].as_u32(), func_traps);
511 }
512
513 let traps = traps.finish();
514
515 let mut result = wasm_encoder::Module::new();
516 result.section(&self.core_types.section);
517 result.section(&self.core_imports);
518 result.section(&funcs);
519 result.section(&exports);
520 result.section(&code);
521 if self.debug {
522 result.section(&CustomSection {
523 name: "wasmtime-trampoline-traps".into(),
524 data: Cow::Borrowed(&traps),
525 });
526 }
527 result.finish()
528 }
529
530 /// Returns the imports that were used, in order, to create this adapter
531 /// module.
532 pub fn imports(&self) -> &[Import] {
533 &self.imports
534 }
535}
536
537/// Possible imports into an adapter module.
538#[derive(Clone)]
539pub enum Import {
540 /// A definition required in the configuration of an `Adapter`.
541 CoreDef(CoreDef),
542 /// A transcoding function from the host to convert between string encodings.
543 Transcode {
544 /// The transcoding operation this performs.
545 op: Transcode,
546 /// The memory being read
547 from: CoreDef,
548 /// Whether or not `from` is a 64-bit memory
549 from64: bool,
550 /// The memory being written
551 to: CoreDef,
552 /// Whether or not `to` is a 64-bit memory
553 to64: bool,
554 },
555 /// Transfers an owned resource from one table to another.
556 ResourceTransferOwn,
557 /// Transfers a borrowed resource from one table to another.
558 ResourceTransferBorrow,
559 /// Sets up entry metadata for a borrow resources when a call starts.
560 ResourceEnterCall,
561 /// Tears down a previous entry and handles checking borrow-related
562 /// metadata.
563 ResourceExitCall,
564}
565
566impl Options {
567 fn ptr(&self) -> ValType {
568 if self.memory64 {
569 ValType::I64
570 } else {
571 ValType::I32
572 }
573 }
574
575 fn ptr_size(&self) -> u8 {
576 if self.memory64 {
577 8
578 } else {
579 4
580 }
581 }
582
583 fn flat_types<'a>(
584 &self,
585 ty: &InterfaceType,
586 types: &'a ComponentTypesBuilder,
587 ) -> Option<&'a [FlatType]> {
588 let flat = types.flat_types(ty)?;
589 Some(if self.memory64 {
590 flat.memory64
591 } else {
592 flat.memory32
593 })
594 }
595}
596
597/// Temporary index which is not the same as `FuncIndex`.
598///
599/// This represents the nth generated function in the adapter module where the
600/// final index of the function is not known at the time of generation since
601/// more imports may be discovered (specifically string transcoders).
602#[derive(Debug, Copy, Clone, PartialEq, Eq)]
603struct FunctionId(u32);
604cranelift_entity::entity_impl!(FunctionId);
605
606/// A generated function to be added to an adapter module.
607///
608/// At least one function is created per-adapter and depending on the type
609/// hierarchy multiple functions may be generated per-adapter.
610struct Function {
611 /// Whether or not the `body` has been finished.
612 ///
613 /// Functions are added to a `Module` before they're defined so this is used
614 /// to assert that the function was in fact actually filled in by the
615 /// time we reach `Module::encode`.
616 filled_in: bool,
617
618 /// The type signature that this function has, as an index into the core
619 /// wasm type index space of the generated adapter module.
620 ty: u32,
621
622 /// The locals that are used by this function, organized by the number of
623 /// types of each local.
624 locals: Vec<(u32, ValType)>,
625
626 /// If specified, the export name of this function.
627 export: Option<String>,
628
629 /// The contents of the function.
630 ///
631 /// See `Body` for more information, and the `Vec` here represents the
632 /// concatenation of all the `Body` fragments.
633 body: Vec<Body>,
634}
635
636/// Representation of a fragment of the body of a core wasm function generated
637/// for adapters.
638///
639/// This variant comes in one of two flavors:
640///
641/// 1. First a `Raw` variant is used to contain general instructions for the
642/// wasm function. This is populated by `Compiler::instruction` primarily.
643/// This also comes with a list of traps. and the byte offset within the
644/// first vector of where the trap information applies to.
645///
646/// 2. A `Call` instruction variant for a `FunctionId` where the final
647/// `FuncIndex` isn't known until emission time.
648///
649/// The purpose of this representation is the `Body::Call` variant. This can't
650/// be encoded as an instruction when it's generated due to not knowing the
651/// final index of the function being called. During `Module::encode`, however,
652/// all indices are known and `Body::Call` is turned into a final
653/// `Instruction::Call`.
654///
655/// One other possible representation in the future would be to encode a `Call`
656/// instruction with a 5-byte leb to fill in later, but for now this felt
657/// easier to represent. A 5-byte leb may be more efficient at compile-time if
658/// necessary, however.
659enum Body {
660 Raw(Vec<u8>, Vec<(usize, traps::Trap)>),
661 Call(FunctionId),
662}
663
664impl Function {
665 fn new(export: Option<String>, ty: u32) -> Function {
666 Function {
667 filled_in: false,
668 ty,
669 locals: Vec::new(),
670 export,
671 body: Vec::new(),
672 }
673 }
674}
675
676impl Helper {
677 fn core_type(
678 &self,
679 types: &ComponentTypesBuilder,
680 core_types: &mut core_types::CoreTypes,
681 ) -> u32 {
682 let mut params = Vec::new();
683 let mut results = Vec::new();
684 // The source type being translated is always pushed onto the
685 // parameters first, either a pointer for memory or its flat
686 // representation.
687 self.src.push_flat(&mut params, types);
688
689 // The destination type goes into the parameter list if it's from
690 // memory or otherwise is the result of the function itself for a
691 // stack-based representation.
692 match self.dst.loc {
693 HelperLocation::Stack => self.dst.push_flat(&mut results, types),
694 HelperLocation::Memory => params.push(self.dst.opts.ptr()),
695 }
696
697 core_types.function(¶ms, &results)
698 }
699}
700
701impl HelperType {
702 fn push_flat(&self, dst: &mut Vec<ValType>, types: &ComponentTypesBuilder) {
703 match self.loc {
704 HelperLocation::Stack => {
705 for ty in self.opts.flat_types(&self.ty, types).unwrap() {
706 dst.push((*ty).into());
707 }
708 }
709 HelperLocation::Memory => {
710 dst.push(self.opts.ptr());
711 }
712 }
713 }
714}