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