1use std::collections::{BTreeMap, BTreeSet};
2use std::fmt::Write;
3use std::mem;
4
5use heck::{ToLowerCamelCase, ToUpperCamelCase};
6use wasmtime_environ::component::{
7 CanonicalOptions, 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#[derive(Debug, Clone, PartialEq)]
31pub enum ErrHandling {
32 None,
35 ThrowResultErr,
37 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#[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)]
69pub enum ResourceExtraData {
70 Stream {
71 table_idx: TypeStreamTableIndex,
72 elem_ty: Option<Type>,
73 },
74 Future {
75 table_idx: TypeFutureTableIndex,
76 elem_ty: Option<Type>,
77 },
78 ErrorContext {
79 table_idx: TypeComponentLocalErrorContextTableIndex,
80 },
81}
82
83#[derive(Clone, Debug, PartialEq)]
108pub struct ResourceTable {
109 pub imported: bool,
113
114 pub data: ResourceData,
116}
117
118pub type ResourceMap = BTreeMap<TypeId, ResourceTable>;
120
121pub struct FunctionBindgen<'a> {
122 pub resource_map: &'a ResourceMap,
124
125 pub clear_resource_borrows: bool,
127
128 pub intrinsics: &'a mut BTreeSet<Intrinsic>,
130
131 pub valid_lifting_optimization: bool,
133
134 pub sizes: &'a SizeAlign,
136
137 pub err: ErrHandling,
139
140 pub tmp: usize,
142
143 pub src: source::Source,
145
146 pub block_storage: Vec<source::Source>,
148
149 pub blocks: Vec<(String, Vec<String>)>,
151
152 pub params: Vec<String>,
154
155 pub memory: Option<&'a String>,
157
158 pub realloc: Option<&'a String>,
160
161 pub post_return: Option<&'a String>,
163
164 pub tracing_prefix: &'a String,
166
167 pub tracing_enabled: bool,
169
170 pub encoding: StringEncoding,
172
173 pub callee: &'a str,
175
176 pub callee_resource_dynamic: bool,
178
179 pub resolve: &'a Resolve,
181
182 pub requires_async_porcelain: bool,
187
188 pub is_async: bool,
190
191 pub canon_opts: &'a CanonicalOptions,
193
194 pub iface_name: Option<&'a str>,
196}
197
198impl FunctionBindgen<'_> {
199 fn tmp(&mut self) -> usize {
200 let ret = self.tmp;
201 self.tmp += 1;
202 ret
203 }
204
205 fn intrinsic(&mut self, intrinsic: Intrinsic) -> String {
206 self.intrinsics.insert(intrinsic);
207 intrinsic.name().to_string()
208 }
209
210 fn clamp_guest<T>(&mut self, results: &mut Vec<String>, operands: &[String], min: T, max: T)
211 where
212 T: std::fmt::Display,
213 {
214 let clamp = self.intrinsic(Intrinsic::ClampGuest);
215 results.push(format!("{}({}, {}, {})", clamp, operands[0], min, max));
216 }
217
218 fn load(
219 &mut self,
220 method: &str,
221 offset: ArchitectureSize,
222 operands: &[String],
223 results: &mut Vec<String>,
224 ) {
225 let view = self.intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::DataView));
226 let Some(memory) = self.memory.as_ref() else {
227 panic!(
228 "unexpectedly missing memory during bindgen for interface [{:?}] (callee {})",
229 self.iface_name, self.callee,
230 );
231 };
232 results.push(format!(
233 "{view}({memory}).{method}({} + {offset}, true)",
234 operands[0],
235 offset = offset.size_wasm32()
236 ));
237 }
238
239 fn store(&mut self, method: &str, offset: ArchitectureSize, operands: &[String]) {
240 let view = self.intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::DataView));
241 let memory = self.memory.as_ref().unwrap();
242 uwriteln!(
243 self.src,
244 "{view}({memory}).{method}({} + {offset}, {}, true);",
245 operands[1],
246 operands[0],
247 offset = offset.size_wasm32()
248 );
249 }
250
251 fn generate_result_assignment_lhs(
273 &mut self,
274 amt: usize,
275 results: &mut Vec<String>,
276 is_async: bool,
277 ) -> String {
278 let mut s = String::new();
279 match amt {
280 0 => {
281 if is_async {
284 uwrite!(s, "let ret = ")
285 } else {
286 uwrite!(s, "let ret;")
287 }
288 }
289 1 => {
290 uwrite!(s, "let ret = ");
291 results.push("ret".to_string());
292 }
293 n => {
294 uwrite!(s, "var [");
295 for i in 0..n {
296 if i > 0 {
297 uwrite!(s, ", ");
298 }
299 uwrite!(s, "ret{}", i);
300 results.push(format!("ret{i}"));
301 }
302 uwrite!(s, "] = ");
303 }
304 }
305 s
306 }
307
308 fn bitcast(&mut self, cast: &Bitcast, op: &str) -> String {
309 match cast {
310 Bitcast::I32ToF32 => {
311 let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::I32ToF32));
312 format!("{cvt}({op})")
313 }
314 Bitcast::F32ToI32 => {
315 let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::F32ToI32));
316 format!("{cvt}({op})")
317 }
318 Bitcast::I64ToF64 => {
319 let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::I64ToF64));
320 format!("{cvt}({op})")
321 }
322 Bitcast::F64ToI64 => {
323 let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::F64ToI64));
324 format!("{cvt}({op})")
325 }
326 Bitcast::I32ToI64 => format!("BigInt({op})"),
327 Bitcast::I64ToI32 => format!("Number({op})"),
328 Bitcast::I64ToF32 => {
329 let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::I32ToF32));
330 format!("{cvt}(Number({op}))")
331 }
332 Bitcast::F32ToI64 => {
333 let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::F32ToI32));
334 format!("BigInt({cvt}({op}))")
335 }
336 Bitcast::None
337 | Bitcast::P64ToI64
338 | Bitcast::LToI32
339 | Bitcast::I32ToL
340 | Bitcast::LToP
341 | Bitcast::PToL
342 | Bitcast::PToI32
343 | Bitcast::I32ToP => op.to_string(),
344 Bitcast::PToP64 | Bitcast::I64ToP64 | Bitcast::LToI64 => format!("BigInt({op})"),
345 Bitcast::P64ToP | Bitcast::I64ToL => format!("Number({op})"),
346 Bitcast::Sequence(casts) => {
347 let mut statement = op.to_string();
348 for cast in casts.iter() {
349 statement = self.bitcast(cast, &statement);
350 }
351 statement
352 }
353 }
354 }
355
356 fn start_current_task(&mut self, instr: &Instruction) {
361 let is_async = self.is_async;
362 let is_manual_async = self.requires_async_porcelain;
363 let fn_name = self.callee;
364 let err_handling = self.err.to_js_string();
365 let callback_fn_js = self
366 .canon_opts
367 .callback
368 .as_ref()
369 .map(|v| format!("callback_{}", v.as_u32()))
370 .unwrap_or_else(|| "null".into());
371 let (calling_wasm_export, prefix) = match instr {
372 Instruction::CallWasm { .. } => (true, "_wasm_call_"),
373 Instruction::CallInterface { .. } => (false, "_interface_call_"),
374 _ => unreachable!(
375 "unrecognized instruction triggering start of current task: [{instr:?}]"
376 ),
377 };
378 let start_current_task_fn = self.intrinsic(Intrinsic::AsyncTask(
379 AsyncTaskIntrinsic::CreateNewCurrentTask,
380 ));
381 let global_task_map = self.intrinsic(Intrinsic::AsyncTask(
382 AsyncTaskIntrinsic::GlobalAsyncCurrentTaskMap,
383 ));
384 let component_instance_idx = self.canon_opts.instance.as_u32();
385
386 if is_async && self.requires_async_porcelain {
389 uwriteln!(
390 self.src,
391 r#"
392 // All other tasks must finish before we can start this one
393 const taskMetas = {global_task_map}.get({component_instance_idx});
394 if (taskMetas) {{
395 const taskPromises = taskMetas
396 .filter(mt => mt.componentIdx === {component_instance_idx})
397 .map(mt => mt.task)
398 .filter(t => !t.getParentSubtask())
399 .map(t => t.exitPromise());
400 await Promise.allSettled(taskPromises);
401 }}
402 "#,
403 );
404 }
405
406 uwriteln!(
407 self.src,
408 r#"
409 const [task, {prefix}currentTaskID] = {start_current_task_fn}({{
410 componentIdx: {component_instance_idx},
411 isAsync: {is_async},
412 isManualAsync: {is_manual_async},
413 entryFnName: '{fn_name}',
414 getCallbackFn: () => {callback_fn_js},
415 callbackFnName: '{callback_fn_js}',
416 errHandling: '{err_handling}',
417 callingWasmExport: {calling_wasm_export},
418 }});
419 "#,
420 );
421 }
422}
423
424impl ManagesIntrinsics for FunctionBindgen<'_> {
425 fn add_intrinsic(&mut self, intrinsic: Intrinsic) {
427 self.intrinsic(intrinsic);
428 }
429}
430
431impl Bindgen for FunctionBindgen<'_> {
432 type Operand = String;
433
434 fn sizes(&self) -> &SizeAlign {
436 self.sizes
437 }
438
439 fn push_block(&mut self) {
441 let prev = mem::take(&mut self.src);
442 self.block_storage.push(prev);
443 }
444
445 fn finish_block(&mut self, operands: &mut Vec<String>) {
447 let to_restore = self.block_storage.pop().unwrap();
448 let src = mem::replace(&mut self.src, to_restore);
449 self.blocks.push((src.into(), mem::take(operands)));
450 }
451
452 fn return_pointer(&mut self, _size: ArchitectureSize, _align: Alignment) -> String {
454 unimplemented!("determining the return pointer for this function is not implemented");
455 }
456
457 fn is_list_canonical(&self, resolve: &Resolve, elem_ty: &Type) -> bool {
465 js_array_ty(resolve, elem_ty).is_some()
466 }
467
468 fn emit(
469 &mut self,
470 resolve: &Resolve,
471 inst: &Instruction<'_>,
472 operands: &mut Vec<String>,
473 results: &mut Vec<String>,
474 ) {
475 match inst {
476 Instruction::GetArg { nth } => results.push(self.params[*nth].clone()),
477
478 Instruction::I32Const { val } => results.push(val.to_string()),
479
480 Instruction::ConstZero { tys } => {
481 for t in tys.iter() {
482 match t {
483 WasmType::I64 | WasmType::PointerOrI64 => results.push("0n".to_string()),
484 WasmType::I32
485 | WasmType::F32
486 | WasmType::F64
487 | WasmType::Pointer
488 | WasmType::Length => results.push("0".to_string()),
489 }
490 }
491 }
492
493 Instruction::U8FromI32 => self.clamp_guest(results, operands, u8::MIN, u8::MAX),
494
495 Instruction::S8FromI32 => self.clamp_guest(results, operands, i8::MIN, i8::MAX),
496
497 Instruction::U16FromI32 => self.clamp_guest(results, operands, u16::MIN, u16::MAX),
498
499 Instruction::S16FromI32 => self.clamp_guest(results, operands, i16::MIN, i16::MAX),
500
501 Instruction::U32FromI32 => results.push(format!("{} >>> 0", operands[0])),
502
503 Instruction::U64FromI64 => {
504 results.push(format!("BigInt.asUintN(64, BigInt({}))", operands[0]))
505 }
506
507 Instruction::S32FromI32 | Instruction::S64FromI64 => {
508 results.push(operands.pop().unwrap())
509 }
510
511 Instruction::I32FromU8 => {
512 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToUint8));
513 results.push(format!("{conv}({op})", op = operands[0]))
514 }
515
516 Instruction::I32FromS8 => {
517 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToInt8));
518 results.push(format!("{conv}({op})", op = operands[0]))
519 }
520
521 Instruction::I32FromU16 => {
522 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToUint16));
523 results.push(format!("{conv}({op})", op = operands[0]))
524 }
525
526 Instruction::I32FromS16 => {
527 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToInt16));
528 results.push(format!("{conv}({op})", op = operands[0]))
529 }
530
531 Instruction::I32FromU32 => {
532 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToUint32));
533 results.push(format!("{conv}({op})", op = operands[0]))
534 }
535
536 Instruction::I32FromS32 => {
537 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToInt32));
538 results.push(format!("{conv}({op})", op = operands[0]))
539 }
540
541 Instruction::I64FromU64 => {
542 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToBigUint64));
543 results.push(format!("{conv}({op})", op = operands[0]))
544 }
545
546 Instruction::I64FromS64 => {
547 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToBigInt64));
548 results.push(format!("{conv}({op})", op = operands[0]))
549 }
550
551 Instruction::F32FromCoreF32 | Instruction::F64FromCoreF64 => {
552 results.push(operands.pop().unwrap())
553 }
554
555 Instruction::CoreF32FromF32 | Instruction::CoreF64FromF64 => {
556 results.push(format!("+{}", operands[0]))
557 }
558
559 Instruction::CharFromI32 => {
560 let validate =
561 self.intrinsic(Intrinsic::String(StringIntrinsic::ValidateGuestChar));
562 results.push(format!("{}({})", validate, operands[0]));
563 }
564
565 Instruction::I32FromChar => {
566 let validate = self.intrinsic(Intrinsic::String(StringIntrinsic::ValidateHostChar));
567 results.push(format!("{}({})", validate, operands[0]));
568 }
569
570 Instruction::Bitcasts { casts } => {
571 for (cast, op) in casts.iter().zip(operands) {
572 results.push(self.bitcast(cast, op));
573 }
574 }
575
576 Instruction::BoolFromI32 => {
577 let tmp = self.tmp();
578 uwrite!(self.src, "var bool{} = {};\n", tmp, operands[0]);
579 if self.valid_lifting_optimization {
580 results.push(format!("!!bool{tmp}"));
581 } else {
582 let throw = self.intrinsic(Intrinsic::ThrowInvalidBool);
583 results.push(format!(
584 "bool{tmp} == 0 ? false : (bool{tmp} == 1 ? true : {throw}())"
585 ));
586 }
587 }
588
589 Instruction::I32FromBool => {
590 results.push(format!("{} ? 1 : 0", operands[0]));
591 }
592
593 Instruction::RecordLower { record, .. } => {
594 let tmp = self.tmp();
597 let mut expr = "var {".to_string();
598 for (i, field) in record.fields.iter().enumerate() {
599 if i > 0 {
600 expr.push_str(", ");
601 }
602 let name = format!("v{tmp}_{i}");
603 expr.push_str(&field.name.to_lower_camel_case());
604 expr.push_str(": ");
605 expr.push_str(&name);
606 results.push(name);
607 }
608 uwrite!(self.src, "{} }} = {};\n", expr, operands[0]);
609 }
610
611 Instruction::RecordLift { record, .. } => {
612 let mut result = "{\n".to_string();
616 for (field, op) in record.fields.iter().zip(operands) {
617 result.push_str(&format!("{}: {},\n", field.name.to_lower_camel_case(), op));
618 }
619 result.push('}');
620 results.push(result);
621 }
622
623 Instruction::TupleLower { tuple, .. } => {
624 let tmp = self.tmp();
628 let mut expr = "var [".to_string();
629 for i in 0..tuple.types.len() {
630 if i > 0 {
631 expr.push_str(", ");
632 }
633 let name = format!("tuple{tmp}_{i}");
634 expr.push_str(&name);
635 results.push(name);
636 }
637 uwrite!(self.src, "{}] = {};\n", expr, operands[0]);
638 }
639
640 Instruction::TupleLift { .. } => {
641 results.push(format!("[{}]", operands.join(", ")));
644 }
645
646 Instruction::FlagsLower { flags, .. } => {
647 let op0 = &operands[0];
648
649 for _ in 0..flags.repr().count() {
651 let tmp = self.tmp();
652 let name = format!("flags{tmp}");
653 uwrite!(self.src, "let {name} = 0;\n");
656 results.push(name);
657 }
658
659 uwrite!(
660 self.src,
661 "if (typeof {op0} === 'object' && {op0} !== null) {{\n"
662 );
663
664 for (i, chunk) in flags.flags.chunks(32).enumerate() {
665 let result_name = &results[i];
666
667 uwrite!(self.src, "{result_name} = ");
668 for (i, flag) in chunk.iter().enumerate() {
669 if i != 0 {
670 uwrite!(self.src, " | ");
671 }
672
673 let flag = flag.name.to_lower_camel_case();
674 uwrite!(self.src, "Boolean({op0}.{flag}) << {i}");
675 }
676 uwrite!(self.src, ";\n");
677 }
678
679 uwrite!(
680 self.src,
681 "\
682 }} else if ({op0} !== null && {op0} !== undefined) {{
683 throw new TypeError('only an object, undefined or null can be converted to flags');
684 }}
685 ");
686
687 }
691
692 Instruction::FlagsLift { flags, .. } => {
693 let tmp = self.tmp();
694 results.push(format!("flags{tmp}"));
695
696 if let Some(op) = operands.last() {
697 if flags.flags.len() % 32 != 0 && !self.valid_lifting_optimization {
701 let mask: u32 = 0xffffffff << (flags.flags.len() % 32);
702 uwriteln!(
703 self.src,
704 "if (({op} & {mask}) !== 0) {{
705 throw new TypeError('flags have extraneous bits set');
706 }}"
707 );
708 }
709 }
710
711 uwriteln!(self.src, "var flags{tmp} = {{");
712
713 for (i, flag) in flags.flags.iter().enumerate() {
714 let flag = flag.name.to_lower_camel_case();
715 let op = &operands[i / 32];
716 let mask: u32 = 1 << (i % 32);
717 uwriteln!(self.src, "{flag}: Boolean({op} & {mask}),");
718 }
719
720 uwriteln!(self.src, "}};");
721 }
722
723 Instruction::VariantPayloadName => results.push("e".to_string()),
724
725 Instruction::VariantLower {
726 variant,
727 results: result_types,
728 name,
729 ..
730 } => {
731 let blocks = self
732 .blocks
733 .drain(self.blocks.len() - variant.cases.len()..)
734 .collect::<Vec<_>>();
735 let tmp = self.tmp();
736 let op = &operands[0];
737 uwriteln!(self.src, "var variant{tmp} = {op};");
738
739 for i in 0..result_types.len() {
740 uwriteln!(self.src, "let variant{tmp}_{i};");
741 results.push(format!("variant{tmp}_{i}"));
742 }
743
744 let expr_to_match = format!("variant{tmp}.tag");
745
746 uwriteln!(self.src, "switch ({expr_to_match}) {{");
747 for (case, (block, block_results)) in variant.cases.iter().zip(blocks) {
748 uwriteln!(self.src, "case '{}': {{", case.name.as_str());
749 if case.ty.is_some() {
750 uwriteln!(self.src, "const e = variant{tmp}.val;");
751 }
752 self.src.push_str(&block);
753
754 for (i, result) in block_results.iter().enumerate() {
755 uwriteln!(self.src, "variant{tmp}_{i} = {result};");
756 }
757 uwriteln!(
758 self.src,
759 "break;
760 }}"
761 );
762 }
763 let variant_name = name.to_upper_camel_case();
764 uwriteln!(
765 self.src,
766 r#"default: {{
767 throw new TypeError(`invalid variant tag value \`${{JSON.stringify({expr_to_match})}}\` (received \`${{variant{tmp}}}\`) specified for \`{variant_name}\``);
768 }}"#,
769 );
770 uwriteln!(self.src, "}}");
771 }
772
773 Instruction::VariantLift { variant, name, .. } => {
774 let blocks = self
775 .blocks
776 .drain(self.blocks.len() - variant.cases.len()..)
777 .collect::<Vec<_>>();
778
779 let tmp = self.tmp();
780 let op = &operands[0];
781
782 uwriteln!(
783 self.src,
784 "let variant{tmp};
785 switch ({op}) {{"
786 );
787
788 for (i, (case, (block, block_results))) in
789 variant.cases.iter().zip(blocks).enumerate()
790 {
791 let tag = case.name.as_str();
792 uwriteln!(
793 self.src,
794 "case {i}: {{
795 {block}\
796 variant{tmp} = {{
797 tag: '{tag}',"
798 );
799 if case.ty.is_some() {
800 assert!(block_results.len() == 1);
801 uwriteln!(self.src, " val: {}", block_results[0]);
802 } else {
803 assert!(block_results.is_empty());
804 }
805 uwriteln!(
806 self.src,
807 " }};
808 break;
809 }}"
810 );
811 }
812 let variant_name = name.to_upper_camel_case();
813 if !self.valid_lifting_optimization {
814 uwriteln!(
815 self.src,
816 "default: {{
817 throw new TypeError('invalid variant discriminant for {variant_name}');
818 }}",
819 );
820 }
821 uwriteln!(self.src, "}}");
822 results.push(format!("variant{tmp}"));
823 }
824
825 Instruction::OptionLower {
826 payload,
827 results: result_types,
828 ..
829 } => {
830 let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
831 let (mut some, some_results) = self.blocks.pop().unwrap();
832 let (mut none, none_results) = self.blocks.pop().unwrap();
833
834 let tmp = self.tmp();
835 let op = &operands[0];
836 uwriteln!(self.src, "var variant{tmp} = {op};");
837
838 for i in 0..result_types.len() {
839 uwriteln!(self.src, "let variant{tmp}_{i};");
840 results.push(format!("variant{tmp}_{i}"));
841
842 let some_result = &some_results[i];
843 let none_result = &none_results[i];
844 uwriteln!(some, "variant{tmp}_{i} = {some_result};");
845 uwriteln!(none, "variant{tmp}_{i} = {none_result};");
846 }
847
848 if maybe_null(resolve, payload) {
849 uwriteln!(
850 self.src,
851 r#"switch (variant{tmp}.tag) {{
852 case 'none': {{
853 {none}
854 break;
855 }}
856 case 'some': {{
857 const e = variant{tmp}.val;
858 {some}
859 break;
860 }}
861 default: {{
862 {debug_log_fn}("ERROR: invalid value (expected option as object with 'tag' member)", {{ value: variant{tmp}, valueType: typeof variant{tmp} }});
863 throw new TypeError('invalid variant specified for option');
864 }}
865 }}"#,
866 );
867 } else {
868 uwriteln!(
869 self.src,
870 "if (variant{tmp} === null || variant{tmp} === undefined) {{
871 {none}\
872 }} else {{
873 const e = variant{tmp};
874 {some}\
875 }}"
876 );
877 }
878 }
879
880 Instruction::OptionLift { payload, .. } => {
881 let (some, some_results) = self.blocks.pop().unwrap();
882 let (none, none_results) = self.blocks.pop().unwrap();
883 assert!(none_results.is_empty());
884 assert!(some_results.len() == 1);
885 let some_result = &some_results[0];
886
887 let tmp = self.tmp();
888 let op = &operands[0];
889
890 let (v_none, v_some) = if maybe_null(resolve, payload) {
891 (
892 "{ tag: 'none' }",
893 format!(
894 "{{
895 tag: 'some',
896 val: {some_result}
897 }}"
898 ),
899 )
900 } else {
901 ("undefined", some_result.into())
902 };
903
904 if !self.valid_lifting_optimization {
905 uwriteln!(
906 self.src,
907 "let variant{tmp};
908 switch ({op}) {{
909 case 0: {{
910 {none}\
911 variant{tmp} = {v_none};
912 break;
913 }}
914 case 1: {{
915 {some}\
916 variant{tmp} = {v_some};
917 break;
918 }}
919 default: {{
920 throw new TypeError('invalid variant discriminant for option');
921 }}
922 }}",
923 );
924 } else {
925 uwriteln!(
926 self.src,
927 "let variant{tmp};
928 if ({op}) {{
929 {some}\
930 variant{tmp} = {v_some};
931 }} else {{
932 {none}\
933 variant{tmp} = {v_none};
934 }}"
935 );
936 }
937
938 results.push(format!("variant{tmp}"));
939 }
940
941 Instruction::ResultLower {
942 results: result_types,
943 ..
944 } => {
945 let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
946 let (mut err, err_results) = self.blocks.pop().unwrap();
947 let (mut ok, ok_results) = self.blocks.pop().unwrap();
948
949 let tmp = self.tmp();
950 let op = &operands[0];
951 uwriteln!(self.src, "var variant{tmp} = {op};");
952
953 for i in 0..result_types.len() {
954 uwriteln!(self.src, "let variant{tmp}_{i};");
955 results.push(format!("variant{tmp}_{i}"));
956
957 let ok_result = &ok_results[i];
958 let err_result = &err_results[i];
959 uwriteln!(ok, "variant{tmp}_{i} = {ok_result};");
960 uwriteln!(err, "variant{tmp}_{i} = {err_result};");
961 }
962
963 uwriteln!(
964 self.src,
965 r#"switch (variant{tmp}.tag) {{
966 case 'ok': {{
967 const e = variant{tmp}.val;
968 {ok}
969 break;
970 }}
971 case 'err': {{
972 const e = variant{tmp}.val;
973 {err}
974 break;
975 }}
976 default: {{
977 {debug_log_fn}("ERROR: invalid value (expected result as object with 'tag' member)", {{ value: variant{tmp}, valueType: typeof variant{tmp} }});
978 throw new TypeError('invalid variant specified for result');
979 }}
980 }}"#,
981 );
982 }
983
984 Instruction::ResultLift { result, .. } => {
985 let (err, err_results) = self.blocks.pop().unwrap();
986 let (ok, ok_results) = self.blocks.pop().unwrap();
987 let ok_result = if result.ok.is_some() {
988 assert_eq!(ok_results.len(), 1);
989 ok_results[0].to_string()
990 } else {
991 assert_eq!(ok_results.len(), 0);
992 String::from("undefined")
993 };
994 let err_result = if result.err.is_some() {
995 assert_eq!(err_results.len(), 1);
996 err_results[0].to_string()
997 } else {
998 assert_eq!(err_results.len(), 0);
999 String::from("undefined")
1000 };
1001 let tmp = self.tmp();
1002 let op0 = &operands[0];
1003
1004 if !self.valid_lifting_optimization {
1005 uwriteln!(
1006 self.src,
1007 "let variant{tmp};
1008 switch ({op0}) {{
1009 case 0: {{
1010 {ok}\
1011 variant{tmp} = {{
1012 tag: 'ok',
1013 val: {ok_result}
1014 }};
1015 break;
1016 }}
1017 case 1: {{
1018 {err}\
1019 variant{tmp} = {{
1020 tag: 'err',
1021 val: {err_result}
1022 }};
1023 break;
1024 }}
1025 default: {{
1026 throw new TypeError('invalid variant discriminant for expected');
1027 }}
1028 }}",
1029 );
1030 } else {
1031 uwriteln!(
1032 self.src,
1033 "let variant{tmp};
1034 if ({op0}) {{
1035 {err}\
1036 variant{tmp} = {{
1037 tag: 'err',
1038 val: {err_result}
1039 }};
1040 }} else {{
1041 {ok}\
1042 variant{tmp} = {{
1043 tag: 'ok',
1044 val: {ok_result}
1045 }};
1046 }}"
1047 );
1048 }
1049 results.push(format!("variant{tmp}"));
1050 }
1051
1052 Instruction::EnumLower { name, enum_, .. } => {
1053 let tmp = self.tmp();
1054
1055 let op = &operands[0];
1056 uwriteln!(self.src, "var val{tmp} = {op};");
1057
1058 uwriteln!(
1060 self.src,
1061 "let enum{tmp};
1062 switch (val{tmp}) {{"
1063 );
1064 for (i, case) in enum_.cases.iter().enumerate() {
1065 uwriteln!(
1066 self.src,
1067 "case '{case}': {{
1068 enum{tmp} = {i};
1069 break;
1070 }}",
1071 case = case.name
1072 );
1073 }
1074 uwriteln!(self.src, "default: {{");
1075 if !self.valid_lifting_optimization {
1076 uwriteln!(
1077 self.src,
1078 "if (({op}) instanceof Error) {{
1079 console.error({op});
1080 }}"
1081 );
1082 }
1083 uwriteln!(
1084 self.src,
1085 "
1086 throw new TypeError(`\"${{val{tmp}}}\" is not one of the cases of {name}`);
1087 }}
1088 }}",
1089 );
1090
1091 results.push(format!("enum{tmp}"));
1092 }
1093
1094 Instruction::EnumLift { name, enum_, .. } => {
1095 let tmp = self.tmp();
1096
1097 uwriteln!(
1098 self.src,
1099 "let enum{tmp};
1100 switch ({}) {{",
1101 operands[0]
1102 );
1103 for (i, case) in enum_.cases.iter().enumerate() {
1104 uwriteln!(
1105 self.src,
1106 "case {i}: {{
1107 enum{tmp} = '{case}';
1108 break;
1109 }}",
1110 case = case.name
1111 );
1112 }
1113 if !self.valid_lifting_optimization {
1114 let name = name.to_upper_camel_case();
1115 uwriteln!(
1116 self.src,
1117 "default: {{
1118 throw new TypeError('invalid discriminant specified for {name}');
1119 }}",
1120 );
1121 }
1122 uwriteln!(self.src, "}}");
1123
1124 results.push(format!("enum{tmp}"));
1125 }
1126
1127 Instruction::ListCanonLower { element, .. } => {
1139 let tmp = self.tmp();
1140 let memory = self.memory.as_ref().unwrap();
1141 let realloc = self.realloc.unwrap();
1142
1143 let size = self.sizes.size(element).size_wasm32();
1145 let align = ArchitectureSize::from(self.sizes.align(element)).size_wasm32();
1146
1147 uwriteln!(self.src, "var val{tmp} = {};", operands[0]);
1149 if matches!(element, Type::U8) {
1150 uwriteln!(
1151 self.src,
1152 "var len{tmp} = Array.isArray(val{tmp}) ? val{tmp}.length : val{tmp}.byteLength;"
1153 );
1154 } else {
1155 uwriteln!(self.src, "var len{tmp} = val{tmp}.length;");
1156 }
1157
1158 uwriteln!(
1160 self.src,
1161 "var ptr{tmp} = {realloc_call}(0, 0, {align}, len{tmp} * {size});",
1162 realloc_call = if self.is_async {
1163 format!("await {realloc}")
1164 } else {
1165 realloc.to_string()
1166 },
1167 );
1168
1169 let dataview_set_method = match element {
1174 Type::Bool | Type::U8 => "setUint8",
1175 Type::U16 => "setUint16",
1176 Type::U32 => "setUint32",
1177 Type::U64 => "setBigUint64",
1178 Type::S8 => "setInt8",
1179 Type::S16 => "setInt16",
1180 Type::S32 => "setInt32",
1181 Type::S64 => "setBigInt64",
1182 Type::F32 => "setFloat32",
1183 Type::F64 => "setFloat64",
1184 _ => unreachable!("unsupported type [{element:?}] for canonical list lower"),
1185 };
1186
1187 uwriteln!(
1189 self.src,
1190 r#"
1191 let valData{tmp};
1192 const valLenBytes{tmp} = len{tmp} * {size};
1193 if (Array.isArray(val{tmp})) {{
1194 // Regular array likely containing numbers, write values to memory
1195 let offset = 0;
1196 const dv{tmp} = new DataView({memory}.buffer);
1197 for (const v of val{tmp}) {{
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(), );
1225 results.push(format!("result{tmp}"));
1226 }
1227
1228 Instruction::StringLower { .. } => {
1229 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 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 uwriteln!(self.src, "var {vec} = {};", operands[0]);
1314 uwriteln!(self.src, "var {len} = {vec}.length;");
1315
1316 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 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 uwriteln!(self.src, "const hostProvided = false;");
1440
1441 self.start_current_task(inst);
1444
1445 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: preparedTask.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 if let Some(mem_idx) = self.canon_opts.memory() {
1473 let idx = mem_idx.as_u32();
1474 uwriteln!(self.src, "task.setReturnMemoryIdx({idx});");
1475 uwriteln!(self.src, "task.setReturnMemory(memory{idx});");
1476 }
1477
1478 let sig_results_length = sig.results.len();
1481 let s = self.generate_result_assignment_lhs(sig_results_length, results, is_async);
1482
1483 let (call_prefix, call_wrapper) = if self.requires_async_porcelain | self.is_async {
1484 ("await ", Intrinsic::WithGlobalCurrentTaskMetaFnAsync.name())
1485 } else {
1486 ("", Intrinsic::WithGlobalCurrentTaskMetaFn.name())
1487 };
1488 uwriteln!(
1489 self.src,
1490 r#"{s} {call_prefix} {call_wrapper}({{
1491 taskID: task.id(),
1492 componentIdx: task.componentIdx(),
1493 fn: () => {callee}({args}),
1494 }});
1495 "#,
1496 callee = self.callee,
1497 args = operands.join(", "),
1498 );
1499
1500 if self.tracing_enabled {
1501 let prefix = self.tracing_prefix;
1502 let to_result_string =
1503 self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToResultString));
1504 uwriteln!(
1505 self.src,
1506 "console.error(`{prefix} return {}`);",
1507 if sig_results_length > 0 || !results.is_empty() {
1508 format!("result=${{{to_result_string}(ret)}}")
1509 } else {
1510 "".to_string()
1511 }
1512 );
1513 }
1514 }
1515
1516 Instruction::CallInterface { func, async_ } => {
1518 let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
1519 let start_current_task_fn = self.intrinsic(Intrinsic::AsyncTask(
1520 AsyncTaskIntrinsic::CreateNewCurrentTask,
1521 ));
1522 let current_task_get_fn =
1523 self.intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::GetCurrentTask));
1524 let component_instance_idx = self.canon_opts.instance.as_u32();
1525
1526 uwriteln!(
1527 self.src,
1528 "{debug_log_fn}('{prefix} [Instruction::CallInterface] ({async_}, @ enter)');",
1529 prefix = self.tracing_prefix,
1530 async_ = async_.then_some("async").unwrap_or("sync"),
1531 );
1532
1533 let (callee_fn_js, callee_args_js) = if self.callee_resource_dynamic {
1535 (
1536 format!("{}.{}", operands[0], self.callee),
1537 operands[1..].join(", "),
1538 )
1539 } else {
1540 (self.callee.into(), operands.join(", "))
1541 };
1542
1543 uwriteln!(self.src, "let hostProvided = true;");
1544
1545 uwriteln!(
1560 self.src,
1561 r#"
1562 let parentTask;
1563 let task;
1564 let subtask;
1565
1566 const createTask = () => {{
1567 const results = {start_current_task_fn}({{
1568 componentIdx: -1, // {component_instance_idx},
1569 isAsync: {is_async},
1570 entryFnName: '{fn_name}',
1571 getCallbackFn: () => {callback_fn_js},
1572 callbackFnName: '{callback_fn_js}',
1573 errHandling: '{err_handling}',
1574 callingWasmExport: false,
1575 }});
1576 task = results[0];
1577 }};
1578
1579 taskCreation: {{
1580 parentTask = {current_task_get_fn}({component_instance_idx})?.task;
1581 if (!parentTask) {{
1582 createTask();
1583 break taskCreation;
1584 }}
1585
1586 createTask();
1587
1588 if (hostProvided) {{
1589 subtask = parentTask.getLatestSubtask();
1590 if (!subtask) {{
1591 throw new Error(`Missing subtask (in parent task [${{parentTask.id()}}]) for host import, has the import been lowered? (ensure asyncImports are set properly)`);
1592 }}
1593 task.setParentSubtask(subtask);
1594 }}
1595 }}
1596 "#,
1597 is_async = self.is_async,
1598 fn_name = self.callee,
1599 err_handling = self.err.to_js_string(),
1600 callback_fn_js = self
1601 .canon_opts
1602 .callback
1603 .as_ref()
1604 .map(|v| format!("callback_{}", v.as_u32()))
1605 .unwrap_or_else(|| "null".into()),
1606 );
1607
1608 let is_async = self.requires_async_porcelain || *async_;
1609
1610 let fn_wasm_result_count = if func.result.is_none() { 0 } else { 1 };
1613
1614 if is_async {
1616 uwriteln!(
1617 self.src,
1618 r#"
1619 const started = await task.enter({{ isHost: hostProvided }});
1620 if (!started) {{
1621 {debug_log_fn}('[Instruction::CallInterface] failed to enter task', {{
1622 taskID: preparedTask.id(),
1623 subtaskID: currentSubtask?.id(),
1624 }});
1625 throw new Error("failed to enter task");
1626 }}
1627 "#,
1628 );
1629 } else {
1630 uwriteln!(self.src, "const started = task.enterSync();",);
1631 }
1632
1633 let (call_prefix, call_wrapper) = if is_async || self.requires_async_porcelain {
1635 ("await ", Intrinsic::WithGlobalCurrentTaskMetaFnAsync.name())
1636 } else {
1637 ("", Intrinsic::WithGlobalCurrentTaskMetaFn.name())
1638 };
1639 let call = format!(
1640 r#"{call_prefix} {call_wrapper}({{
1641 componentIdx: task.componentIdx(),
1642 taskID: task.id(),
1643 fn: () => {callee_fn_js}({callee_args_js})
1644 }})
1645 "#,
1646 );
1647
1648 match self.err {
1649 ErrHandling::None | ErrHandling::ThrowResultErr => {
1652 let s = self.generate_result_assignment_lhs(
1653 fn_wasm_result_count,
1654 results,
1655 is_async,
1656 );
1657 uwriteln!(self.src, "{s}{call};");
1658 }
1659 ErrHandling::ResultCatchHandler => {
1662 let err_payload = if let (_, Some(Type::Id(err_ty))) =
1665 get_thrown_type(self.resolve, func.result).unwrap()
1666 {
1667 match &self.resolve.types[*err_ty].kind {
1668 TypeDefKind::Type(Type::String) => {
1669 self.intrinsic(Intrinsic::GetErrorPayloadString)
1670 }
1671 _ => self.intrinsic(Intrinsic::GetErrorPayload),
1672 }
1673 } else {
1674 self.intrinsic(Intrinsic::GetErrorPayload)
1675 };
1676 uwriteln!(
1677 self.src,
1678 r#"
1679 let ret;
1680 try {{
1681 ret = {{ tag: 'ok', val: {call} }};
1682 }} catch (e) {{
1683 ret = {{ tag: 'err', val: {err_payload}(e) }};
1684 }}
1685 "#,
1686 );
1687 results.push("ret".to_string());
1688 }
1689 }
1690
1691 if self.tracing_enabled {
1692 let prefix = self.tracing_prefix;
1693 let to_result_string =
1694 self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToResultString));
1695 uwriteln!(
1696 self.src,
1697 "console.error(`{prefix} return {}`);",
1698 if fn_wasm_result_count > 0 || !results.is_empty() {
1699 format!("result=${{{to_result_string}(ret)}}")
1700 } else {
1701 "".to_string()
1702 }
1703 );
1704 }
1705
1706 if self.clear_resource_borrows {
1714 let symbol_resource_handle = self.intrinsic(Intrinsic::SymbolResourceHandle);
1715 let cur_resource_borrows =
1716 self.intrinsic(Intrinsic::Resource(ResourceIntrinsic::CurResourceBorrows));
1717 let is_host = matches!(
1718 self.resource_map.iter().nth(0).unwrap().1.data,
1719 ResourceData::Host { .. }
1720 );
1721
1722 if is_host {
1723 uwriteln!(
1724 self.src,
1725 "for (const rsc of {cur_resource_borrows}) {{
1726 rsc[{symbol_resource_handle}] = undefined;
1727 }}
1728 {cur_resource_borrows} = [];"
1729 );
1730 } else {
1731 uwriteln!(
1732 self.src,
1733 "for (const {{ rsc, drop }} of {cur_resource_borrows}) {{
1734 if (rsc[{symbol_resource_handle}]) {{
1735 drop(rsc[{symbol_resource_handle}]);
1736 rsc[{symbol_resource_handle}] = undefined;
1737 }}
1738 }}
1739 {cur_resource_borrows} = [];"
1740 );
1741 }
1742 self.clear_resource_borrows = false;
1743 }
1744 }
1745
1746 Instruction::Return {
1747 func,
1748 amt: stack_value_count,
1749 } => {
1750 let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
1751 uwriteln!(
1752 self.src,
1753 "{debug_log_fn}('{prefix} [Instruction::Return]', {{
1754 funcName: '{func_name}',
1755 paramCount: {stack_value_count},
1756 async: {is_async},
1757 postReturn: {post_return_present}
1758 }});",
1759 func_name = func.name,
1760 post_return_present = self.post_return.is_some(),
1761 is_async = self.is_async,
1762 prefix = self.tracing_prefix,
1763 );
1764
1765 let get_or_create_async_state_fn = self.intrinsic(Intrinsic::Component(
1768 ComponentIntrinsic::GetOrCreateAsyncState,
1769 ));
1770 let gen_post_return_js =
1771 |(post_return_call, ret_stmt): (String, Option<String>)| {
1772 format!(
1773 r#"
1774 let cstate = {get_or_create_async_state_fn}({component_idx});
1775 cstate.mayLeave = false;
1776 {post_return_call}
1777 cstate.mayLeave = true;
1778 task.exit();
1779 {ret_stmt}
1780 "#,
1781 component_idx = self.canon_opts.instance.as_u32(),
1782 ret_stmt = ret_stmt.unwrap_or_default(),
1783 )
1784 };
1785
1786 assert!(!self.is_async, "async functions should use AsyncTaskReturn");
1787
1788 match stack_value_count {
1794 0 => {
1796 uwriteln!(self.src, "task.resolve([ret]);");
1797 if let Some(f) = &self.post_return {
1798 uwriteln!(
1799 self.src,
1800 "{post_return_js}",
1801 post_return_js = gen_post_return_js((format!("{f}();"), None)),
1802 );
1803 } else {
1804 uwriteln!(self.src, "task.exit();");
1805 }
1806 }
1807
1808 1 if self.err == ErrHandling::ThrowResultErr => {
1810 let component_err = self.intrinsic(Intrinsic::ComponentError);
1811 let op = &operands[0];
1812
1813 uwriteln!(self.src, "const retCopy = {op};");
1814 uwriteln!(self.src, "task.resolve([retCopy.val]);");
1815
1816 if let Some(f) = &self.post_return {
1817 uwriteln!(
1818 self.src,
1819 "{}",
1820 gen_post_return_js((format!("{f}(ret);"), None))
1821 );
1822 } else {
1823 uwriteln!(self.src, "task.exit();");
1824 }
1825
1826 uwriteln!(
1827 self.src,
1828 r#"
1829 if (typeof retCopy === 'object' && retCopy.tag === 'err') {{
1830 throw new {component_err}(retCopy.val);
1831 }}
1832 return retCopy.val;
1833 "#
1834 );
1835 }
1836
1837 stack_value_count => {
1839 let ret_val = match stack_value_count {
1840 0 => unreachable!(
1841 "unexpectedly zero return values for synchronous return"
1842 ),
1843 1 => operands[0].to_string(),
1844 _ => format!("[{}]", operands.join(", ")),
1845 };
1846
1847 uwriteln!(self.src, "task.resolve([{ret_val}]);");
1848
1849 if let Some(post_return_fn) = self.post_return {
1851 uwriteln!(self.src, "const retCopy = {ret_val};");
1856
1857 let post_return_js = gen_post_return_js((
1860 format!("{post_return_fn}(ret);"),
1861 Some(["return retCopy;"].join("\n")),
1862 ));
1863 uwriteln!(self.src, "{post_return_js}");
1864 } else {
1865 uwriteln!(self.src, "task.exit();");
1866 uwriteln!(self.src, "return {ret_val};")
1867 }
1868 }
1869 }
1870 }
1871
1872 Instruction::I32Load { offset } => self.load("getInt32", *offset, operands, results),
1873
1874 Instruction::I64Load { offset } => self.load("getBigInt64", *offset, operands, results),
1875
1876 Instruction::F32Load { offset } => self.load("getFloat32", *offset, operands, results),
1877
1878 Instruction::F64Load { offset } => self.load("getFloat64", *offset, operands, results),
1879
1880 Instruction::I32Load8U { offset } => self.load("getUint8", *offset, operands, results),
1881
1882 Instruction::I32Load8S { offset } => self.load("getInt8", *offset, operands, results),
1883
1884 Instruction::I32Load16U { offset } => {
1885 self.load("getUint16", *offset, operands, results)
1886 }
1887
1888 Instruction::I32Load16S { offset } => self.load("getInt16", *offset, operands, results),
1889
1890 Instruction::I32Store { offset } => self.store("setInt32", *offset, operands),
1891
1892 Instruction::I64Store { offset } => self.store("setBigInt64", *offset, operands),
1893
1894 Instruction::F32Store { offset } => self.store("setFloat32", *offset, operands),
1895
1896 Instruction::F64Store { offset } => self.store("setFloat64", *offset, operands),
1897
1898 Instruction::I32Store8 { offset } => self.store("setInt8", *offset, operands),
1899
1900 Instruction::I32Store16 { offset } => self.store("setInt16", *offset, operands),
1901
1902 Instruction::LengthStore { offset } => self.store("setUint32", *offset, operands),
1903
1904 Instruction::LengthLoad { offset } => {
1905 self.load("getUint32", *offset, operands, results)
1906 }
1907
1908 Instruction::PointerStore { offset } => self.store("setUint32", *offset, operands),
1909
1910 Instruction::PointerLoad { offset } => {
1911 self.load("getUint32", *offset, operands, results)
1912 }
1913
1914 Instruction::Malloc { size, align, .. } => {
1915 let tmp = self.tmp();
1916 let realloc = self.realloc.as_ref().unwrap();
1917 let ptr = format!("ptr{tmp}");
1918 uwriteln!(
1919 self.src,
1920 "var {ptr} = {realloc_call}(0, 0, {align}, {size});",
1921 align = align.align_wasm32(),
1922 realloc_call = if self.is_async {
1923 format!("await {realloc}")
1924 } else {
1925 realloc.to_string()
1926 },
1927 size = size.size_wasm32()
1928 );
1929 results.push(ptr);
1930 }
1931
1932 Instruction::HandleLift { handle, .. } => {
1933 let (Handle::Own(ty) | Handle::Borrow(ty)) = handle;
1934 let resource_ty = &crate::dealias(self.resolve, *ty);
1935 let ResourceTable { imported, data } = &self.resource_map[resource_ty];
1936
1937 let is_own = matches!(handle, Handle::Own(_));
1938 let rsc = format!("rsc{}", self.tmp());
1939 let handle = format!("handle{}", self.tmp());
1940 uwriteln!(self.src, "var {handle} = {};", &operands[0]);
1941
1942 match data {
1943 ResourceData::Host {
1944 tid,
1945 rid,
1946 local_name,
1947 dtor_name,
1948 } => {
1949 let tid = tid.as_u32();
1950 let rid = rid.as_u32();
1951 let symbol_dispose = self.intrinsic(Intrinsic::SymbolDispose);
1952 let rsc_table_remove = self
1953 .intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableRemove));
1954 let rsc_flag = self
1955 .intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableFlag));
1956 if !imported {
1957 let symbol_resource_handle =
1958 self.intrinsic(Intrinsic::SymbolResourceHandle);
1959
1960 uwriteln!(
1961 self.src,
1962 "var {rsc} = new.target === {local_name} ? this : Object.create({local_name}.prototype);"
1963 );
1964
1965 if is_own {
1966 let empty_func = self
1968 .intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::EmptyFunc));
1969 uwriteln!(self.src,
1970 "Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
1971 finalizationRegistry{tid}.register({rsc}, {handle}, {rsc});");
1972 if let Some(dtor) = dtor_name {
1973 uwriteln!(
1975 self.src,
1976 "Object.defineProperty({rsc}, {symbol_dispose}, {{ writable: true, value: function () {{
1977 finalizationRegistry{tid}.unregister({rsc});
1978 {rsc_table_remove}(handleTable{tid}, {handle});
1979 {rsc}[{symbol_dispose}] = {empty_func};
1980 {rsc}[{symbol_resource_handle}] = undefined;
1981 {dtor}(handleTable{tid}[({handle} << 1) + 1] & ~{rsc_flag});
1982 }}}});"
1983 );
1984 } else {
1985 uwriteln!(
1987 self.src,
1988 "Object.defineProperty({rsc}, {symbol_dispose}, {{ writable: true, value: {empty_func} }});",
1989 );
1990 }
1991 } else {
1992 uwriteln!(
1994 self.src,
1995 "Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});"
1996 );
1997 }
1998 } else {
1999 let rep = format!("rep{}", self.tmp());
2000 let symbol_resource_rep = self.intrinsic(Intrinsic::SymbolResourceRep);
2003 let symbol_resource_handle =
2004 self.intrinsic(Intrinsic::SymbolResourceHandle);
2005
2006 uwriteln!(
2007 self.src,
2008 r#"
2009 var {rep} = handleTable{tid}[({handle} << 1) + 1] & ~{rsc_flag};
2010 var {rsc} = captureTable{rid}.get({rep});
2011 if (!{rsc}) {{
2012 {rsc} = Object.create({local_name}.prototype);
2013 Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
2014 Object.defineProperty({rsc}, {symbol_resource_rep}, {{ writable: true, value: {rep} }});
2015 }}
2016 "#,
2017 );
2018
2019 if is_own {
2020 uwriteln!(
2022 self.src,
2023 "else {{
2024 captureTable{rid}.delete({rep});
2025 }}
2026 {rsc_table_remove}(handleTable{tid}, {handle});"
2027 );
2028 }
2029 }
2030
2031 if !is_own {
2033 let cur_resource_borrows = self.intrinsic(Intrinsic::Resource(
2034 ResourceIntrinsic::CurResourceBorrows,
2035 ));
2036 uwriteln!(self.src, "{cur_resource_borrows}.push({rsc});");
2037 self.clear_resource_borrows = true;
2038 }
2039 }
2040
2041 ResourceData::Guest {
2042 resource_name,
2043 prefix,
2044 extra,
2045 } => {
2046 assert!(
2047 extra.is_none(),
2048 "plain resource handles do not carry extra data"
2049 );
2050
2051 let symbol_resource_handle =
2052 self.intrinsic(Intrinsic::SymbolResourceHandle);
2053 let prefix = prefix.as_deref().unwrap_or("");
2054 let lower_camel = resource_name.to_lower_camel_case();
2055
2056 if !imported {
2057 if is_own {
2058 uwriteln!(
2059 self.src,
2060 "var {rsc} = repTable.get($resource_{prefix}rep${lower_camel}({handle})).rep;"
2061 );
2062 uwrite!(
2063 self.src,
2064 r#"
2065 repTable.delete({handle});
2066 delete {rsc}[{symbol_resource_handle}];
2067 finalizationRegistry_export${prefix}{lower_camel}.unregister({rsc});
2068 "#
2069 );
2070 } else {
2071 uwriteln!(self.src, "var {rsc} = repTable.get({handle}).rep;");
2072 }
2073 } else {
2074 let upper_camel = resource_name.to_upper_camel_case();
2075
2076 uwrite!(
2077 self.src,
2078 r#"
2079 var {rsc} = new.target === import_{prefix}{upper_camel} ? this : Object.create(import_{prefix}{upper_camel}.prototype);
2080 Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
2081 "#
2082 );
2083
2084 uwriteln!(
2085 self.src,
2086 "finalizationRegistry_import${prefix}{lower_camel}.register({rsc}, {handle}, {rsc});",
2087 );
2088
2089 if !is_own {
2090 let cur_resource_borrows = self.intrinsic(Intrinsic::Resource(
2091 ResourceIntrinsic::CurResourceBorrows,
2092 ));
2093 uwriteln!(
2094 self.src,
2095 "{cur_resource_borrows}.push({{ rsc: {rsc}, drop: $resource_import${prefix}drop${lower_camel} }});"
2096 );
2097 self.clear_resource_borrows = true;
2098 }
2099 }
2100 }
2101 }
2102 results.push(rsc);
2103 }
2104
2105 Instruction::HandleLower { handle, name, .. } => {
2106 let (Handle::Own(ty) | Handle::Borrow(ty)) = handle;
2107 let is_own = matches!(handle, Handle::Own(_));
2108 let ResourceTable { imported, data } =
2109 &self.resource_map[&crate::dealias(self.resolve, *ty)];
2110
2111 let class_name = name.to_upper_camel_case();
2112 let handle = format!("handle{}", self.tmp());
2113 let symbol_resource_handle = self.intrinsic(Intrinsic::SymbolResourceHandle);
2114 let symbol_dispose = self.intrinsic(Intrinsic::SymbolDispose);
2115 let op = &operands[0];
2116
2117 match data {
2118 ResourceData::Host {
2119 tid,
2120 rid,
2121 local_name,
2122 ..
2123 } => {
2124 let tid = tid.as_u32();
2125 let rid = rid.as_u32();
2126 if !imported {
2127 if is_own {
2128 let empty_func = self
2129 .intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::EmptyFunc));
2130 uwriteln!(
2131 self.src,
2132 "var {handle} = {op}[{symbol_resource_handle}];
2133 if (!{handle}) {{
2134 throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
2135 }}
2136 finalizationRegistry{tid}.unregister({op});
2137 {op}[{symbol_dispose}] = {empty_func};
2138 {op}[{symbol_resource_handle}] = undefined;",
2139 );
2140 } else {
2141 let rsc_flag = self.intrinsic(Intrinsic::Resource(
2146 ResourceIntrinsic::ResourceTableFlag,
2147 ));
2148 let own_handle = format!("handle{}", self.tmp());
2149 uwriteln!(self.src,
2150 "var {own_handle} = {op}[{symbol_resource_handle}];
2151 if (!{own_handle} || (handleTable{tid}[({own_handle} << 1) + 1] & {rsc_flag}) === 0) {{
2152 throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
2153 }}
2154 var {handle} = handleTable{tid}[({own_handle} << 1) + 1] & ~{rsc_flag};",
2155 );
2156 }
2157 } else {
2158 uwriteln!(
2161 self.src,
2162 "if (!({op} instanceof {local_name})) {{
2163 throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
2164 }}
2165 var {handle} = {op}[{symbol_resource_handle}];",
2166 );
2167 let symbol_resource_rep = self.intrinsic(Intrinsic::SymbolResourceRep);
2172
2173 let handle_init_js = if is_own {
2175 let create_own_fn = self.intrinsic(Intrinsic::Resource(
2176 ResourceIntrinsic::ResourceTableCreateOwn,
2177 ));
2178 format!("{handle} = {create_own_fn}(handleTable{tid}, rep);")
2179 } else {
2180 let scope_id = self.intrinsic(Intrinsic::ScopeId);
2181 let create_borrow_fn = self.intrinsic(Intrinsic::Resource(
2182 ResourceIntrinsic::ResourceTableCreateBorrow,
2183 ));
2184 format!(
2185 "{handle} = {create_borrow_fn}(handleTable{tid}, rep, {scope_id});"
2186 )
2187 };
2188
2189 uwriteln!(
2190 self.src,
2191 "if (!{handle}) {{
2192 const rep = {op}[{symbol_resource_rep}] || ++captureCnt{rid};
2193 captureTable{rid}.set(rep, {op});
2194 {handle_init_js}
2195 }}"
2196 );
2197 }
2198 }
2199
2200 ResourceData::Guest {
2201 resource_name,
2202 prefix,
2203 extra,
2204 } => {
2205 assert!(
2206 extra.is_none(),
2207 "plain resource handles do not carry extra data"
2208 );
2209
2210 let upper_camel = resource_name.to_upper_camel_case();
2211 let lower_camel = resource_name.to_lower_camel_case();
2212 let prefix = prefix.as_deref().unwrap_or("");
2213
2214 if !imported {
2215 let local_rep = format!("localRep{}", self.tmp());
2216 uwriteln!(
2217 self.src,
2218 "if (!({op} instanceof {upper_camel})) {{
2219 throw new TypeError('Resource error: Not a valid \"{upper_camel}\" resource.');
2220 }}
2221 let {handle} = {op}[{symbol_resource_handle}];",
2222 );
2223
2224 if is_own {
2225 uwriteln!(
2226 self.src,
2227 "if ({handle} === undefined) {{
2228 var {local_rep} = repCnt++;
2229 repTable.set({local_rep}, {{ rep: {op}, own: true }});
2230 {handle} = $resource_{prefix}new${lower_camel}({local_rep});
2231 {op}[{symbol_resource_handle}] = {handle};
2232 finalizationRegistry_export${prefix}{lower_camel}.register({op}, {handle}, {op});
2233 }}
2234 "
2235 );
2236 } else {
2237 uwriteln!(
2238 self.src,
2239 "if ({handle} === undefined) {{
2240 var {local_rep} = repCnt++;
2241 repTable.set({local_rep}, {{ rep: {op}, own: false }});
2242 {op}[{symbol_resource_handle}] = {local_rep};
2243 }}
2244 "
2245 );
2246 }
2247 } else {
2248 let symbol_resource_handle =
2249 self.intrinsic(Intrinsic::SymbolResourceHandle);
2250 uwrite!(
2251 self.src,
2252 "var {handle} = {op}[{symbol_resource_handle}];
2253 finalizationRegistry_import${prefix}{lower_camel}.unregister({op});
2254 "
2255 );
2256 }
2257 }
2258 }
2259 results.push(handle);
2260 }
2261
2262 Instruction::DropHandle { ty } => {
2263 let _ = ty;
2264 todo!("[Instruction::DropHandle] not yet implemented")
2265 }
2266
2267 Instruction::Flush { amt } => {
2268 for item in operands.iter().take(*amt) {
2269 results.push(item.clone());
2270 }
2271 }
2272
2273 Instruction::ErrorContextLift => {
2274 let item = operands
2275 .first()
2276 .expect("unexpectedly missing ErrorContextLift arg");
2277 results.push(item.clone());
2278 }
2279
2280 Instruction::ErrorContextLower => {
2281 let item = operands
2282 .first()
2283 .expect("unexpectedly missing ErrorContextLower arg");
2284 results.push(item.clone());
2285 }
2286
2287 Instruction::FutureLower { .. } => {
2288 let future_arg = operands
2297 .first()
2298 .expect("unexpectedly missing ErrorContextLower arg");
2299 results.push(future_arg.clone());
2300 }
2301
2302 Instruction::FutureLift { payload, ty } => {
2303 let future_ty = &crate::dealias(self.resolve, *ty);
2304
2305 match payload {
2314 Some(payload_ty) => {
2315 match payload_ty {
2316 Type::Bool
2318 | Type::U8
2319 | Type::U16
2320 | Type::U32
2321 | Type::U64
2322 | Type::S8
2323 | Type::S16
2324 | Type::S32
2325 | Type::S64
2326 | Type::F32
2327 | Type::F64
2328 | Type::Char
2329 | Type::String
2330 | Type::ErrorContext => uwriteln!(
2331 self.src,
2332 "const payloadLiftFn = () => {{ throw new Error('lift for {payload_ty:?}'); }}",
2333 ),
2334 Type::Id(payload_ty_id) => {
2335 if self.resource_map.contains_key(payload_ty_id) {
2336 let ResourceTable { data, .. } =
2337 &self.resource_map[payload_ty_id];
2338 uwriteln!(
2339 self.src,
2340 "const payloadLiftFn = () => {{ throw new Error('lift for {} (identifier {})'); }}",
2341 payload_ty_id.index(),
2342 match data {
2343 ResourceData::Host { local_name, .. } => local_name,
2344 ResourceData::Guest { resource_name, .. } =>
2345 resource_name,
2346 }
2347 );
2348 } else {
2349 }
2355 }
2356 };
2357
2358 let tmp = self.tmp();
2367 let result_var = format!("futureResult{tmp}");
2368 let component_idx = self.canon_opts.instance.as_u32();
2369 let future_new_fn =
2370 self.intrinsic(Intrinsic::AsyncFuture(AsyncFutureIntrinsic::FutureNew));
2371 uwriteln!(
2372 self.src,
2373 "const {result_var} = {future_new_fn}({{ componentIdx: {component_idx}, futureTypeRep: {} }});",
2374 future_ty.index(),
2375 );
2376 results.push(result_var.clone());
2377 }
2378
2379 None => unreachable!("future with no payload unsupported"),
2380 }
2381 }
2382
2383 Instruction::StreamLower { .. } => {
2384 let stream_arg = operands
2392 .first()
2393 .expect("unexpectedly missing StreamLower arg");
2394 results.push(stream_arg.clone());
2395 }
2396
2397 Instruction::StreamLift { payload, ty } => {
2398 let component_idx = self.canon_opts.instance.as_u32();
2399 let stream_new_from_lift_fn = self.intrinsic(Intrinsic::AsyncStream(
2400 AsyncStreamIntrinsic::StreamNewFromLift,
2401 ));
2402
2403 let type_id = &crate::dealias(self.resolve, *ty);
2405 let ResourceTable {
2406 imported: true,
2407 data:
2408 ResourceData::Guest {
2409 extra:
2410 Some(ResourceExtraData::Stream {
2411 table_idx: stream_table_idx_ty,
2412 elem_ty: stream_element_ty,
2413 }),
2414 ..
2415 },
2416 } = self
2417 .resource_map
2418 .get(type_id)
2419 .expect("missing resource mapping for stream lift")
2420 else {
2421 unreachable!("invalid resource table observed during stream lift");
2422 };
2423
2424 assert_eq!(
2425 *stream_element_ty, **payload,
2426 "stream element type mismatch"
2427 );
2428
2429 let arg_stream_end_idx = operands
2430 .first()
2431 .expect("unexpectedly missing stream table idx arg in StreamLift");
2432
2433 let (payload_lift_fn, payload_lower_fn) = match payload {
2434 None => ("null".into(), "null".into()),
2435 Some(payload_ty) => {
2436 match payload_ty {
2437 Type::Bool
2439 | Type::U8
2440 | Type::U16
2441 | Type::U32
2442 | Type::U64
2443 | Type::S8
2444 | Type::S16
2445 | Type::S32
2446 | Type::S64
2447 | Type::F32
2448 | Type::F64
2449 | Type::Char
2450 | Type::String
2451 | Type::ErrorContext => (
2452 format!(
2453 "const payloadLiftFn = () => {{ throw new Error('lift for {payload_ty:?}'); }};"
2454 ),
2455 format!(
2456 "const payloadLowerFn = () => {{ throw new Error('lower for {payload_ty:?}'); }};"
2457 ),
2458 ),
2459
2460 Type::Id(payload_ty_id) => {
2461 if let Some(ResourceTable { data, .. }) =
2462 &self.resource_map.get(payload_ty_id)
2463 {
2464 let identifier = match data {
2465 ResourceData::Host { local_name, .. } => local_name,
2466 ResourceData::Guest { resource_name, .. } => resource_name,
2467 };
2468
2469 (
2470 format!(
2471 "const payloadLiftFn = () => {{ throw new Error('lift for {} (identifier {identifier})'); }};",
2472 payload_ty_id.index(),
2473 ),
2474 format!(
2475 "const payloadLowerFn = () => {{ throw new Error('lower for {} (identifier {identifier})'); }};",
2476 payload_ty_id.index(),
2477 ),
2478 )
2479 } else {
2480 (
2481 format!(
2482 "const payloadLiftFn = () => {{ throw new Error('lift for missing type with type idx {payload_ty:?}'); }};",
2483 ),
2484 format!(
2485 "const payloadLowerFn = () => {{ throw new Error('lower for missing type with type idx {payload_ty:?}'); }};",
2486 ),
2487 )
2488 }
2489 }
2490 }
2491 }
2492 };
2493
2494 let payload_ty_size_js = if let Some(payload_ty) = payload {
2495 self.sizes.size(payload_ty).size_wasm32().to_string()
2496 } else {
2497 "null".into()
2498 };
2499
2500 let stream_table_idx = stream_table_idx_ty.as_u32();
2501 let is_unit_stream = payload.is_none();
2502
2503 let tmp = self.tmp();
2504 let result_var = format!("streamResult{tmp}");
2505 uwriteln!(
2506 self.src,
2507 "
2508 {payload_lift_fn}
2509 {payload_lower_fn}
2510 const {result_var} = {stream_new_from_lift_fn}({{
2511 componentIdx: {component_idx},
2512 streamTableIdx: {stream_table_idx},
2513 streamEndWaitableIdx: {arg_stream_end_idx},
2514 payloadLiftFn,
2515 payloadTypeSize32: {payload_ty_size_js},
2516 payloadLowerFn,
2517 isUnitStream: {is_unit_stream},
2518 }});",
2519 );
2520 results.push(result_var.clone());
2521 }
2522
2523 Instruction::AsyncTaskReturn { name, params } => {
2544 let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
2545 let component_instance_idx = self.canon_opts.instance.as_u32();
2546 let is_async_js = self.requires_async_porcelain | self.is_async;
2547 let async_driver_loop_fn =
2548 self.intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::DriverLoop));
2549 let get_or_create_async_state_fn = self.intrinsic(Intrinsic::Component(
2550 ComponentIntrinsic::GetOrCreateAsyncState,
2551 ));
2552
2553 uwriteln!(
2554 self.src,
2555 "{debug_log_fn}('{prefix} [Instruction::AsyncTaskReturn]', {{
2556 funcName: '{name}',
2557 paramCount: {param_count},
2558 componentIdx: {component_instance_idx},
2559 postReturn: {post_return_present},
2560 hostProvided,
2561 }});",
2562 param_count = params.len(),
2563 post_return_present = self.post_return.is_some(),
2564 prefix = self.tracing_prefix,
2565 );
2566
2567 assert!(
2568 self.is_async,
2569 "non-async functions should not be performing async returns (func {name})",
2570 );
2571
2572 uwriteln!(
2604 self.src,
2605 r#"
2606 if (hostProvided) {{
2607 {debug_log_fn}('[Instruction::AsyncTaskReturn] signaling host-provided async return completion', {{
2608 task: task.id(),
2609 subtask: subtask?.id(),
2610 result: ret,
2611 }})
2612 task.resolve([ret]);
2613 task.exit();
2614 return task.completionPromise();
2615 }}
2616
2617 const componentState = {get_or_create_async_state_fn}({component_instance_idx});
2618 if (!componentState) {{ throw new Error('failed to lookup current component state'); }}
2619
2620 queueMicrotask(async (resolve, reject) => {{
2621 try {{
2622 {debug_log_fn}("[Instruction::AsyncTaskReturn] starting driver loop", {{
2623 fnName: '{name}',
2624 componentInstanceIdx: {component_instance_idx},
2625 taskID: task.id(),
2626 }});
2627 await {async_driver_loop_fn}({{
2628 componentInstanceIdx: {component_instance_idx},
2629 componentState,
2630 task,
2631 fnName: '{name}',
2632 isAsync: {is_async_js},
2633 callbackResult: ret,
2634 }});
2635 }} catch (err) {{
2636 {debug_log_fn}("[Instruction::AsyncTaskReturn] driver loop call failure", {{ err }});
2637 }}
2638 }});
2639
2640 let taskRes = await task.completionPromise();
2641 if (task.getErrHandling() === 'throw-result-err') {{
2642 if (typeof taskRes !== 'object') {{ return taskRes; }}
2643 if (taskRes.tag === 'err') {{ throw taskRes.val; }}
2644 if (taskRes.tag === 'ok') {{ taskRes = taskRes.val; }}
2645 }}
2646
2647 return taskRes;
2648 "#,
2649 );
2650 }
2651
2652 Instruction::GuestDeallocate { .. }
2653 | Instruction::GuestDeallocateString
2654 | Instruction::GuestDeallocateList { .. }
2655 | Instruction::GuestDeallocateVariant { .. } => unimplemented!("Guest deallocation"),
2656 }
2657 }
2658}
2659
2660pub fn as_nullable<'a>(resolve: &'a Resolve, ty: &'a Type) -> Option<&'a Type> {
2665 let id = match ty {
2666 Type::Id(id) => *id,
2667 _ => return None,
2668 };
2669 match &resolve.types[id].kind {
2670 TypeDefKind::Option(t) => {
2686 if !maybe_null(resolve, t) {
2687 Some(t)
2688 } else {
2689 None
2690 }
2691 }
2692 TypeDefKind::Type(t) => as_nullable(resolve, t),
2693 _ => None,
2694 }
2695}
2696
2697pub fn maybe_null(resolve: &Resolve, ty: &Type) -> bool {
2698 as_nullable(resolve, ty).is_some()
2699}
2700
2701pub fn js_array_ty(resolve: &Resolve, element_ty: &Type) -> Option<&'static str> {
2711 match element_ty {
2712 Type::Bool => None,
2713 Type::U8 => Some("Uint8Array"),
2714 Type::S8 => Some("Int8Array"),
2715 Type::U16 => Some("Uint16Array"),
2716 Type::S16 => Some("Int16Array"),
2717 Type::U32 => Some("Uint32Array"),
2718 Type::S32 => Some("Int32Array"),
2719 Type::U64 => Some("BigUint64Array"),
2720 Type::S64 => Some("BigInt64Array"),
2721 Type::F32 => Some("Float32Array"),
2722 Type::F64 => Some("Float64Array"),
2723 Type::Char => None,
2724 Type::String => None,
2725 Type::ErrorContext => None,
2726 Type::Id(id) => match &resolve.types[*id].kind {
2727 TypeDefKind::Type(t) => js_array_ty(resolve, t),
2729 _ => None,
2730 },
2731 }
2732}