js_component_bindgen/function_bindgen.rs
1use std::collections::{BTreeMap, BTreeSet};
2use std::fmt::Write;
3use std::mem;
4
5use heck::{ToLowerCamelCase, ToUpperCamelCase};
6use wasmtime_environ::component::{
7 CanonicalOptions, CanonicalOptionsDataModel, InterfaceType, LinearMemoryOptions, ResourceIndex,
8 TypeComponentLocalErrorContextTableIndex, TypeFutureTableIndex, TypeResourceTableIndex,
9 TypeStreamTableIndex,
10};
11use wit_bindgen_core::abi::{Bindgen, Bitcast, Instruction};
12use wit_component::StringEncoding;
13use wit_parser::abi::WasmType;
14use wit_parser::{
15 Alignment, ArchitectureSize, Handle, Resolve, SizeAlign, Type, TypeDefKind, TypeId,
16};
17
18use crate::intrinsics::Intrinsic;
19use crate::intrinsics::component::ComponentIntrinsic;
20use crate::intrinsics::conversion::ConversionIntrinsic;
21use crate::intrinsics::js_helper::JsHelperIntrinsic;
22use crate::intrinsics::p3::async_future::AsyncFutureIntrinsic;
23use crate::intrinsics::p3::async_stream::AsyncStreamIntrinsic;
24use crate::intrinsics::p3::async_task::AsyncTaskIntrinsic;
25use crate::intrinsics::resource::ResourceIntrinsic;
26use crate::intrinsics::string::StringIntrinsic;
27use crate::{ManagesIntrinsics, get_thrown_type, source};
28use crate::{uwrite, uwriteln};
29
30/// Method of error handling
31#[derive(Debug, Clone, PartialEq)]
32pub enum ErrHandling {
33 /// Do no special handling of errors, requiring users to return objects that represent
34 /// errors as represented in WIT
35 None,
36 /// Require throwing of result error objects
37 ThrowResultErr,
38 /// Catch thrown errors and convert them into result<t,e> error variants
39 ResultCatchHandler,
40}
41
42impl ErrHandling {
43 fn to_js_string(&self) -> String {
44 match self {
45 ErrHandling::None => "none".into(),
46 ErrHandling::ThrowResultErr => "throw-result-err".into(),
47 ErrHandling::ResultCatchHandler => "result-catch-handler".into(),
48 }
49 }
50}
51
52/// Data related to a given resource
53#[derive(Clone, Debug, PartialEq)]
54pub enum ResourceData {
55 Host {
56 tid: TypeResourceTableIndex,
57 rid: ResourceIndex,
58 local_name: String,
59 dtor_name: Option<String>,
60 },
61 Guest {
62 resource_name: String,
63 prefix: Option<String>,
64 extra: Option<ResourceExtraData>,
65 },
66}
67
68#[derive(Clone, Debug, PartialEq)]
69pub struct PayloadTypeMetadata {
70 pub(crate) ty: Type,
71 pub(crate) iface_ty: InterfaceType,
72 /// JS expression that serves as a function that lifts a given type
73 pub(crate) lift_js_expr: String,
74 /// JS expression that serves as a function that lowers a given type
75 pub(crate) lower_js_expr: String,
76 pub(crate) size32: u32,
77 pub(crate) align32: u32,
78 pub(crate) flat_count: Option<u8>,
79}
80
81/// Supplemental data kept along with [`ResourceData`]
82#[derive(Clone, Debug, PartialEq)]
83pub enum ResourceExtraData {
84 Stream {
85 table_idx: TypeStreamTableIndex,
86 elem_ty: Option<PayloadTypeMetadata>,
87 },
88 Future {
89 table_idx: TypeFutureTableIndex,
90 elem_ty: Option<PayloadTypeMetadata>,
91 nesting_level: u32,
92 },
93 ErrorContext {
94 table_idx: TypeComponentLocalErrorContextTableIndex,
95 },
96}
97
98/// Map used for resource function bindgen within a given component
99///
100/// Mapping from the instance + resource index in that component (internal or external)
101/// to the unique global resource id used to key the resource tables for this resource.
102///
103/// The id value uniquely identifies the resource table so that if a resource is used
104/// by n components, there should be n different indices and spaces in use. The map is
105/// therefore entirely unique and fully distinct for each instance's function bindgen.
106///
107/// The second bool is true if it is an imported resource.
108///
109/// For a given resource table id {x}, with resource index {y} the local variables are assumed:
110/// - handleTable{x}
111/// - captureTable{y} (rep to instance map for captured imported tables, only for JS import bindgen, not hybrid)
112/// - captureCnt{y} for assigning capture rep
113///
114/// For component-defined resources:
115/// - finalizationRegistry{x}
116///
117/// handleTable internally will be allocated with { rep: i32, own: bool } entries
118///
119/// In the case of an imported resource tables, in place of "rep" we just store
120/// the direct JS object being referenced, since in JS the object is its own handle.
121///
122#[derive(Clone, Debug, PartialEq)]
123pub struct ResourceTable {
124 /// Whether a resource was imported
125 ///
126 /// This should be tracked because imported types cannot be re-exported uniquely (?)
127 pub imported: bool,
128
129 /// Data related to the actual resource
130 pub data: ResourceData,
131}
132
133/// A mapping of type IDs to the resources that they represent
134pub type ResourceMap = BTreeMap<TypeId, ResourceTable>;
135
136pub struct FunctionBindgen<'a> {
137 /// Mapping of resources for types that have corresponding definitions locally
138 pub resource_map: &'a ResourceMap,
139
140 /// Whether current resource borrows need to be deactivated
141 pub clear_resource_borrows: bool,
142
143 /// Set of intrinsics
144 pub intrinsics: &'a mut BTreeSet<Intrinsic>,
145
146 /// Whether to perform valid lifting optimization
147 pub valid_lifting_optimization: bool,
148
149 /// Sizes and alignments for sub elements
150 pub sizes: &'a SizeAlign,
151
152 /// Method of error handling
153 pub err: ErrHandling,
154
155 /// Temporary values
156 pub tmp: usize,
157
158 /// Source code of the function
159 pub src: source::Source,
160
161 /// Block storage
162 pub block_storage: Vec<source::Source>,
163
164 /// Blocks of the function
165 pub blocks: Vec<(String, Vec<String>)>,
166
167 /// Parameters of the function
168 pub params: Vec<String>,
169
170 /// Memory variable
171 pub memory: Option<&'a String>,
172
173 /// Realloc function name
174 pub realloc: Option<&'a String>,
175
176 /// Post return function name
177 pub post_return: Option<&'a String>,
178
179 /// Prefix to use when printing tracing information
180 pub tracing_prefix: &'a String,
181
182 /// Whether tracing is enabled
183 pub tracing_enabled: bool,
184
185 /// Method if string encoding
186 pub encoding: StringEncoding,
187
188 /// Callee of the function
189 pub callee: &'a str,
190
191 /// Whether the callee is dynamic (i.e. has multiple operands)
192 pub callee_resource_dynamic: bool,
193
194 /// The [`wit_bindgen::Resolve`] containing extracted WIT information
195 pub resolve: &'a Resolve,
196
197 /// Whether the function requires async porcelain
198 ///
199 /// In the case of an import this likely implies the use of JSPI
200 /// and in the case of an export this is simply code generation metadata.
201 pub requires_async_porcelain: bool,
202
203 /// Whether the function is guest async lifted (i.e. WASI P3)
204 pub is_async: bool,
205
206 /// Canon opts
207 pub canon_opts: &'a CanonicalOptions,
208
209 /// Interface name
210 pub iface_name: Option<&'a str>,
211
212 /// Whether the callee was transpiled from Wasm to JS (asm.js) and thus needs shimming for i64
213 pub asmjs: bool,
214}
215
216impl FunctionBindgen<'_> {
217 fn tmp(&mut self) -> usize {
218 let ret = self.tmp;
219 self.tmp += 1;
220 ret
221 }
222
223 fn intrinsic(&mut self, intrinsic: Intrinsic) -> String {
224 self.intrinsics.insert(intrinsic);
225 intrinsic.name().to_string()
226 }
227
228 fn clamp_guest<T>(&mut self, results: &mut Vec<String>, operands: &[String], min: T, max: T)
229 where
230 T: std::fmt::Display,
231 {
232 let clamp = self.intrinsic(Intrinsic::ClampGuest);
233 results.push(format!("{}({}, {}, {})", clamp, operands[0], min, max));
234 }
235
236 fn load(
237 &mut self,
238 method: &str,
239 offset: ArchitectureSize,
240 operands: &[String],
241 results: &mut Vec<String>,
242 ) {
243 let view = self.intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::DataView));
244 let Some(memory) = self.memory.as_ref() else {
245 panic!(
246 "unexpectedly missing memory during bindgen for interface [{:?}] (callee {})",
247 self.iface_name, self.callee,
248 );
249 };
250 results.push(format!(
251 "{view}({memory}).{method}({} + {offset}, true)",
252 operands[0],
253 offset = offset.size_wasm32()
254 ));
255 }
256
257 fn store(&mut self, method: &str, offset: ArchitectureSize, operands: &[String]) {
258 let view = self.intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::DataView));
259 let memory = self.memory.as_ref().unwrap();
260 uwriteln!(
261 self.src,
262 "{view}({memory}).{method}({} + {offset}, {}, true);",
263 operands[1],
264 operands[0],
265 offset = offset.size_wasm32()
266 );
267 }
268
269 /// Write result assignment lines to output
270 ///
271 /// In general this either means writing preambles, for example that look like the following:
272 ///
273 /// ```js
274 /// let ret =
275 /// ```
276 ///
277 /// ```
278 /// var [ ret0, ret1, ret2 ] =
279 /// ```
280 ///
281 /// ```js
282 /// let ret;
283 /// ```
284 ///
285 /// This function returns as a first tuple parameter a list of
286 /// variables that should be created via let statements beforehand.
287 ///
288 /// # Arguments
289 ///
290 /// * `amt` - number of results
291 /// * `results` - list of variables that will be returned
292 ///
293 fn generate_result_assignment_lhs(
294 &mut self,
295 amt: usize,
296 results: &mut Vec<String>,
297 is_async: bool,
298 ) -> (String, String) {
299 let mut s = String::new();
300 let mut vars_init = String::new();
301 match amt {
302 0 => {
303 // Async functions with no returns still return async code,
304 // which will be used as the initial callback result going into the async driver
305 if is_async {
306 uwrite!(s, "ret = ")
307 }
308 uwriteln!(vars_init, "let ret;");
309 }
310 1 => {
311 uwrite!(s, "ret = ");
312 results.push("ret".to_string());
313 uwriteln!(vars_init, "let ret;");
314 }
315 n => {
316 uwrite!(s, "[");
317 for i in 0..n {
318 if i > 0 {
319 uwrite!(s, ", ");
320 }
321 uwrite!(s, "ret{i}");
322 results.push(format!("ret{i}"));
323 uwriteln!(vars_init, "let ret;");
324 }
325 uwrite!(s, "] = ");
326 }
327 }
328 (vars_init, s)
329 }
330
331 fn bitcast(&mut self, cast: &Bitcast, op: &str) -> String {
332 match cast {
333 Bitcast::I32ToF32 => {
334 let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::I32ToF32));
335 format!("{cvt}({op})")
336 }
337 Bitcast::F32ToI32 => {
338 let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::F32ToI32));
339 format!("{cvt}({op})")
340 }
341 Bitcast::I64ToF64 => {
342 let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::I64ToF64));
343 format!("{cvt}({op})")
344 }
345 Bitcast::F64ToI64 => {
346 let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::F64ToI64));
347 format!("{cvt}({op})")
348 }
349 Bitcast::I32ToI64 => format!("BigInt({op})"),
350 Bitcast::I64ToI32 => format!("Number({op})"),
351 Bitcast::I64ToF32 => {
352 let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::I32ToF32));
353 format!("{cvt}(Number({op}))")
354 }
355 Bitcast::F32ToI64 => {
356 let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::F32ToI32));
357 format!("BigInt({cvt}({op}))")
358 }
359 Bitcast::None
360 | Bitcast::P64ToI64
361 | Bitcast::LToI32
362 | Bitcast::I32ToL
363 | Bitcast::LToP
364 | Bitcast::PToL
365 | Bitcast::PToI32
366 | Bitcast::I32ToP => op.to_string(),
367 Bitcast::PToP64 | Bitcast::I64ToP64 | Bitcast::LToI64 => format!("BigInt({op})"),
368 Bitcast::P64ToP | Bitcast::I64ToL => format!("Number({op})"),
369 Bitcast::Sequence(casts) => {
370 let mut statement = op.to_string();
371 for cast in casts.iter() {
372 statement = self.bitcast(cast, &statement);
373 }
374 statement
375 }
376 }
377 }
378
379 /// Start the current task
380 ///
381 /// The code generated by this function *may* also start a subtask
382 /// where appropriate.
383 fn start_current_task(&mut self, instr: &Instruction) {
384 let is_async = self.is_async;
385 let is_manual_async = self.requires_async_porcelain;
386 let fn_name = self.callee;
387 let err_handling = self.err.to_js_string();
388 let callback_fn_js = self
389 .canon_opts
390 .callback
391 .as_ref()
392 .map(|v| format!("callback_{}", v.as_u32()))
393 .unwrap_or_else(|| "null".into());
394 let (calling_wasm_export, prefix) = match instr {
395 Instruction::CallWasm { .. } => (true, "_wasm_call_"),
396 Instruction::CallInterface { .. } => (false, "_interface_call_"),
397 _ => unreachable!(
398 "unrecognized instruction triggering start of current task: [{instr:?}]"
399 ),
400 };
401 let start_current_task_fn = self.intrinsic(Intrinsic::AsyncTask(
402 AsyncTaskIntrinsic::CreateNewCurrentTask,
403 ));
404 let component_instance_idx = self.canon_opts.instance.as_u32();
405
406 uwriteln!(
407 self.src,
408 r#"
409 const [task, {prefix}currentTaskID] = {start_current_task_fn}({{
410 componentIdx: {component_instance_idx},
411 isAsync: {is_async},
412 isManualAsync: {is_manual_async},
413 entryFnName: '{fn_name}',
414 getCallbackFn: () => {callback_fn_js},
415 callbackFnName: '{callback_fn_js}',
416 errHandling: '{err_handling}',
417 callingWasmExport: {calling_wasm_export},
418 }});
419 "#,
420 );
421 }
422}
423
424impl ManagesIntrinsics for FunctionBindgen<'_> {
425 /// Add an intrinsic, supplying it's name afterwards
426 fn add_intrinsic(&mut self, intrinsic: Intrinsic) {
427 self.intrinsic(intrinsic);
428 }
429}
430
431impl Bindgen for FunctionBindgen<'_> {
432 type Operand = String;
433
434 /// Get the sizes and alignment for a given structure
435 fn sizes(&self) -> &SizeAlign {
436 self.sizes
437 }
438
439 /// Push a new block of code
440 fn push_block(&mut self) {
441 let prev = mem::take(&mut self.src);
442 self.block_storage.push(prev);
443 }
444
445 /// Finish a block of code
446 fn finish_block(&mut self, operands: &mut Vec<String>) {
447 let to_restore = self.block_storage.pop().unwrap();
448 let src = mem::replace(&mut self.src, to_restore);
449 self.blocks.push((src.into(), mem::take(operands)));
450 }
451
452 /// Output the return pointer
453 fn return_pointer(&mut self, _size: ArchitectureSize, _align: Alignment) -> String {
454 unimplemented!("determining the return pointer for this function is not implemented");
455 }
456
457 /// Check whether a list of the given element type can be represented as a builtni JS type
458 ///
459 /// # Arguments
460 ///
461 /// * `resolve` - the [`Resolve`] that might be used to resolve nested types (i.e. [`Type::TypeId`])
462 /// * `elem_ty` - the [`Type`] of the element stored in the list
463 ///
464 fn is_list_canonical(&self, resolve: &Resolve, elem_ty: &Type) -> bool {
465 js_array_ty(resolve, elem_ty).is_some()
466 }
467
468 fn emit(
469 &mut self,
470 resolve: &Resolve,
471 inst: &Instruction<'_>,
472 operands: &mut Vec<String>,
473 results: &mut Vec<String>,
474 ) {
475 match inst {
476 Instruction::GetArg { nth } => results.push(self.params[*nth].clone()),
477
478 Instruction::I32Const { val } => results.push(val.to_string()),
479
480 Instruction::ConstZero { tys } => {
481 for t in tys.iter() {
482 match t {
483 WasmType::I64 | WasmType::PointerOrI64 => results.push("0n".to_string()),
484 WasmType::I32
485 | WasmType::F32
486 | WasmType::F64
487 | WasmType::Pointer
488 | WasmType::Length => results.push("0".to_string()),
489 }
490 }
491 }
492
493 Instruction::U8FromI32 => self.clamp_guest(results, operands, u8::MIN, u8::MAX),
494
495 Instruction::S8FromI32 => self.clamp_guest(results, operands, i8::MIN, i8::MAX),
496
497 Instruction::U16FromI32 => self.clamp_guest(results, operands, u16::MIN, u16::MAX),
498
499 Instruction::S16FromI32 => self.clamp_guest(results, operands, i16::MIN, i16::MAX),
500
501 Instruction::U32FromI32 => results.push(format!("{} >>> 0", operands[0])),
502
503 Instruction::U64FromI64 => {
504 results.push(format!("BigInt.asUintN(64, BigInt({}))", operands[0]))
505 }
506
507 Instruction::S32FromI32 | Instruction::S64FromI64 => {
508 results.push(operands.pop().unwrap())
509 }
510
511 Instruction::I32FromU8 => {
512 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToUint8));
513 results.push(format!("{conv}({op})", op = operands[0]))
514 }
515
516 Instruction::I32FromS8 => {
517 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToInt8));
518 results.push(format!("{conv}({op})", op = operands[0]))
519 }
520
521 Instruction::I32FromU16 => {
522 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToUint16));
523 results.push(format!("{conv}({op})", op = operands[0]))
524 }
525
526 Instruction::I32FromS16 => {
527 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToInt16));
528 results.push(format!("{conv}({op})", op = operands[0]))
529 }
530
531 Instruction::I32FromU32 => {
532 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToUint32));
533 results.push(format!("{conv}({op})", op = operands[0]))
534 }
535
536 Instruction::I32FromS32 => {
537 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToInt32));
538 results.push(format!("{conv}({op})", op = operands[0]))
539 }
540
541 Instruction::I64FromU64 => {
542 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToBigUint64));
543 results.push(format!("{conv}({op})", op = operands[0]))
544 }
545
546 Instruction::I64FromS64 => {
547 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToBigInt64));
548 results.push(format!("{conv}({op})", op = operands[0]))
549 }
550
551 Instruction::F32FromCoreF32 | Instruction::F64FromCoreF64 => {
552 results.push(operands.pop().unwrap())
553 }
554
555 Instruction::CoreF32FromF32 | Instruction::CoreF64FromF64 => {
556 results.push(format!("+{}", operands[0]))
557 }
558
559 Instruction::CharFromI32 => {
560 let validate =
561 self.intrinsic(Intrinsic::String(StringIntrinsic::ValidateGuestChar));
562 results.push(format!("{}({})", validate, operands[0]));
563 }
564
565 Instruction::I32FromChar => {
566 let validate = self.intrinsic(Intrinsic::String(StringIntrinsic::ValidateHostChar));
567 results.push(format!("{}({})", validate, operands[0]));
568 }
569
570 Instruction::Bitcasts { casts } => {
571 for (cast, op) in casts.iter().zip(operands) {
572 results.push(self.bitcast(cast, op));
573 }
574 }
575
576 Instruction::BoolFromI32 => {
577 let tmp = self.tmp();
578 uwrite!(self.src, "var bool{} = {};\n", tmp, operands[0]);
579 if self.valid_lifting_optimization {
580 results.push(format!("!!bool{tmp}"));
581 } else {
582 let throw = self.intrinsic(Intrinsic::ThrowInvalidBool);
583 results.push(format!(
584 "bool{tmp} == 0 ? false : (bool{tmp} == 1 ? true : {throw}())"
585 ));
586 }
587 }
588
589 Instruction::I32FromBool => {
590 results.push(format!("{} ? 1 : 0", operands[0]));
591 }
592
593 Instruction::RecordLower { record, .. } => {
594 // use destructuring field access to get each
595 // field individually.
596 let tmp = self.tmp();
597 let mut expr = "var {".to_string();
598 for (i, field) in record.fields.iter().enumerate() {
599 if i > 0 {
600 expr.push_str(", ");
601 }
602 let name = format!("v{tmp}_{i}");
603 expr.push_str(&field.name.to_lower_camel_case());
604 expr.push_str(": ");
605 expr.push_str(&name);
606 results.push(name);
607 }
608 uwrite!(self.src, "{} }} = {};\n", expr, operands[0]);
609 }
610
611 Instruction::RecordLift { record, .. } => {
612 // records are represented as plain objects, so we
613 // make a new object and set all the fields with an object
614 // literal.
615 let mut result = "{\n".to_string();
616 for (field, op) in record.fields.iter().zip(operands) {
617 result.push_str(&format!("{}: {},\n", field.name.to_lower_camel_case(), op));
618 }
619 result.push('}');
620 results.push(result);
621 }
622
623 Instruction::TupleLower { tuple, .. } => {
624 // Tuples are represented as an array, sowe can use
625 // destructuring assignment to lower the tuple into its
626 // components.
627 let tmp = self.tmp();
628 let mut expr = "var [".to_string();
629 for i in 0..tuple.types.len() {
630 if i > 0 {
631 expr.push_str(", ");
632 }
633 let name = format!("tuple{tmp}_{i}");
634 expr.push_str(&name);
635 results.push(name);
636 }
637 uwrite!(self.src, "{}] = {};\n", expr, operands[0]);
638 }
639
640 Instruction::TupleLift { .. } => {
641 // Tuples are represented as an array, so we just shove all
642 // the operands into an array.
643 results.push(format!("[{}]", operands.join(", ")));
644 }
645
646 Instruction::FlagsLower { flags, .. } => {
647 let op0 = &operands[0];
648
649 // Generate the result names.
650 for _ in 0..flags.repr().count() {
651 let tmp = self.tmp();
652 let name = format!("flags{tmp}");
653 // Default to 0 so that in the null/undefined case, everything is false by
654 // default.
655 uwrite!(self.src, "let {name} = 0;\n");
656 results.push(name);
657 }
658
659 uwrite!(
660 self.src,
661 "if (typeof {op0} === 'object' && {op0} !== null) {{\n"
662 );
663
664 for (i, chunk) in flags.flags.chunks(32).enumerate() {
665 let result_name = &results[i];
666
667 uwrite!(self.src, "{result_name} = ");
668 for (i, flag) in chunk.iter().enumerate() {
669 if i != 0 {
670 uwrite!(self.src, " | ");
671 }
672
673 let flag = flag.name.to_lower_camel_case();
674 uwrite!(self.src, "Boolean({op0}.{flag}) << {i}");
675 }
676 uwrite!(self.src, ";\n");
677 }
678
679 uwrite!(
680 self.src,
681 "\
682 }} else if ({op0} !== null && {op0} !== undefined) {{
683 throw new TypeError('only an object, undefined or null can be converted to flags');
684 }}
685 ");
686
687 // We don't need to do anything else for the null/undefined
688 // case, since that's interpreted as everything false, and we
689 // already defaulted everyting to 0.
690 }
691
692 Instruction::FlagsLift { flags, .. } => {
693 let tmp = self.tmp();
694 results.push(format!("flags{tmp}"));
695
696 if let Some(op) = operands.last() {
697 // We only need an extraneous bits check if the number of flags isn't a multiple
698 // of 32, because if it is then all the bits are used and there are no
699 // extraneous bits.
700 if flags.flags.len() % 32 != 0 && !self.valid_lifting_optimization {
701 let mask: u32 = 0xffffffff << (flags.flags.len() % 32);
702 uwriteln!(
703 self.src,
704 "if (({op} & {mask}) !== 0) {{
705 throw new TypeError('flags have extraneous bits set');
706 }}"
707 );
708 }
709 }
710
711 uwriteln!(self.src, "var flags{tmp} = {{");
712
713 for (i, flag) in flags.flags.iter().enumerate() {
714 let flag = flag.name.to_lower_camel_case();
715 let op = &operands[i / 32];
716 let mask: u32 = 1 << (i % 32);
717 uwriteln!(self.src, "{flag}: Boolean({op} & {mask}),");
718 }
719
720 uwriteln!(self.src, "}};");
721 }
722
723 Instruction::VariantPayloadName => results.push("e".to_string()),
724
725 Instruction::VariantLower {
726 variant,
727 results: result_types,
728 name,
729 ..
730 } => {
731 let blocks = self
732 .blocks
733 .drain(self.blocks.len() - variant.cases.len()..)
734 .collect::<Vec<_>>();
735 let tmp = self.tmp();
736 let op = &operands[0];
737 uwriteln!(self.src, "var variant{tmp} = {op};");
738
739 for i in 0..result_types.len() {
740 uwriteln!(self.src, "let variant{tmp}_{i};");
741 results.push(format!("variant{tmp}_{i}"));
742 }
743
744 let expr_to_match = format!("variant{tmp}.tag");
745
746 uwriteln!(self.src, "switch ({expr_to_match}) {{");
747 for (case, (block, block_results)) in variant.cases.iter().zip(blocks) {
748 uwriteln!(self.src, "case '{}': {{", case.name.as_str());
749 if case.ty.is_some() {
750 uwriteln!(self.src, "const e = variant{tmp}.val;");
751 }
752 self.src.push_str(&block);
753
754 for (i, result) in block_results.iter().enumerate() {
755 uwriteln!(self.src, "variant{tmp}_{i} = {result};");
756 }
757 uwriteln!(
758 self.src,
759 "break;
760 }}"
761 );
762 }
763 let variant_name = name.to_upper_camel_case();
764 uwriteln!(
765 self.src,
766 r#"default: {{
767 throw new TypeError(`invalid variant tag value \`${{JSON.stringify({expr_to_match})}}\` (received \`${{variant{tmp}}}\`) specified for \`{variant_name}\``);
768 }}"#,
769 );
770 uwriteln!(self.src, "}}");
771 }
772
773 Instruction::VariantLift { variant, name, .. } => {
774 let blocks = self
775 .blocks
776 .drain(self.blocks.len() - variant.cases.len()..)
777 .collect::<Vec<_>>();
778
779 let tmp = self.tmp();
780 let op = &operands[0];
781
782 uwriteln!(
783 self.src,
784 "let variant{tmp};
785 switch ({op}) {{"
786 );
787
788 for (i, (case, (block, block_results))) in
789 variant.cases.iter().zip(blocks).enumerate()
790 {
791 let tag = case.name.as_str();
792 uwriteln!(
793 self.src,
794 "case {i}: {{
795 {block}\
796 variant{tmp} = {{
797 tag: '{tag}',"
798 );
799 if case.ty.is_some() {
800 assert!(block_results.len() == 1);
801 uwriteln!(self.src, " val: {}", block_results[0]);
802 } else {
803 assert!(block_results.is_empty());
804 }
805 uwriteln!(
806 self.src,
807 " }};
808 break;
809 }}"
810 );
811 }
812 let variant_name = name.to_upper_camel_case();
813 if !self.valid_lifting_optimization {
814 uwriteln!(
815 self.src,
816 "default: {{
817 throw new TypeError('invalid variant discriminant for {variant_name}');
818 }}",
819 );
820 }
821 uwriteln!(self.src, "}}");
822 results.push(format!("variant{tmp}"));
823 }
824
825 Instruction::OptionLower {
826 payload,
827 results: result_types,
828 ..
829 } => {
830 let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
831 let (mut some, some_results) = self.blocks.pop().unwrap();
832 let (mut none, none_results) = self.blocks.pop().unwrap();
833
834 let tmp = self.tmp();
835 let op = &operands[0];
836 uwriteln!(self.src, "var variant{tmp} = {op};");
837
838 for i in 0..result_types.len() {
839 uwriteln!(self.src, "let variant{tmp}_{i};");
840 results.push(format!("variant{tmp}_{i}"));
841
842 let some_result = &some_results[i];
843 let none_result = &none_results[i];
844 uwriteln!(some, "variant{tmp}_{i} = {some_result};");
845 uwriteln!(none, "variant{tmp}_{i} = {none_result};");
846 }
847
848 if maybe_null(resolve, payload) {
849 uwriteln!(
850 self.src,
851 r#"switch (variant{tmp}.tag) {{
852 case 'none': {{
853 {none}
854 break;
855 }}
856 case 'some': {{
857 const e = variant{tmp}.val;
858 {some}
859 break;
860 }}
861 default: {{
862 {debug_log_fn}("ERROR: invalid value (expected option as object with 'tag' member)", {{ value: variant{tmp}, valueType: typeof variant{tmp} }});
863 throw new TypeError('invalid variant specified for option');
864 }}
865 }}"#,
866 );
867 } else {
868 uwriteln!(
869 self.src,
870 "if (variant{tmp} === null || variant{tmp} === undefined) {{
871 {none}\
872 }} else {{
873 const e = variant{tmp};
874 {some}\
875 }}"
876 );
877 }
878 }
879
880 Instruction::OptionLift { payload, .. } => {
881 let (some, some_results) = self.blocks.pop().unwrap();
882 let (none, none_results) = self.blocks.pop().unwrap();
883 assert!(none_results.is_empty());
884 assert!(some_results.len() == 1);
885 let some_result = &some_results[0];
886
887 let tmp = self.tmp();
888 let op = &operands[0];
889
890 let (v_none, v_some) = if maybe_null(resolve, payload) {
891 (
892 "{ tag: 'none' }",
893 format!(
894 "{{
895 tag: 'some',
896 val: {some_result}
897 }}"
898 ),
899 )
900 } else {
901 ("undefined", some_result.into())
902 };
903
904 if !self.valid_lifting_optimization {
905 uwriteln!(
906 self.src,
907 "let variant{tmp};
908 switch ({op}) {{
909 case 0: {{
910 {none}\
911 variant{tmp} = {v_none};
912 break;
913 }}
914 case 1: {{
915 {some}\
916 variant{tmp} = {v_some};
917 break;
918 }}
919 default: {{
920 throw new TypeError('invalid variant discriminant for option');
921 }}
922 }}",
923 );
924 } else {
925 uwriteln!(
926 self.src,
927 "let variant{tmp};
928 if ({op}) {{
929 {some}\
930 variant{tmp} = {v_some};
931 }} else {{
932 {none}\
933 variant{tmp} = {v_none};
934 }}"
935 );
936 }
937
938 results.push(format!("variant{tmp}"));
939 }
940
941 Instruction::ResultLower {
942 results: result_types,
943 ..
944 } => {
945 let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
946 let (mut err, err_results) = self.blocks.pop().unwrap();
947 let (mut ok, ok_results) = self.blocks.pop().unwrap();
948
949 let tmp = self.tmp();
950 let op = &operands[0];
951 uwriteln!(self.src, "var variant{tmp} = {op};");
952
953 for i in 0..result_types.len() {
954 uwriteln!(self.src, "let variant{tmp}_{i};");
955 results.push(format!("variant{tmp}_{i}"));
956
957 let ok_result = &ok_results[i];
958 let err_result = &err_results[i];
959 uwriteln!(ok, "variant{tmp}_{i} = {ok_result};");
960 uwriteln!(err, "variant{tmp}_{i} = {err_result};");
961 }
962
963 uwriteln!(
964 self.src,
965 r#"switch (variant{tmp}.tag) {{
966 case 'ok': {{
967 const e = variant{tmp}.val;
968 {ok}
969 break;
970 }}
971 case 'err': {{
972 const e = variant{tmp}.val;
973 {err}
974 break;
975 }}
976 default: {{
977 {debug_log_fn}("ERROR: invalid value (expected result as object with 'tag' member)", {{ value: variant{tmp}, valueType: typeof variant{tmp} }});
978 throw new TypeError('invalid variant specified for result');
979 }}
980 }}"#,
981 );
982 }
983
984 Instruction::ResultLift { result, .. } => {
985 let (err, err_results) = self.blocks.pop().unwrap();
986 let (ok, ok_results) = self.blocks.pop().unwrap();
987 let ok_result = if result.ok.is_some() {
988 assert_eq!(ok_results.len(), 1);
989 ok_results[0].to_string()
990 } else {
991 assert_eq!(ok_results.len(), 0);
992 String::from("undefined")
993 };
994 let err_result = if result.err.is_some() {
995 assert_eq!(err_results.len(), 1);
996 err_results[0].to_string()
997 } else {
998 assert_eq!(err_results.len(), 0);
999 String::from("undefined")
1000 };
1001 let tmp = self.tmp();
1002 let op0 = &operands[0];
1003
1004 if !self.valid_lifting_optimization {
1005 uwriteln!(
1006 self.src,
1007 "let variant{tmp};
1008 switch ({op0}) {{
1009 case 0: {{
1010 {ok}\
1011 variant{tmp} = {{
1012 tag: 'ok',
1013 val: {ok_result}
1014 }};
1015 break;
1016 }}
1017 case 1: {{
1018 {err}\
1019 variant{tmp} = {{
1020 tag: 'err',
1021 val: {err_result}
1022 }};
1023 break;
1024 }}
1025 default: {{
1026 throw new TypeError('invalid variant discriminant for expected');
1027 }}
1028 }}",
1029 );
1030 } else {
1031 uwriteln!(
1032 self.src,
1033 "let variant{tmp};
1034 if ({op0}) {{
1035 {err}\
1036 variant{tmp} = {{
1037 tag: 'err',
1038 val: {err_result}
1039 }};
1040 }} else {{
1041 {ok}\
1042 variant{tmp} = {{
1043 tag: 'ok',
1044 val: {ok_result}
1045 }};
1046 }}"
1047 );
1048 }
1049 results.push(format!("variant{tmp}"));
1050 }
1051
1052 Instruction::EnumLower { name, enum_, .. } => {
1053 let tmp = self.tmp();
1054
1055 let op = &operands[0];
1056 uwriteln!(self.src, "var val{tmp} = {op};");
1057
1058 // Declare a variable to hold the result.
1059 uwriteln!(
1060 self.src,
1061 "let enum{tmp};
1062 switch (val{tmp}) {{"
1063 );
1064 for (i, case) in enum_.cases.iter().enumerate() {
1065 uwriteln!(
1066 self.src,
1067 "case '{case}': {{
1068 enum{tmp} = {i};
1069 break;
1070 }}",
1071 case = case.name
1072 );
1073 }
1074 uwriteln!(self.src, "default: {{");
1075 if !self.valid_lifting_optimization {
1076 uwriteln!(
1077 self.src,
1078 "if (({op}) instanceof Error) {{
1079 console.error({op});
1080 }}"
1081 );
1082 }
1083 uwriteln!(
1084 self.src,
1085 "
1086 throw new TypeError(`\"${{val{tmp}}}\" is not one of the cases of {name}`);
1087 }}
1088 }}",
1089 );
1090
1091 results.push(format!("enum{tmp}"));
1092 }
1093
1094 Instruction::EnumLift { name, enum_, .. } => {
1095 let tmp = self.tmp();
1096
1097 uwriteln!(
1098 self.src,
1099 "let enum{tmp};
1100 switch ({}) {{",
1101 operands[0]
1102 );
1103 for (i, case) in enum_.cases.iter().enumerate() {
1104 uwriteln!(
1105 self.src,
1106 "case {i}: {{
1107 enum{tmp} = '{case}';
1108 break;
1109 }}",
1110 case = case.name
1111 );
1112 }
1113 if !self.valid_lifting_optimization {
1114 let name = name.to_upper_camel_case();
1115 uwriteln!(
1116 self.src,
1117 "default: {{
1118 throw new TypeError('invalid discriminant specified for {name}');
1119 }}",
1120 );
1121 }
1122 uwriteln!(self.src, "}}");
1123
1124 results.push(format!("enum{tmp}"));
1125 }
1126
1127 // The ListCanonLower instruction is called on async function parameter lowers,
1128 // which are separated in memory by one pointer follow.
1129 //
1130 // We ignore `realloc` in the instruction because it's the name of the *import* from the
1131 // component's side (i.e. `"cabi_realloc"`). Bindings have already set up the appropriate
1132 // realloc for the current component (e.g. `realloc0`) and it is available in the bindgen
1133 // object @ `self.realloc`
1134 //
1135 // Note that this can be called *inside* a "regular" ListCanonLower, for example
1136 // when a list of lists or list of Uint8Arrays is sent.
1137 //
1138 Instruction::ListCanonLower { element, .. } => {
1139 let tmp = self.tmp();
1140 let memory = self.memory.as_ref().unwrap();
1141 let realloc = self.realloc.unwrap();
1142
1143 // Alias the list to a local variable
1144 uwriteln!(self.src, "var val{tmp} = {};", operands[0]);
1145 if matches!(element, Type::U8) {
1146 uwriteln!(
1147 self.src,
1148 "var len{tmp} = Array.isArray(val{tmp}) ? val{tmp}.length : val{tmp}.byteLength;"
1149 );
1150 } else {
1151 uwriteln!(self.src, "var len{tmp} = val{tmp}.length;");
1152 }
1153
1154 // Gather metadata about list element
1155 let size = self.sizes.size(element).size_wasm32();
1156 let align = self.sizes.align(element).align_wasm32();
1157
1158 // Allocate space for the type in question
1159 uwriteln!(
1160 self.src,
1161 "var ptr{tmp} = {realloc_call}(0, 0, {align}, len{tmp} * {size});",
1162 realloc_call = if self.is_async {
1163 format!("await {realloc}")
1164 } else {
1165 realloc.to_string()
1166 },
1167 );
1168
1169 // Determine what methods to use with a DataView when setting the data
1170 let (dataview_set_method, check_fn_intrinsic) =
1171 gen_dataview_set_and_check_fn_js_for_numeric_type(element);
1172
1173 // Detect whether we're dealing with a regular array
1174 uwriteln!(
1175 self.src,
1176 r#"
1177 let valData{tmp};
1178 const valLenBytes{tmp} = len{tmp} * {size};
1179 if (Array.isArray(val{tmp})) {{
1180 // Regular array likely containing numbers, write values to memory
1181 let offset = 0;
1182 const dv{tmp} = new DataView({memory}.buffer);
1183 for (const v of val{tmp}) {{
1184 {check_fn_intrinsic}(v);
1185 dv{tmp}.{dataview_set_method}(ptr{tmp} + offset, v, true);
1186 offset += {size};
1187 }}
1188 }} else {{
1189 // TypedArray / ArrayBuffer-like, direct copy
1190 valData{tmp} = new Uint8Array(val{tmp}.buffer || val{tmp}, val{tmp}.byteOffset, valLenBytes{tmp});
1191 const out{tmp} = new Uint8Array({memory}.buffer, ptr{tmp}, valLenBytes{tmp});
1192 out{tmp}.set(valData{tmp});
1193 }}
1194 "#,
1195 );
1196
1197 results.push(format!("ptr{tmp}"));
1198 results.push(format!("len{tmp}"));
1199 }
1200
1201 Instruction::ListCanonLift { element, .. } => {
1202 let tmp = self.tmp();
1203 let memory = self.memory.as_ref().unwrap();
1204 uwriteln!(self.src, "var ptr{tmp} = {};", operands[0]);
1205 uwriteln!(self.src, "var len{tmp} = {};", operands[1]);
1206 uwriteln!(
1207 self.src,
1208 "var result{tmp} = new {array_ty}({memory}.buffer.slice(ptr{tmp}, ptr{tmp} + len{tmp} * {elem_size}));",
1209 elem_size = self.sizes.size(element).size_wasm32(),
1210 array_ty = js_array_ty(resolve, element).unwrap(), // TODO: this is the wrong endianness
1211 );
1212 results.push(format!("result{tmp}"));
1213 }
1214
1215 Instruction::StringLower { .. } => {
1216 // Only Utf8 and Utf16 supported for now
1217 assert!(matches!(
1218 self.encoding,
1219 StringEncoding::UTF8 | StringEncoding::UTF16
1220 ));
1221
1222 let (call_prefix, encode_intrinsic) = match (self.encoding, self.is_async) {
1223 (StringEncoding::UTF16, true) => (
1224 "await ",
1225 Intrinsic::String(StringIntrinsic::Utf16EncodeAsync),
1226 ),
1227 (StringEncoding::UTF16, false) => {
1228 ("", Intrinsic::String(StringIntrinsic::Utf16Encode))
1229 }
1230 (StringEncoding::UTF8, true) => (
1231 "await ",
1232 Intrinsic::String(StringIntrinsic::Utf8EncodeAsync),
1233 ),
1234 (StringEncoding::UTF8, false) => {
1235 ("", Intrinsic::String(StringIntrinsic::Utf8Encode))
1236 }
1237 _ => unreachable!("unsupported encoding {}", self.encoding),
1238 };
1239 let encode = self.intrinsic(encode_intrinsic);
1240
1241 let tmp = self.tmp();
1242 let memory = self.memory.as_ref().unwrap();
1243 let str = String::from("cabi_realloc");
1244 let realloc = self.realloc.unwrap_or(&str);
1245 let s = &operands[0];
1246 uwriteln!(
1247 self.src,
1248 r#"
1249 var encodeRes = {call_prefix}{encode}({s}, {realloc}, {memory});
1250 var ptr{tmp} = encodeRes.ptr;
1251 var len{tmp} = {encoded_len};
1252 "#,
1253 encoded_len = match self.encoding {
1254 StringEncoding::UTF8 => "encodeRes.len".into(),
1255 _ => format!("{}.length", s),
1256 }
1257 );
1258 results.push(format!("ptr{tmp}"));
1259 results.push(format!("len{tmp}"));
1260 }
1261
1262 Instruction::StringLift => {
1263 // Only Utf8 and Utf16 supported for now
1264 assert!(matches!(
1265 self.encoding,
1266 StringEncoding::UTF8 | StringEncoding::UTF16
1267 ));
1268 let decoder = self.intrinsic(match self.encoding {
1269 StringEncoding::UTF16 => Intrinsic::String(StringIntrinsic::Utf16Decoder),
1270 _ => Intrinsic::String(StringIntrinsic::GlobalTextDecoderUtf8),
1271 });
1272 let tmp = self.tmp();
1273 let memory = self.memory.as_ref().unwrap();
1274 uwriteln!(self.src, "var ptr{tmp} = {};", operands[0]);
1275 uwriteln!(self.src, "var len{tmp} = {};", operands[1]);
1276 uwriteln!(
1277 self.src,
1278 "var result{tmp} = {decoder}.decode(new Uint{}Array({memory}.buffer, ptr{tmp}, len{tmp}));",
1279 if self.encoding == StringEncoding::UTF16 {
1280 "16"
1281 } else {
1282 "8"
1283 }
1284 );
1285 results.push(format!("result{tmp}"));
1286 }
1287
1288 Instruction::ListLower { element, .. } => {
1289 let (body, body_results) = self.blocks.pop().unwrap();
1290 assert!(body_results.is_empty());
1291 let tmp = self.tmp();
1292 let vec = format!("vec{tmp}");
1293 let result = format!("result{tmp}");
1294 let len = format!("len{tmp}");
1295 let size = self.sizes.size(element).size_wasm32();
1296 let align = ArchitectureSize::from(self.sizes.align(element)).size_wasm32();
1297
1298 // first store our vec-to-lower in a temporary since we'll
1299 // reference it multiple times.
1300 uwriteln!(self.src, "var {vec} = {};", operands[0]);
1301 uwriteln!(self.src, "var {len} = {vec}.length;");
1302
1303 // ... then realloc space for the result in the guest module
1304 let realloc = self.realloc.as_ref().unwrap();
1305 uwriteln!(
1306 self.src,
1307 "var {result} = {realloc_call}(0, 0, {align}, {len} * {size});",
1308 realloc_call = if self.is_async {
1309 format!("await {realloc}")
1310 } else {
1311 realloc.to_string()
1312 },
1313 );
1314
1315 // ... then consume the vector and use the block to lower the
1316 // result.
1317 uwriteln!(self.src, "for (let i = 0; i < {vec}.length; i++) {{");
1318 uwriteln!(self.src, "const e = {vec}[i];");
1319 uwrite!(self.src, "const base = {result} + i * {size};");
1320 self.src.push_str(&body);
1321 uwrite!(self.src, "}}\n");
1322
1323 results.push(result);
1324 results.push(len);
1325 }
1326
1327 Instruction::ListLift { element, .. } => {
1328 let (body, body_results) = self.blocks.pop().unwrap();
1329 let tmp = self.tmp();
1330 let size = self.sizes.size(element).size_wasm32();
1331 let len = format!("len{tmp}");
1332 uwriteln!(self.src, "var {len} = {};", operands[1]);
1333 let base = format!("base{tmp}");
1334 uwriteln!(self.src, "var {base} = {};", operands[0]);
1335 let result = format!("result{tmp}");
1336 uwriteln!(self.src, "var {result} = [];");
1337 results.push(result.clone());
1338
1339 uwriteln!(self.src, "for (let i = 0; i < {len}; i++) {{");
1340 uwriteln!(self.src, "const base = {base} + i * {size};");
1341 self.src.push_str(&body);
1342 assert_eq!(body_results.len(), 1);
1343 uwriteln!(self.src, "{result}.push({});", body_results[0]);
1344 uwrite!(self.src, "}}\n");
1345 }
1346
1347 Instruction::FixedLengthListLower { size, .. } => {
1348 let tmp = self.tmp();
1349 let array = format!("array{tmp}");
1350 uwriteln!(self.src, "const {array} = {};", operands[0]);
1351 for i in 0..*size {
1352 results.push(format!("{array}[{i}]"));
1353 }
1354 }
1355
1356 Instruction::FixedLengthListLift { .. } => {
1357 let tmp = self.tmp();
1358 let result = format!("result{tmp}");
1359 uwriteln!(self.src, "const {result} = [{}];", operands.join(", "));
1360 results.push(result);
1361 }
1362
1363 Instruction::FixedLengthListLowerToMemory {
1364 element, size: len, ..
1365 } => {
1366 let (body, body_results) = self.blocks.pop().unwrap();
1367 assert!(body_results.is_empty());
1368
1369 let tmp = self.tmp();
1370 let array = format!("array{tmp}");
1371 uwriteln!(self.src, "const {array} = {};", operands[0]);
1372 let addr = format!("addr{tmp}");
1373 uwriteln!(self.src, "const {addr} = {};", operands[1]);
1374 let elem_size = self.sizes.size(element).size_wasm32();
1375
1376 uwriteln!(self.src, "for (let i = 0; i < {len}; i++) {{");
1377 uwriteln!(self.src, "const e = {array}[i];");
1378 uwrite!(self.src, "const base = {addr} + i * {elem_size};");
1379 self.src.push_str(&body);
1380 uwrite!(self.src, "}}\n");
1381 }
1382
1383 Instruction::FixedLengthListLiftFromMemory {
1384 element, size: len, ..
1385 } => {
1386 let (body, body_results) = self.blocks.pop().unwrap();
1387 assert_eq!(body_results.len(), 1);
1388
1389 let tmp = self.tmp();
1390 let addr = format!("addr{tmp}");
1391 uwriteln!(self.src, "const {addr} = {};", operands[0]);
1392 let elem_size = self.sizes.size(element).size_wasm32();
1393 let result = format!("result{tmp}");
1394 uwriteln!(self.src, "const {result} = [];");
1395 results.push(result.clone());
1396
1397 uwriteln!(self.src, "for (let i = 0; i < {len}; i++) {{");
1398 uwrite!(self.src, "const base = {addr} + i * {elem_size};");
1399 self.src.push_str(&body);
1400 uwriteln!(self.src, "{result}.push({});", body_results[0]);
1401 uwrite!(self.src, "}}\n");
1402 }
1403
1404 Instruction::IterElem { .. } => results.push("e".to_string()),
1405
1406 Instruction::IterBasePointer => results.push("base".to_string()),
1407
1408 Instruction::CallWasm { name, sig } => {
1409 let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
1410 let has_post_return = self.post_return.is_some();
1411 let is_async = self.is_async;
1412 uwriteln!(
1413 self.src,
1414 "{debug_log_fn}('{prefix} [Instruction::CallWasm] enter', {{
1415 funcName: '{name}',
1416 paramCount: {param_count},
1417 async: {is_async},
1418 postReturn: {has_post_return},
1419 }});",
1420 param_count = sig.params.len(),
1421 prefix = self.tracing_prefix,
1422 );
1423
1424 // Write out whether the caller was host provided
1425 // (if we're calling into wasm then we know it was not)
1426 uwriteln!(self.src, "const hostProvided = false;");
1427
1428 // Inject machinery for starting a 'current' task
1429 // (this will define the 'task' variable)
1430 self.start_current_task(inst);
1431
1432 // TODO: trap if this component is already on the call stack (re-entrancy)
1433
1434 // TODO(threads): start a thread
1435 // TODO(threads): Task#enter needs to be called with the thread that is executing (inside thread_func)
1436 // TODO(threads): thread_func will contain the actual call rather than attempting to execute immediately
1437
1438 // If we're dealing with an async task, do explicit task enter
1439 if self.is_async || self.requires_async_porcelain {
1440 uwriteln!(
1441 self.src,
1442 r#"
1443 const started = await task.enter();
1444 if (!started) {{
1445 {debug_log_fn}('[Instruction::AsyncTaskReturn] failed to enter task', {{
1446 taskID: task.id(),
1447 subtaskID: currentSubtask?.id(),
1448 }});
1449 throw new Error("failed to enter task");
1450 }}
1451 "#,
1452 );
1453 } else {
1454 uwriteln!(self.src, "const started = task.enterSync();",);
1455 }
1456
1457 // Set up resource scope tracking, if we're in a resource call
1458 if self.callee_resource_dynamic {
1459 let resource_borrows =
1460 self.intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceCallBorrows));
1461 let handle_tables = self.intrinsic(Intrinsic::HandleTables);
1462 let scope_id = self.intrinsic(Intrinsic::ScopeId);
1463 uwriteln!(
1464 self.src,
1465 r#"
1466 {scope_id}++;
1467 task.registerOnResolveHandler(() => {{
1468 {scope_id}--;
1469 for (const {{ rid, handle }} of {resource_borrows}) {{
1470 const storedScopeId = {handle_tables}[rid][handle << 1]
1471 if (storedScopeId === {scope_id}) {{
1472 throw new TypeError('borrows not dropped for resource call');
1473 }}
1474 }}
1475 {resource_borrows} = [];
1476 }}
1477
1478 }});
1479 "#
1480 );
1481 uwriteln!(self.src, "{scope_id}++;");
1482 }
1483
1484 // Save the memory for this task,
1485 // which will be used for any subtasks that might be spawned
1486 if let Some(mem_idx) = self.canon_opts.memory() {
1487 let idx = mem_idx.as_u32();
1488 uwriteln!(self.src, "task.setReturnMemoryIdx({idx});");
1489 uwriteln!(self.src, "task.setReturnMemory(memory{idx});");
1490 }
1491
1492 // Output result binding preamble (e.g. 'var ret =', 'var [ ret0, ret1] = exports...() ')
1493 // along with the code to perofrm the call
1494 let sig_results_length = sig.results.len();
1495 let (vars_init, assignment_lhs) =
1496 self.generate_result_assignment_lhs(sig_results_length, results, is_async);
1497
1498 let (call_prefix, call_wrapper, call_err_cleanup) =
1499 if self.requires_async_porcelain | self.is_async {
1500 (
1501 "await ",
1502 Intrinsic::WithGlobalCurrentTaskMetaFnAsync.name(),
1503 r#"
1504 task.setErrored(err);
1505 task.reject(err);
1506 task.exit();
1507 return task.completionPromise();
1508 "#,
1509 )
1510 } else {
1511 (
1512 "",
1513 Intrinsic::WithGlobalCurrentTaskMetaFn.name(),
1514 r#"
1515 task.setErrored(err);
1516 task.reject(err);
1517 task.exit();
1518 throw err;
1519 "#,
1520 )
1521 };
1522
1523 let args = if self.asmjs {
1524 let split_i64 =
1525 self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::SplitBigInt64));
1526
1527 let mut args = Vec::new();
1528 for (i, op) in operands
1529 .drain(operands.len() - sig.params.len()..)
1530 .enumerate()
1531 {
1532 if matches!(sig.params[i], WasmType::I64) {
1533 args.push(format!("...({split_i64}({op}))"));
1534 } else {
1535 args.push(op);
1536 }
1537 }
1538 args
1539 } else {
1540 mem::take(operands)
1541 };
1542
1543 let mut callee_invoke = format!(
1544 "{callee}({args})",
1545 callee = self.callee,
1546 args = args.join(", ")
1547 );
1548
1549 if self.asmjs {
1550 // wasm2js does not support multivalue return
1551 // if/when it does, this will need changing.
1552 assert!(sig.results.len() <= 1);
1553 // same with async(?)
1554 assert!(!self.requires_async_porcelain && !self.is_async);
1555
1556 if sig.results.len() == 1 && matches!(sig.results[0], WasmType::I64) {
1557 let merge_i64 = self
1558 .intrinsic(Intrinsic::Conversion(ConversionIntrinsic::MergeBigInt64));
1559 callee_invoke =
1560 format!("{merge_i64}({callee_invoke}, task.tmpRetI64HighBits)");
1561 }
1562 }
1563
1564 uwriteln!(
1565 self.src,
1566 r#"
1567 {vars_init}
1568 try {{
1569 {assignment_lhs} {call_prefix} {call_wrapper}({{
1570 taskID: task.id(),
1571 componentIdx: task.componentIdx(),
1572 fn: () => {callee_invoke},
1573 }});
1574 }} catch (err) {{
1575 {call_err_cleanup}
1576 }}
1577 "#,
1578 );
1579
1580 if self.tracing_enabled {
1581 let prefix = self.tracing_prefix;
1582 let to_result_string =
1583 self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToResultString));
1584 uwriteln!(
1585 self.src,
1586 "console.error(`{prefix} return {}`);",
1587 if sig_results_length > 0 || !results.is_empty() {
1588 format!("result=${{{to_result_string}(ret)}}")
1589 } else {
1590 "".to_string()
1591 }
1592 );
1593 }
1594 }
1595
1596 // Call to an imported interface (normally provided by the host)
1597 Instruction::CallInterface { func, async_ } => {
1598 let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
1599 let start_current_task_fn = self.intrinsic(Intrinsic::AsyncTask(
1600 AsyncTaskIntrinsic::CreateNewCurrentTask,
1601 ));
1602 let current_task_get_fn =
1603 self.intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::GetCurrentTask));
1604 let component_instance_idx = self.canon_opts.instance.as_u32();
1605
1606 // At first, use the global current task metadata, in case we are executing from
1607 // inside a with-global-current-task wrapper
1608 let get_global_current_task_meta_fn =
1609 self.intrinsic(Intrinsic::GetGlobalCurrentTaskMetaFn);
1610
1611 uwriteln!(
1612 self.src,
1613 "{debug_log_fn}('{prefix} [Instruction::CallInterface] ({async_}, @ enter)');",
1614 prefix = self.tracing_prefix,
1615 async_ = async_.then_some("async").unwrap_or("sync"),
1616 );
1617
1618 // Determine the callee function and arguments
1619 let (callee_fn_js, callee_args_js) = if self.callee_resource_dynamic {
1620 (
1621 format!("{}.{}", operands[0], self.callee),
1622 operands[1..].join(", "),
1623 )
1624 } else {
1625 (self.callee.into(), operands.join(", "))
1626 };
1627
1628 uwriteln!(self.src, "let hostProvided = true;");
1629
1630 // Start the necessary subtasks and/or host task
1631 //
1632 // We must create a subtask in the case of an async host import.
1633 //
1634 // If there's no parent task, we're not executing in a subtask situation,
1635 // so we can just create the new task and immediately continue execution.
1636 //
1637 // If there *is* a parent task, then we are likely about to create new task that
1638 // matches/belongs to an existing subtask in the parent task.
1639 //
1640 // If we're dealing with a function that has been marked as a host import, then
1641 // we expect that `Trampoline::LowerImport` and relevant intrinsics were called before
1642 // this, and a subtask has been set up.
1643 //
1644 uwriteln!(
1645 self.src,
1646 r#"
1647 let parentTask;
1648 let task;
1649 let subtask;
1650
1651 const createTask = () => {{
1652 const results = {start_current_task_fn}({{
1653 componentIdx: -1, // {component_instance_idx},
1654 isAsync: {is_async},
1655 entryFnName: '{fn_name}',
1656 getCallbackFn: () => {callback_fn_js},
1657 callbackFnName: '{callback_fn_js}',
1658 errHandling: '{err_handling}',
1659 callingWasmExport: false,
1660 }});
1661 task = results[0];
1662 }};
1663
1664 taskCreation: {{
1665 parentTask = {current_task_get_fn}(
1666 {component_instance_idx},
1667 {get_global_current_task_meta_fn}({component_instance_idx})?.taskID,
1668 )?.task;
1669
1670 if (!parentTask) {{
1671 createTask();
1672 break taskCreation;
1673 }}
1674
1675 createTask();
1676
1677 if (hostProvided) {{
1678 subtask = parentTask.getLatestSubtask();
1679 if (!subtask) {{
1680 throw new Error(`Missing subtask (in parent task [${{parentTask.id()}}]) for host import, has the import been lowered? (ensure asyncImports are set properly)`);
1681 }}
1682 task.setParentSubtask(subtask);
1683 }}
1684 }}
1685 "#,
1686 is_async = self.is_async,
1687 fn_name = self.callee,
1688 err_handling = self.err.to_js_string(),
1689 callback_fn_js = self
1690 .canon_opts
1691 .callback
1692 .as_ref()
1693 .map(|v| format!("callback_{}", v.as_u32()))
1694 .unwrap_or_else(|| "null".into()),
1695 );
1696
1697 let is_async = self.requires_async_porcelain || *async_;
1698
1699 // If we're async then we *know* that there is a result, even if the functoin doesn't have one
1700 // at the CM level -- async functions always return
1701 let fn_wasm_result_count = if func.result.is_none() { 0 } else { 1 };
1702
1703 // If the task is async, do an explicit wait for backpressure before the call execution
1704 if is_async {
1705 uwriteln!(
1706 self.src,
1707 r#"
1708 const started = await task.enter({{ isHost: hostProvided }});
1709 if (!started) {{
1710 {debug_log_fn}('[Instruction::CallInterface] failed to enter task', {{
1711 taskID: task.id(),
1712 subtaskID: currentSubtask?.id(),
1713 }});
1714 throw new Error("failed to enter task");
1715 }}
1716 "#,
1717 );
1718 } else {
1719 uwriteln!(self.src, "const started = task.enterSync();",);
1720 }
1721
1722 // Build the JS expression that calls the callee
1723 let (call_prefix, call_wrapper, call_err_cleanup) =
1724 if is_async || self.requires_async_porcelain {
1725 (
1726 "await ",
1727 Intrinsic::WithGlobalCurrentTaskMetaFnAsync.name(),
1728 r#"
1729 task.setErrored(err);
1730 task.reject(err);
1731 task.exit();
1732 return task.completionPromise();
1733 "#,
1734 )
1735 } else {
1736 (
1737 "",
1738 Intrinsic::WithGlobalCurrentTaskMetaFn.name(),
1739 r#"
1740 task.setErrored(err);
1741 task.reject(err);
1742 task.exit();
1743 throw err;
1744 "#,
1745 )
1746 };
1747 let call = format!(
1748 r#"{call_prefix} {call_wrapper}({{
1749 componentIdx: task.componentIdx(),
1750 taskID: task.id(),
1751 fn: () => {callee_fn_js}({callee_args_js}),
1752 }})
1753 "#,
1754 );
1755
1756 match self.err {
1757 // If configured to do *no* error handling at all or throw
1758 // error objects directly, we can simply perform the call
1759 ErrHandling::None | ErrHandling::ThrowResultErr => {
1760 let (vars_init, assignment_lhs) = self.generate_result_assignment_lhs(
1761 fn_wasm_result_count,
1762 results,
1763 is_async,
1764 );
1765 uwriteln!(
1766 self.src,
1767 r#"
1768 {vars_init}
1769 try {{
1770 {assignment_lhs}{call};
1771 }} catch (err) {{
1772 {call_err_cleanup}
1773 }}
1774 "#
1775 );
1776 }
1777 // If configured to force all thrown errors into result objects,
1778 // then we add a try/catch around the call
1779 ErrHandling::ResultCatchHandler => {
1780 // result<_, string> allows JS error coercion only, while
1781 // any other result type will trap for arbitrary JS errors.
1782 let err_payload = if let (_, Some(Type::Id(err_ty))) =
1783 get_thrown_type(self.resolve, func.result).unwrap()
1784 {
1785 match &self.resolve.types[*err_ty].kind {
1786 TypeDefKind::Type(Type::String) => {
1787 self.intrinsic(Intrinsic::GetErrorPayloadString)
1788 }
1789 _ => self.intrinsic(Intrinsic::GetErrorPayload),
1790 }
1791 } else {
1792 self.intrinsic(Intrinsic::GetErrorPayload)
1793 };
1794 uwriteln!(
1795 self.src,
1796 r#"
1797 let ret;
1798 try {{
1799 ret = {{ tag: 'ok', val: {call} }};
1800 }} catch (e) {{
1801 ret = {{ tag: 'err', val: {err_payload}(e) }};
1802 }}
1803 "#,
1804 );
1805 results.push("ret".to_string());
1806 }
1807 }
1808
1809 if self.tracing_enabled {
1810 let prefix = self.tracing_prefix;
1811 let to_result_string =
1812 self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToResultString));
1813 uwriteln!(
1814 self.src,
1815 "console.error(`{prefix} return {}`);",
1816 if fn_wasm_result_count > 0 || !results.is_empty() {
1817 format!("result=${{{to_result_string}(ret)}}")
1818 } else {
1819 "".to_string()
1820 }
1821 );
1822 }
1823
1824 // TODO: if it was an async call, we may not be able to clear the borrows yet.
1825 // save them to the task/ensure they are added to the task's list of borrows?
1826 //
1827 // TODO: if there is a subtask, we must not clear borrows until subtask.deliverReturn
1828 // is called.
1829
1830 // After a high level call, we need to deactivate the component resource borrows.
1831 if self.clear_resource_borrows {
1832 let symbol_resource_handle = self.intrinsic(Intrinsic::SymbolResourceHandle);
1833 let cur_resource_borrows =
1834 self.intrinsic(Intrinsic::Resource(ResourceIntrinsic::CurResourceBorrows));
1835 let is_host = matches!(
1836 self.resource_map.iter().nth(0).unwrap().1.data,
1837 ResourceData::Host { .. }
1838 );
1839
1840 if is_host {
1841 uwriteln!(
1842 self.src,
1843 "for (const rsc of {cur_resource_borrows}) {{
1844 rsc[{symbol_resource_handle}] = undefined;
1845 }}
1846 {cur_resource_borrows} = [];"
1847 );
1848 } else {
1849 uwriteln!(
1850 self.src,
1851 "for (const {{ rsc, drop }} of {cur_resource_borrows}) {{
1852 if (rsc[{symbol_resource_handle}]) {{
1853 drop(rsc[{symbol_resource_handle}]);
1854 rsc[{symbol_resource_handle}] = undefined;
1855 }}
1856 }}
1857 {cur_resource_borrows} = [];"
1858 );
1859 }
1860 self.clear_resource_borrows = false;
1861 }
1862 }
1863
1864 Instruction::Return {
1865 func,
1866 amt: stack_value_count,
1867 } => {
1868 let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
1869 uwriteln!(
1870 self.src,
1871 "{debug_log_fn}('{prefix} [Instruction::Return]', {{
1872 funcName: '{func_name}',
1873 paramCount: {stack_value_count},
1874 async: {is_async},
1875 postReturn: {post_return_present}
1876 }});",
1877 func_name = func.name,
1878 post_return_present = self.post_return.is_some(),
1879 is_async = self.is_async,
1880 prefix = self.tracing_prefix,
1881 );
1882
1883 // Build the post return functionality
1884 // to clean up tasks and possibly return values
1885 let get_or_create_async_state_fn = self.intrinsic(Intrinsic::Component(
1886 ComponentIntrinsic::GetOrCreateAsyncState,
1887 ));
1888 let gen_post_return_js =
1889 |(post_return_call, ret_stmt): (String, Option<String>)| {
1890 format!(
1891 r#"
1892 let cstate = {get_or_create_async_state_fn}({component_idx});
1893 cstate.mayLeave = false;
1894 {post_return_call}
1895 cstate.mayLeave = true;
1896 task.exit();
1897 {ret_stmt}
1898 "#,
1899 component_idx = self.canon_opts.instance.as_u32(),
1900 ret_stmt = ret_stmt.unwrap_or_default(),
1901 )
1902 };
1903
1904 assert!(!self.is_async, "async functions should use AsyncTaskReturn");
1905
1906 // Depending how many values are on the stack after returning, we must execute differently.
1907 //
1908 // In particular, if this function is async (distinct from whether async porcelain was necessary or not),
1909 // rather than simply executing the function we must return (or block for) the promise that was created
1910 // for the task.
1911 match stack_value_count {
1912 // (sync) Handle no result case
1913 0 => {
1914 uwriteln!(self.src, "task.resolve([ret]);");
1915 if let Some(f) = &self.post_return {
1916 uwriteln!(
1917 self.src,
1918 "{post_return_js}",
1919 post_return_js = gen_post_return_js((format!("{f}();"), None)),
1920 );
1921 } else {
1922 uwriteln!(self.src, "task.exit();");
1923 }
1924 }
1925
1926 // (sync) Handle single `result<t>` case
1927 1 if self.err == ErrHandling::ThrowResultErr => {
1928 let component_err = self.intrinsic(Intrinsic::ComponentError);
1929 let op = &operands[0];
1930
1931 uwriteln!(self.src, "const retCopy = {op};");
1932 uwriteln!(self.src, "task.resolve([retCopy.val]);");
1933
1934 if let Some(f) = &self.post_return {
1935 uwriteln!(
1936 self.src,
1937 "{}",
1938 gen_post_return_js((format!("{f}(ret);"), None))
1939 );
1940 } else {
1941 uwriteln!(self.src, "task.exit();");
1942 }
1943
1944 uwriteln!(
1945 self.src,
1946 r#"
1947 if (typeof retCopy === 'object' && retCopy.tag === 'err') {{
1948 throw new {component_err}(retCopy.val);
1949 }}
1950 return retCopy.val;
1951 "#
1952 );
1953 }
1954
1955 // (sync) Handle all other cases (including single parameter non-result<t>)
1956 stack_value_count => {
1957 let ret_val = match stack_value_count {
1958 0 => unreachable!(
1959 "unexpectedly zero return values for synchronous return"
1960 ),
1961 1 => operands[0].to_string(),
1962 _ => format!("[{}]", operands.join(", ")),
1963 };
1964
1965 uwriteln!(self.src, "task.resolve([{ret_val}]);");
1966
1967 // Handle the post return if necessary
1968 if let Some(post_return_fn) = self.post_return {
1969 // In the case there is a post return function, we'll want to copy the value
1970 // then perform the post return before leaving
1971
1972 // Write out the assignment for the given return value
1973 uwriteln!(self.src, "const retCopy = {ret_val};");
1974
1975 // Generate the JS that should perform the post return w/ the result
1976 // and pass a copy fo the result to the actual caller
1977 let post_return_js = gen_post_return_js((
1978 format!("{post_return_fn}(ret);"),
1979 Some(["return retCopy;"].join("\n")),
1980 ));
1981 uwriteln!(self.src, "{post_return_js}");
1982 } else {
1983 uwriteln!(self.src, "task.exit();");
1984 uwriteln!(self.src, "return {ret_val};")
1985 }
1986 }
1987 }
1988 }
1989
1990 Instruction::I32Load { offset } => self.load("getInt32", *offset, operands, results),
1991
1992 Instruction::I64Load { offset } => self.load("getBigInt64", *offset, operands, results),
1993
1994 Instruction::F32Load { offset } => self.load("getFloat32", *offset, operands, results),
1995
1996 Instruction::F64Load { offset } => self.load("getFloat64", *offset, operands, results),
1997
1998 Instruction::I32Load8U { offset } => self.load("getUint8", *offset, operands, results),
1999
2000 Instruction::I32Load8S { offset } => self.load("getInt8", *offset, operands, results),
2001
2002 Instruction::I32Load16U { offset } => {
2003 self.load("getUint16", *offset, operands, results)
2004 }
2005
2006 Instruction::I32Load16S { offset } => self.load("getInt16", *offset, operands, results),
2007
2008 Instruction::I32Store { offset } => self.store("setInt32", *offset, operands),
2009
2010 Instruction::I64Store { offset } => self.store("setBigInt64", *offset, operands),
2011
2012 Instruction::F32Store { offset } => self.store("setFloat32", *offset, operands),
2013
2014 Instruction::F64Store { offset } => self.store("setFloat64", *offset, operands),
2015
2016 Instruction::I32Store8 { offset } => self.store("setInt8", *offset, operands),
2017
2018 Instruction::I32Store16 { offset } => self.store("setInt16", *offset, operands),
2019
2020 Instruction::LengthStore { offset } => self.store("setUint32", *offset, operands),
2021
2022 Instruction::LengthLoad { offset } => {
2023 self.load("getUint32", *offset, operands, results)
2024 }
2025
2026 Instruction::PointerStore { offset } => self.store("setUint32", *offset, operands),
2027
2028 Instruction::PointerLoad { offset } => {
2029 self.load("getUint32", *offset, operands, results)
2030 }
2031
2032 Instruction::Malloc { size, align, .. } => {
2033 let tmp = self.tmp();
2034 let realloc = self.realloc.as_ref().unwrap();
2035 let ptr = format!("ptr{tmp}");
2036 uwriteln!(
2037 self.src,
2038 "var {ptr} = {realloc_call}(0, 0, {align}, {size});",
2039 align = align.align_wasm32(),
2040 realloc_call = if self.is_async {
2041 format!("await {realloc}")
2042 } else {
2043 realloc.to_string()
2044 },
2045 size = size.size_wasm32()
2046 );
2047 results.push(ptr);
2048 }
2049
2050 Instruction::HandleLift { handle, .. } => {
2051 let (Handle::Own(ty) | Handle::Borrow(ty)) = handle;
2052 let resource_ty = &crate::dealias(self.resolve, *ty);
2053 let ResourceTable { imported, data } = &self.resource_map[resource_ty];
2054
2055 let is_own = matches!(handle, Handle::Own(_));
2056 let rsc = format!("rsc{}", self.tmp());
2057 let handle = format!("handle{}", self.tmp());
2058 uwriteln!(self.src, "var {handle} = {};", &operands[0]);
2059
2060 match data {
2061 ResourceData::Host {
2062 tid,
2063 rid,
2064 local_name,
2065 dtor_name,
2066 } => {
2067 let tid = tid.as_u32();
2068 let rid = rid.as_u32();
2069 let symbol_dispose = self.intrinsic(Intrinsic::SymbolDispose);
2070 let rsc_table_remove = self
2071 .intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableRemove));
2072 let rsc_flag = self
2073 .intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableFlag));
2074 if !imported {
2075 let symbol_resource_handle =
2076 self.intrinsic(Intrinsic::SymbolResourceHandle);
2077
2078 uwriteln!(
2079 self.src,
2080 "var {rsc} = new.target === {local_name} ? this : Object.create({local_name}.prototype);"
2081 );
2082
2083 if is_own {
2084 // Sending an own handle out to JS as a return value - set up finalizer and disposal.
2085 let empty_func = self
2086 .intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::EmptyFunc));
2087 uwriteln!(self.src,
2088 "Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
2089 finalizationRegistry{tid}.register({rsc}, {handle}, {rsc});");
2090 if let Some(dtor) = dtor_name {
2091 // The Symbol.dispose function gets disabled on drop, so we can rely on the own handle remaining valid.
2092 uwriteln!(
2093 self.src,
2094 "Object.defineProperty({rsc}, {symbol_dispose}, {{ writable: true, value: function () {{
2095 finalizationRegistry{tid}.unregister({rsc});
2096 {rsc_table_remove}(handleTable{tid}, {handle});
2097 {rsc}[{symbol_dispose}] = {empty_func};
2098 {rsc}[{symbol_resource_handle}] = undefined;
2099 {dtor}(handleTable{tid}[({handle} << 1) + 1] & ~{rsc_flag});
2100 }}}});"
2101 );
2102 } else {
2103 // Set up Symbol.dispose for borrows to allow its call, even though it does nothing.
2104 uwriteln!(
2105 self.src,
2106 "Object.defineProperty({rsc}, {symbol_dispose}, {{ writable: true, value: {empty_func} }});",
2107 );
2108 }
2109 } else {
2110 // Borrow handles of local resources have rep handles, which we carry through here.
2111 uwriteln!(
2112 self.src,
2113 "Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});"
2114 );
2115 }
2116 } else {
2117 let rep = format!("rep{}", self.tmp());
2118 // Imported handles either lift as instance capture from a previous lowering,
2119 // or we create a new JS class to represent it.
2120 let symbol_resource_rep = self.intrinsic(Intrinsic::SymbolResourceRep);
2121 let symbol_resource_handle =
2122 self.intrinsic(Intrinsic::SymbolResourceHandle);
2123
2124 uwriteln!(
2125 self.src,
2126 r#"
2127 var {rep} = handleTable{tid}[({handle} << 1) + 1] & ~{rsc_flag};
2128 var {rsc} = captureTable{rid}.get({rep});
2129 if (!{rsc}) {{
2130 {rsc} = Object.create({local_name}.prototype);
2131 Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
2132 Object.defineProperty({rsc}, {symbol_resource_rep}, {{ writable: true, value: {rep} }});
2133 }}
2134 "#,
2135 );
2136
2137 if is_own {
2138 // An own lifting is a transfer to JS, so existing own handle is implicitly dropped.
2139 uwriteln!(
2140 self.src,
2141 "else {{
2142 captureTable{rid}.delete({rep});
2143 }}
2144 {rsc_table_remove}(handleTable{tid}, {handle});"
2145 );
2146 }
2147 }
2148
2149 // Borrow handles are tracked to release after the call by CallInterface.
2150 if !is_own {
2151 let cur_resource_borrows = self.intrinsic(Intrinsic::Resource(
2152 ResourceIntrinsic::CurResourceBorrows,
2153 ));
2154 uwriteln!(self.src, "{cur_resource_borrows}.push({rsc});");
2155 self.clear_resource_borrows = true;
2156 }
2157 }
2158
2159 ResourceData::Guest {
2160 resource_name,
2161 prefix,
2162 extra,
2163 } => {
2164 assert!(
2165 extra.is_none(),
2166 "plain resource handles do not carry extra data"
2167 );
2168
2169 let symbol_resource_handle =
2170 self.intrinsic(Intrinsic::SymbolResourceHandle);
2171 let prefix = prefix.as_deref().unwrap_or("");
2172 let lower_camel = resource_name.to_lower_camel_case();
2173
2174 if !imported {
2175 if is_own {
2176 uwriteln!(
2177 self.src,
2178 "var {rsc} = repTable.get($resource_{prefix}rep${lower_camel}({handle})).rep;"
2179 );
2180 uwrite!(
2181 self.src,
2182 r#"
2183 repTable.delete({handle});
2184 delete {rsc}[{symbol_resource_handle}];
2185 finalizationRegistry_export${prefix}{lower_camel}.unregister({rsc});
2186 "#
2187 );
2188 } else {
2189 uwriteln!(self.src, "var {rsc} = repTable.get({handle}).rep;");
2190 }
2191 } else {
2192 let upper_camel = resource_name.to_upper_camel_case();
2193
2194 uwrite!(
2195 self.src,
2196 r#"
2197 var {rsc} = new.target === import_{prefix}{upper_camel} ? this : Object.create(import_{prefix}{upper_camel}.prototype);
2198 Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
2199 "#
2200 );
2201
2202 uwriteln!(
2203 self.src,
2204 "finalizationRegistry_import${prefix}{lower_camel}.register({rsc}, {handle}, {rsc});",
2205 );
2206
2207 if !is_own {
2208 let cur_resource_borrows = self.intrinsic(Intrinsic::Resource(
2209 ResourceIntrinsic::CurResourceBorrows,
2210 ));
2211 uwriteln!(
2212 self.src,
2213 "{cur_resource_borrows}.push({{ rsc: {rsc}, drop: $resource_import${prefix}drop${lower_camel} }});"
2214 );
2215 self.clear_resource_borrows = true;
2216 }
2217 }
2218 }
2219 }
2220 results.push(rsc);
2221 }
2222
2223 // Instruction::HandleLift { handle, .. } => {
2224 // let (Handle::Own(ty) | Handle::Borrow(ty)) = handle;
2225 // let resource_ty = &crate::dealias(self.resolve, *ty);
2226 // let ResourceTable { imported, data } = &self.resource_map[resource_ty];
2227
2228 // let is_own = matches!(handle, Handle::Own(_));
2229 // let rsc = format!("rsc{}", self.tmp());
2230 // let handle = format!("handle{}", self.tmp());
2231 // uwriteln!(self.src, "var {handle} = {};", &operands[0]);
2232
2233 // match data {
2234 // ResourceData::Host {
2235 // tid,
2236 // rid,
2237 // local_name,
2238 // dtor_name,
2239 // } => {
2240 // let tid = tid.as_u32();
2241 // let rid = rid.as_u32();
2242 // let symbol_dispose = self.intrinsic(Intrinsic::SymbolDispose);
2243 // let rsc_table_remove = self
2244 // .intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableRemove));
2245 // let rsc_flag = self
2246 // .intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableFlag));
2247 // let symbol_resource_handle =
2248 // self.intrinsic(Intrinsic::SymbolResourceHandle);
2249 // let empty_func =
2250 // self.intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::EmptyFunc));
2251
2252 // match (imported, is_own) {
2253 // // Non-imported owned host resource
2254 // (_imported @ false, _owned @ true) => {
2255 // let dtor_setup_js = if let Some(dtor) = dtor_name {
2256 // // The Symbol.dispose function gets disabled on drop, so we can rely on the own handle remaining valid.
2257 // format!(
2258 // r#",
2259 // Object.defineProperty(
2260 // {rsc},
2261 // {symbol_dispose},
2262 // {{
2263 // writable: true,
2264 // value: function () {{
2265 // finalizationRegistry{tid}.unregister({rsc});
2266 // {rsc_table_remove}(handleTable{tid}, {handle});
2267 // {rsc}[{symbol_dispose}] = {empty_func};
2268 // {rsc}[{symbol_resource_handle}] = undefined;
2269 // {dtor}(handleTable{tid}[({handle} << 1) + 1] & ~{rsc_flag});
2270 // }}
2271 // }},
2272 // );
2273 // "#
2274 // )
2275 // } else {
2276 // // Set up Symbol.dispose for borrows to allow its call, even though it does nothing.
2277 // format!(
2278 // "Object.defineProperty({rsc}, {symbol_dispose}, {{ writable: true, value: {empty_func} }});"
2279 // )
2280 // };
2281
2282 // // Sending an own handle out to JS as a return value - set up finalizer and disposal.
2283 // uwriteln!(
2284 // self.src,
2285 // r#"
2286 // var {rsc};
2287 // if (new.target === {local_name}) {{
2288 // {rsc} = this;
2289 // }} else {{
2290 // {rsc} = Object.create({local_name}.prototype);
2291 // }}
2292 // Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
2293 // finalizationRegistry{tid}.register({rsc}, {handle}, {rsc});
2294 // {dtor_setup_js}
2295 // "#
2296 // );
2297 // }
2298
2299 // // Non-imported borrowed host resource
2300 // (_imported @ false, _owned @ false) => {
2301 // // Borrow handles of local resources have rep handles, which we carry through here.
2302 // uwriteln!(
2303 // self.src,
2304 // "Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});"
2305 // );
2306 // }
2307
2308 // // Imported owned host resource
2309 // (_imported @ true, _owned @ true) => {
2310 // let rep = format!("rep{}", self.tmp());
2311 // // Imported handles either lift as instance capture from a previous lowering,
2312 // // or we create a new JS class to represent it.
2313 // let symbol_resource_rep =
2314 // self.intrinsic(Intrinsic::SymbolResourceRep);
2315 // let symbol_resource_handle =
2316 // self.intrinsic(Intrinsic::SymbolResourceHandle);
2317
2318 // uwriteln!(
2319 // self.src,
2320 // r#"
2321 // var {rep} = handleTable{tid}[({handle} << 1) + 1] & ~{rsc_flag};
2322 // var {rsc} = captureTable{rid}.get({rep});
2323 // if (!{rsc}) {{
2324 // {rsc} = Object.create({local_name}.prototype);
2325 // Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
2326 // Object.defineProperty({rsc}, {symbol_resource_rep}, {{ writable: true, value: {rep} }});
2327 // }} else {{
2328 // captureTable{rid}.delete({rep});
2329 // }}
2330 // // NOTE: owned lifting is a transfer to JS, so existing own handle must be dropped
2331 // {rsc_table_remove}(handleTable{tid}, {handle});
2332 // "#,
2333 // );
2334 // }
2335
2336 // // Imported borrowed host resource
2337 // (_imported @ true, _owned @ false) => {
2338 // let rep = format!("rep{}", self.tmp());
2339 // // Imported handles either lift as instance capture from a previous lowering,
2340 // // or we create a new JS class to represent it.
2341 // let symbol_resource_rep =
2342 // self.intrinsic(Intrinsic::SymbolResourceRep);
2343 // let symbol_resource_handle =
2344 // self.intrinsic(Intrinsic::SymbolResourceHandle);
2345
2346 // uwriteln!(
2347 // self.src,
2348 // r#"
2349 // var {rep} = handleTable{tid}[({handle} << 1) + 1] & ~{rsc_flag};
2350 // var {rsc} = captureTable{rid}.get({rep});
2351 // if (!{rsc}) {{
2352 // {rsc} = Object.create({local_name}.prototype);
2353 // Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
2354 // Object.defineProperty({rsc}, {symbol_resource_rep}, {{ writable: true, value: {rep} }});
2355 // }}
2356 // "#,
2357 // );
2358 // }
2359 // }
2360
2361 // // Borrow handles are tracked to release after the call by CallInterface.
2362 // if !is_own {
2363 // let cur_resource_borrows = self.intrinsic(Intrinsic::Resource(
2364 // ResourceIntrinsic::CurResourceBorrows,
2365 // ));
2366 // uwriteln!(self.src, "{cur_resource_borrows}.push({rsc});");
2367 // self.clear_resource_borrows = true;
2368 // }
2369 // }
2370
2371 // ResourceData::Guest {
2372 // resource_name,
2373 // prefix,
2374 // extra,
2375 // } => {
2376 // assert!(
2377 // extra.is_none(),
2378 // "plain resource handles do not carry extra data"
2379 // );
2380
2381 // let symbol_resource_handle =
2382 // self.intrinsic(Intrinsic::SymbolResourceHandle);
2383 // let prefix = prefix.as_deref().unwrap_or("");
2384 // let lower_camel = resource_name.to_lower_camel_case();
2385
2386 // match (imported, is_own) {
2387 // // Non-imported, owned guest resource
2388 // (_imported @ false, _owned @ true) => {
2389 // uwrite!(
2390 // self.src,
2391 // r#"
2392 // var {rsc} = repTable.get($resource_{prefix}rep${lower_camel}({handle})).rep;
2393 // repTable.delete({handle});
2394 // delete {rsc}[{symbol_resource_handle}];
2395 // finalizationRegistry_export${prefix}{lower_camel}.unregister({rsc});
2396 // "#
2397 // );
2398 // }
2399
2400 // // Non-imported, borrowed guest resource
2401 // (_imported @ false, _owned @ false) => {
2402 // uwriteln!(self.src, "var {rsc} = repTable.get({handle}).rep;");
2403 // }
2404
2405 // // Imported, owned guest resource
2406 // (_imported @ true, _owned @ true) => {
2407 // let upper_camel = resource_name.to_upper_camel_case();
2408
2409 // uwrite!(
2410 // self.src,
2411 // r#"
2412 // var {rsc};
2413 // if (new.target === import_{prefix}{upper_camel}) {{
2414 // {rsc} = this;
2415 // }} else {{
2416 // {rsc} = Object.create(import_{prefix}{upper_camel}.prototype);
2417 // }}
2418 // Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
2419 // finalizationRegistry_import${prefix}{lower_camel}.register({rsc}, {handle}, {rsc});
2420 // "#
2421 // );
2422 // }
2423
2424 // // Imported, borrowed guest resource
2425 // (_imported @ true, _owned @ false) => {
2426 // let upper_camel = resource_name.to_upper_camel_case();
2427
2428 // uwrite!(
2429 // self.src,
2430 // r#"
2431 // var {rsc};
2432 // if (new.target === import_{prefix}{upper_camel}) {{
2433 // {rsc} = this;
2434 // }} else {{
2435 // {rsc} = Object.create(import_{prefix}{upper_camel}.prototype);
2436 // }}
2437 // Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
2438 // finalizationRegistry_import${prefix}{lower_camel}.register({rsc}, {handle}, {rsc});
2439 // "#
2440 // );
2441
2442 // // TODO(fix): should this be similar to host and *not* be here, but apply no matter what?
2443 // if !is_own {
2444 // let cur_resource_borrows = self.intrinsic(Intrinsic::Resource(
2445 // ResourceIntrinsic::CurResourceBorrows,
2446 // ));
2447 // uwriteln!(
2448 // self.src,
2449 // "{cur_resource_borrows}.push({{ rsc: {rsc}, drop: $resource_import${prefix}drop${lower_camel} }});"
2450 // );
2451 // self.clear_resource_borrows = true;
2452 // }
2453 // }
2454 // }
2455 // }
2456 // }
2457
2458 // results.push(rsc);
2459 // }
2460 Instruction::HandleLower { handle, name, .. } => {
2461 let (Handle::Own(ty) | Handle::Borrow(ty)) = handle;
2462 let is_own = matches!(handle, Handle::Own(_));
2463 let ResourceTable { imported, data } =
2464 &self.resource_map[&crate::dealias(self.resolve, *ty)];
2465
2466 let class_name = name.to_upper_camel_case();
2467 let handle = format!("handle{}", self.tmp());
2468 let symbol_resource_handle = self.intrinsic(Intrinsic::SymbolResourceHandle);
2469 let symbol_dispose = self.intrinsic(Intrinsic::SymbolDispose);
2470 let op = &operands[0];
2471
2472 match data {
2473 ResourceData::Host {
2474 tid,
2475 rid,
2476 local_name,
2477 ..
2478 } => {
2479 let tid = tid.as_u32();
2480 let rid = rid.as_u32();
2481
2482 match (imported, is_own) {
2483 // Imported, owned host-provided resource
2484 (_imported @ false, _owned @ true) => {
2485 let empty_func = self
2486 .intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::EmptyFunc));
2487 uwriteln!(
2488 self.src,
2489 r#"
2490 var {handle} = {op}[{symbol_resource_handle}];
2491 if (!{handle}) {{
2492 throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
2493 }}
2494 finalizationRegistry{tid}.unregister({op});
2495 {op}[{symbol_dispose}] = {empty_func};
2496 {op}[{symbol_resource_handle}] = undefined;
2497 "#,
2498 );
2499 }
2500
2501 // Imported, borrowed host-provdied resource
2502 (_imported @ false, _owned @ false) => {
2503 // When expecting a borrow, the JS resource provided will always be an own
2504 // handle. This is because it is not possible for borrow handles to be passed
2505 // back reentrantly.
2506 // We then set the handle to the rep per the local borrow rule.
2507 let rsc_flag = self.intrinsic(Intrinsic::Resource(
2508 ResourceIntrinsic::ResourceTableFlag,
2509 ));
2510 let own_handle = format!("handle{}", self.tmp());
2511 uwriteln!(
2512 self.src,
2513 r#"
2514 var {own_handle} = {op}[{symbol_resource_handle}];
2515 if (!{own_handle} || (handleTable{tid}[({own_handle} << 1) + 1] & {rsc_flag}) === 0) {{
2516 throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
2517 }}
2518 var {handle} = handleTable{tid}[({own_handle} << 1) + 1] & ~{rsc_flag};
2519 "#,
2520 );
2521 }
2522
2523 // Imported, owned guest-provided resource
2524 (_imported @ true, _owned @ true) => {
2525 // Imported resources may already have a handle if they were constructed
2526 // by a component and then passed out.
2527 //
2528 // If the handle is not present, in hybrid bindgen we check for a Symbol.for('cabiRep')
2529 // to get the resource rep.
2530 //
2531 // Fall back to assign a new rep in the capture table, when the imported
2532 // resource was constructed externally.
2533 let symbol_resource_rep =
2534 self.intrinsic(Intrinsic::SymbolResourceRep);
2535 let create_own_fn = self.intrinsic(Intrinsic::Resource(
2536 ResourceIntrinsic::ResourceTableCreateOwn,
2537 ));
2538
2539 uwriteln!(
2540 self.src,
2541 r#"
2542 if (!({op} instanceof {local_name})) {{
2543 throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
2544 }}
2545 var {handle} = {op}[{symbol_resource_handle}];
2546 if (!{handle}) {{
2547 const rep = {op}[{symbol_resource_rep}] || ++captureCnt{rid};
2548 captureTable{rid}.set(rep, {op});
2549 {handle} = {create_own_fn}(handleTable{tid}, rep);
2550 }}
2551 "#
2552 );
2553 }
2554
2555 // Imported, borrowed guest-provided resource
2556 (_imported @ true, _owned @ false) => {
2557 // Imported resources may already have a handle if they were constructed
2558 // by a component and then passed out.
2559 //
2560 // Otherwise, in hybrid bindgen we check for a Symbol.for('cabiRep')
2561 // to get the resource rep.
2562 // Fall back to assign a new rep in the capture table, when the imported
2563 // resource was constructed externally.
2564
2565 let symbol_resource_rep =
2566 self.intrinsic(Intrinsic::SymbolResourceRep);
2567 let scope_id = self.intrinsic(Intrinsic::ScopeId);
2568 let create_borrow_fn = self.intrinsic(Intrinsic::Resource(
2569 ResourceIntrinsic::ResourceTableCreateBorrow,
2570 ));
2571
2572 uwriteln!(
2573 self.src,
2574 r#"
2575 if (!({op} instanceof {local_name})) {{
2576 throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
2577 }}
2578 var {handle} = {op}[{symbol_resource_handle}];
2579 if (!{handle}) {{
2580 const rep = {op}[{symbol_resource_rep}] || ++captureCnt{rid};
2581 captureTable{rid}.set(rep, {op});
2582 {handle} = {create_borrow_fn}(handleTable{tid}, rep, {scope_id});
2583 }}
2584 "#
2585 );
2586 }
2587 }
2588 }
2589
2590 ResourceData::Guest {
2591 resource_name,
2592 prefix,
2593 extra,
2594 } => {
2595 assert!(
2596 extra.is_none(),
2597 "plain resource handles do not carry extra data"
2598 );
2599
2600 let upper_camel = resource_name.to_upper_camel_case();
2601 let lower_camel = resource_name.to_lower_camel_case();
2602 let prefix = prefix.as_deref().unwrap_or("");
2603
2604 let symbol_resource_handle =
2605 self.intrinsic(Intrinsic::SymbolResourceHandle);
2606
2607 let tmp = self.tmp();
2608 match (imported, is_own) {
2609 // imported owned/borrowed guest resource
2610 (_imported @ true, _owned) => {
2611 uwrite!(
2612 self.src,
2613 r#"
2614 var {handle} = {op}[{symbol_resource_handle}];
2615 finalizationRegistry_import${prefix}{lower_camel}.unregister({op});
2616 "#
2617 );
2618 }
2619
2620 // Not-imported, borrowed guest resource
2621 (_imported @ false, _owned @ false) => {
2622 let local_rep = format!("localRep{tmp}");
2623 uwriteln!(
2624 self.src,
2625 r#"
2626 if (!({op} instanceof {upper_camel})) {{
2627 throw new TypeError('Resource error: Not a valid \"{upper_camel}\" resource.');
2628 }}
2629 let {handle} = {op}[{symbol_resource_handle}];
2630 if ({handle} === undefined) {{
2631 var {local_rep} = repCnt++;
2632 repTable.set({local_rep}, {{ rep: {op}, own: false }});
2633 {op}[{symbol_resource_handle}] = {local_rep};
2634 }}
2635 "#
2636 );
2637 }
2638
2639 // Not-imported, owned guest resource
2640 (_imported @ false, _owned @ true) => {
2641 let local_rep = format!("localRep{tmp}");
2642 uwriteln!(
2643 self.src,
2644 r#"
2645 if (!({op} instanceof {upper_camel})) {{
2646 throw new TypeError('Resource error: Not a valid \"{upper_camel}\" resource.');
2647 }}
2648 let {handle} = {op}[{symbol_resource_handle}];
2649 if ({handle} === undefined) {{
2650 var {local_rep} = repCnt++;
2651 repTable.set({local_rep}, {{ rep: {op}, own: true }});
2652 {handle} = $resource_{prefix}new${lower_camel}({local_rep});
2653 {op}[{symbol_resource_handle}] = {handle};
2654 finalizationRegistry_export${prefix}{lower_camel}.register({op}, {handle}, {op});
2655 }}
2656 "#
2657 );
2658 }
2659 }
2660 }
2661 }
2662 results.push(handle);
2663 }
2664
2665 Instruction::DropHandle { ty } => {
2666 let _ = ty;
2667 todo!("[Instruction::DropHandle] not yet implemented")
2668 }
2669
2670 Instruction::Flush { amt } => {
2671 for item in operands.iter().take(*amt) {
2672 results.push(item.clone());
2673 }
2674 }
2675
2676 Instruction::ErrorContextLift => {
2677 let item = operands
2678 .first()
2679 .expect("unexpectedly missing ErrorContextLift arg");
2680 results.push(item.clone());
2681 }
2682
2683 Instruction::ErrorContextLower => {
2684 let item = operands
2685 .first()
2686 .expect("unexpectedly missing ErrorContextLower arg");
2687 results.push(item.clone());
2688 }
2689
2690 Instruction::FutureLower { ty, .. } => {
2691 let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
2692 let get_or_create_async_state_fn = self.intrinsic(Intrinsic::Component(
2693 ComponentIntrinsic::GetOrCreateAsyncState,
2694 ));
2695 let gen_future_host_inject_fn = self.intrinsic(Intrinsic::AsyncFuture(
2696 AsyncFutureIntrinsic::GenFutureHostInjectFn,
2697 ));
2698 let is_future_lowerable_object_fn = self.intrinsic(Intrinsic::AsyncFuture(
2699 AsyncFutureIntrinsic::IsFutureLowerableObject,
2700 ));
2701 let nested_future_symbol = self.intrinsic(Intrinsic::AsyncFuture(
2702 AsyncFutureIntrinsic::NestedFutureSymbol,
2703 ));
2704
2705 let component_idx = self.canon_opts.instance.as_u32();
2706
2707 let future_arg = operands
2708 .first()
2709 .expect("unexpectedly missing ErrorContextLower arg");
2710
2711 // Build the lowering function for the type produced by the future
2712 let type_id = &crate::dealias(self.resolve, *ty);
2713 let ResourceTable {
2714 imported: true,
2715 data:
2716 ResourceData::Guest {
2717 extra:
2718 Some(ResourceExtraData::Future {
2719 table_idx: future_table_idx_ty,
2720 nesting_level,
2721 elem_ty,
2722 }),
2723 ..
2724 },
2725 } = self
2726 .resource_map
2727 .get(type_id)
2728 .expect("missing resource mapping for future lower")
2729 else {
2730 unreachable!("invalid resource table observed during future lower");
2731 };
2732 let future_table_idx = future_table_idx_ty.as_u32();
2733
2734 // Generate payload metadata ('elemMeta')
2735 let (
2736 payload_type_name_js,
2737 lift_fn_js,
2738 lower_fn_js,
2739 payload_is_none,
2740 payload_is_numeric,
2741 payload_is_borrow,
2742 payload_is_async_value,
2743 payload_size32_js,
2744 payload_align32_js,
2745 payload_flat_count_js,
2746 ) = match elem_ty {
2747 Some(PayloadTypeMetadata {
2748 ty: _,
2749 iface_ty,
2750 lift_js_expr,
2751 lower_js_expr,
2752 size32,
2753 align32,
2754 flat_count,
2755 }) => (
2756 format!("'{iface_ty:?}'"),
2757 lift_js_expr.as_str(),
2758 lower_js_expr.as_str(),
2759 "false",
2760 format!(
2761 "{}",
2762 matches!(
2763 iface_ty,
2764 InterfaceType::U8
2765 | InterfaceType::U16
2766 | InterfaceType::U32
2767 | InterfaceType::U64
2768 | InterfaceType::S8
2769 | InterfaceType::S16
2770 | InterfaceType::S32
2771 | InterfaceType::S64
2772 | InterfaceType::Float32
2773 | InterfaceType::Float64
2774 )
2775 ),
2776 format!("{}", matches!(iface_ty, InterfaceType::Borrow(_))),
2777 format!(
2778 "{}",
2779 matches!(
2780 iface_ty,
2781 InterfaceType::Stream(_) | InterfaceType::Future(_)
2782 )
2783 ),
2784 size32.to_string(),
2785 align32.to_string(),
2786 flat_count.unwrap_or(0).to_string(),
2787 ),
2788 None => (
2789 "null".into(),
2790 "() => {{ throw new Error('no lift fn'); }}",
2791 "() => {{ throw new Error('no lower fn'); }}",
2792 "true",
2793 "false".into(),
2794 "false".into(),
2795 "false".into(),
2796 "null".into(),
2797 "null".into(),
2798 "0".into(),
2799 ),
2800 };
2801
2802 // Retrieve the realloc fn if present, in case lowering fns need to allocate
2803 //
2804 // The realloc fn is saved on the element metadata which is passed through to
2805 // stream end and underlying buffer
2806 let get_realloc_fn_js = match self.canon_opts.data_model {
2807 CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions {
2808 realloc: Some(realloc_idx),
2809 ..
2810 }) => format!("() => realloc{}", realloc_idx.as_u32()),
2811 _ => "undefined".into(),
2812 };
2813
2814 let tmp = self.tmp();
2815 let lowered_future_waitable_idx = format!("futureWaitableIdx{tmp}");
2816
2817 uwriteln!(
2818 self.src,
2819 r#"
2820 if (!{is_future_lowerable_object_fn}({future_arg})) {{
2821 {debug_log_fn}('[Instruction::FutureLower] object is not a Promise/Thenable', {{ {future_arg} }});
2822 throw new Error('unrecognized future object (not Promise/Thenable)');
2823 }}
2824
2825 const cstate{tmp} = {get_or_create_async_state_fn}({component_idx});
2826 if (!cstate{tmp}) {{
2827 throw new Error(`missing component state for component [{component_idx}]`);
2828 }}
2829
2830 // TODO(feat): facilitate non utf8 string encoding for lowered futures
2831 const stringEncoding = 'utf8';
2832
2833 let outermostReadEnd{tmp};
2834 let futuresList{tmp} = [];
2835 let future{tmp} = {future_arg};
2836 let nextFuture{tmp};
2837 let openedCount = -1;
2838 let futureNestingLevel{tmp} = {nesting_level};
2839
2840 while (futureNestingLevel{tmp} >= 0) {{
2841 const {{
2842 writeEnd,
2843 writeEndWaitableIdx,
2844 readEnd,
2845 readEndWaitableIdx
2846 }} = cstate{tmp}.createFuture({{
2847 tableIdx: {future_table_idx},
2848 elemMeta: {{
2849 liftFn: {lift_fn_js},
2850 lowerFn: {lower_fn_js},
2851 payloadTypeName: {payload_type_name_js},
2852 isNone: {payload_is_none},
2853 isNumeric: {payload_is_numeric},
2854 isBorrowed: {payload_is_borrow},
2855 isAsyncValue: {payload_is_async_value},
2856 flatCount: {payload_flat_count_js},
2857 align32: {payload_align32_js},
2858 size32: {payload_size32_js},
2859 stringEncoding,
2860 getReallocFn: {get_realloc_fn_js},
2861 }}
2862 }});
2863
2864 const hostInjectFn = {gen_future_host_inject_fn}({{
2865 promise: future{tmp},
2866 stringEncoding,
2867 hostWriteEnd: writeEnd,
2868 }});
2869 readEnd.setHostInjectFn(hostInjectFn);
2870
2871 const meta{tmp} = {{
2872 isInnermost: futureNestingLevel{tmp} === {nesting_level},
2873 level: futureNestingLevel{tmp},
2874 }};
2875
2876 const innerFuture = future{tmp};
2877 future{tmp} = {{ }};
2878 future{tmp}[{nested_future_symbol}] = meta{tmp};
2879 future{tmp}.readEndWaitableIdx = readEndWaitableIdx;
2880 future{tmp}.writeEndWaitableIdx = writeEndWaitableIdx;
2881 future{tmp}.futureTableIdx = {future_table_idx};
2882 future{tmp}.componentIdx = {component_idx};
2883 future{tmp}.then = async (resolve, reject) => {{
2884 let p;
2885 if (openedCount === {nesting_level}) {{
2886 p = innerFuture;
2887 }} else {{
2888 openedCount++;
2889 p = futuresList{tmp}[futuresList{tmp}.length - (openedCount + 1)];
2890 }}
2891
2892 try {{
2893 resolve(await p);
2894 }} catch (err) {{
2895 reject(err);
2896 }}
2897 }};
2898
2899 outermostReadEnd{tmp} = readEnd;
2900
2901 futuresList{tmp}.push(future{tmp});
2902 futureNestingLevel{tmp}--;
2903 }}
2904
2905 const readEnd{tmp} = outermostReadEnd{tmp};
2906
2907 // TODO: need to *lower* the internal future???
2908
2909 const {lowered_future_waitable_idx} = readEnd{tmp}.waitableIdx();
2910 "#
2911 );
2912
2913 results.push(lowered_future_waitable_idx);
2914 }
2915
2916 Instruction::FutureLift { payload, ty } => {
2917 let future_new_from_lift_fn = self.intrinsic(Intrinsic::AsyncFuture(
2918 AsyncFutureIntrinsic::FutureNewFromLift,
2919 ));
2920
2921 // We must look up the type idx to find the future
2922 let type_id = &crate::dealias(self.resolve, *ty);
2923 let ResourceTable {
2924 imported: true,
2925 data:
2926 ResourceData::Guest {
2927 extra:
2928 Some(ResourceExtraData::Future {
2929 table_idx: future_table_idx_ty,
2930 elem_ty: future_element_ty,
2931 ..
2932 }),
2933 ..
2934 },
2935 } = self
2936 .resource_map
2937 .get(type_id)
2938 .expect("missing resource mapping for future lift")
2939 else {
2940 unreachable!("invalid resource table observed during future lift");
2941 };
2942
2943 // if a future element is present, it should match the payload we're getting
2944 let (lift_fn_js, lower_fn_js) = match future_element_ty {
2945 Some(PayloadTypeMetadata {
2946 ty,
2947 lift_js_expr,
2948 lower_js_expr,
2949 ..
2950 }) => {
2951 assert_eq!(Some(*ty), **payload, "future element type mismatch");
2952 (lift_js_expr.to_string(), lower_js_expr.to_string())
2953 }
2954 None => (
2955 "() => {{ throw new Error('no lift fn'); }}".into(),
2956 "() => {{ throw new Error('no lower fn'); }}".into(),
2957 ),
2958 };
2959 if let Some(PayloadTypeMetadata { ty, .. }) = future_element_ty {
2960 assert_eq!(Some(*ty), **payload, "future element type mismatch");
2961 }
2962
2963 let tmp = self.tmp();
2964 let result_var = format!("futureResult{tmp}");
2965
2966 // We only need to attempt to do an immediate lift in non-async cases,
2967 // as the return of the function execution ('above' in the code)
2968 // will be the future idx
2969 if !self.is_async {
2970 // If we're dealing with a sync function, we can use the return directly
2971 let arg_future_end_idx = operands
2972 .first()
2973 .expect("unexpectedly missing future end return arg in FutureLift");
2974
2975 let (payload_ty_size32_js, payload_ty_align32_js) =
2976 if let Some(payload_ty) = payload {
2977 (
2978 self.sizes.size(payload_ty).size_wasm32().to_string(),
2979 self.sizes.align(payload_ty).align_wasm32().to_string(),
2980 )
2981 } else {
2982 ("null".into(), "null".into())
2983 };
2984
2985 let future_table_idx = future_table_idx_ty.as_u32();
2986 let component_idx = self.canon_opts.instance.as_u32();
2987
2988 uwriteln!(
2989 self.src,
2990 "
2991 const {result_var} = {future_new_from_lift_fn}({{
2992 componentIdx: {component_idx},
2993 futureTableIdx: {future_table_idx},
2994 futureEndWaitableIdx: {arg_future_end_idx},
2995 payloadLiftFn: {lift_fn_js},
2996 payloadLowerFn: {lower_fn_js},
2997 payloadTypeSize32: {payload_ty_size32_js},
2998 payloadTypeAlign32: {payload_ty_align32_js},
2999 }});",
3000 );
3001 }
3002
3003 results.push(result_var.clone());
3004 }
3005
3006 Instruction::StreamLower { ty, .. } => {
3007 let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
3008 let stream_arg = operands
3009 .first()
3010 .expect("unexpectedly missing StreamLower arg");
3011 let async_iterator_symbol = self.intrinsic(Intrinsic::SymbolAsyncIterator);
3012 let iterator_symbol = self.intrinsic(Intrinsic::SymbolIterator);
3013 let external_readable_stream_class =
3014 self.intrinsic(Intrinsic::PlatformReadableStreamClass);
3015 let get_or_create_async_state_fn = self.intrinsic(Intrinsic::Component(
3016 ComponentIntrinsic::GetOrCreateAsyncState,
3017 ));
3018 let gen_stream_host_inject_fn = self.intrinsic(Intrinsic::AsyncStream(
3019 AsyncStreamIntrinsic::GenStreamHostInjectFn,
3020 ));
3021
3022 // TODO(???): A component could end up receiving a stream that it outputted,
3023 // and the below would fail (imported: false)?
3024
3025 // Build the lowering function for the type produced by the stream
3026 let type_id = &crate::dealias(self.resolve, *ty);
3027 let ResourceTable {
3028 imported: true,
3029 data:
3030 ResourceData::Guest {
3031 extra:
3032 Some(ResourceExtraData::Stream {
3033 table_idx: stream_table_idx_ty,
3034 elem_ty,
3035 }),
3036 ..
3037 },
3038 } = self
3039 .resource_map
3040 .get(type_id)
3041 .expect("missing resource mapping for stream lower")
3042 else {
3043 unreachable!("invalid resource table observed during stream lower");
3044 };
3045
3046 let component_idx = self.canon_opts.instance.as_u32();
3047 let stream_table_idx = stream_table_idx_ty.as_u32();
3048
3049 let (
3050 payload_type_name_js,
3051 lift_fn_js,
3052 lower_fn_js,
3053 payload_is_none,
3054 payload_is_numeric,
3055 payload_is_borrow,
3056 payload_is_async_value,
3057 payload_size32_js,
3058 payload_align32_js,
3059 payload_flat_count_js,
3060 ) = match elem_ty {
3061 Some(PayloadTypeMetadata {
3062 ty: _,
3063 iface_ty,
3064 lift_js_expr,
3065 lower_js_expr,
3066 size32,
3067 align32,
3068 flat_count,
3069 }) => (
3070 format!("'{iface_ty:?}'"),
3071 lift_js_expr.as_str(),
3072 lower_js_expr.as_str(),
3073 "false",
3074 format!(
3075 "{}",
3076 matches!(
3077 iface_ty,
3078 InterfaceType::U8
3079 | InterfaceType::U16
3080 | InterfaceType::U32
3081 | InterfaceType::U64
3082 | InterfaceType::S8
3083 | InterfaceType::S16
3084 | InterfaceType::S32
3085 | InterfaceType::S64
3086 | InterfaceType::Float32
3087 | InterfaceType::Float64
3088 )
3089 ),
3090 format!("{}", matches!(iface_ty, InterfaceType::Borrow(_))),
3091 format!(
3092 "{}",
3093 matches!(
3094 iface_ty,
3095 InterfaceType::Stream(_) | InterfaceType::Future(_)
3096 )
3097 ),
3098 size32.to_string(),
3099 align32.to_string(),
3100 flat_count.unwrap_or(0).to_string(),
3101 ),
3102 None => (
3103 "null".into(),
3104 "() => {{ throw new Error('no lift fn'); }}",
3105 "() => {{ throw new Error('no lower fn'); }}",
3106 "true",
3107 "false".into(),
3108 "false".into(),
3109 "false".into(),
3110 "null".into(),
3111 "null".into(),
3112 "0".into(),
3113 ),
3114 };
3115
3116 // Retrieve the realloc fn if present, in case lowering fns need to allocate
3117 //
3118 // The realloc fn is saved on the element metadata which is passed through to
3119 // stream end and underlying buffer
3120 let get_realloc_fn_js = match self.canon_opts.data_model {
3121 CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions {
3122 realloc: Some(realloc_idx),
3123 ..
3124 }) => format!("() => realloc{}", realloc_idx.as_u32()),
3125 _ => "undefined".into(),
3126 };
3127
3128 let tmp = self.tmp();
3129 let lowered_stream_waitable_idx = format!("streamWaitableIdx{tmp}");
3130 uwriteln!(
3131 self.src,
3132 r#"
3133 if (!({async_iterator_symbol} in {stream_arg})
3134 && !({iterator_symbol} in {stream_arg})
3135 && !({stream_arg} instanceof {external_readable_stream_class})) {{
3136 {debug_log_fn}('[Instruction::StreamLower] object with no supported stream protocol', {{ {stream_arg} }});
3137 throw new Error('unrecognized stream object (no supported stream protocol)');
3138 }}
3139
3140 const cstate{tmp} = {get_or_create_async_state_fn}({component_idx});
3141 if (!cstate{tmp}) {{ throw new Error(`missing component state for component [{component_idx}]`); }}
3142
3143 const {{ writeEnd: hostWriteEnd{tmp}, readEnd: readEnd{tmp} }} = cstate{tmp}.createStream({{
3144 tableIdx: {stream_table_idx},
3145 elemMeta: {{
3146 liftFn: {lift_fn_js},
3147 lowerFn: {lower_fn_js},
3148 payloadTypeName: {payload_type_name_js},
3149 isNone: {payload_is_none},
3150 isNumeric: {payload_is_numeric},
3151 isBorrowed: {payload_is_borrow},
3152 isAsyncValue: {payload_is_async_value},
3153 flatCount: {payload_flat_count_js},
3154 align32: {payload_align32_js},
3155 size32: {payload_size32_js},
3156 // TODO(feat): facilitate non utf8 string encoding for lowered streams
3157 stringEncoding: 'utf8',
3158 geReallocFn: {get_realloc_fn_js},
3159 }},
3160 }});
3161
3162 let readFn{tmp};
3163 if ({async_iterator_symbol} in {stream_arg}) {{
3164 let asyncIterator = {stream_arg}[{async_iterator_symbol}]();
3165 readFn{tmp} = () => asyncIterator.next();
3166 }} else if ({iterator_symbol} in {stream_arg}) {{
3167 let iterator = {stream_arg}[{iterator_symbol}]();
3168 readFn{tmp} = async () => iterator.next();
3169 }} else if ({stream_arg} instanceof {external_readable_stream_class}) {{
3170 // At this point we're dealing with a readable stream that *somehow *does not*
3171 // implement the async iterator protocol.
3172 const lockedReader = {stream_arg}.getReader();
3173 readFn{tmp} = () => lockedReader.read();
3174 }}
3175
3176 const hostInjectFn = {gen_stream_host_inject_fn}({{
3177 readFn: readFn{tmp},
3178 hostWriteEnd: hostWriteEnd{tmp},
3179 }});
3180 readEnd{tmp}.setHostInjectFn(hostInjectFn);
3181
3182 const {lowered_stream_waitable_idx} = readEnd{tmp}.waitableIdx();
3183 "#
3184 );
3185
3186 results.push(lowered_stream_waitable_idx);
3187 }
3188
3189 Instruction::StreamLift { payload, ty } => {
3190 let component_idx = self.canon_opts.instance.as_u32();
3191 let stream_new_from_lift_fn = self.intrinsic(Intrinsic::AsyncStream(
3192 AsyncStreamIntrinsic::StreamNewFromLift,
3193 ));
3194
3195 // We must look up the type idx to find the stream
3196 let type_id = &crate::dealias(self.resolve, *ty);
3197 let ResourceTable {
3198 imported: true,
3199 data:
3200 ResourceData::Guest {
3201 extra:
3202 Some(ResourceExtraData::Stream {
3203 table_idx: stream_table_idx_ty,
3204 elem_ty: stream_element_ty,
3205 }),
3206 ..
3207 },
3208 } = self
3209 .resource_map
3210 .get(type_id)
3211 .expect("missing resource mapping for stream lift")
3212 else {
3213 unreachable!("invalid resource table observed during stream lift");
3214 };
3215
3216 // if a stream element is present, it should match the payload we're getting
3217 let (lift_fn_js, lower_fn_js) = match stream_element_ty {
3218 Some(PayloadTypeMetadata {
3219 ty,
3220 lift_js_expr,
3221 lower_js_expr,
3222 ..
3223 }) => {
3224 assert_eq!(Some(*ty), **payload, "stream element type mismatch");
3225 (lift_js_expr.to_string(), lower_js_expr.to_string())
3226 }
3227 None => (
3228 "() => {{ throw new Error('no lift fn'); }}".into(),
3229 "() => {{ throw new Error('no lower fn'); }}".into(),
3230 ),
3231 };
3232 if let Some(PayloadTypeMetadata { ty, .. }) = stream_element_ty {
3233 assert_eq!(Some(*ty), **payload, "stream element type mismatch");
3234 }
3235
3236 let tmp = self.tmp();
3237 let result_var = format!("streamResult{tmp}");
3238
3239 // We only need to attempt to do an immediate lift in non-async cases,
3240 // as the return of the function execution ('above' in the code)
3241 // will be the stream idx
3242 if !self.is_async {
3243 // If we're dealing with a sync function, we can use the return directly
3244 let arg_stream_end_idx = operands
3245 .first()
3246 .expect("unexpectedly missing stream end return arg in StreamLift");
3247
3248 let (payload_ty_size32_js, payload_ty_align32_js) =
3249 if let Some(payload_ty) = payload {
3250 (
3251 self.sizes.size(payload_ty).size_wasm32().to_string(),
3252 self.sizes.align(payload_ty).align_wasm32().to_string(),
3253 )
3254 } else {
3255 ("null".into(), "null".into())
3256 };
3257
3258 let stream_table_idx = stream_table_idx_ty.as_u32();
3259
3260 uwriteln!(
3261 self.src,
3262 "
3263 const {result_var} = {stream_new_from_lift_fn}({{
3264 componentIdx: {component_idx},
3265 streamTableIdx: {stream_table_idx},
3266 streamEndWaitableIdx: {arg_stream_end_idx},
3267 payloadLiftFn: {lift_fn_js},
3268 payloadLowerFn: {lower_fn_js},
3269 payloadTypeSize32: {payload_ty_size32_js},
3270 payloadTypeAlign32: {payload_ty_align32_js},
3271 }});",
3272 );
3273 }
3274
3275 // TODO(fix): in the async case we return an uninitialized var, which should not be necessary
3276 results.push(result_var.clone());
3277 }
3278
3279 // Instruction::AsyncTaskReturn does *not* correspond to an canonical `task.return`,
3280 // but rather to a "return"/exit from an a lifted async function (e.g. pre-callback)
3281 //
3282 // To modify behavior of the `task.return` intrinsic, see:
3283 // - `Trampoline::TaskReturn`
3284 // - `AsyncTaskIntrinsic::TaskReturn`
3285 //
3286 // This is simply the end of the async function definition (e.g. `CallWasm`) that has been
3287 // lifted, which contains information about the async state.
3288 //
3289 // For an async function 'some-func', this instruction is triggered w/ the following `name`s:
3290 // - '[task-return]some-func'
3291 //
3292 // At this point in code generation, the following things have already been set:
3293 // - `parentTask`: A parent task, if one was executing before
3294 // - `subtask`: A subtask, if the current task is a subtask of a parent task
3295 // - `task`: the currently executing task
3296 // - `ret`: the original function return value, via (i.e. via `CallWasm`/`CallInterface`)
3297 // - `hostProvided`: whether the original function was a host-provided (i.e. host provided import)
3298 //
3299 Instruction::AsyncTaskReturn { name, params } => {
3300 let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
3301 let component_instance_idx = self.canon_opts.instance.as_u32();
3302 let is_async_js = self.requires_async_porcelain | self.is_async;
3303 let async_driver_loop_fn =
3304 self.intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::DriverLoop));
3305 let get_or_create_async_state_fn = self.intrinsic(Intrinsic::Component(
3306 ComponentIntrinsic::GetOrCreateAsyncState,
3307 ));
3308
3309 uwriteln!(
3310 self.src,
3311 "{debug_log_fn}('{prefix} [Instruction::AsyncTaskReturn]', {{
3312 funcName: '{name}',
3313 paramCount: {param_count},
3314 componentIdx: {component_instance_idx},
3315 postReturn: {post_return_present},
3316 hostProvided,
3317 }});",
3318 param_count = params.len(),
3319 post_return_present = self.post_return.is_some(),
3320 prefix = self.tracing_prefix,
3321 );
3322
3323 assert!(
3324 self.is_async,
3325 "non-async functions should not be performing async returns (func {name})",
3326 );
3327
3328 // If we're dealing with an async call, then `ret` is actually the
3329 // state of async behavior.
3330 //
3331 // The result *should* be a Promise that resolves to whatever the current task
3332 // will eventually resolve to.
3333 //
3334 // NOTE: Regardless of whether async porcelain is required here, we want to return the result
3335 // of the computation as a whole, not the current async state (which is what `ret` currently is).
3336 //
3337 // `ret` is only a Promise if we have async-lowered the function in question (e.g. via JSPI)
3338 //
3339 // ```ts
3340 // type ret = number | Promise<number>;
3341 // ```ts
3342 //
3343 // If the import was host provided we *already* have the result via
3344 // JSPI and simply calling the host provided JS function -- there is no need
3345 // to drive the async loop as with an async import that came from a component.
3346 //
3347 // If a subtask is defined, then we're in the case of a lowered async import,
3348 // which means that the first async call (to the callee fn) has occurred,
3349 // and a subtask has been created, but has not been triggered as started.
3350 //
3351 // NOTE: for host provided functions, we know that the resolution fo the
3352 // function itself are the lifted (component model -- i.e. a string not a pointer + len)
3353 // results. In those cases, we can simply return the result that was provided by the host.
3354 //
3355 // Alternatively, if we have entered an async return, and are part of a subtask
3356 // then we should start it, given that the task we have recently created (however we got to
3357 // the async return) is going to continue to be polled soon (via the driver loop).
3358 //
3359 uwriteln!(
3360 self.src,
3361 r#"
3362 if (hostProvided) {{
3363 {debug_log_fn}('[Instruction::AsyncTaskReturn] signaling host-provided async return completion', {{
3364 task: task.id(),
3365 subtask: subtask?.id(),
3366 result: ret,
3367 }})
3368 task.resolve([ret]);
3369 task.exit();
3370 return task.completionPromise();
3371 }}
3372
3373 const componentState = {get_or_create_async_state_fn}({component_instance_idx});
3374 if (!componentState) {{ throw new Error('failed to lookup current component state'); }}
3375
3376 queueMicrotask(async (resolve, reject) => {{
3377 try {{
3378 {debug_log_fn}("[Instruction::AsyncTaskReturn] starting driver loop", {{
3379 fnName: '{name}',
3380 componentInstanceIdx: {component_instance_idx},
3381 taskID: task.id(),
3382 }});
3383 await {async_driver_loop_fn}({{
3384 componentInstanceIdx: {component_instance_idx},
3385 componentState,
3386 task,
3387 fnName: '{name}',
3388 isAsync: {is_async_js},
3389 callbackResult: ret,
3390 }});
3391 }} catch (err) {{
3392 {debug_log_fn}("[Instruction::AsyncTaskReturn] driver loop call failure", {{ err }});
3393 }}
3394 }});
3395
3396 let taskRes = await task.completionPromise();
3397 if (task.getErrHandling() === 'throw-result-err') {{
3398 if (typeof taskRes !== 'object') {{ return taskRes; }}
3399 if (taskRes.tag === 'err') {{ throw taskRes.val; }}
3400 if (taskRes.tag === 'ok') {{ taskRes = taskRes.val; }}
3401 }}
3402
3403 return taskRes;
3404 "#,
3405 );
3406 }
3407
3408 Instruction::GuestDeallocate { .. }
3409 | Instruction::GuestDeallocateString
3410 | Instruction::GuestDeallocateList { .. }
3411 | Instruction::GuestDeallocateVariant { .. } => unimplemented!("Guest deallocation"),
3412 }
3413 }
3414}
3415
3416/// Tests whether `ty` can be represented with `null`, and if it can then
3417/// the "other type" is returned. If `Some` is returned that means that `ty`
3418/// is `null | <return>`. If `None` is returned that means that `null` can't
3419/// be used to represent `ty`.
3420pub fn as_nullable<'a>(resolve: &'a Resolve, ty: &'a Type) -> Option<&'a Type> {
3421 let id = match ty {
3422 Type::Id(id) => *id,
3423 _ => return None,
3424 };
3425 match &resolve.types[id].kind {
3426 // If `ty` points to an `option<T>`, then `ty` can be represented
3427 // with `null` if `t` itself can't be represented with null. For
3428 // example `option<option<u32>>` can't be represented with `null`
3429 // since that's ambiguous if it's `none` or `some(none)`.
3430 //
3431 // Note, oddly enough, that `option<option<option<u32>>>` can be
3432 // represented as `null` since:
3433 //
3434 // * `null` => `none`
3435 // * `{ tag: "none" }` => `some(none)`
3436 // * `{ tag: "some", val: null }` => `some(some(none))`
3437 // * `{ tag: "some", val: 1 }` => `some(some(some(1)))`
3438 //
3439 // It's doubtful anyone would actually rely on that though due to
3440 // how confusing it is.
3441 TypeDefKind::Option(t) => {
3442 if !maybe_null(resolve, t) {
3443 Some(t)
3444 } else {
3445 None
3446 }
3447 }
3448 TypeDefKind::Type(t) => as_nullable(resolve, t),
3449 _ => None,
3450 }
3451}
3452
3453pub fn maybe_null(resolve: &Resolve, ty: &Type) -> bool {
3454 as_nullable(resolve, ty).is_some()
3455}
3456
3457/// Retrieve the specialized JS array type that would contain a given element type,
3458/// if one exists.
3459///
3460/// e.g. a Wasm [`Type::U8`] would be represetned by a JS `Uint8Array`
3461///
3462/// # Arguments
3463///
3464/// * `resolve` - The [`Resolve`] used to look up nested type IDs if necessary
3465/// * `element_ty` - The [`Type`] that represents elements of the array
3466pub fn js_array_ty(resolve: &Resolve, element_ty: &Type) -> Option<&'static str> {
3467 match element_ty {
3468 Type::Bool => None,
3469 Type::U8 => Some("Uint8Array"),
3470 Type::S8 => Some("Int8Array"),
3471 Type::U16 => Some("Uint16Array"),
3472 Type::S16 => Some("Int16Array"),
3473 Type::U32 => Some("Uint32Array"),
3474 Type::S32 => Some("Int32Array"),
3475 Type::U64 => Some("BigUint64Array"),
3476 Type::S64 => Some("BigInt64Array"),
3477 Type::F32 => Some("Float32Array"),
3478 Type::F64 => Some("Float64Array"),
3479 Type::Char => None,
3480 Type::String => None,
3481 Type::ErrorContext => None,
3482 Type::Id(id) => match &resolve.types[*id].kind {
3483 // Recur to resolve type aliases, etc.
3484 TypeDefKind::Type(t) => js_array_ty(resolve, t),
3485 _ => None,
3486 },
3487 }
3488}
3489
3490/// Generate the JS `DataView` set and numeric checks for a given numeric type
3491///
3492/// # Arguments
3493///
3494/// * `ty` - the [`Type`] to check
3495///
3496fn gen_dataview_set_and_check_fn_js_for_numeric_type(ty: &Type) -> (&str, String) {
3497 let check_fn = Intrinsic::Conversion(ConversionIntrinsic::RequireValidNumericPrimitive).name();
3498 match ty {
3499 // Unsigned Integers
3500 Type::Bool => ("setUint8", format!("{check_fn}.bind(null, 'u8')",)),
3501 Type::U8 => ("setUint8", format!("{check_fn}.bind(null, 'u8')",)),
3502 Type::U16 => ("setUint16", format!("{check_fn}.bind(null, 'u16')",)),
3503 Type::U32 => ("setUint32", format!("{check_fn}.bind(null, 'u32')",)),
3504 Type::U64 => ("setBigUint64", format!("{check_fn}.bind(null, 'u64')",)),
3505 // Signed integers
3506 Type::S8 => ("setInt8", format!("{check_fn}.bind(null, 's8')",)),
3507 Type::S16 => ("setInt16", format!("{check_fn}.bind(null, 's16')",)),
3508 Type::S32 => ("setInt32", format!("{check_fn}.bind(null, 's32')",)),
3509 Type::S64 => ("setBigInt64", format!("{check_fn}.bind(null, 's64')",)),
3510 // Floating point
3511 Type::F32 => ("setFloat32", format!("{check_fn}.bind(null, 'f32')",)),
3512 Type::F64 => ("setFloat64", format!("{check_fn}.bind(null, 'f64')",)),
3513 _ => unreachable!("unsupported type [{ty:?}] for canonical list lower"),
3514 }
3515}