1use std::collections::{BTreeMap, BTreeSet};
2use std::fmt::Write;
3use std::mem;
4
5use heck::{ToLowerCamelCase, ToUpperCamelCase};
6use wasmtime_environ::component::{
7 InterfaceType, ResourceIndex, RuntimeCallbackIndex, RuntimeComponentInstanceIndex,
8 RuntimeMemoryIndex, RuntimeReallocIndex, TypeComponentLocalErrorContextTableIndex,
9 TypeFutureTableIndex, TypeResourceTableIndex, TypeStreamTableIndex,
10};
11use wit_bindgen_core::abi::{Bindgen, Bitcast, Instruction};
12use wit_component::StringEncoding;
13use wit_parser::abi::WasmType;
14use wit_parser::{
15 Alignment, ArchitectureSize, Handle, Resolve, SizeAlign, Type, TypeDef, TypeDefKind, TypeId,
16};
17
18use crate::intrinsics::Intrinsic;
19use crate::intrinsics::component::ComponentIntrinsic;
20use crate::intrinsics::conversion::ConversionIntrinsic;
21use crate::intrinsics::js_helper::JsHelperIntrinsic;
22use crate::intrinsics::p3::async_future::AsyncFutureIntrinsic;
23use crate::intrinsics::p3::async_stream::AsyncStreamIntrinsic;
24use crate::intrinsics::p3::async_task::AsyncTaskIntrinsic;
25use crate::intrinsics::resource::ResourceIntrinsic;
26use crate::intrinsics::string::StringIntrinsic;
27use crate::{ManagesIntrinsics, get_thrown_type, source};
28use crate::{uwrite, uwriteln};
29
30#[derive(Debug, Clone, PartialEq)]
32pub enum ErrHandling {
33 None,
36 ThrowResultErr,
38 ResultCatchHandler,
40}
41
42impl ErrHandling {
43 fn to_js_string(&self) -> String {
44 match self {
45 ErrHandling::None => "none".into(),
46 ErrHandling::ThrowResultErr => "throw-result-err".into(),
47 ErrHandling::ResultCatchHandler => "result-catch-handler".into(),
48 }
49 }
50}
51
52#[derive(Clone, Debug, PartialEq)]
54pub enum ResourceData {
55 Host {
56 tid: TypeResourceTableIndex,
57 rid: ResourceIndex,
58 local_name: String,
59 dtor_name: Option<String>,
60 },
61 Guest {
62 resource_name: String,
63 prefix: Option<String>,
64 extra: Option<ResourceExtraData>,
65 },
66}
67
68#[derive(Clone, Debug, PartialEq)]
69pub struct PayloadTypeMetadata {
70 pub(crate) ty: Type,
71 pub(crate) iface_ty: InterfaceType,
72 pub(crate) lift_js_expr: String,
74 pub(crate) lower_js_expr: String,
76 pub(crate) size32: u32,
77 pub(crate) align32: u32,
78 pub(crate) flat_count: Option<u8>,
79}
80
81#[derive(Clone, Debug, PartialEq)]
83pub enum ResourceExtraData {
84 Stream {
85 table_idx: TypeStreamTableIndex,
86 elem_ty: Option<PayloadTypeMetadata>,
87 },
88 Future {
89 table_idx: TypeFutureTableIndex,
90 elem_ty: Option<PayloadTypeMetadata>,
91 nesting_level: u32,
92 },
93 ErrorContext {
94 table_idx: TypeComponentLocalErrorContextTableIndex,
95 },
96}
97
98#[derive(Clone, Debug, PartialEq)]
123pub struct ResourceTable {
124 pub imported: bool,
128
129 pub data: ResourceData,
131}
132
133pub type ResourceMap = BTreeMap<TypeId, ResourceTable>;
135
136#[derive(bon::Builder)]
137#[non_exhaustive]
138pub struct FunctionBindgen<'a> {
139 pub resource_map: &'a ResourceMap,
141
142 pub clear_resource_borrows: bool,
144
145 pub intrinsics: &'a mut BTreeSet<Intrinsic>,
147
148 pub valid_lifting_optimization: bool,
150
151 pub sizes: &'a SizeAlign,
153
154 pub err: ErrHandling,
156
157 pub tmp: usize,
159
160 pub src: source::Source,
162
163 pub block_storage: Vec<source::Source>,
165
166 pub blocks: Vec<(String, Vec<String>)>,
168
169 pub params: Vec<String>,
171
172 pub memory: Option<&'a String>,
174
175 pub realloc: Option<&'a String>,
177
178 pub post_return: Option<&'a String>,
180
181 pub tracing_prefix: &'a String,
183
184 pub tracing_enabled: bool,
186
187 pub no_component_error_wrapping: bool,
189
190 pub encoding: StringEncoding,
192
193 pub callee: &'a str,
195
196 pub callee_resource_dynamic: bool,
198
199 pub resolve: &'a Resolve,
201
202 pub requires_async_porcelain: bool,
207
208 pub is_async: bool,
210
211 pub wrap_async_future_result: bool,
214
215 pub iface_name: Option<&'a str>,
217
218 pub asmjs: bool,
220
221 pub component_state: Option<FunctionBindgenComponentState>,
232
233 pub(crate) for_import: Option<bool>,
236}
237
238#[derive(bon::Builder)]
248#[non_exhaustive]
249pub struct FunctionBindgenComponentState {
250 pub(crate) component_idx: RuntimeComponentInstanceIndex,
251 pub(crate) realloc_fn_idx: Option<RuntimeReallocIndex>,
252 pub(crate) memory_idx: Option<RuntimeMemoryIndex>,
253 pub(crate) callback_fn_idx: Option<RuntimeCallbackIndex>,
254}
255
256#[derive(bon::Builder)]
258#[non_exhaustive]
259pub struct ComponentStateJsExprs {
260 pub(crate) component_idx: String,
262 pub(crate) callback_fn_name: String,
264 pub(crate) get_callback_fn: String,
266 pub(crate) memory_idx: String,
268 pub(crate) get_memory_fn: String,
270 pub(crate) get_realloc_fn: String,
272}
273
274impl FunctionBindgenComponentState {
275 fn get_js_exprs(&self) -> ComponentStateJsExprs {
280 ComponentStateJsExprs {
281 component_idx: self.component_idx.as_u32().to_string(),
282 callback_fn_name: match self.callback_fn_idx {
283 Some(idx) => format!("callback_{}", idx.as_u32()),
284 None => "null".into(),
285 },
286 get_callback_fn: match self.callback_fn_idx {
287 Some(idx) => format!("() => callback_{}", idx.as_u32()),
288 None => "() => null".into(),
289 },
290 memory_idx: match self.memory_idx {
291 Some(idx) => idx.as_u32().to_string(),
292 None => "null".into(),
293 },
294 get_memory_fn: match self.memory_idx {
295 Some(idx) => format!("() => memory{}", idx.as_u32()),
296 None => "() => null".into(),
297 },
298 get_realloc_fn: match self.realloc_fn_idx {
299 Some(idx) => format!("() => realloc{}", idx.as_u32()),
300 None => "undefined".into(),
301 },
302 }
303 }
304}
305
306fn unambiguous_result_wrapper_tags(resolve: &Resolve, ty: Option<&Type>) -> (bool, bool) {
313 let Some(ty) = ty else {
314 return (true, true);
315 };
316 let Type::Id(id) = ty else {
317 return (true, true);
318 };
319
320 match &resolve.types[crate::dealias(resolve, *id)].kind {
321 TypeDefKind::Variant(variant) => (
322 !variant.cases.iter().any(|case| case.name == "ok"),
323 !variant.cases.iter().any(|case| case.name == "err"),
324 ),
325 TypeDefKind::Result(_) => (false, false),
326 TypeDefKind::Record(record) => {
327 let unambiguous = !record.fields.iter().any(|field| field.name == "tag");
328 (unambiguous, unambiguous)
329 }
330 TypeDefKind::Flags(flags) => {
331 let unambiguous = !flags.flags.iter().any(|flag| flag.name == "tag");
332 (unambiguous, unambiguous)
333 }
334 TypeDefKind::Option(payload) => unambiguous_result_wrapper_tags(resolve, Some(payload)),
335 _ => (true, true),
336 }
337}
338
339impl FunctionBindgen<'_> {
340 fn tmp(&mut self) -> usize {
341 let ret = self.tmp;
342 self.tmp += 1;
343 ret
344 }
345
346 fn intrinsic(&mut self, intrinsic: Intrinsic) -> String {
347 self.intrinsics.insert(intrinsic);
348 intrinsic.name().to_string()
349 }
350
351 fn clamp_guest<T>(&mut self, results: &mut Vec<String>, operands: &[String], min: T, max: T)
352 where
353 T: std::fmt::Display,
354 {
355 let clamp = self.intrinsic(Intrinsic::ClampGuest);
356 results.push(format!("{}({}, {}, {})", clamp, operands[0], min, max));
357 }
358
359 fn load(
360 &mut self,
361 method: &str,
362 offset: ArchitectureSize,
363 operands: &[String],
364 results: &mut Vec<String>,
365 ) {
366 let view = self.intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::DataView));
367 let Some(memory) = self.memory.as_ref() else {
368 panic!(
369 "unexpectedly missing memory during bindgen for interface [{:?}] (callee {})",
370 self.iface_name, self.callee,
371 );
372 };
373 results.push(format!(
374 "{view}({memory}).{method}({} + {offset}, true)",
375 operands[0],
376 offset = offset.size_wasm32()
377 ));
378 }
379
380 fn store(&mut self, method: &str, offset: ArchitectureSize, operands: &[String]) {
381 let view = self.intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::DataView));
382 let memory = self.memory.as_ref().unwrap();
383 uwriteln!(
384 self.src,
385 "{view}({memory}).{method}({} + {offset}, {}, true);",
386 operands[1],
387 operands[0],
388 offset = offset.size_wasm32()
389 );
390 }
391
392 fn generate_result_assignment_lhs(
417 &mut self,
418 amt: usize,
419 results: &mut Vec<String>,
420 is_async: bool,
421 ) -> (String, String) {
422 let mut s = String::new();
423 let mut vars_init = String::new();
424 match amt {
425 0 => {
426 if is_async {
429 uwrite!(s, "ret = ")
430 }
431 uwriteln!(vars_init, "let ret;");
432 }
433 1 => {
434 uwrite!(s, "ret = ");
435 results.push("ret".to_string());
436 uwriteln!(vars_init, "let ret;");
437 }
438 n => {
439 uwrite!(s, "[");
440 for i in 0..n {
441 if i > 0 {
442 uwrite!(s, ", ");
443 }
444 uwrite!(s, "ret{i}");
445 results.push(format!("ret{i}"));
446 uwriteln!(vars_init, "let ret;");
447 }
448 uwrite!(s, "] = ");
449 }
450 }
451 (vars_init, s)
452 }
453
454 fn bitcast(&mut self, cast: &Bitcast, op: &str) -> String {
455 match cast {
456 Bitcast::I32ToF32 => {
457 let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::I32ToF32));
458 format!("{cvt}({op})")
459 }
460 Bitcast::F32ToI32 => {
461 let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::F32ToI32));
462 format!("{cvt}({op})")
463 }
464 Bitcast::I64ToF64 => {
465 let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::I64ToF64));
466 format!("{cvt}({op})")
467 }
468 Bitcast::F64ToI64 => {
469 let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::F64ToI64));
470 format!("{cvt}({op})")
471 }
472 Bitcast::I32ToI64 => format!("BigInt({op})"),
473 Bitcast::I64ToI32 => format!("Number({op})"),
474 Bitcast::I64ToF32 => {
475 let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::I32ToF32));
476 format!("{cvt}(Number({op}))")
477 }
478 Bitcast::F32ToI64 => {
479 let cvt = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::F32ToI32));
480 format!("BigInt({cvt}({op}))")
481 }
482 Bitcast::None
483 | Bitcast::P64ToI64
484 | Bitcast::LToI32
485 | Bitcast::I32ToL
486 | Bitcast::LToP
487 | Bitcast::PToL
488 | Bitcast::PToI32
489 | Bitcast::I32ToP => op.to_string(),
490 Bitcast::PToP64 | Bitcast::I64ToP64 | Bitcast::LToI64 => format!("BigInt({op})"),
491 Bitcast::P64ToP | Bitcast::I64ToL => format!("Number({op})"),
492 Bitcast::Sequence(casts) => {
493 let mut statement = op.to_string();
494 for cast in casts.iter() {
495 statement = self.bitcast(cast, &statement);
496 }
497 statement
498 }
499 }
500 }
501
502 fn start_current_task(&mut self, instr: &Instruction) {
507 let is_async = self.is_async;
508 let is_manual_async = self.requires_async_porcelain;
509 let preserve_future_result = self.wrap_async_future_result;
510 let fn_name = self.callee;
511 let err_handling = self.err.to_js_string();
512
513 let (calling_wasm_export, prefix) = match instr {
514 Instruction::CallWasm { .. } => (true, "_wasm_call_"),
515 Instruction::CallInterface { .. } => (false, "_interface_call_"),
516 _ => unreachable!(
517 "unrecognized instruction triggering start of current task: [{instr:?}]"
518 ),
519 };
520 let start_current_task_fn = self.intrinsic(Intrinsic::AsyncTask(
521 AsyncTaskIntrinsic::CreateNewCurrentTask,
522 ));
523
524 let (component_idx_expr, get_callback_fn_expr, callback_fn_name) =
525 if let Some(state) = &self.component_state {
526 let ComponentStateJsExprs {
527 component_idx,
528 callback_fn_name,
529 get_callback_fn,
530 ..
531 } = state.get_js_exprs();
532 (component_idx, get_callback_fn, callback_fn_name)
533 } else {
534 ("1".into(), "() => null".into(), "null".into())
535 };
536
537 uwriteln!(
538 self.src,
539 r#"
540 const [task, {prefix}currentTaskID] = {start_current_task_fn}({{
541 componentIdx: {component_idx_expr},
542 isAsync: {is_async},
543 isManualAsync: {is_manual_async},
544 preserveFutureResult: {preserve_future_result},
545 entryFnName: '{fn_name}',
546 getCallbackFn: {get_callback_fn_expr},
547 callbackFnName: {callback_fn_name},
548 errHandling: '{err_handling}',
549 callingWasmExport: {calling_wasm_export},
550 }});
551 "#,
552 );
553 }
554}
555
556impl ManagesIntrinsics for FunctionBindgen<'_> {
557 fn add_intrinsic(&mut self, intrinsic: Intrinsic) {
559 self.intrinsic(intrinsic);
560 }
561}
562
563impl Bindgen for FunctionBindgen<'_> {
564 type Operand = String;
565
566 fn sizes(&self) -> &SizeAlign {
568 self.sizes
569 }
570
571 fn push_block(&mut self) {
573 let prev = mem::take(&mut self.src);
574 self.block_storage.push(prev);
575 }
576
577 fn finish_block(&mut self, operands: &mut Vec<String>) {
579 let to_restore = self.block_storage.pop().unwrap();
580 let src = mem::replace(&mut self.src, to_restore);
581 self.blocks.push((src.into(), mem::take(operands)));
582 }
583
584 fn return_pointer(&mut self, _size: ArchitectureSize, _align: Alignment) -> String {
586 unimplemented!("determining the return pointer for this function is not implemented");
587 }
588
589 fn is_list_canonical(&self, resolve: &Resolve, elem_ty: &Type) -> bool {
597 js_array_ty(resolve, elem_ty).is_some()
598 }
599
600 fn emit(
601 &mut self,
602 resolve: &Resolve,
603 inst: &Instruction<'_>,
604 operands: &mut Vec<String>,
605 results: &mut Vec<String>,
606 ) {
607 match inst {
608 Instruction::GetArg { nth } => results.push(self.params[*nth].clone()),
609
610 Instruction::I32Const { val } => results.push(val.to_string()),
611
612 Instruction::ConstZero { tys } => {
613 for t in tys.iter() {
614 match t {
615 WasmType::I64 | WasmType::PointerOrI64 => results.push("0n".to_string()),
616 WasmType::I32
617 | WasmType::F32
618 | WasmType::F64
619 | WasmType::Pointer
620 | WasmType::Length => results.push("0".to_string()),
621 }
622 }
623 }
624
625 Instruction::U8FromI32 => self.clamp_guest(results, operands, u8::MIN, u8::MAX),
626
627 Instruction::S8FromI32 => self.clamp_guest(results, operands, i8::MIN, i8::MAX),
628
629 Instruction::U16FromI32 => self.clamp_guest(results, operands, u16::MIN, u16::MAX),
630
631 Instruction::S16FromI32 => self.clamp_guest(results, operands, i16::MIN, i16::MAX),
632
633 Instruction::U32FromI32 => results.push(format!("{} >>> 0", operands[0])),
634
635 Instruction::U64FromI64 => {
636 results.push(format!("BigInt.asUintN(64, BigInt({}))", operands[0]))
637 }
638
639 Instruction::S32FromI32 | Instruction::S64FromI64 => {
640 results.push(operands.pop().unwrap())
641 }
642
643 Instruction::I32FromU8 => {
644 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToUint8));
645 results.push(format!("{conv}({op})", op = operands[0]))
646 }
647
648 Instruction::I32FromS8 => {
649 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToInt8));
650 results.push(format!("{conv}({op})", op = operands[0]))
651 }
652
653 Instruction::I32FromU16 => {
654 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToUint16));
655 results.push(format!("{conv}({op})", op = operands[0]))
656 }
657
658 Instruction::I32FromS16 => {
659 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToInt16));
660 results.push(format!("{conv}({op})", op = operands[0]))
661 }
662
663 Instruction::I32FromU32 => {
664 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToUint32));
665 results.push(format!("{conv}({op})", op = operands[0]))
666 }
667
668 Instruction::I32FromS32 => {
669 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToInt32));
670 results.push(format!("{conv}({op})", op = operands[0]))
671 }
672
673 Instruction::I64FromU64 => {
674 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToBigUint64));
675 results.push(format!("{conv}({op})", op = operands[0]))
676 }
677
678 Instruction::I64FromS64 => {
679 let conv = self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToBigInt64));
680 results.push(format!("{conv}({op})", op = operands[0]))
681 }
682
683 Instruction::F32FromCoreF32 | Instruction::F64FromCoreF64 => {
684 results.push(operands.pop().unwrap())
685 }
686
687 Instruction::CoreF32FromF32 | Instruction::CoreF64FromF64 => {
688 results.push(format!("+{}", operands[0]))
689 }
690
691 Instruction::CharFromI32 => {
692 let validate =
693 self.intrinsic(Intrinsic::String(StringIntrinsic::ValidateGuestChar));
694 results.push(format!("{}({})", validate, operands[0]));
695 }
696
697 Instruction::I32FromChar => {
698 let validate = self.intrinsic(Intrinsic::String(StringIntrinsic::ValidateHostChar));
699 results.push(format!("{}({})", validate, operands[0]));
700 }
701
702 Instruction::Bitcasts { casts } => {
703 for (cast, op) in casts.iter().zip(operands) {
704 results.push(self.bitcast(cast, op));
705 }
706 }
707
708 Instruction::BoolFromI32 => {
709 let tmp = self.tmp();
710 uwrite!(self.src, "var bool{} = {};\n", tmp, operands[0]);
711 if self.valid_lifting_optimization {
712 results.push(format!("!!bool{tmp}"));
713 } else {
714 let throw = self.intrinsic(Intrinsic::ThrowInvalidBool);
715 results.push(format!(
716 "bool{tmp} == 0 ? false : (bool{tmp} == 1 ? true : {throw}())"
717 ));
718 }
719 }
720
721 Instruction::I32FromBool => {
722 results.push(format!("{} ? 1 : 0", operands[0]));
723 }
724
725 Instruction::RecordLower { record, .. } => {
726 let tmp = self.tmp();
729 let mut expr = "var {".to_string();
730 for (i, field) in record.fields.iter().enumerate() {
731 if i > 0 {
732 expr.push_str(", ");
733 }
734 let name = format!("v{tmp}_{i}");
735 expr.push_str(&field.name.to_lower_camel_case());
736 expr.push_str(": ");
737 expr.push_str(&name);
738 results.push(name);
739 }
740 uwrite!(self.src, "{} }} = {};\n", expr, operands[0]);
741 }
742
743 Instruction::RecordLift { record, .. } => {
744 let mut result = "{\n".to_string();
748 for (field, op) in record.fields.iter().zip(operands) {
749 result.push_str(&format!("{}: {},\n", field.name.to_lower_camel_case(), op));
750 }
751 result.push('}');
752 results.push(result);
753 }
754
755 Instruction::TupleLower { tuple, .. } => {
756 let tmp = self.tmp();
760 let mut expr = "var [".to_string();
761 for i in 0..tuple.types.len() {
762 if i > 0 {
763 expr.push_str(", ");
764 }
765 let name = format!("tuple{tmp}_{i}");
766 expr.push_str(&name);
767 results.push(name);
768 }
769 uwrite!(self.src, "{}] = {};\n", expr, operands[0]);
770 }
771
772 Instruction::TupleLift { .. } => {
773 results.push(format!("[{}]", operands.join(", ")));
776 }
777
778 Instruction::FlagsLower { flags, .. } => {
779 let op0 = &operands[0];
780
781 for _ in 0..flags.repr().count() {
783 let tmp = self.tmp();
784 let name = format!("flags{tmp}");
785 uwrite!(self.src, "let {name} = 0;\n");
788 results.push(name);
789 }
790
791 uwrite!(
792 self.src,
793 "if (typeof {op0} === 'object' && {op0} !== null) {{\n"
794 );
795
796 for (i, chunk) in flags.flags.chunks(32).enumerate() {
797 let result_name = &results[i];
798
799 uwrite!(self.src, "{result_name} = ");
800 for (i, flag) in chunk.iter().enumerate() {
801 if i != 0 {
802 uwrite!(self.src, " | ");
803 }
804
805 let flag = flag.name.to_lower_camel_case();
806 uwrite!(self.src, "Boolean({op0}.{flag}) << {i}");
807 }
808 uwrite!(self.src, ";\n");
809 }
810
811 uwrite!(
812 self.src,
813 "\
814 }} else if ({op0} !== null && {op0} !== undefined) {{
815 throw new TypeError('only an object, undefined or null can be converted to flags');
816 }}
817 ");
818
819 }
823
824 Instruction::FlagsLift { flags, .. } => {
825 let tmp = self.tmp();
826 results.push(format!("flags{tmp}"));
827
828 if let Some(op) = operands.last() {
829 if flags.flags.len() % 32 != 0 && !self.valid_lifting_optimization {
833 let mask: u32 = 0xffffffff << (flags.flags.len() % 32);
834 uwriteln!(
835 self.src,
836 "if (({op} & {mask}) !== 0) {{
837 throw new TypeError('flags have extraneous bits set');
838 }}"
839 );
840 }
841 }
842
843 uwriteln!(self.src, "var flags{tmp} = {{");
844
845 for (i, flag) in flags.flags.iter().enumerate() {
846 let flag = flag.name.to_lower_camel_case();
847 let op = &operands[i / 32];
848 let mask: u32 = 1 << (i % 32);
849 uwriteln!(self.src, "{flag}: Boolean({op} & {mask}),");
850 }
851
852 uwriteln!(self.src, "}};");
853 }
854
855 Instruction::VariantPayloadName => results.push("e".to_string()),
856
857 Instruction::VariantLower {
858 variant,
859 results: result_types,
860 name,
861 ..
862 } => {
863 let blocks = self
864 .blocks
865 .drain(self.blocks.len() - variant.cases.len()..)
866 .collect::<Vec<_>>();
867 let tmp = self.tmp();
868 let op = &operands[0];
869 uwriteln!(self.src, "var variant{tmp} = {op};");
870
871 for i in 0..result_types.len() {
872 uwriteln!(self.src, "let variant{tmp}_{i};");
873 results.push(format!("variant{tmp}_{i}"));
874 }
875
876 let expr_to_match = format!("variant{tmp}.tag");
877
878 uwriteln!(self.src, "switch ({expr_to_match}) {{");
879 for (case, (block, block_results)) in variant.cases.iter().zip(blocks) {
880 uwriteln!(self.src, "case '{}': {{", case.name.as_str());
881 if case.ty.is_some() {
882 uwriteln!(self.src, "const e = variant{tmp}.val;");
883 }
884 self.src.push_str(&block);
885
886 for (i, result) in block_results.iter().enumerate() {
887 uwriteln!(self.src, "variant{tmp}_{i} = {result};");
888 }
889 uwriteln!(
890 self.src,
891 "break;
892 }}"
893 );
894 }
895 let variant_name = name.to_upper_camel_case();
896 uwriteln!(
897 self.src,
898 r#"default: {{
899 throw new TypeError(`invalid variant tag value \`${{JSON.stringify({expr_to_match})}}\` (received \`${{variant{tmp}}}\`) specified for \`{variant_name}\``);
900 }}"#,
901 );
902 uwriteln!(self.src, "}}");
903 }
904
905 Instruction::VariantLift { variant, name, .. } => {
906 let blocks = self
907 .blocks
908 .drain(self.blocks.len() - variant.cases.len()..)
909 .collect::<Vec<_>>();
910
911 let tmp = self.tmp();
912 let op = &operands[0];
913
914 uwriteln!(
915 self.src,
916 "let variant{tmp};
917 switch ({op}) {{"
918 );
919
920 for (i, (case, (block, block_results))) in
921 variant.cases.iter().zip(blocks).enumerate()
922 {
923 let tag = case.name.as_str();
924 uwriteln!(
925 self.src,
926 "case {i}: {{
927 {block}\
928 variant{tmp} = {{
929 tag: '{tag}',"
930 );
931 if case.ty.is_some() {
932 assert!(block_results.len() == 1);
933 uwriteln!(self.src, " val: {}", block_results[0]);
934 } else {
935 assert!(block_results.is_empty());
936 }
937 uwriteln!(
938 self.src,
939 " }};
940 break;
941 }}"
942 );
943 }
944 let variant_name = name.to_upper_camel_case();
945 if !self.valid_lifting_optimization {
946 uwriteln!(
947 self.src,
948 "default: {{
949 throw new TypeError('invalid variant discriminant for {variant_name}');
950 }}",
951 );
952 }
953 uwriteln!(self.src, "}}");
954 results.push(format!("variant{tmp}"));
955 }
956
957 Instruction::OptionLower {
958 payload,
959 results: result_types,
960 ..
961 } => {
962 let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
963 let (mut some, some_results) = self.blocks.pop().unwrap();
964 let (mut none, none_results) = self.blocks.pop().unwrap();
965
966 let tmp = self.tmp();
967 let op = &operands[0];
968 uwriteln!(self.src, "var variant{tmp} = {op};");
969
970 for i in 0..result_types.len() {
971 uwriteln!(self.src, "let variant{tmp}_{i};");
972 results.push(format!("variant{tmp}_{i}"));
973
974 let some_result = &some_results[i];
975 let none_result = &none_results[i];
976 uwriteln!(some, "variant{tmp}_{i} = {some_result};");
977 uwriteln!(none, "variant{tmp}_{i} = {none_result};");
978 }
979
980 if maybe_null(resolve, payload) {
981 uwriteln!(
982 self.src,
983 r#"switch (variant{tmp}.tag) {{
984 case 'none': {{
985 {none}
986 break;
987 }}
988 case 'some': {{
989 const e = variant{tmp}.val;
990 {some}
991 break;
992 }}
993 default: {{
994 {debug_log_fn}("ERROR: invalid value (expected option as object with 'tag' member)", {{ value: variant{tmp}, valueType: typeof variant{tmp} }});
995 throw new TypeError('invalid variant specified for option');
996 }}
997 }}"#,
998 );
999 } else {
1000 uwriteln!(
1001 self.src,
1002 "if (variant{tmp} === null || variant{tmp} === undefined) {{
1003 {none}\
1004 }} else {{
1005 const e = variant{tmp};
1006 {some}\
1007 }}"
1008 );
1009 }
1010 }
1011
1012 Instruction::OptionLift { payload, .. } => {
1013 let (some, some_results) = self.blocks.pop().unwrap();
1014 let (none, none_results) = self.blocks.pop().unwrap();
1015 assert!(none_results.is_empty());
1016 assert!(some_results.len() == 1);
1017 let some_result = &some_results[0];
1018
1019 let tmp = self.tmp();
1020 let op = &operands[0];
1021
1022 let (v_none, v_some) = if maybe_null(resolve, payload) {
1023 (
1024 "{ tag: 'none' }",
1025 format!(
1026 "{{
1027 tag: 'some',
1028 val: {some_result}
1029 }}"
1030 ),
1031 )
1032 } else {
1033 ("undefined", some_result.into())
1034 };
1035
1036 if !self.valid_lifting_optimization {
1037 uwriteln!(
1038 self.src,
1039 "let variant{tmp};
1040 switch ({op}) {{
1041 case 0: {{
1042 {none}\
1043 variant{tmp} = {v_none};
1044 break;
1045 }}
1046 case 1: {{
1047 {some}\
1048 variant{tmp} = {v_some};
1049 break;
1050 }}
1051 default: {{
1052 throw new TypeError('invalid variant discriminant for option');
1053 }}
1054 }}",
1055 );
1056 } else {
1057 uwriteln!(
1058 self.src,
1059 "let variant{tmp};
1060 if ({op}) {{
1061 {some}\
1062 variant{tmp} = {v_some};
1063 }} else {{
1064 {none}\
1065 variant{tmp} = {v_none};
1066 }}"
1067 );
1068 }
1069
1070 results.push(format!("variant{tmp}"));
1071 }
1072
1073 Instruction::ResultLower {
1074 results: result_types,
1075 ..
1076 } => {
1077 let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
1078 let (mut err, err_results) = self.blocks.pop().unwrap();
1079 let (mut ok, ok_results) = self.blocks.pop().unwrap();
1080
1081 let tmp = self.tmp();
1082 let op = &operands[0];
1083 uwriteln!(self.src, "var variant{tmp} = {op};");
1084
1085 for i in 0..result_types.len() {
1086 uwriteln!(self.src, "let variant{tmp}_{i};");
1087 results.push(format!("variant{tmp}_{i}"));
1088
1089 let ok_result = &ok_results[i];
1090 let err_result = &err_results[i];
1091 uwriteln!(ok, "variant{tmp}_{i} = {ok_result};");
1092 uwriteln!(err, "variant{tmp}_{i} = {err_result};");
1093 }
1094
1095 uwriteln!(
1096 self.src,
1097 r#"switch (variant{tmp}.tag) {{
1098 case 'ok': {{
1099 const e = variant{tmp}.val;
1100 {ok}
1101 break;
1102 }}
1103 case 'err': {{
1104 const e = variant{tmp}.val;
1105 {err}
1106 break;
1107 }}
1108 default: {{
1109 {debug_log_fn}("ERROR: invalid value (expected result as object with 'tag' member)", {{ value: variant{tmp}, valueType: typeof variant{tmp} }});
1110 throw new TypeError('invalid variant specified for result');
1111 }}
1112 }}"#,
1113 );
1114 }
1115
1116 Instruction::ResultLift { result, .. } => {
1117 let (err, err_results) = self.blocks.pop().unwrap();
1118 let (ok, ok_results) = self.blocks.pop().unwrap();
1119 let ok_result = if result.ok.is_some() {
1120 assert_eq!(ok_results.len(), 1);
1121 ok_results[0].to_string()
1122 } else {
1123 assert_eq!(ok_results.len(), 0);
1124 String::from("undefined")
1125 };
1126 let err_result = if result.err.is_some() {
1127 assert_eq!(err_results.len(), 1);
1128 err_results[0].to_string()
1129 } else {
1130 assert_eq!(err_results.len(), 0);
1131 String::from("undefined")
1132 };
1133 let tmp = self.tmp();
1134 let op0 = &operands[0];
1135
1136 if !self.valid_lifting_optimization {
1137 uwriteln!(
1138 self.src,
1139 "let variant{tmp};
1140 switch ({op0}) {{
1141 case 0: {{
1142 {ok}\
1143 variant{tmp} = {{
1144 tag: 'ok',
1145 val: {ok_result}
1146 }};
1147 break;
1148 }}
1149 case 1: {{
1150 {err}\
1151 variant{tmp} = {{
1152 tag: 'err',
1153 val: {err_result}
1154 }};
1155 break;
1156 }}
1157 default: {{
1158 throw new TypeError('invalid variant discriminant for expected');
1159 }}
1160 }}",
1161 );
1162 } else {
1163 uwriteln!(
1164 self.src,
1165 "let variant{tmp};
1166 if ({op0}) {{
1167 {err}\
1168 variant{tmp} = {{
1169 tag: 'err',
1170 val: {err_result}
1171 }};
1172 }} else {{
1173 {ok}\
1174 variant{tmp} = {{
1175 tag: 'ok',
1176 val: {ok_result}
1177 }};
1178 }}"
1179 );
1180 }
1181 results.push(format!("variant{tmp}"));
1182 }
1183
1184 Instruction::EnumLower { name, enum_, .. } => {
1185 let tmp = self.tmp();
1186
1187 let op = &operands[0];
1188 uwriteln!(self.src, "var val{tmp} = {op};");
1189
1190 uwriteln!(
1192 self.src,
1193 "let enum{tmp};
1194 switch (val{tmp}) {{"
1195 );
1196 for (i, case) in enum_.cases.iter().enumerate() {
1197 uwriteln!(
1198 self.src,
1199 "case '{case}': {{
1200 enum{tmp} = {i};
1201 break;
1202 }}",
1203 case = case.name
1204 );
1205 }
1206 uwriteln!(self.src, "default: {{");
1207 if !self.valid_lifting_optimization {
1208 uwriteln!(
1209 self.src,
1210 "if (({op}) instanceof Error) {{
1211 console.error({op});
1212 }}"
1213 );
1214 }
1215 uwriteln!(
1216 self.src,
1217 "
1218 throw new TypeError(`\"${{val{tmp}}}\" is not one of the cases of {name}`);
1219 }}
1220 }}",
1221 );
1222
1223 results.push(format!("enum{tmp}"));
1224 }
1225
1226 Instruction::EnumLift { name, enum_, .. } => {
1227 let tmp = self.tmp();
1228
1229 uwriteln!(
1230 self.src,
1231 "let enum{tmp};
1232 switch ({}) {{",
1233 operands[0]
1234 );
1235 for (i, case) in enum_.cases.iter().enumerate() {
1236 uwriteln!(
1237 self.src,
1238 "case {i}: {{
1239 enum{tmp} = '{case}';
1240 break;
1241 }}",
1242 case = case.name
1243 );
1244 }
1245 if !self.valid_lifting_optimization {
1246 let name = name.to_upper_camel_case();
1247 uwriteln!(
1248 self.src,
1249 "default: {{
1250 throw new TypeError('invalid discriminant specified for {name}');
1251 }}",
1252 );
1253 }
1254 uwriteln!(self.src, "}}");
1255
1256 results.push(format!("enum{tmp}"));
1257 }
1258
1259 Instruction::ListCanonLower { element, .. } => {
1271 let tmp = self.tmp();
1272 let memory = self.memory.as_ref().unwrap();
1273 let realloc = self.realloc.unwrap();
1274
1275 uwriteln!(self.src, "var val{tmp} = {};", operands[0]);
1277 if matches!(element, Type::U8) {
1278 uwriteln!(
1279 self.src,
1280 "var len{tmp} = Array.isArray(val{tmp}) ? val{tmp}.length : val{tmp}.byteLength;"
1281 );
1282 } else {
1283 uwriteln!(self.src, "var len{tmp} = val{tmp}.length;");
1284 }
1285
1286 let size = self.sizes.size(element).size_wasm32();
1288 let align = self.sizes.align(element).align_wasm32();
1289
1290 uwriteln!(
1292 self.src,
1293 "var ptr{tmp} = {realloc_call}(0, 0, {align}, len{tmp} * {size});",
1294 realloc_call = if self.is_async {
1295 format!("await {realloc}")
1296 } else {
1297 realloc.to_string()
1298 },
1299 );
1300
1301 let (dataview_set_method, check_fn_intrinsic) =
1303 gen_dataview_set_and_check_fn_js_for_numeric_type(resolve, element);
1304
1305 uwriteln!(
1307 self.src,
1308 r#"
1309 let valData{tmp};
1310 const valLenBytes{tmp} = len{tmp} * {size};
1311 if (Array.isArray(val{tmp})) {{
1312 // Regular array likely containing numbers, write values to memory
1313 let offset = 0;
1314 const dv{tmp} = new DataView({memory}.buffer);
1315 for (const v of val{tmp}) {{
1316 {check_fn_intrinsic}(v);
1317 dv{tmp}.{dataview_set_method}(ptr{tmp} + offset, v, true);
1318 offset += {size};
1319 }}
1320 }} else {{
1321 // TypedArray / ArrayBuffer-like, direct copy
1322 valData{tmp} = new Uint8Array(val{tmp}.buffer || val{tmp}, val{tmp}.byteOffset, valLenBytes{tmp});
1323 const out{tmp} = new Uint8Array({memory}.buffer, ptr{tmp}, valLenBytes{tmp});
1324 out{tmp}.set(valData{tmp});
1325 }}
1326 "#,
1327 );
1328
1329 results.push(format!("ptr{tmp}"));
1330 results.push(format!("len{tmp}"));
1331 }
1332
1333 Instruction::ListCanonLift { element, .. } => {
1334 let tmp = self.tmp();
1335 let memory = self.memory.as_ref().unwrap();
1336 uwriteln!(self.src, "var ptr{tmp} = {};", operands[0]);
1337 uwriteln!(self.src, "var len{tmp} = {};", operands[1]);
1338 uwriteln!(
1339 self.src,
1340 "var result{tmp} = new {array_ty}({memory}.buffer.slice(ptr{tmp}, ptr{tmp} + len{tmp} * {elem_size}));",
1341 elem_size = self.sizes.size(element).size_wasm32(),
1342 array_ty = js_array_ty(resolve, element).unwrap(), );
1344 results.push(format!("result{tmp}"));
1345 }
1346
1347 Instruction::StringLower { .. } => {
1348 assert!(matches!(
1350 self.encoding,
1351 StringEncoding::UTF8 | StringEncoding::UTF16
1352 ));
1353
1354 let (call_prefix, encode_intrinsic) = match (self.encoding, self.is_async) {
1355 (StringEncoding::UTF16, true) => (
1356 "await ",
1357 Intrinsic::String(StringIntrinsic::Utf16EncodeAsync),
1358 ),
1359 (StringEncoding::UTF16, false) => {
1360 ("", Intrinsic::String(StringIntrinsic::Utf16Encode))
1361 }
1362 (StringEncoding::UTF8, true) => (
1363 "await ",
1364 Intrinsic::String(StringIntrinsic::Utf8EncodeAsync),
1365 ),
1366 (StringEncoding::UTF8, false) => {
1367 ("", Intrinsic::String(StringIntrinsic::Utf8Encode))
1368 }
1369 _ => unreachable!("unsupported encoding {}", self.encoding),
1370 };
1371 let encode = self.intrinsic(encode_intrinsic);
1372
1373 let tmp = self.tmp();
1374 let memory = self.memory.as_ref().unwrap();
1375 let str = String::from("cabi_realloc");
1376 let realloc = self.realloc.unwrap_or(&str);
1377 let s = &operands[0];
1378 uwriteln!(
1379 self.src,
1380 r#"
1381 var encodeRes = {call_prefix}{encode}({s}, {realloc}, {memory});
1382 var ptr{tmp} = encodeRes.ptr;
1383 var len{tmp} = {encoded_len};
1384 "#,
1385 encoded_len = match self.encoding {
1386 StringEncoding::UTF8 => "encodeRes.len".into(),
1387 _ => format!("{}.length", s),
1388 }
1389 );
1390 results.push(format!("ptr{tmp}"));
1391 results.push(format!("len{tmp}"));
1392 }
1393
1394 Instruction::StringLift => {
1395 assert!(matches!(
1397 self.encoding,
1398 StringEncoding::UTF8 | StringEncoding::UTF16
1399 ));
1400 let decoder = self.intrinsic(match self.encoding {
1401 StringEncoding::UTF16 => Intrinsic::String(StringIntrinsic::Utf16Decoder),
1402 _ => Intrinsic::String(StringIntrinsic::GlobalTextDecoderUtf8),
1403 });
1404 let tmp = self.tmp();
1405 let memory = self.memory.as_ref().unwrap();
1406 uwriteln!(self.src, "var ptr{tmp} = {};", operands[0]);
1407 uwriteln!(self.src, "var len{tmp} = {};", operands[1]);
1408 uwriteln!(
1409 self.src,
1410 "var result{tmp} = {decoder}.decode(new Uint{}Array({memory}.buffer, ptr{tmp}, len{tmp}));",
1411 if self.encoding == StringEncoding::UTF16 {
1412 "16"
1413 } else {
1414 "8"
1415 }
1416 );
1417 results.push(format!("result{tmp}"));
1418 }
1419
1420 Instruction::ListLower { element, .. } => {
1421 let (body, body_results) = self.blocks.pop().unwrap();
1422 assert!(body_results.is_empty());
1423 let tmp = self.tmp();
1424 let vec = format!("vec{tmp}");
1425 let result = format!("result{tmp}");
1426 let len = format!("len{tmp}");
1427 let size = self.sizes.size(element).size_wasm32();
1428 let align = ArchitectureSize::from(self.sizes.align(element)).size_wasm32();
1429
1430 uwriteln!(self.src, "var {vec} = {};", operands[0]);
1433 uwriteln!(self.src, "var {len} = {vec}.length;");
1434
1435 let realloc = self.realloc.as_ref().unwrap();
1437 uwriteln!(
1438 self.src,
1439 "var {result} = {realloc_call}(0, 0, {align}, {len} * {size});",
1440 realloc_call = if self.is_async {
1441 format!("await {realloc}")
1442 } else {
1443 realloc.to_string()
1444 },
1445 );
1446
1447 uwriteln!(self.src, "for (let i = 0; i < {vec}.length; i++) {{");
1450 uwriteln!(self.src, "const e = {vec}[i];");
1451 uwrite!(self.src, "const base = {result} + i * {size};");
1452 self.src.push_str(&body);
1453 uwrite!(self.src, "}}\n");
1454
1455 results.push(result);
1456 results.push(len);
1457 }
1458
1459 Instruction::ListLift { element, .. } => {
1460 let (body, body_results) = self.blocks.pop().unwrap();
1461 let tmp = self.tmp();
1462 let size = self.sizes.size(element).size_wasm32();
1463 let len = format!("len{tmp}");
1464 uwriteln!(self.src, "var {len} = {};", operands[1]);
1465 let base = format!("base{tmp}");
1466 uwriteln!(self.src, "var {base} = {};", operands[0]);
1467 let result = format!("result{tmp}");
1468 uwriteln!(self.src, "var {result} = [];");
1469 results.push(result.clone());
1470
1471 uwriteln!(self.src, "for (let i = 0; i < {len}; i++) {{");
1472 uwriteln!(self.src, "const base = {base} + i * {size};");
1473 self.src.push_str(&body);
1474 assert_eq!(body_results.len(), 1);
1475 uwriteln!(self.src, "{result}.push({});", body_results[0]);
1476 uwrite!(self.src, "}}\n");
1477 }
1478
1479 Instruction::MapLower { key, value, .. } => {
1480 let (body, body_results) = self.blocks.pop().unwrap();
1481 assert!(body_results.is_empty());
1482
1483 let tmp = self.tmp();
1484 let map = format!("map{tmp}");
1485 let entries = format!("entries{tmp}");
1486 let result = format!("result{tmp}");
1487 let len = format!("len{tmp}");
1488 let entry = self.sizes.record([*key, *value]);
1489 let size = entry.size.size_wasm32();
1490 let align = ArchitectureSize::from(entry.align).size_wasm32();
1491
1492 uwriteln!(self.src, "const {map} = {};", operands[0]);
1493 uwriteln!(
1494 self.src,
1495 "if (!({map} instanceof Map)) throw new TypeError('expected a Map');"
1496 );
1497 uwriteln!(self.src, "const {entries} = {map}.entries();");
1498 uwriteln!(self.src, "const {len} = {map}.size;");
1499
1500 let realloc = self.realloc.as_ref().unwrap();
1501 uwriteln!(
1502 self.src,
1503 "const {result} = {realloc_call}(0, 0, {align}, {len} * {size});",
1504 realloc_call = if self.is_async {
1505 format!("await {realloc}")
1506 } else {
1507 realloc.to_string()
1508 },
1509 );
1510
1511 uwriteln!(self.src, "let i = 0;");
1512 uwriteln!(self.src, "for (const [key, value] of {entries}) {{");
1513 uwriteln!(self.src, "const base = {result} + i * {size};");
1514 self.src.push_str(&body);
1515 uwriteln!(self.src, "i++;");
1516 uwrite!(self.src, "}}\n");
1517
1518 results.push(result);
1519 results.push(len);
1520 }
1521
1522 Instruction::MapLift { key, value, .. } => {
1523 let (body, body_results) = self.blocks.pop().unwrap();
1524 assert_eq!(body_results.len(), 2);
1525
1526 let tmp = self.tmp();
1527 let entry_size = self.sizes.record([*key, *value]).size.size_wasm32();
1528 let len = format!("len{tmp}");
1529 uwriteln!(self.src, "const {len} = {};", operands[1]);
1530 let base = format!("base{tmp}");
1531 uwriteln!(self.src, "const {base} = {};", operands[0]);
1532 let result = format!("result{tmp}");
1533 uwriteln!(self.src, "const {result} = new Map();");
1534 results.push(result.clone());
1535
1536 uwriteln!(self.src, "for (let i = 0; i < {len}; i++) {{");
1537 uwriteln!(self.src, "const base = {base} + i * {entry_size};");
1538 self.src.push_str(&body);
1539 uwriteln!(
1540 self.src,
1541 "{result}.set({}, {});",
1542 body_results[0],
1543 body_results[1]
1544 );
1545 uwrite!(self.src, "}}\n");
1546 }
1547
1548 Instruction::FixedLengthListLower { size, .. } => {
1549 let tmp = self.tmp();
1550 let array = format!("array{tmp}");
1551 uwriteln!(self.src, "const {array} = {};", operands[0]);
1552 for i in 0..*size {
1553 results.push(format!("{array}[{i}]"));
1554 }
1555 }
1556
1557 Instruction::FixedLengthListLift { .. } => {
1558 let tmp = self.tmp();
1559 let result = format!("result{tmp}");
1560 uwriteln!(self.src, "const {result} = [{}];", operands.join(", "));
1561 results.push(result);
1562 }
1563
1564 Instruction::FixedLengthListLowerToMemory {
1565 element, size: len, ..
1566 } => {
1567 let (body, body_results) = self.blocks.pop().unwrap();
1568 assert!(body_results.is_empty());
1569
1570 let tmp = self.tmp();
1571 let array = format!("array{tmp}");
1572 uwriteln!(self.src, "const {array} = {};", operands[0]);
1573 let addr = format!("addr{tmp}");
1574 uwriteln!(self.src, "const {addr} = {};", operands[1]);
1575 let elem_size = self.sizes.size(element).size_wasm32();
1576
1577 uwriteln!(self.src, "for (let i = 0; i < {len}; i++) {{");
1578 uwriteln!(self.src, "const e = {array}[i];");
1579 uwrite!(self.src, "const base = {addr} + i * {elem_size};");
1580 self.src.push_str(&body);
1581 uwrite!(self.src, "}}\n");
1582 }
1583
1584 Instruction::FixedLengthListLiftFromMemory {
1585 element, size: len, ..
1586 } => {
1587 let (body, body_results) = self.blocks.pop().unwrap();
1588 assert_eq!(body_results.len(), 1);
1589
1590 let tmp = self.tmp();
1591 let addr = format!("addr{tmp}");
1592 uwriteln!(self.src, "const {addr} = {};", operands[0]);
1593 let elem_size = self.sizes.size(element).size_wasm32();
1594 let result = format!("result{tmp}");
1595 uwriteln!(self.src, "const {result} = [];");
1596 results.push(result.clone());
1597
1598 uwriteln!(self.src, "for (let i = 0; i < {len}; i++) {{");
1599 uwrite!(self.src, "const base = {addr} + i * {elem_size};");
1600 self.src.push_str(&body);
1601 uwriteln!(self.src, "{result}.push({});", body_results[0]);
1602 uwrite!(self.src, "}}\n");
1603 }
1604
1605 Instruction::IterElem { .. } => results.push("e".to_string()),
1606
1607 Instruction::IterMapKey { .. } => results.push("key".to_string()),
1608
1609 Instruction::IterMapValue { .. } => results.push("value".to_string()),
1610
1611 Instruction::IterBasePointer => results.push("base".to_string()),
1612
1613 Instruction::CallWasm { name, sig } => {
1614 let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
1615 let get_component_state = self.intrinsic(Intrinsic::Component(
1616 ComponentIntrinsic::GetOrCreateAsyncState,
1617 ));
1618 let component_idx_expr = self
1619 .component_state
1620 .as_ref()
1621 .map(|state| state.get_js_exprs().component_idx)
1622 .unwrap_or_else(|| "-1".into());
1623 let has_post_return = self.post_return.is_some();
1624 let is_async = self.is_async;
1625 uwriteln!(
1626 self.src,
1627 "{debug_log_fn}('{prefix} [Instruction::CallWasm] enter', {{
1628 funcName: '{name}',
1629 paramCount: {param_count},
1630 async: {is_async},
1631 postReturn: {has_post_return},
1632 }});",
1633 param_count = sig.params.len(),
1634 prefix = self.tracing_prefix,
1635 );
1636
1637 uwriteln!(self.src, "const hostProvided = false;");
1640
1641 uwriteln!(
1644 self.src,
1645 "{get_component_state}({component_idx_expr}).throwIfTrapped();"
1646 );
1647
1648 self.start_current_task(inst);
1651
1652 if self.is_async || self.requires_async_porcelain {
1660 uwriteln!(
1661 self.src,
1662 r#"
1663 const started = await task.enter();
1664 if (!started) {{
1665 {debug_log_fn}('[Instruction::AsyncTaskReturn] failed to enter task', {{
1666 taskID: task.id(),
1667 subtaskID: task.currentSubtask()?.id(),
1668 }});
1669 throw new Error("failed to enter task");
1670 }}
1671 "#,
1672 );
1673 } else {
1674 uwriteln!(self.src, "const started = task.enterSync();",);
1675 }
1676
1677 if self.callee_resource_dynamic {
1679 let resource_borrows =
1680 self.intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceCallBorrows));
1681 let handle_tables = self.intrinsic(Intrinsic::HandleTables);
1682 let scope_id = self.intrinsic(Intrinsic::ScopeId);
1683 uwriteln!(
1684 self.src,
1685 r#"
1686 {scope_id}++;
1687 task.registerOnResolveHandler(() => {{
1688 {scope_id}--;
1689 for (const {{ rid, handle }} of {resource_borrows}) {{
1690 const storedScopeId = {handle_tables}[rid][handle << 1]
1691 if (storedScopeId === {scope_id}) {{
1692 throw new TypeError('borrows not dropped for resource call');
1693 }}
1694 }}
1695 {resource_borrows} = [];
1696 }}
1697
1698 }});
1699 "#
1700 );
1701 uwriteln!(self.src, "{scope_id}++;");
1702 }
1703
1704 let (memory_idx_expr, get_memory_fn_expr) =
1706 if let Some(state) = &self.component_state {
1707 let ComponentStateJsExprs {
1708 memory_idx,
1709 get_memory_fn,
1710 ..
1711 } = state.get_js_exprs();
1712 (memory_idx, get_memory_fn)
1713 } else {
1714 ("null".into(), "() => null".into())
1715 };
1716 uwriteln!(
1717 self.src,
1718 r#"
1719 if ({memory_idx_expr} !== null) {{
1720 task.setReturnMemoryIdx({memory_idx_expr});
1721 task.setReturnMemory({get_memory_fn_expr}());
1722 }}
1723 "#
1724 );
1725
1726 let sig_results_length = sig.results.len();
1729 let (vars_init, assignment_lhs) =
1730 self.generate_result_assignment_lhs(sig_results_length, results, is_async);
1731
1732 let (call_prefix, call_wrapper, call_err_cleanup) =
1733 if self.requires_async_porcelain | self.is_async {
1734 (
1735 "await ",
1736 Intrinsic::WithGlobalCurrentTaskMetaFnAsync.name(),
1737 format!(
1738 r#"
1739 {debug_log_fn}('[Instruction::CallWasm] error during async call', {{
1740 taskID: task.id(),
1741 err,
1742 }});
1743 {get_component_state}({component_idx_expr}).markTrapped(err);
1744 task.setErrored(err);
1745 task.reject(err);
1746 task.exit();
1747 return task.completionPromise();
1748 "#
1749 ),
1750 )
1751 } else {
1752 (
1753 "",
1754 Intrinsic::WithGlobalCurrentTaskMetaFn.name(),
1755 format!(
1756 r#"
1757 {debug_log_fn}('[Instruction::CallWasm] error during sync call', {{
1758 taskID: task.id(),
1759 err,
1760 }});
1761 {get_component_state}({component_idx_expr}).markTrapped(err);
1762 task.setErrored(err);
1763 task.reject(err);
1764 task.exit();
1765 throw err;
1766 "#
1767 ),
1768 )
1769 };
1770
1771 let args = if self.asmjs {
1772 let split_i64 =
1773 self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::SplitBigInt64));
1774
1775 let mut args = Vec::new();
1776 for (i, op) in operands
1777 .drain(operands.len() - sig.params.len()..)
1778 .enumerate()
1779 {
1780 if matches!(sig.params[i], WasmType::I64) {
1781 args.push(format!("...({split_i64}({op}))"));
1782 } else {
1783 args.push(op);
1784 }
1785 }
1786 args
1787 } else {
1788 mem::take(operands)
1789 };
1790
1791 let mut callee_invoke = format!(
1792 "{callee}({args})",
1793 callee = self.callee,
1794 args = args.join(", ")
1795 );
1796
1797 if self.asmjs {
1798 assert!(sig.results.len() <= 1);
1801 assert!(!self.requires_async_porcelain && !self.is_async);
1803
1804 if sig.results.len() == 1 && matches!(sig.results[0], WasmType::I64) {
1805 let merge_i64 = self
1806 .intrinsic(Intrinsic::Conversion(ConversionIntrinsic::MergeBigInt64));
1807 callee_invoke =
1808 format!("{merge_i64}({callee_invoke}, task.tmpRetI64HighBits)");
1809 }
1810 }
1811
1812 uwriteln!(
1813 self.src,
1814 r#"
1815 {vars_init}
1816 try {{
1817 {assignment_lhs} {call_prefix} {call_wrapper}({{
1818 taskID: task.id(),
1819 componentIdx: task.componentIdx(),
1820 fn: () => {callee_invoke},
1821 }});
1822 }} catch (err) {{
1823 {call_err_cleanup}
1824 }}
1825 "#,
1826 );
1827
1828 if self.tracing_enabled {
1829 let prefix = self.tracing_prefix;
1830 let to_result_string =
1831 self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToResultString));
1832 uwriteln!(
1833 self.src,
1834 "console.error(`{prefix} return {}`);",
1835 if sig_results_length > 0 || !results.is_empty() {
1836 format!("result=${{{to_result_string}(ret)}}")
1837 } else {
1838 "".to_string()
1839 }
1840 );
1841 }
1842 }
1843
1844 Instruction::CallInterface { func, async_ } => {
1846 let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
1847 let get_component_state = self.intrinsic(Intrinsic::Component(
1848 ComponentIntrinsic::GetOrCreateAsyncState,
1849 ));
1850 let start_current_task_fn = self.intrinsic(Intrinsic::AsyncTask(
1851 AsyncTaskIntrinsic::CreateNewCurrentTask,
1852 ));
1853 let current_task_get_fn =
1854 self.intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::GetCurrentTask));
1855
1856 let get_global_current_task_meta_fn =
1859 self.intrinsic(Intrinsic::GetGlobalCurrentTaskMetaFn);
1860
1861 uwriteln!(
1862 self.src,
1863 "{debug_log_fn}('{prefix} [Instruction::CallInterface] ({async_}, @ enter)');",
1864 prefix = self.tracing_prefix,
1865 async_ = async_.then_some("async").unwrap_or("sync"),
1866 );
1867
1868 let (callee_fn_js, callee_args_js) = if self.callee_resource_dynamic {
1870 (
1871 format!("{}.{}", operands[0], self.callee),
1872 operands[1..].join(", "),
1873 )
1874 } else {
1875 (self.callee.into(), operands.join(", "))
1876 };
1877
1878 uwriteln!(self.src, "const hostProvided = true;");
1879
1880 let (component_idx_expr, callback_fn_name_expr, get_callback_fn_expr) =
1882 if let Some(state) = &self.component_state {
1883 let ComponentStateJsExprs {
1884 component_idx,
1885 callback_fn_name,
1886 get_callback_fn,
1887 ..
1888 } = state.get_js_exprs();
1889 (component_idx, callback_fn_name, get_callback_fn)
1890 } else {
1891 ("-1".into(), "null".into(), "() => null".into())
1892 };
1893
1894 uwriteln!(
1909 self.src,
1910 r#"
1911 let parentTask;
1912 let task;
1913 let subtask;
1914
1915 const createTask = () => {{
1916 const results = {start_current_task_fn}({{
1917 componentIdx: -1,
1918 isAsync: {is_async},
1919 entryFnName: '{fn_name}',
1920 getCallbackFn: {get_callback_fn_expr},
1921 callbackFnName: {callback_fn_name_expr},
1922 errHandling: '{err_handling}',
1923 callingWasmExport: false,
1924 }});
1925 task = results[0];
1926 }};
1927
1928 taskCreation: {{
1929 parentTask = {current_task_get_fn}(
1930 {component_idx_expr},
1931 {get_global_current_task_meta_fn}({component_idx_expr})?.taskID,
1932 )?.task;
1933
1934 if (!parentTask) {{
1935 createTask();
1936 break taskCreation;
1937 }}
1938
1939 createTask();
1940
1941 if (hostProvided) {{
1942 subtask = parentTask.getLatestSubtask();
1943 if (!subtask) {{
1944 throw new Error(`Missing subtask (in parent task [${{parentTask.id()}}]) for host import, has the import been lowered? (ensure asyncImports are set properly)`);
1945 }}
1946 task.setParentSubtask(subtask);
1947 }}
1948 }}
1949 "#,
1950 is_async = self.is_async,
1951 fn_name = self.callee,
1952 err_handling = self.err.to_js_string(),
1953 );
1954
1955 let is_async = self.requires_async_porcelain || *async_;
1956
1957 let fn_wasm_result_count = if func.result.is_none() { 0 } else { 1 };
1960
1961 if is_async {
1963 uwriteln!(
1964 self.src,
1965 r#"
1966 const started = await task.enter({{ isHost: hostProvided }});
1967 if (!started) {{
1968 {debug_log_fn}('[Instruction::CallInterface] failed to enter task', {{
1969 taskID: task.id(),
1970 subtaskID: task.getParentSubtask()?.id(),
1971 }});
1972 throw new Error("failed to enter task");
1973 }}
1974 "#,
1975 );
1976 } else {
1977 uwriteln!(self.src, "const started = task.enterSync();",);
1978 }
1979
1980 let (call_prefix, call_wrapper, call_err_cleanup) = if is_async
1982 || self.requires_async_porcelain
1983 {
1984 (
1985 "await ",
1986 Intrinsic::WithGlobalCurrentTaskMetaFnAsync.name(),
1987 format!(
1988 r#"
1989 {debug_log_fn}('[Instruction::CallInterface] error during async call', {{
1990 taskID: task.id(),
1991 subtaskID: task.getParentSubtask()?.id(),
1992 err,
1993 }});
1994 {get_component_state}({component_idx_expr}).markTrapped(err);
1995 task.setErrored(err);
1996 task.reject(err);
1997 task.exit();
1998 return task.completionPromise();
1999 "#
2000 ),
2001 )
2002 } else {
2003 (
2004 "",
2005 Intrinsic::WithGlobalCurrentTaskMetaFn.name(),
2006 format!(
2007 r#"
2008 {debug_log_fn}('[Instruction::CallInterface] error during sync call', {{
2009 taskID: task.id(),
2010 subtaskID: task.getParentSubtask()?.id(),
2011 err,
2012 }});
2013 {get_component_state}({component_idx_expr}).markTrapped(err);
2014 task.setErrored(err);
2015 task.reject(err);
2016 task.exit();
2017 throw err;
2018 "#
2019 ),
2020 )
2021 };
2022
2023 let call = format!(
2024 r#"{call_prefix} {call_wrapper}({{
2025 componentIdx: task.componentIdx(),
2026 taskID: task.id(),
2027 fn: () => {callee_fn_js}({callee_args_js}),
2028 }})
2029 "#,
2030 );
2031
2032 match self.err {
2033 ErrHandling::None | ErrHandling::ThrowResultErr => {
2036 let (vars_init, assignment_lhs) = self.generate_result_assignment_lhs(
2037 fn_wasm_result_count,
2038 results,
2039 is_async,
2040 );
2041 uwriteln!(
2042 self.src,
2043 r#"
2044 {vars_init}
2045 try {{
2046 {assignment_lhs}{call};
2047 }} catch (err) {{
2048 {call_err_cleanup}
2049 }}
2050 "#
2051 );
2052 }
2053 ErrHandling::ResultCatchHandler => {
2056 let (wrapped_ok, wrapped_err) = unambiguous_result_wrapper_tags(
2057 self.resolve,
2058 get_thrown_type(self.resolve, func.result).unwrap().0,
2059 );
2060 let host_ret = format!("hostRet{}", self.tmp());
2061 let wrapper_test = match (wrapped_ok, wrapped_err) {
2062 (true, true) => {
2063 format!("({host_ret}.tag === 'ok' || {host_ret}.tag === 'err')")
2064 }
2065 (true, false) => format!("{host_ret}.tag === 'ok'"),
2066 (false, true) => format!("{host_ret}.tag === 'err'"),
2067 (false, false) => "false".to_string(),
2068 };
2069 let err_payload = if let (_, Some(Type::Id(err_ty))) =
2072 get_thrown_type(self.resolve, func.result).unwrap()
2073 {
2074 match &self.resolve.types[*err_ty].kind {
2075 TypeDefKind::Type(Type::String) => {
2076 self.intrinsic(Intrinsic::GetErrorPayloadString)
2077 }
2078 _ => self.intrinsic(Intrinsic::GetErrorPayload),
2079 }
2080 } else {
2081 self.intrinsic(Intrinsic::GetErrorPayload)
2082 };
2083 uwriteln!(
2084 self.src,
2085 r#"
2086 let ret;
2087 try {{
2088 const {host_ret} = {call};
2089 ret = {host_ret} !== null && typeof {host_ret} === 'object' && {wrapper_test}
2090 ? {host_ret}
2091 : {{ tag: 'ok', val: {host_ret} }};
2092 }} catch (e) {{
2093 if ({get_component_state}({component_idx_expr}).markTrapped(e)) {{ throw e; }}
2094 ret = {{ tag: 'err', val: {err_payload}(e) }};
2095 }}
2096 "#,
2097 );
2098 results.push("ret".to_string());
2099 }
2100 }
2101
2102 if self.tracing_enabled {
2103 let prefix = self.tracing_prefix;
2104 let to_result_string =
2105 self.intrinsic(Intrinsic::Conversion(ConversionIntrinsic::ToResultString));
2106 uwriteln!(
2107 self.src,
2108 "console.error(`{prefix} return {}`);",
2109 if fn_wasm_result_count > 0 || !results.is_empty() {
2110 format!("result=${{{to_result_string}(ret)}}")
2111 } else {
2112 "".to_string()
2113 }
2114 );
2115 }
2116
2117 if self.clear_resource_borrows {
2125 let symbol_resource_handle = self.intrinsic(Intrinsic::SymbolResourceHandle);
2126 let cur_resource_borrows =
2127 self.intrinsic(Intrinsic::Resource(ResourceIntrinsic::CurResourceBorrows));
2128 uwriteln!(
2129 self.src,
2130 "for (const entry of {cur_resource_borrows}) {{
2131 const rsc = entry.rsc ?? entry;
2132 if (entry.drop) {{
2133 if (rsc[{symbol_resource_handle}]) {{
2134 entry.drop(rsc[{symbol_resource_handle}]);
2135 }}
2136 }}
2137 rsc[{symbol_resource_handle}] = undefined;
2138 }}
2139 {cur_resource_borrows} = [];"
2140 );
2141 self.clear_resource_borrows = false;
2142 }
2143 }
2144
2145 Instruction::Return {
2146 func,
2147 amt: stack_value_count,
2148 } => {
2149 let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
2150 uwriteln!(
2151 self.src,
2152 "{debug_log_fn}('{prefix} [Instruction::Return]', {{
2153 funcName: '{func_name}',
2154 paramCount: {stack_value_count},
2155 async: {is_async},
2156 postReturn: {post_return_present}
2157 }});",
2158 func_name = func.name,
2159 post_return_present = self.post_return.is_some(),
2160 is_async = self.is_async,
2161 prefix = self.tracing_prefix,
2162 );
2163
2164 let component_idx_expr = if let Some(state) = &self.component_state {
2166 let ComponentStateJsExprs { component_idx, .. } = state.get_js_exprs();
2167 component_idx
2168 } else {
2169 "-1".into()
2170 };
2171
2172 let get_or_create_async_state_fn = self.intrinsic(Intrinsic::Component(
2175 ComponentIntrinsic::GetOrCreateAsyncState,
2176 ));
2177 let gen_post_return_js =
2178 |(post_return_call, ret_stmt): (String, Option<String>)| {
2179 format!(
2180 r#"
2181 let cstate = {get_or_create_async_state_fn}({component_idx_expr});
2182 cstate.mayLeave = false;
2183 {post_return_call}
2184 cstate.mayLeave = true;
2185 task.exit();
2186 {ret_stmt}
2187 "#,
2188 ret_stmt = ret_stmt.unwrap_or_default(),
2189 )
2190 };
2191
2192 assert!(!self.is_async, "async functions should use AsyncTaskReturn");
2193
2194 match stack_value_count {
2200 0 => {
2202 uwriteln!(self.src, "task.resolve([ret]);");
2203 if let Some(f) = &self.post_return {
2204 uwriteln!(
2205 self.src,
2206 "{post_return_js}",
2207 post_return_js = gen_post_return_js((format!("{f}();"), None)),
2208 );
2209 } else {
2210 uwriteln!(self.src, "task.exit();");
2211 }
2212 }
2213
2214 1 if self.err == ErrHandling::ThrowResultErr => {
2216 let op = &operands[0];
2217 let throw_err = if self.no_component_error_wrapping {
2218 "throw retCopy.val;".to_string()
2219 } else {
2220 let component_err = self.intrinsic(Intrinsic::ComponentError);
2221 format!("throw new {component_err}(retCopy.val);")
2222 };
2223
2224 uwriteln!(self.src, "const retCopy = {op};");
2225 uwriteln!(self.src, "task.resolve([retCopy.val]);");
2226
2227 if let Some(f) = &self.post_return {
2228 uwriteln!(
2229 self.src,
2230 "{}",
2231 gen_post_return_js((format!("{f}(ret);"), None))
2232 );
2233 } else {
2234 uwriteln!(self.src, "task.exit();");
2235 }
2236
2237 uwriteln!(
2238 self.src,
2239 r#"
2240 if (typeof retCopy === 'object' && retCopy.tag === 'err') {{
2241 {throw_err}
2242 }}
2243 return retCopy.val;
2244 "#
2245 );
2246 }
2247
2248 stack_value_count => {
2250 let ret_val = match stack_value_count {
2251 0 => unreachable!(
2252 "unexpectedly zero return values for synchronous return"
2253 ),
2254 1 => operands[0].to_string(),
2255 _ => format!("[{}]", operands.join(", ")),
2256 };
2257 let return_val = if self.wrap_async_future_result {
2261 format!("{{ value: await {ret_val} }}")
2262 } else {
2263 ret_val.clone()
2264 };
2265
2266 uwriteln!(self.src, "task.resolve([{ret_val}]);");
2267
2268 if let Some(post_return_fn) = self.post_return {
2270 uwriteln!(self.src, "const retCopy = {ret_val};");
2275 let post_return_val = if self.wrap_async_future_result {
2276 "{ value: await retCopy }"
2277 } else {
2278 "retCopy"
2279 };
2280
2281 let post_return_js = gen_post_return_js((
2284 format!("{post_return_fn}(ret);"),
2285 Some([format!("return {post_return_val};")].join("\n")),
2286 ));
2287 uwriteln!(self.src, "{post_return_js}");
2288 } else {
2289 uwriteln!(self.src, "task.exit();");
2290 uwriteln!(self.src, "return {return_val};")
2291 }
2292 }
2293 }
2294 }
2295
2296 Instruction::I32Load { offset } => self.load("getInt32", *offset, operands, results),
2297
2298 Instruction::I64Load { offset } => self.load("getBigInt64", *offset, operands, results),
2299
2300 Instruction::F32Load { offset } => self.load("getFloat32", *offset, operands, results),
2301
2302 Instruction::F64Load { offset } => self.load("getFloat64", *offset, operands, results),
2303
2304 Instruction::I32Load8U { offset } => self.load("getUint8", *offset, operands, results),
2305
2306 Instruction::I32Load8S { offset } => self.load("getInt8", *offset, operands, results),
2307
2308 Instruction::I32Load16U { offset } => {
2309 self.load("getUint16", *offset, operands, results)
2310 }
2311
2312 Instruction::I32Load16S { offset } => self.load("getInt16", *offset, operands, results),
2313
2314 Instruction::I32Store { offset } => self.store("setInt32", *offset, operands),
2315
2316 Instruction::I64Store { offset } => self.store("setBigInt64", *offset, operands),
2317
2318 Instruction::F32Store { offset } => self.store("setFloat32", *offset, operands),
2319
2320 Instruction::F64Store { offset } => self.store("setFloat64", *offset, operands),
2321
2322 Instruction::I32Store8 { offset } => self.store("setInt8", *offset, operands),
2323
2324 Instruction::I32Store16 { offset } => self.store("setInt16", *offset, operands),
2325
2326 Instruction::LengthStore { offset } => self.store("setUint32", *offset, operands),
2327
2328 Instruction::LengthLoad { offset } => {
2329 self.load("getUint32", *offset, operands, results)
2330 }
2331
2332 Instruction::PointerStore { offset } => self.store("setUint32", *offset, operands),
2333
2334 Instruction::PointerLoad { offset } => {
2335 self.load("getUint32", *offset, operands, results)
2336 }
2337
2338 Instruction::Malloc { size, align, .. } => {
2339 let tmp = self.tmp();
2340 let realloc = self.realloc.as_ref().unwrap();
2341 let ptr = format!("ptr{tmp}");
2342 uwriteln!(
2343 self.src,
2344 "var {ptr} = {realloc_call}(0, 0, {align}, {size});",
2345 align = align.align_wasm32(),
2346 realloc_call = if self.is_async {
2347 format!("await {realloc}")
2348 } else {
2349 realloc.to_string()
2350 },
2351 size = size.size_wasm32()
2352 );
2353 results.push(ptr);
2354 }
2355
2356 Instruction::HandleLift { handle, .. } => {
2357 let (Handle::Own(ty) | Handle::Borrow(ty)) = handle;
2358 let resource_ty = &crate::dealias(self.resolve, *ty);
2359 let ResourceTable { imported, data } = &self.resource_map[resource_ty];
2360
2361 let is_own = matches!(handle, Handle::Own(_));
2362 let rsc = format!("rsc{}", self.tmp());
2363 let handle = format!("handle{}", self.tmp());
2364 uwriteln!(self.src, "var {handle} = {};", &operands[0]);
2365
2366 match data {
2367 ResourceData::Host {
2368 tid,
2369 rid,
2370 local_name,
2371 dtor_name,
2372 } => {
2373 let tid = tid.as_u32();
2374 let rid = rid.as_u32();
2375 let symbol_dispose = self.intrinsic(Intrinsic::SymbolDispose);
2376 let rsc_table_remove = self
2377 .intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableRemove));
2378 let rsc_flag = self
2379 .intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableFlag));
2380 if !imported {
2381 let symbol_resource_handle =
2382 self.intrinsic(Intrinsic::SymbolResourceHandle);
2383
2384 uwriteln!(
2385 self.src,
2386 "var {rsc} = new.target === {local_name} ? this : Object.create({local_name}.prototype);"
2387 );
2388
2389 if is_own {
2390 let empty_func = self
2392 .intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::EmptyFunc));
2393 uwriteln!(self.src,
2394 "Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
2395 finalizationRegistry{tid}.register({rsc}, {handle}, {rsc});");
2396 let dtor_call = dtor_name
2397 .as_ref()
2398 .map(|dtor| format!("{dtor}(handleEntry.rep);"))
2399 .unwrap_or_default();
2400 uwriteln!(
2407 self.src,
2408 "Object.defineProperty({rsc}, {symbol_dispose}, {{ writable: true, value: function () {{
2409 finalizationRegistry{tid}.unregister({rsc});
2410 const handleEntry = {rsc_table_remove}(handleTable{tid}, {handle});
2411 {rsc}[{symbol_dispose}] = {empty_func};
2412 {rsc}[{symbol_resource_handle}] = undefined;
2413 {dtor_call}
2414 }}}});"
2415 );
2416 } else {
2417 uwriteln!(
2419 self.src,
2420 "Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});"
2421 );
2422 }
2423 } else {
2424 let rep = format!("rep{}", self.tmp());
2425 let symbol_resource_rep = self.intrinsic(Intrinsic::SymbolResourceRep);
2428 let symbol_resource_handle =
2429 self.intrinsic(Intrinsic::SymbolResourceHandle);
2430
2431 uwriteln!(
2432 self.src,
2433 r#"
2434 var {rep} = handleTable{tid}[({handle} << 1) + 1] & ~{rsc_flag};
2435 var {rsc} = captureTable{rid}.get({rep});
2436 if (!{rsc}) {{
2437 {rsc} = Object.create({local_name}.prototype);
2438 Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
2439 Object.defineProperty({rsc}, {symbol_resource_rep}, {{ writable: true, value: {rep} }});
2440 }}
2441 "#,
2442 );
2443
2444 if is_own {
2445 uwriteln!(
2447 self.src,
2448 "else {{
2449 captureTable{rid}.delete({rep});
2450 }}
2451 {rsc_table_remove}(handleTable{tid}, {handle});"
2452 );
2453 }
2454 }
2455
2456 if !is_own {
2458 let cur_resource_borrows = self.intrinsic(Intrinsic::Resource(
2459 ResourceIntrinsic::CurResourceBorrows,
2460 ));
2461 uwriteln!(self.src, "{cur_resource_borrows}.push({rsc});");
2462 self.clear_resource_borrows = true;
2463 }
2464 }
2465
2466 ResourceData::Guest {
2467 resource_name,
2468 prefix,
2469 extra,
2470 } => {
2471 assert!(
2472 extra.is_none(),
2473 "plain resource handles do not carry extra data"
2474 );
2475
2476 let symbol_resource_handle =
2477 self.intrinsic(Intrinsic::SymbolResourceHandle);
2478 let prefix = prefix.as_deref().unwrap_or("");
2479 let lower_camel = resource_name.to_lower_camel_case();
2480
2481 if !imported {
2482 if is_own {
2483 uwriteln!(
2484 self.src,
2485 "var {rsc} = repTable.get($resource_{prefix}rep${lower_camel}({handle})).rep;"
2486 );
2487 uwrite!(
2488 self.src,
2489 r#"
2490 repTable.delete({handle});
2491 delete {rsc}[{symbol_resource_handle}];
2492 finalizationRegistry_export${prefix}{lower_camel}.unregister({rsc});
2493 "#
2494 );
2495 } else {
2496 uwriteln!(self.src, "var {rsc} = repTable.get({handle}).rep;");
2497 }
2498 } else {
2499 let upper_camel = resource_name.to_upper_camel_case();
2500
2501 uwrite!(
2502 self.src,
2503 r#"
2504 var {rsc} = new.target === import_{prefix}{upper_camel} ? this : Object.create(import_{prefix}{upper_camel}.prototype);
2505 Object.defineProperty({rsc}, {symbol_resource_handle}, {{ writable: true, value: {handle} }});
2506 "#
2507 );
2508
2509 uwriteln!(
2510 self.src,
2511 "finalizationRegistry_import${prefix}{lower_camel}.register({rsc}, {handle}, {rsc});",
2512 );
2513
2514 if !is_own {
2515 let cur_resource_borrows = self.intrinsic(Intrinsic::Resource(
2516 ResourceIntrinsic::CurResourceBorrows,
2517 ));
2518 uwriteln!(
2519 self.src,
2520 "{cur_resource_borrows}.push({{ rsc: {rsc}, drop: $resource_import${prefix}drop${lower_camel} }});"
2521 );
2522 self.clear_resource_borrows = true;
2523 }
2524 }
2525 }
2526 }
2527 results.push(rsc);
2528 }
2529
2530 Instruction::HandleLower { handle, name, .. } => {
2531 let (Handle::Own(ty) | Handle::Borrow(ty)) = handle;
2532 let is_own = matches!(handle, Handle::Own(_));
2533 let ResourceTable { imported, data } =
2534 &self.resource_map[&crate::dealias(self.resolve, *ty)];
2535
2536 let class_name = name.to_upper_camel_case();
2537 let handle = format!("handle{}", self.tmp());
2538 let symbol_resource_handle = self.intrinsic(Intrinsic::SymbolResourceHandle);
2539 let symbol_dispose = self.intrinsic(Intrinsic::SymbolDispose);
2540 let op = &operands[0];
2541
2542 match data {
2543 ResourceData::Host {
2544 tid,
2545 rid,
2546 local_name,
2547 ..
2548 } => {
2549 let tid = tid.as_u32();
2550 let rid = rid.as_u32();
2551
2552 match (imported, is_own) {
2553 (_imported @ false, _owned @ true) => {
2555 let empty_func = self
2556 .intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::EmptyFunc));
2557 uwriteln!(
2558 self.src,
2559 r#"
2560 var {handle} = {op}[{symbol_resource_handle}];
2561 if (!{handle}) {{
2562 throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
2563 }}
2564 finalizationRegistry{tid}.unregister({op});
2565 {op}[{symbol_dispose}] = {empty_func};
2566 {op}[{symbol_resource_handle}] = undefined;
2567 "#,
2568 );
2569 }
2570
2571 (_imported @ false, _owned @ false) => {
2573 let rsc_flag = self.intrinsic(Intrinsic::Resource(
2578 ResourceIntrinsic::ResourceTableFlag,
2579 ));
2580 let own_handle = format!("handle{}", self.tmp());
2581 uwriteln!(
2582 self.src,
2583 r#"
2584 var {own_handle} = {op}[{symbol_resource_handle}];
2585 if (!{own_handle} || (handleTable{tid}[({own_handle} << 1) + 1] & {rsc_flag}) === 0) {{
2586 throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
2587 }}
2588 var {handle} = handleTable{tid}[({own_handle} << 1) + 1] & ~{rsc_flag};
2589 "#,
2590 );
2591 }
2592
2593 (_imported @ true, _owned @ true) => {
2595 let symbol_resource_rep =
2604 self.intrinsic(Intrinsic::SymbolResourceRep);
2605 let create_own_fn = self.intrinsic(Intrinsic::Resource(
2606 ResourceIntrinsic::ResourceTableCreateOwn,
2607 ));
2608
2609 uwriteln!(
2610 self.src,
2611 r#"
2612 if (!({op} instanceof {local_name})) {{
2613 throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
2614 }}
2615 var {handle} = {op}[{symbol_resource_handle}];
2616 if (!{handle}) {{
2617 const rep = {op}[{symbol_resource_rep}] || ++captureCnt{rid};
2618 captureTable{rid}.set(rep, {op});
2619 {handle} = {create_own_fn}(handleTable{tid}, rep);
2620 }}
2621 "#
2622 );
2623 }
2624
2625 (_imported @ true, _owned @ false) => {
2627 let symbol_resource_rep =
2636 self.intrinsic(Intrinsic::SymbolResourceRep);
2637 let scope_id = self.intrinsic(Intrinsic::ScopeId);
2638 let create_borrow_fn = self.intrinsic(Intrinsic::Resource(
2639 ResourceIntrinsic::ResourceTableCreateBorrow,
2640 ));
2641
2642 uwriteln!(
2643 self.src,
2644 r#"
2645 if (!({op} instanceof {local_name})) {{
2646 throw new TypeError('Resource error: Not a valid \"{class_name}\" resource.');
2647 }}
2648 var {handle} = {op}[{symbol_resource_handle}];
2649 if (!{handle}) {{
2650 const rep = {op}[{symbol_resource_rep}] || ++captureCnt{rid};
2651 captureTable{rid}.set(rep, {op});
2652 {handle} = {create_borrow_fn}(handleTable{tid}, rep, {scope_id});
2653 }}
2654 "#
2655 );
2656 }
2657 }
2658 }
2659
2660 ResourceData::Guest {
2661 resource_name,
2662 prefix,
2663 extra,
2664 } => {
2665 assert!(
2666 extra.is_none(),
2667 "plain resource handles do not carry extra data"
2668 );
2669
2670 let upper_camel = resource_name.to_upper_camel_case();
2671 let lower_camel = resource_name.to_lower_camel_case();
2672 let prefix = prefix.as_deref().unwrap_or("");
2673
2674 let symbol_resource_handle =
2675 self.intrinsic(Intrinsic::SymbolResourceHandle);
2676
2677 let tmp = self.tmp();
2678 match (imported, is_own) {
2679 (_imported @ true, _owned) => {
2681 uwrite!(
2682 self.src,
2683 r#"
2684 var {handle} = {op}[{symbol_resource_handle}];
2685 finalizationRegistry_import${prefix}{lower_camel}.unregister({op});
2686 "#
2687 );
2688 }
2689
2690 (_imported @ false, _owned @ false) => {
2692 let local_rep = format!("localRep{tmp}");
2693 uwriteln!(
2694 self.src,
2695 r#"
2696 if (!({op} instanceof {upper_camel})) {{
2697 throw new TypeError('Resource error: Not a valid \"{upper_camel}\" resource.');
2698 }}
2699 let {handle} = {op}[{symbol_resource_handle}];
2700 if ({handle} === undefined) {{
2701 var {local_rep} = repCnt++;
2702 repTable.set({local_rep}, {{ rep: {op}, own: false }});
2703 {op}[{symbol_resource_handle}] = {local_rep};
2704 }}
2705 "#
2706 );
2707 }
2708
2709 (_imported @ false, _owned @ true) => {
2711 let local_rep = format!("localRep{tmp}");
2712 uwriteln!(
2713 self.src,
2714 r#"
2715 if (!({op} instanceof {upper_camel})) {{
2716 throw new TypeError('Resource error: Not a valid \"{upper_camel}\" resource.');
2717 }}
2718 let {handle} = {op}[{symbol_resource_handle}];
2719 if ({handle} === undefined) {{
2720 var {local_rep} = repCnt++;
2721 repTable.set({local_rep}, {{ rep: {op}, own: true }});
2722 {handle} = $resource_{prefix}new${lower_camel}({local_rep});
2723 {op}[{symbol_resource_handle}] = {handle};
2724 finalizationRegistry_export${prefix}{lower_camel}.register({op}, {handle}, {op});
2725 }}
2726 "#
2727 );
2728 }
2729 }
2730 }
2731 }
2732 results.push(handle);
2733 }
2734
2735 Instruction::DropHandle { ty } => {
2736 let _ = ty;
2737 todo!("[Instruction::DropHandle] not yet implemented")
2738 }
2739
2740 Instruction::Flush { amt } => {
2741 for item in operands.iter().take(*amt) {
2742 results.push(item.clone());
2743 }
2744 }
2745
2746 Instruction::ErrorContextLift => {
2747 let item = operands
2748 .first()
2749 .expect("unexpectedly missing ErrorContextLift arg");
2750 results.push(item.clone());
2751 }
2752
2753 Instruction::ErrorContextLower => {
2754 let item = operands
2755 .first()
2756 .expect("unexpectedly missing ErrorContextLower arg");
2757 results.push(item.clone());
2758 }
2759
2760 Instruction::FutureLower { ty, .. } => {
2761 let future_arg = operands
2762 .first()
2763 .expect("unexpectedly missing FutureLower arg");
2764
2765 if self.is_async && self.for_import.unwrap_or_default() {
2776 results.push(future_arg.clone());
2777 return;
2778 }
2779
2780 let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
2781 let get_or_create_async_state_fn = self.intrinsic(Intrinsic::Component(
2782 ComponentIntrinsic::GetOrCreateAsyncState,
2783 ));
2784 let gen_future_host_inject_fn = self.intrinsic(Intrinsic::AsyncFuture(
2785 AsyncFutureIntrinsic::GenFutureHostInjectFn,
2786 ));
2787 let is_future_lowerable_object_fn = self.intrinsic(Intrinsic::AsyncFuture(
2788 AsyncFutureIntrinsic::IsFutureLowerableObject,
2789 ));
2790 let nested_future_symbol = self.intrinsic(Intrinsic::AsyncFuture(
2791 AsyncFutureIntrinsic::NestedFutureSymbol,
2792 ));
2793
2794 let type_id = &crate::dealias(self.resolve, *ty);
2796 let ResourceTable {
2797 imported: true,
2798 data:
2799 ResourceData::Guest {
2800 extra:
2801 Some(ResourceExtraData::Future {
2802 table_idx: future_table_idx_ty,
2803 nesting_level,
2804 elem_ty,
2805 }),
2806 ..
2807 },
2808 } = self
2809 .resource_map
2810 .get(type_id)
2811 .expect("missing resource mapping for future lower")
2812 else {
2813 unreachable!("invalid resource table observed during future lower");
2814 };
2815 let future_table_idx = future_table_idx_ty.as_u32();
2816
2817 let (
2819 payload_type_name_js,
2820 lift_fn_js,
2821 lower_fn_js,
2822 payload_is_none,
2823 payload_is_numeric,
2824 payload_is_borrow,
2825 payload_is_async_value,
2826 payload_size32_js,
2827 payload_align32_js,
2828 payload_flat_count_js,
2829 ) = match elem_ty {
2830 Some(PayloadTypeMetadata {
2831 ty: _,
2832 iface_ty,
2833 lift_js_expr,
2834 lower_js_expr,
2835 size32,
2836 align32,
2837 flat_count,
2838 }) => (
2839 format!("'{iface_ty:?}'"),
2840 lift_js_expr.as_str(),
2841 lower_js_expr.as_str(),
2842 "false",
2843 format!(
2844 "{}",
2845 matches!(
2846 iface_ty,
2847 InterfaceType::U8
2848 | InterfaceType::U16
2849 | InterfaceType::U32
2850 | InterfaceType::U64
2851 | InterfaceType::S8
2852 | InterfaceType::S16
2853 | InterfaceType::S32
2854 | InterfaceType::S64
2855 | InterfaceType::Float32
2856 | InterfaceType::Float64
2857 )
2858 ),
2859 format!("{}", matches!(iface_ty, InterfaceType::Borrow(_))),
2860 format!(
2861 "{}",
2862 matches!(
2863 iface_ty,
2864 InterfaceType::Stream(_) | InterfaceType::Future(_)
2865 )
2866 ),
2867 size32.to_string(),
2868 align32.to_string(),
2869 flat_count.unwrap_or(0).to_string(),
2870 ),
2871 None => (
2872 "null".into(),
2873 "() => {{ throw new Error('no lift fn'); }}",
2874 "() => {{ throw new Error('no lower fn'); }}",
2875 "true",
2876 "false".into(),
2877 "false".into(),
2878 "false".into(),
2879 "null".into(),
2880 "null".into(),
2881 "0".into(),
2882 ),
2883 };
2884
2885 let tmp = self.tmp();
2886 let lowered_future_waitable_idx = format!("futureWaitableIdx{tmp}");
2887
2888 let (component_idx_expr, get_realloc_fn_expr) =
2889 if let Some(state) = &self.component_state {
2890 let ComponentStateJsExprs {
2891 component_idx,
2892 get_realloc_fn,
2893 ..
2894 } = state.get_js_exprs();
2895 (component_idx, get_realloc_fn)
2896 } else {
2897 ("-1".into(), "undefined".into())
2898 };
2899
2900 uwriteln!(
2901 self.src,
2902 r#"
2903 if (!{is_future_lowerable_object_fn}({future_arg})) {{
2904 {debug_log_fn}('[Instruction::FutureLower] object is not a Promise/Thenable', {{ {future_arg} }});
2905 throw new Error('unrecognized future object (not Promise/Thenable)');
2906 }}
2907
2908 const cstate{tmp} = {get_or_create_async_state_fn}({component_idx_expr});
2909 if (!cstate{tmp}) {{
2910 throw new Error(`missing component state for component [{component_idx_expr}]`);
2911 }}
2912
2913 // TODO(feat): facilitate non utf8 string encoding for lowered futures
2914 const stringEncoding = 'utf8';
2915
2916 let outermostReadEnd{tmp};
2917 let futuresList{tmp} = [];
2918 let future{tmp} = {future_arg};
2919 let nextFuture{tmp};
2920 let openedCount = -1;
2921 // Lower exactly this future layer. If its payload is another
2922 // future, elemMeta.lowerFn recursively creates that endpoint.
2923 let futureNestingLevel{tmp} = 0;
2924
2925 while (futureNestingLevel{tmp} >= 0) {{
2926 const {{
2927 writeEnd,
2928 writeEndWaitableIdx,
2929 readEnd,
2930 readEndWaitableIdx
2931 }} = cstate{tmp}.createFuture({{
2932 tableIdx: {future_table_idx},
2933 elemMeta: {{
2934 liftFn: {lift_fn_js},
2935 lowerFn: {lower_fn_js},
2936 payloadTypeName: {payload_type_name_js},
2937 isNone: {payload_is_none},
2938 isNumeric: {payload_is_numeric},
2939 isBorrowed: {payload_is_borrow},
2940 isAsyncValue: {payload_is_async_value},
2941 flatCount: {payload_flat_count_js},
2942 align32: {payload_align32_js},
2943 size32: {payload_size32_js},
2944 stringEncoding,
2945 getReallocFn: {get_realloc_fn_expr},
2946 }}
2947 }});
2948
2949 const hostInjectFn = {gen_future_host_inject_fn}({{
2950 promise: future{tmp},
2951 stringEncoding,
2952 hostWriteEnd: writeEnd,
2953 }});
2954 readEnd.setHostInjectFn(hostInjectFn);
2955
2956 const meta{tmp} = {{
2957 isInnermost: futureNestingLevel{tmp} === {nesting_level},
2958 level: futureNestingLevel{tmp},
2959 }};
2960
2961 const innerFuture = future{tmp};
2962 future{tmp} = {{ }};
2963 future{tmp}[{nested_future_symbol}] = meta{tmp};
2964 future{tmp}.readEndWaitableIdx = readEndWaitableIdx;
2965 future{tmp}.writeEndWaitableIdx = writeEndWaitableIdx;
2966 future{tmp}.futureTableIdx = {future_table_idx};
2967 future{tmp}.componentIdx = {component_idx_expr};
2968 future{tmp}.then = async (resolve, reject) => {{
2969 let p;
2970 if (openedCount === {nesting_level}) {{
2971 p = innerFuture;
2972 }} else {{
2973 openedCount++;
2974 p = futuresList{tmp}[futuresList{tmp}.length - (openedCount + 1)];
2975 }}
2976
2977 try {{
2978 resolve(await p);
2979 }} catch (err) {{
2980 reject(err);
2981 }}
2982 }};
2983
2984 outermostReadEnd{tmp} = readEnd;
2985
2986 futuresList{tmp}.push(future{tmp});
2987 futureNestingLevel{tmp}--;
2988 }}
2989
2990 const readEnd{tmp} = outermostReadEnd{tmp};
2991
2992 // TODO: need to *lower* the internal future???
2993
2994 const {lowered_future_waitable_idx} = readEnd{tmp}.waitableIdx();
2995 "#
2996 );
2997
2998 results.push(lowered_future_waitable_idx);
2999 }
3000
3001 Instruction::FutureLift { payload, ty } => {
3002 let future_new_from_lift_fn = self.intrinsic(Intrinsic::AsyncFuture(
3003 AsyncFutureIntrinsic::FutureNewFromLift,
3004 ));
3005
3006 let type_id = &crate::dealias(self.resolve, *ty);
3008 let ResourceTable {
3009 imported: true,
3010 data:
3011 ResourceData::Guest {
3012 extra:
3013 Some(ResourceExtraData::Future {
3014 table_idx: future_table_idx_ty,
3015 elem_ty: future_element_ty,
3016 ..
3017 }),
3018 ..
3019 },
3020 } = self
3021 .resource_map
3022 .get(type_id)
3023 .expect("missing resource mapping for future lift")
3024 else {
3025 unreachable!("invalid resource table observed during future lift");
3026 };
3027
3028 let (lift_fn_js, lower_fn_js) = match future_element_ty {
3030 Some(PayloadTypeMetadata {
3031 ty,
3032 lift_js_expr,
3033 lower_js_expr,
3034 ..
3035 }) => {
3036 assert_eq!(Some(*ty), **payload, "future element type mismatch");
3037 (lift_js_expr.to_string(), lower_js_expr.to_string())
3038 }
3039 None => (
3040 "() => {{ throw new Error('no lift fn'); }}".into(),
3041 "() => {{ throw new Error('no lower fn'); }}".into(),
3042 ),
3043 };
3044 if let Some(PayloadTypeMetadata { ty, .. }) = future_element_ty {
3045 assert_eq!(Some(*ty), **payload, "future element type mismatch");
3046 }
3047
3048 let tmp = self.tmp();
3049 let result_var = format!("futureResult{tmp}");
3050
3051 match (self.is_async, self.for_import.unwrap_or_default()) {
3053 (_is_async @ false, _for_import @ false) | (_is_async, _for_import @ true) => {
3067 let arg_future_end_idx = operands
3069 .first()
3070 .expect("unexpectedly missing future end return arg in FutureLift");
3071
3072 let (payload_ty_size32_js, payload_ty_align32_js) =
3073 if let Some(payload_ty) = payload {
3074 (
3075 self.sizes.size(payload_ty).size_wasm32().to_string(),
3076 self.sizes.align(payload_ty).align_wasm32().to_string(),
3077 )
3078 } else {
3079 ("null".into(), "null".into())
3080 };
3081
3082 let future_table_idx = future_table_idx_ty.as_u32();
3083
3084 let component_idx_expr = if let Some(state) = &self.component_state {
3086 let ComponentStateJsExprs { component_idx, .. } = state.get_js_exprs();
3087 component_idx
3088 } else {
3089 "-1".into()
3090 };
3091
3092 uwriteln!(
3100 self.src,
3101 r#"
3102 const {result_var} = {future_new_from_lift_fn}({{
3103 componentIdx: {component_idx_expr},
3104 futureTableIdx: {future_table_idx},
3105 futureEndWaitableIdx: {arg_future_end_idx},
3106 payloadLiftFn: {lift_fn_js},
3107 payloadLowerFn: {lower_fn_js},
3108 payloadTypeSize32: {payload_ty_size32_js},
3109 payloadTypeAlign32: {payload_ty_align32_js},
3110 }});
3111 "#,
3112 );
3113 }
3114
3115 _ => {}
3117 }
3118
3119 results.push(result_var.clone());
3120 }
3121
3122 Instruction::StreamLower { ty, .. } => {
3123 let stream_arg = operands
3124 .first()
3125 .expect("unexpectedly missing StreamLower arg");
3126
3127 if self.is_async && self.for_import.unwrap_or_default() {
3138 results.push(stream_arg.clone());
3139 return;
3140 }
3141
3142 let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
3143 let async_iterator_symbol = self.intrinsic(Intrinsic::SymbolAsyncIterator);
3144 let iterator_symbol = self.intrinsic(Intrinsic::SymbolIterator);
3145 let external_readable_stream_class =
3146 self.intrinsic(Intrinsic::PlatformReadableStreamClass);
3147 let get_or_create_async_state_fn = self.intrinsic(Intrinsic::Component(
3148 ComponentIntrinsic::GetOrCreateAsyncState,
3149 ));
3150 let gen_stream_host_inject_fn = self.intrinsic(Intrinsic::AsyncStream(
3151 AsyncStreamIntrinsic::GenStreamHostInjectFn,
3152 ));
3153 let gen_read_fn_from_lowerable_stream_fn = self.intrinsic(Intrinsic::AsyncStream(
3154 AsyncStreamIntrinsic::GenReadFnFromLowerableStream,
3155 ));
3156
3157 let type_id = &crate::dealias(self.resolve, *ty);
3162 let ResourceTable {
3163 imported: true,
3164 data:
3165 ResourceData::Guest {
3166 extra:
3167 Some(ResourceExtraData::Stream {
3168 table_idx: stream_table_idx_ty,
3169 elem_ty,
3170 }),
3171 ..
3172 },
3173 } = self
3174 .resource_map
3175 .get(type_id)
3176 .expect("missing resource mapping for stream lower")
3177 else {
3178 unreachable!("invalid resource table observed during stream lower");
3179 };
3180
3181 let stream_table_idx = stream_table_idx_ty.as_u32();
3182
3183 let (
3184 payload_type_name_js,
3185 lift_fn_js,
3186 lower_fn_js,
3187 payload_is_none,
3188 payload_is_numeric,
3189 payload_is_borrow,
3190 payload_is_async_value,
3191 payload_size32_js,
3192 payload_align32_js,
3193 payload_flat_count_js,
3194 ) = match elem_ty {
3195 Some(PayloadTypeMetadata {
3196 ty: _,
3197 iface_ty,
3198 lift_js_expr,
3199 lower_js_expr,
3200 size32,
3201 align32,
3202 flat_count,
3203 }) => (
3204 format!("'{iface_ty:?}'"),
3205 lift_js_expr.as_str(),
3206 lower_js_expr.as_str(),
3207 "false",
3208 format!(
3209 "{}",
3210 matches!(
3211 iface_ty,
3212 InterfaceType::U8
3213 | InterfaceType::U16
3214 | InterfaceType::U32
3215 | InterfaceType::U64
3216 | InterfaceType::S8
3217 | InterfaceType::S16
3218 | InterfaceType::S32
3219 | InterfaceType::S64
3220 | InterfaceType::Float32
3221 | InterfaceType::Float64
3222 )
3223 ),
3224 format!("{}", matches!(iface_ty, InterfaceType::Borrow(_))),
3225 format!(
3226 "{}",
3227 matches!(
3228 iface_ty,
3229 InterfaceType::Stream(_) | InterfaceType::Future(_)
3230 )
3231 ),
3232 size32.to_string(),
3233 align32.to_string(),
3234 flat_count.unwrap_or(0).to_string(),
3235 ),
3236 None => (
3237 "null".into(),
3238 "() => {{ throw new Error('no lift fn'); }}",
3239 "() => {{ throw new Error('no lower fn'); }}",
3240 "true",
3241 "false".into(),
3242 "false".into(),
3243 "false".into(),
3244 "null".into(),
3245 "null".into(),
3246 "0".into(),
3247 ),
3248 };
3249
3250 let (component_idx_expr, get_realloc_fn_expr) =
3252 if let Some(state) = &self.component_state {
3253 let ComponentStateJsExprs {
3254 component_idx,
3255 get_realloc_fn,
3256 ..
3257 } = state.get_js_exprs();
3258 (component_idx, get_realloc_fn)
3259 } else {
3260 ("-1".into(), "undefined".into())
3261 };
3262
3263 let tmp = self.tmp();
3264 let lowered_stream_waitable_idx = format!("streamWaitableIdx{tmp}");
3265 uwriteln!(
3266 self.src,
3267 r#"
3268 if (!({async_iterator_symbol} in {stream_arg})
3269 && !({iterator_symbol} in {stream_arg})
3270 && !({stream_arg} instanceof {external_readable_stream_class})) {{
3271 {debug_log_fn}('[Instruction::StreamLower] object with no supported stream protocol', {{ {stream_arg} }});
3272 throw new Error('unrecognized stream object (no supported stream protocol)');
3273 }}
3274
3275 const cstate{tmp} = {get_or_create_async_state_fn}({component_idx_expr});
3276 if (!cstate{tmp}) {{ throw new Error(`missing component state for component [{component_idx_expr}]`); }}
3277
3278 const {{ writeEnd: hostWriteEnd{tmp}, readEnd: readEnd{tmp} }} = cstate{tmp}.createStream({{
3279 tableIdx: {stream_table_idx},
3280 elemMeta: {{
3281 liftFn: {lift_fn_js},
3282 lowerFn: {lower_fn_js},
3283 payloadTypeName: {payload_type_name_js},
3284 isNone: {payload_is_none},
3285 isNumeric: {payload_is_numeric},
3286 isBorrowed: {payload_is_borrow},
3287 isAsyncValue: {payload_is_async_value},
3288 flatCount: {payload_flat_count_js},
3289 align32: {payload_align32_js},
3290 size32: {payload_size32_js},
3291 // TODO(feat): facilitate non utf8 string encoding for lowered streams
3292 stringEncoding: 'utf8',
3293 getReallocFn: {get_realloc_fn_expr},
3294 }},
3295 }});
3296
3297 const readFn{tmp} = {gen_read_fn_from_lowerable_stream_fn}({stream_arg});
3298
3299 const hostInjectFn = {gen_stream_host_inject_fn}({{
3300 readFn: readFn{tmp},
3301 hostWriteEnd: hostWriteEnd{tmp},
3302 readEnd: readEnd{tmp},
3303 }});
3304 readEnd{tmp}.setHostInjectFn(hostInjectFn);
3305 readEnd{tmp}.setHostDropFn(readFn{tmp}.drop);
3306
3307 const {lowered_stream_waitable_idx} = readEnd{tmp}.waitableIdx();
3308 "#
3309 );
3310
3311 results.push(lowered_stream_waitable_idx);
3312 }
3313
3314 Instruction::StreamLift { payload, ty } => {
3315 let stream_new_from_lift_fn = self.intrinsic(Intrinsic::AsyncStream(
3316 AsyncStreamIntrinsic::StreamNewFromLift,
3317 ));
3318
3319 let type_id = &crate::dealias(self.resolve, *ty);
3321 let ResourceTable {
3322 imported: true,
3323 data:
3324 ResourceData::Guest {
3325 extra:
3326 Some(ResourceExtraData::Stream {
3327 table_idx: stream_table_idx_ty,
3328 elem_ty: stream_element_ty,
3329 }),
3330 ..
3331 },
3332 } = self
3333 .resource_map
3334 .get(type_id)
3335 .expect("missing resource mapping for stream lift")
3336 else {
3337 unreachable!("invalid resource table observed during stream lift");
3338 };
3339
3340 let (lift_fn_js, lower_fn_js) = match stream_element_ty {
3342 Some(PayloadTypeMetadata {
3343 ty,
3344 lift_js_expr,
3345 lower_js_expr,
3346 ..
3347 }) => {
3348 assert_eq!(Some(*ty), **payload, "stream element type mismatch");
3349 (lift_js_expr.to_string(), lower_js_expr.to_string())
3350 }
3351 None => (
3352 "() => {{ throw new Error('no lift fn'); }}".into(),
3353 "() => {{ throw new Error('no lower fn'); }}".into(),
3354 ),
3355 };
3356 if let Some(PayloadTypeMetadata { ty, .. }) = stream_element_ty {
3357 assert_eq!(Some(*ty), **payload, "stream element type mismatch");
3358 }
3359
3360 let tmp = self.tmp();
3361 let result_var = format!("streamResult{tmp}");
3362
3363 match (self.is_async, self.for_import.unwrap_or_default()) {
3365 (_is_async @ false, _for_import @ false) | (_is_async, _for_import @ true) => {
3379 let arg_stream_end_idx = operands
3380 .first()
3381 .expect("unexpectedly missing stream end return arg in StreamLift");
3382
3383 let (payload_ty_size32_js, payload_ty_align32_js) =
3384 if let Some(payload_ty) = payload {
3385 (
3386 self.sizes.size(payload_ty).size_wasm32().to_string(),
3387 self.sizes.align(payload_ty).align_wasm32().to_string(),
3388 )
3389 } else {
3390 ("null".into(), "null".into())
3391 };
3392
3393 let stream_table_idx = stream_table_idx_ty.as_u32();
3394
3395 let component_idx_expr = if let Some(state) = &self.component_state {
3397 let ComponentStateJsExprs { component_idx, .. } = state.get_js_exprs();
3398 component_idx
3399 } else {
3400 "-1".into()
3401 };
3402
3403 uwriteln!(
3404 self.src,
3405 r#"
3406 const {result_var} = {stream_new_from_lift_fn}({{
3407 componentIdx: {component_idx_expr},
3408 streamTableIdx: {stream_table_idx},
3409 streamEndWaitableIdx: {arg_stream_end_idx},
3410 payloadLiftFn: {lift_fn_js},
3411 payloadLowerFn: {lower_fn_js},
3412 payloadTypeSize32: {payload_ty_size32_js},
3413 payloadTypeAlign32: {payload_ty_align32_js},
3414 }});
3415 "#,
3416 );
3417 }
3418
3419 _ => {}
3421 };
3422
3423 results.push(result_var.clone());
3425 }
3426
3427 Instruction::AsyncTaskReturn { name, params } => {
3448 let debug_log_fn = self.intrinsic(Intrinsic::DebugLog);
3449 let is_async_js = self.requires_async_porcelain | self.is_async;
3450 let async_driver_loop_fn =
3451 self.intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::DriverLoop));
3452 let get_or_create_async_state_fn = self.intrinsic(Intrinsic::Component(
3453 ComponentIntrinsic::GetOrCreateAsyncState,
3454 ));
3455
3456 let component_idx_expr = if let Some(state) = &self.component_state {
3458 let ComponentStateJsExprs { component_idx, .. } = state.get_js_exprs();
3459 component_idx
3460 } else {
3461 "-1".into()
3462 };
3463
3464 let throw_result_err = if self.no_component_error_wrapping {
3465 "throw taskRes.val;".to_string()
3466 } else {
3467 let component_err = self.intrinsic(Intrinsic::ComponentError);
3468 format!("throw new {component_err}(taskRes.val);")
3469 };
3470
3471 uwriteln!(
3472 self.src,
3473 "{debug_log_fn}('{prefix} [Instruction::AsyncTaskReturn]', {{
3474 funcName: '{name}',
3475 paramCount: {param_count},
3476 componentIdx: {component_idx_expr},
3477 postReturn: {post_return_present},
3478 hostProvided,
3479 }});",
3480 param_count = params.len(),
3481 post_return_present = self.post_return.is_some(),
3482 prefix = self.tracing_prefix,
3483 );
3484
3485 assert!(
3486 self.is_async,
3487 "non-async functions should not be performing async returns (func {name})",
3488 );
3489
3490 uwriteln!(
3522 self.src,
3523 r#"
3524 if (hostProvided) {{
3525 {debug_log_fn}('[Instruction::AsyncTaskReturn] signaling host-provided async return completion', {{
3526 task: task.id(),
3527 subtask: subtask?.id(),
3528 result: ret,
3529 }})
3530 task.resolve([ret]);
3531 task.exit();
3532 return {return_awaited_completion_promise};
3533 }}
3534
3535 const componentState = {get_or_create_async_state_fn}({component_idx_expr});
3536 if (!componentState) {{ throw new Error('failed to lookup current component state'); }}
3537
3538 queueMicrotask(async (resolve, reject) => {{
3539 try {{
3540 {debug_log_fn}("[Instruction::AsyncTaskReturn] starting driver loop", {{
3541 fnName: '{name}',
3542 componentInstanceIdx: {component_idx_expr},
3543 taskID: task.id(),
3544 }});
3545 await {async_driver_loop_fn}({{
3546 componentInstanceIdx: {component_idx_expr},
3547 componentState,
3548 task,
3549 fnName: '{name}',
3550 isAsync: {is_async_js},
3551 callbackResult: ret,
3552 }});
3553 }} catch (err) {{
3554 {debug_log_fn}("[Instruction::AsyncTaskReturn] driver loop call failure", {{ err }});
3555 }}
3556 }});
3557
3558 let taskRes = await task.completionPromise();
3559 if (task.getErrHandling() === 'throw-result-err') {{
3560 if (typeof taskRes !== 'object') {{
3561 return {return_task_res};
3562 }}
3563 if (taskRes.tag === 'err') {{ {throw_result_err} }}
3564 if (taskRes.tag === 'ok') {{ taskRes = taskRes.val; }}
3565 }}
3566
3567 return {return_task_res};
3568 "#,
3569 return_awaited_completion_promise = if self.wrap_async_future_result {
3573 "{ value: await task.completionPromise() }"
3574 } else {
3575 "await task.completionPromise()"
3576 },
3577 return_task_res = if self.wrap_async_future_result {
3580 "{ value: taskRes }"
3581 } else {
3582 "taskRes"
3583 }
3584 );
3585 }
3586
3587 Instruction::GuestDeallocate { .. }
3588 | Instruction::GuestDeallocateString
3589 | Instruction::GuestDeallocateList { .. }
3590 | Instruction::GuestDeallocateVariant { .. } => unimplemented!("Guest deallocation"),
3591
3592 Instruction::GuestDeallocateMap { .. } => unimplemented!("map deallocation support"),
3593 }
3594 }
3595}
3596
3597pub fn as_nullable<'a>(resolve: &'a Resolve, ty: &'a Type) -> Option<&'a Type> {
3602 let id = match ty {
3603 Type::Id(id) => *id,
3604 _ => return None,
3605 };
3606 match &resolve.types[id].kind {
3607 TypeDefKind::Option(t) => {
3623 if !maybe_null(resolve, t) {
3624 Some(t)
3625 } else {
3626 None
3627 }
3628 }
3629 TypeDefKind::Type(t) => as_nullable(resolve, t),
3630 _ => None,
3631 }
3632}
3633
3634pub fn maybe_null(resolve: &Resolve, ty: &Type) -> bool {
3635 as_nullable(resolve, ty).is_some()
3636}
3637
3638pub fn js_array_ty(resolve: &Resolve, element_ty: &Type) -> Option<&'static str> {
3648 match element_ty {
3649 Type::Bool => None,
3650 Type::U8 => Some("Uint8Array"),
3651 Type::S8 => Some("Int8Array"),
3652 Type::U16 => Some("Uint16Array"),
3653 Type::S16 => Some("Int16Array"),
3654 Type::U32 => Some("Uint32Array"),
3655 Type::S32 => Some("Int32Array"),
3656 Type::U64 => Some("BigUint64Array"),
3657 Type::S64 => Some("BigInt64Array"),
3658 Type::F32 => Some("Float32Array"),
3659 Type::F64 => Some("Float64Array"),
3660 Type::Char => None,
3661 Type::String => None,
3662 Type::ErrorContext => None,
3663 Type::Id(id) => match &resolve.types[*id].kind {
3664 TypeDefKind::Type(t) => js_array_ty(resolve, t),
3666 _ => None,
3667 },
3668 }
3669}
3670
3671fn gen_dataview_set_and_check_fn_js_for_numeric_type(
3678 resolve: &Resolve,
3679 ty: &Type,
3680) -> (&'static str, String) {
3681 let check_fn = Intrinsic::Conversion(ConversionIntrinsic::RequireValidNumericPrimitive).name();
3682 match ty {
3683 Type::Bool => ("setUint8", format!("{check_fn}.bind(null, 'u8')",)),
3685 Type::U8 => ("setUint8", format!("{check_fn}.bind(null, 'u8')",)),
3686 Type::U16 => ("setUint16", format!("{check_fn}.bind(null, 'u16')",)),
3687 Type::U32 => ("setUint32", format!("{check_fn}.bind(null, 'u32')",)),
3688 Type::U64 => ("setBigUint64", format!("{check_fn}.bind(null, 'u64')",)),
3689 Type::S8 => ("setInt8", format!("{check_fn}.bind(null, 's8')",)),
3691 Type::S16 => ("setInt16", format!("{check_fn}.bind(null, 's16')",)),
3692 Type::S32 => ("setInt32", format!("{check_fn}.bind(null, 's32')",)),
3693 Type::S64 => ("setBigInt64", format!("{check_fn}.bind(null, 's64')",)),
3694 Type::F32 => ("setFloat32", format!("{check_fn}.bind(null, 'f32')",)),
3696 Type::F64 => ("setFloat64", format!("{check_fn}.bind(null, 'f64')",)),
3697 Type::Id(id) => match resolve.types.get(*id) {
3698 Some(TypeDef {
3700 kind: TypeDefKind::Type(inner_ty),
3701 ..
3702 }) => gen_dataview_set_and_check_fn_js_for_numeric_type(resolve, inner_ty),
3703 Some(inner_ty) => {
3705 unreachable!(
3706 "unexpected non-type-kind typedef [{inner_ty:?}] (as type {ty:?}) for canonical list lower [{ty:?}]",
3707 )
3708 }
3709 None => unreachable!("missing/unresolvable type [{ty:?}]"),
3711 },
3712 _ => unreachable!("unsupported type [{ty:?}] for canonical list lower"),
3713 }
3714}
3715
3716#[cfg(test)]
3717mod tests {
3718 use super::*;
3719
3720 #[test]
3721 fn result_wrapper_detection_preserves_ambiguous_success_payloads() {
3722 let mut resolve = Resolve::new();
3723 let package = resolve
3724 .push_str(
3725 std::path::Path::new("result-wrappers.wit"),
3726 r#"
3727 package test:result-wrappers;
3728
3729 interface imports {
3730 variant ordinary-variant { a(u32), b }
3731 variant ok-variant { ok(u32), other }
3732 record tagged-record { tag: string, val: u32 }
3733
3734 primitive: func() -> result<u32, string>;
3735 ordinary: func() -> result<ordinary-variant, string>;
3736 ok-case: func() -> result<ok-variant, string>;
3737 nested: func() -> result<result<u32, string>, string>;
3738 tagged: func() -> result<tagged-record, string>;
3739 }
3740 "#,
3741 )
3742 .unwrap();
3743 let interface = resolve.packages[package].interfaces["imports"];
3744
3745 let tags_for = |name: &str| {
3746 let ok = get_thrown_type(
3747 &resolve,
3748 resolve.interfaces[interface].functions[name].result,
3749 )
3750 .unwrap()
3751 .0;
3752 unambiguous_result_wrapper_tags(&resolve, ok)
3753 };
3754
3755 assert_eq!(tags_for("primitive"), (true, true));
3756 assert_eq!(tags_for("ordinary"), (true, true));
3757 assert_eq!(tags_for("ok-case"), (false, true));
3758 assert_eq!(tags_for("nested"), (false, false));
3759 assert_eq!(tags_for("tagged"), (false, false));
3760 }
3761
3762 #[test]
3763 fn test_alias_type_gen_dataview_set_and_check_fn_js_for_numeric_type() {
3764 let mut resolve = Resolve::new();
3765
3766 let owner = wit_parser::TypeOwner::Interface(resolve.interfaces.next_id());
3767
3768 let ty_id = resolve.types.alloc(wit_parser::TypeDef {
3769 name: None,
3770 kind: TypeDefKind::Type(Type::U64),
3771 docs: Default::default(),
3772 stability: Default::default(),
3773 owner,
3774 span: Default::default(),
3775 external_id: None,
3776 });
3777
3778 let ty_ = Type::Id(ty_id);
3779
3780 let (dataview_set_method, check_fn_intrinsic) =
3781 gen_dataview_set_and_check_fn_js_for_numeric_type(&resolve, &ty_);
3782
3783 assert_eq!(dataview_set_method, "setBigUint64");
3784
3785 let check_fn =
3786 Intrinsic::Conversion(ConversionIntrinsic::RequireValidNumericPrimitive).name();
3787
3788 assert_eq!(check_fn_intrinsic, format!("{check_fn}.bind(null, 'u64')",));
3789 }
3790}