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