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