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