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