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