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