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 callback: Option<FuncIndex>,
126 async_: bool,
127}
128
129enum Context {
130 Lift,
131 Lower,
132}
133
134/// Representation of a "helper function" which may be generated as part of
135/// generating an adapter trampoline.
136///
137/// Helper functions are created when inlining the translation for a type in its
138/// entirety would make a function excessively large. This is currently done via
139/// a simple fuel/cost heuristic based on the type being translated but may get
140/// fancier over time.
141#[derive(Copy, Clone, PartialEq, Eq, Hash)]
142struct Helper {
143 /// Metadata about the source type of what's being translated.
144 src: HelperType,
145 /// Metadata about the destination type which is being translated to.
146 dst: HelperType,
147}
148
149/// Information about a source or destination type in a `Helper` which is
150/// generated.
151#[derive(Copy, Clone, PartialEq, Eq, Hash)]
152struct HelperType {
153 /// The concrete type being translated.
154 ty: InterfaceType,
155 /// The configuration options (memory, etc) for the adapter.
156 opts: Options,
157 /// Where the type is located (either the stack or in memory)
158 loc: HelperLocation,
159}
160
161/// Where a `HelperType` is located, dictating the signature of the helper
162/// function.
163#[derive(Copy, Clone, PartialEq, Eq, Hash)]
164enum HelperLocation {
165 /// Located on the stack in wasm locals.
166 Stack,
167 /// Located in linear memory as configured by `opts`.
168 Memory,
169}
170
171impl<'a> Module<'a> {
172 /// Creates an empty module.
173 pub fn new(types: &'a ComponentTypesBuilder, debug: bool) -> Module<'a> {
174 Module {
175 debug,
176 types,
177 core_types: Default::default(),
178 core_imports: Default::default(),
179 imported: Default::default(),
180 imports: Default::default(),
181 imported_transcoders: Default::default(),
182 imported_funcs: PrimaryMap::new(),
183 imported_memories: PrimaryMap::new(),
184 imported_globals: PrimaryMap::new(),
185 funcs: PrimaryMap::new(),
186 helper_funcs: HashMap::new(),
187 helper_worklist: Vec::new(),
188 imported_resource_transfer_own: None,
189 imported_resource_transfer_borrow: None,
190 imported_resource_enter_call: None,
191 imported_resource_exit_call: None,
192 }
193 }
194
195 /// Registers a new adapter within this adapter module.
196 ///
197 /// The `name` provided is the export name of the adapter from the final
198 /// module, and `adapter` contains all metadata necessary for compilation.
199 pub fn adapt(&mut self, name: &str, adapter: &Adapter) {
200 // Import any items required by the various canonical options
201 // (memories, reallocs, etc)
202 let mut lift = self.import_options(adapter.lift_ty, &adapter.lift_options);
203 let lower = self.import_options(adapter.lower_ty, &adapter.lower_options);
204
205 // Lowering options are not allowed to specify post-return as per the
206 // current canonical abi specification.
207 assert!(adapter.lower_options.post_return.is_none());
208
209 // Import the core wasm function which was lifted using its appropriate
210 // signature since the exported function this adapter generates will
211 // call the lifted function.
212 let signature = self.types.signature(&lift, Context::Lift);
213 let ty = self
214 .core_types
215 .function(&signature.params, &signature.results);
216 let callee = self.import_func("callee", name, ty, adapter.func.clone());
217
218 // Handle post-return specifically here where we have `core_ty` and the
219 // results of `core_ty` are the parameters to the post-return function.
220 lift.post_return = adapter.lift_options.post_return.as_ref().map(|func| {
221 let ty = self.core_types.function(&signature.results, &[]);
222 self.import_func("post_return", name, ty, func.clone())
223 });
224
225 // This will internally create the adapter as specified and append
226 // anything necessary to `self.funcs`.
227 trampoline::compile(
228 self,
229 &AdapterData {
230 name: name.to_string(),
231 lift,
232 lower,
233 callee,
234 // FIXME(#4185) should be plumbed and handled as part of the new
235 // reentrance rules not yet implemented here.
236 called_as_export: true,
237 },
238 );
239
240 while let Some((result, helper)) = self.helper_worklist.pop() {
241 trampoline::compile_helper(self, result, helper);
242 }
243 }
244
245 fn import_options(&mut self, ty: TypeFuncIndex, options: &AdapterOptionsDfg) -> AdapterOptions {
246 let AdapterOptionsDfg {
247 instance,
248 string_encoding,
249 memory,
250 memory64,
251 realloc,
252 post_return: _, // handled above
253 callback,
254 async_,
255 } = options;
256
257 let flags = self.import_global(
258 "flags",
259 &format!("instance{}", instance.as_u32()),
260 GlobalType {
261 val_type: ValType::I32,
262 mutable: true,
263 shared: false,
264 },
265 CoreDef::InstanceFlags(*instance),
266 );
267 let memory = memory.as_ref().map(|memory| {
268 self.import_memory(
269 "memory",
270 &format!("m{}", self.imported_memories.len()),
271 MemoryType {
272 minimum: 0,
273 maximum: None,
274 shared: false,
275 memory64: *memory64,
276 page_size_log2: None,
277 },
278 memory.clone().into(),
279 )
280 });
281 let realloc = realloc.as_ref().map(|func| {
282 let ptr = if *memory64 {
283 ValType::I64
284 } else {
285 ValType::I32
286 };
287 let ty = self.core_types.function(&[ptr, ptr, ptr, ptr], &[ptr]);
288 self.import_func(
289 "realloc",
290 &format!("f{}", self.imported_funcs.len()),
291 ty,
292 func.clone(),
293 )
294 });
295 let callback = callback.as_ref().map(|func| {
296 let ptr = if *memory64 {
297 ValType::I64
298 } else {
299 ValType::I32
300 };
301 let ty = self.core_types.function(
302 &[ptr, ValType::I32, ValType::I32, ValType::I32],
303 &[ValType::I32],
304 );
305 self.import_func(
306 "callback",
307 &format!("f{}", self.imported_funcs.len()),
308 ty,
309 func.clone(),
310 )
311 });
312
313 AdapterOptions {
314 ty,
315 flags,
316 post_return: None,
317 options: Options {
318 string_encoding: *string_encoding,
319 memory64: *memory64,
320 memory,
321 realloc,
322 callback,
323 async_: *async_,
324 },
325 }
326 }
327
328 fn import_func(&mut self, module: &str, name: &str, ty: u32, def: CoreDef) -> FuncIndex {
329 self.import(module, name, EntityType::Function(ty), def, |m| {
330 &mut m.imported_funcs
331 })
332 }
333
334 fn import_global(
335 &mut self,
336 module: &str,
337 name: &str,
338 ty: GlobalType,
339 def: CoreDef,
340 ) -> GlobalIndex {
341 self.import(module, name, EntityType::Global(ty), def, |m| {
342 &mut m.imported_globals
343 })
344 }
345
346 fn import_memory(
347 &mut self,
348 module: &str,
349 name: &str,
350 ty: MemoryType,
351 def: CoreDef,
352 ) -> MemoryIndex {
353 self.import(module, name, EntityType::Memory(ty), def, |m| {
354 &mut m.imported_memories
355 })
356 }
357
358 fn import<K: EntityRef, V: From<CoreDef>>(
359 &mut self,
360 module: &str,
361 name: &str,
362 ty: EntityType,
363 def: CoreDef,
364 map: impl FnOnce(&mut Self) -> &mut PrimaryMap<K, V>,
365 ) -> K {
366 if let Some(prev) = self.imported.get(&def) {
367 return K::new(*prev);
368 }
369 let idx = map(self).push(def.clone().into());
370 self.core_imports.import(module, name, ty);
371 self.imported.insert(def.clone(), idx.index());
372 self.imports.push(Import::CoreDef(def));
373 idx
374 }
375
376 fn import_transcoder(&mut self, transcoder: transcode::Transcoder) -> FuncIndex {
377 *self
378 .imported_transcoders
379 .entry(transcoder)
380 .or_insert_with(|| {
381 // Add the import to the core wasm import section...
382 let name = transcoder.name();
383 let ty = transcoder.ty(&mut self.core_types);
384 self.core_imports.import("transcode", &name, ty);
385
386 // ... and also record the metadata for what this import
387 // corresponds to.
388 let from = self.imported_memories[transcoder.from_memory].clone();
389 let to = self.imported_memories[transcoder.to_memory].clone();
390 self.imports.push(Import::Transcode {
391 op: transcoder.op,
392 from,
393 from64: transcoder.from_memory64,
394 to,
395 to64: transcoder.to_memory64,
396 });
397
398 self.imported_funcs.push(None)
399 })
400 }
401
402 fn import_simple(
403 &mut self,
404 module: &str,
405 name: &str,
406 params: &[ValType],
407 results: &[ValType],
408 import: Import,
409 get: impl Fn(&mut Self) -> &mut Option<FuncIndex>,
410 ) -> FuncIndex {
411 if let Some(idx) = get(self) {
412 return *idx;
413 }
414 let ty = self.core_types.function(params, results);
415 let ty = EntityType::Function(ty);
416 self.core_imports.import(module, name, ty);
417
418 self.imports.push(import);
419 let idx = self.imported_funcs.push(None);
420 *get(self) = Some(idx);
421 idx
422 }
423
424 fn import_resource_transfer_own(&mut self) -> FuncIndex {
425 self.import_simple(
426 "resource",
427 "transfer-own",
428 &[ValType::I32, ValType::I32, ValType::I32],
429 &[ValType::I32],
430 Import::ResourceTransferOwn,
431 |me| &mut me.imported_resource_transfer_own,
432 )
433 }
434
435 fn import_resource_transfer_borrow(&mut self) -> FuncIndex {
436 self.import_simple(
437 "resource",
438 "transfer-borrow",
439 &[ValType::I32, ValType::I32, ValType::I32],
440 &[ValType::I32],
441 Import::ResourceTransferBorrow,
442 |me| &mut me.imported_resource_transfer_borrow,
443 )
444 }
445
446 fn import_resource_enter_call(&mut self) -> FuncIndex {
447 self.import_simple(
448 "resource",
449 "enter-call",
450 &[],
451 &[],
452 Import::ResourceEnterCall,
453 |me| &mut me.imported_resource_enter_call,
454 )
455 }
456
457 fn import_resource_exit_call(&mut self) -> FuncIndex {
458 self.import_simple(
459 "resource",
460 "exit-call",
461 &[],
462 &[],
463 Import::ResourceExitCall,
464 |me| &mut me.imported_resource_exit_call,
465 )
466 }
467
468 fn translate_helper(&mut self, helper: Helper) -> FunctionId {
469 *self.helper_funcs.entry(helper).or_insert_with(|| {
470 // Generate a fresh `Function` with a unique id for what we're about to
471 // generate.
472 let ty = helper.core_type(self.types, &mut self.core_types);
473 let id = self.funcs.push(Function::new(None, ty));
474 self.helper_worklist.push((id, helper));
475 id
476 })
477 }
478
479 /// Encodes this module into a WebAssembly binary.
480 pub fn encode(&mut self) -> Vec<u8> {
481 // Build the function/export sections of the wasm module in a first pass
482 // which will assign a final `FuncIndex` to all functions defined in
483 // `self.funcs`.
484 let mut funcs = FunctionSection::new();
485 let mut exports = ExportSection::new();
486 let mut id_to_index = PrimaryMap::<FunctionId, FuncIndex>::new();
487 for (id, func) in self.funcs.iter() {
488 assert!(func.filled_in);
489 let idx = FuncIndex::from_u32(self.imported_funcs.next_key().as_u32() + id.as_u32());
490 let id2 = id_to_index.push(idx);
491 assert_eq!(id2, id);
492
493 funcs.function(func.ty);
494
495 if let Some(name) = &func.export {
496 exports.export(name, ExportKind::Func, idx.as_u32());
497 }
498 }
499
500 // With all functions numbered the fragments of the body of each
501 // function can be assigned into one final adapter function.
502 let mut code = CodeSection::new();
503 let mut traps = traps::TrapSection::default();
504 for (id, func) in self.funcs.iter() {
505 let mut func_traps = Vec::new();
506 let mut body = Vec::new();
507
508 // Encode all locals used for this function
509 func.locals.len().encode(&mut body);
510 for (count, ty) in func.locals.iter() {
511 count.encode(&mut body);
512 ty.encode(&mut body);
513 }
514
515 // Then encode each "chunk" of a body which may have optional traps
516 // specified within it. Traps get offset by the current length of
517 // the body and otherwise our `Call` instructions are "relocated"
518 // here to the final function index.
519 for chunk in func.body.iter() {
520 match chunk {
521 Body::Raw(code, traps) => {
522 let start = body.len();
523 body.extend_from_slice(code);
524 for (offset, trap) in traps {
525 func_traps.push((start + offset, *trap));
526 }
527 }
528 Body::Call(id) => {
529 Instruction::Call(id_to_index[*id].as_u32()).encode(&mut body);
530 }
531 }
532 }
533 code.raw(&body);
534 traps.append(id_to_index[id].as_u32(), func_traps);
535 }
536
537 let traps = traps.finish();
538
539 let mut result = wasm_encoder::Module::new();
540 result.section(&self.core_types.section);
541 result.section(&self.core_imports);
542 result.section(&funcs);
543 result.section(&exports);
544 result.section(&code);
545 if self.debug {
546 result.section(&CustomSection {
547 name: "wasmtime-trampoline-traps".into(),
548 data: Cow::Borrowed(&traps),
549 });
550 }
551 result.finish()
552 }
553
554 /// Returns the imports that were used, in order, to create this adapter
555 /// module.
556 pub fn imports(&self) -> &[Import] {
557 &self.imports
558 }
559}
560
561/// Possible imports into an adapter module.
562#[derive(Clone)]
563pub enum Import {
564 /// A definition required in the configuration of an `Adapter`.
565 CoreDef(CoreDef),
566 /// A transcoding function from the host to convert between string encodings.
567 Transcode {
568 /// The transcoding operation this performs.
569 op: Transcode,
570 /// The memory being read
571 from: CoreDef,
572 /// Whether or not `from` is a 64-bit memory
573 from64: bool,
574 /// The memory being written
575 to: CoreDef,
576 /// Whether or not `to` is a 64-bit memory
577 to64: bool,
578 },
579 /// Transfers an owned resource from one table to another.
580 ResourceTransferOwn,
581 /// Transfers a borrowed resource from one table to another.
582 ResourceTransferBorrow,
583 /// Sets up entry metadata for a borrow resources when a call starts.
584 ResourceEnterCall,
585 /// Tears down a previous entry and handles checking borrow-related
586 /// metadata.
587 ResourceExitCall,
588 /// An intrinsic used by FACT-generated modules to begin a call to an
589 /// async-lowered import function.
590 AsyncEnterCall,
591 /// An intrinsic used by FACT-generated modules to complete a call to an
592 /// async-lowered import function.
593 AsyncExitCall {
594 /// The callee's callback function, if any.
595 callback: Option<CoreDef>,
596
597 /// The callee's post-return function, if any.
598 post_return: Option<CoreDef>,
599 },
600 /// An intrinisic used by FACT-generated modules to (partially or entirely) transfer
601 /// ownership of a `future`.
602 FutureTransfer,
603 /// An intrinisic used by FACT-generated modules to (partially or entirely) transfer
604 /// ownership of a `stream`.
605 StreamTransfer,
606 /// An intrinisic used by FACT-generated modules to (partially or entirely) transfer
607 /// ownership of an `error-context`.
608 ErrorContextTransfer,
609}
610
611impl Options {
612 fn ptr(&self) -> ValType {
613 if self.memory64 {
614 ValType::I64
615 } else {
616 ValType::I32
617 }
618 }
619
620 fn ptr_size(&self) -> u8 {
621 if self.memory64 {
622 8
623 } else {
624 4
625 }
626 }
627
628 fn flat_types<'a>(
629 &self,
630 ty: &InterfaceType,
631 types: &'a ComponentTypesBuilder,
632 ) -> Option<&'a [FlatType]> {
633 let flat = types.flat_types(ty)?;
634 Some(if self.memory64 {
635 flat.memory64
636 } else {
637 flat.memory32
638 })
639 }
640}
641
642/// Temporary index which is not the same as `FuncIndex`.
643///
644/// This represents the nth generated function in the adapter module where the
645/// final index of the function is not known at the time of generation since
646/// more imports may be discovered (specifically string transcoders).
647#[derive(Debug, Copy, Clone, PartialEq, Eq)]
648struct FunctionId(u32);
649cranelift_entity::entity_impl!(FunctionId);
650
651/// A generated function to be added to an adapter module.
652///
653/// At least one function is created per-adapter and depending on the type
654/// hierarchy multiple functions may be generated per-adapter.
655struct Function {
656 /// Whether or not the `body` has been finished.
657 ///
658 /// Functions are added to a `Module` before they're defined so this is used
659 /// to assert that the function was in fact actually filled in by the
660 /// time we reach `Module::encode`.
661 filled_in: bool,
662
663 /// The type signature that this function has, as an index into the core
664 /// wasm type index space of the generated adapter module.
665 ty: u32,
666
667 /// The locals that are used by this function, organized by the number of
668 /// types of each local.
669 locals: Vec<(u32, ValType)>,
670
671 /// If specified, the export name of this function.
672 export: Option<String>,
673
674 /// The contents of the function.
675 ///
676 /// See `Body` for more information, and the `Vec` here represents the
677 /// concatenation of all the `Body` fragments.
678 body: Vec<Body>,
679}
680
681/// Representation of a fragment of the body of a core wasm function generated
682/// for adapters.
683///
684/// This variant comes in one of two flavors:
685///
686/// 1. First a `Raw` variant is used to contain general instructions for the
687/// wasm function. This is populated by `Compiler::instruction` primarily.
688/// This also comes with a list of traps. and the byte offset within the
689/// first vector of where the trap information applies to.
690///
691/// 2. A `Call` instruction variant for a `FunctionId` where the final
692/// `FuncIndex` isn't known until emission time.
693///
694/// The purpose of this representation is the `Body::Call` variant. This can't
695/// be encoded as an instruction when it's generated due to not knowing the
696/// final index of the function being called. During `Module::encode`, however,
697/// all indices are known and `Body::Call` is turned into a final
698/// `Instruction::Call`.
699///
700/// One other possible representation in the future would be to encode a `Call`
701/// instruction with a 5-byte leb to fill in later, but for now this felt
702/// easier to represent. A 5-byte leb may be more efficient at compile-time if
703/// necessary, however.
704enum Body {
705 Raw(Vec<u8>, Vec<(usize, traps::Trap)>),
706 Call(FunctionId),
707}
708
709impl Function {
710 fn new(export: Option<String>, ty: u32) -> Function {
711 Function {
712 filled_in: false,
713 ty,
714 locals: Vec::new(),
715 export,
716 body: Vec::new(),
717 }
718 }
719}
720
721impl Helper {
722 fn core_type(
723 &self,
724 types: &ComponentTypesBuilder,
725 core_types: &mut core_types::CoreTypes,
726 ) -> u32 {
727 let mut params = Vec::new();
728 let mut results = Vec::new();
729 // The source type being translated is always pushed onto the
730 // parameters first, either a pointer for memory or its flat
731 // representation.
732 self.src.push_flat(&mut params, types);
733
734 // The destination type goes into the parameter list if it's from
735 // memory or otherwise is the result of the function itself for a
736 // stack-based representation.
737 match self.dst.loc {
738 HelperLocation::Stack => self.dst.push_flat(&mut results, types),
739 HelperLocation::Memory => params.push(self.dst.opts.ptr()),
740 }
741
742 core_types.function(¶ms, &results)
743 }
744}
745
746impl HelperType {
747 fn push_flat(&self, dst: &mut Vec<ValType>, types: &ComponentTypesBuilder) {
748 match self.loc {
749 HelperLocation::Stack => {
750 for ty in self.opts.flat_types(&self.ty, types).unwrap() {
751 dst.push((*ty).into());
752 }
753 }
754 HelperLocation::Memory => {
755 dst.push(self.opts.ptr());
756 }
757 }
758 }
759}