1pub mod analysis;
9mod argument_defaults;
10pub mod bigint_pool;
11pub mod bounds;
12pub mod box_types;
13mod call_arguments;
14pub mod cast_check;
15mod cast_diagnostics;
16pub mod classes;
17mod closure_coercions;
18pub(crate) use closure_coercions::ADAPTER_ORIGINAL_FIELD;
19pub mod closures;
20pub mod dependency_usage;
21pub(crate) mod descriptor_schema;
22pub mod dwarf;
23mod field_guards;
24mod field_lookup;
25pub mod field_name_strings;
26pub mod field_names;
27mod finalizer_argument;
28pub mod function_adapters;
29pub mod function_emitter;
30pub mod imported_classes;
31pub mod init_guard;
32pub mod intrinsics;
33#[cfg(test)]
34mod invariant_tests;
35mod nullish;
36pub mod recursive_validators;
37mod runtime_descriptors;
38mod runtime_values;
39pub mod string_pool;
40pub mod symbol_table;
41mod this_binding;
42pub mod throw;
43pub mod user_subtypes;
44mod vtable_walk;
45
46use crate::compiler_error::{CompilerFailure, CompilerStage};
47use crate::tree_height;
48use std::borrow::Cow;
49use std::collections::{BTreeMap, BTreeSet};
50
51use wasm_encoder::{
52 AbstractHeapType, CodeSection, ConstExpr, CustomSection, DataCountSection, DataSection,
53 EntityType, ExportKind as WasmExportKind, ExportSection, FunctionSection, GlobalSection,
54 GlobalType, HeapType, Ieee64, ImportSection, Instruction, Module, RefType, StartSection,
55 TagKind, TagType, TypeSection, ValType,
56};
57
58use crate::{
59 LineIndex, MangledName, PackageDeclaration, Param, StmtId, Type, TypeKind, TypedAst, ValueKind,
60 ValueSymbol,
61};
62use analysis::CodegenAnalysis;
63use bigint_pool::BigIntPool;
64use function_emitter::FunctionEmitter;
65use function_emitter::stmt::emit_statement;
66use string_pool::StringPool;
67
68pub(crate) fn internal_failure(message: impl Into<String>) -> CompilerFailure {
69 CompilerFailure::Internal {
70 stage: CompilerStage::Codegen,
71 span: None,
72 message: message.into(),
73 }
74}
75
76pub(crate) fn is_host_package(name: &str) -> bool {
79 name.starts_with("submilli:")
80}
81
82fn is_resultless(owner: &str, ret: &Type) -> bool {
86 ret.is_void() && is_host_package(owner)
87}
88
89pub(crate) fn next_index(counter: &mut u32) -> Result<u32, CompilerFailure> {
92 let index = *counter;
93 *counter = index.checked_add(1).ok_or_else(index_space_exhausted)?;
94 Ok(index)
95}
96
97pub(crate) fn wasm_u32(value: usize) -> Result<u32, CompilerFailure> {
99 u32::try_from(value).map_err(|_| index_space_exhausted())
100}
101
102pub(crate) fn parameter_local(position: usize) -> Result<u32, CompilerFailure> {
105 wasm_u32(position)?
106 .checked_add(1)
107 .ok_or_else(index_space_exhausted)
108}
109
110#[derive(Clone, Copy, Debug, Default)]
113pub struct GuardedBodies {
114 pub equals: u32,
115 pub to_json: u32,
116 pub hash: u32,
117}
118
119fn index_space_exhausted() -> CompilerFailure {
120 CompilerFailure::Limit {
121 stage: CompilerStage::Codegen,
122 span: None,
123 message: "the module exceeds a WebAssembly index or size limit".into(),
124 help: vec!["split the program into smaller modules".into()],
125 }
126}
127
128fn static_value_tag(name: &str) -> String {
132 if name.starts_with(|c: char| c.is_ascii_uppercase()) {
133 format!("function {name}() {{ [native code] }}")
134 } else {
135 format!("[object {name}]")
136 }
137}
138
139fn static_interface_of<'a>(
144 defs: &'a PackageDeclaration,
145 ty: &Type,
146) -> Option<&'a crate::TypeSymbol> {
147 fn find<'a>(
148 types: &'a std::collections::BTreeMap<String, crate::TypeSymbol>,
149 namespaces: &'a std::collections::BTreeMap<String, crate::NamespaceSymbol>,
150 mangled: &crate::MangledName,
151 ) -> Option<&'a crate::TypeSymbol> {
152 types
153 .values()
154 .find(|t| t.mangled_name == *mangled)
155 .or_else(|| {
156 namespaces
157 .values()
158 .find_map(|ns| find(&ns.types, &ns.namespaces, mangled))
159 })
160 }
161 let Type::InterfaceRef { mangled, .. } = ty.peel() else {
162 return None;
163 };
164 find(&defs.types, &defs.namespaces, mangled).filter(|ts| {
165 matches!(
166 &ts.kind,
167 TypeKind::Interface {
168 dispatch: crate::Dispatch::Static,
169 ..
170 }
171 )
172 })
173}
174
175#[allow(clippy::too_many_arguments)]
176fn import_value_symbol(
177 name: &str,
178 value: &ValueSymbol,
179 defs: &PackageDeclaration,
180 intrinsics: &intrinsics::IntrinsicTypeIndices,
181 next_type_idx: &mut u32,
182 next_func_idx: &mut u32,
183 next_global_idx: &mut u32,
184 types: &mut wasm_encoder::TypeSection,
185 import_section: &mut wasm_encoder::ImportSection,
186 symbols: &mut SymbolTable,
187) -> Result<(), crate::compiler_error::CompilerFailure> {
188 use wasm_encoder::{EntityType, GlobalType, ValType};
189 match &value.kind {
190 ValueKind::Function { params, ret, .. } => {
191 let sig_idx = *next_type_idx;
192 crate::codegen::next_index(next_type_idx)?;
193 let raw_string_abi = defs.package_name.as_str() == crate::runtime::JSON_MODULE_NAME;
198 let (mut param_types, result_types): (Vec<ValType>, Vec<ValType>) = if raw_string_abi {
199 json_host_signature(name, params, symbols, intrinsics)?
200 } else {
201 (
202 params
203 .iter()
204 .map(|p| symbols.value_type(&p.ty))
205 .collect::<Result<_, _>>()?,
206 if is_resultless(&defs.package_name, ret) {
207 vec![]
208 } else {
209 symbols.wasm_result(ret)?
210 },
211 )
212 };
213 if defs.runtime_generics.contains(&value.mangled_name) {
214 symbols
215 .runtime_generic_functions
216 .insert(value.mangled_name.clone());
217 param_types.push(runtime_descriptors::environment_type(symbols)?);
218 }
219 if is_host_package(&defs.package_name) {
220 symbols.record_host_parameter_defaults(
221 *next_func_idx,
222 params.iter().map(|param| param.default.clone()).collect(),
223 );
224 }
225 if result_types.is_empty() {
226 symbols.resultless_functions.insert(*next_func_idx);
227 }
228 types.ty().function(param_types, result_types);
229 import_section.import(
230 defs.package_name.as_str(),
231 value.mangled_name.as_str(),
232 EntityType::Function(sig_idx),
233 );
234 if let Some(metadata) = call_arguments::metadata(params.iter().map(|param| {
235 (
236 param
237 .default
238 .as_ref()
239 .or(param.optional.then_some(&crate::DefaultValue::Undefined)),
240 param.rest,
241 )
242 })) {
243 symbols
244 .function_argument_metadata
245 .insert(value.mangled_name.clone(), metadata);
246 }
247 symbols.record_imported_fn(
248 value.mangled_name.clone(),
249 *next_func_idx,
250 params.iter().map(|p| p.ty.clone()).collect(),
251 ret.clone(),
252 raw_string_abi,
253 );
254 crate::codegen::next_index(next_func_idx)?;
255 }
256 ValueKind::Let { ty, .. } | ValueKind::Const { ty, .. } => {
257 if let Some(ts) = static_interface_of(defs, ty) {
261 symbols.record_iface_dispatch(ts.mangled_name.clone(), crate::Dispatch::Static);
262 symbols.record_static_value_tag(
263 ts.mangled_name.clone(),
264 static_value_tag(&value.name),
265 );
266 return Ok(());
267 }
268 let global_ty = GlobalType {
273 val_type: global_val_type(ty, symbols)?,
274 mutable: true,
275 shared: false,
276 };
277 import_section.import(
278 defs.package_name.as_str(),
279 value.mangled_name.as_str(),
280 EntityType::Global(global_ty),
281 );
282 symbols.record_typed_global(value.mangled_name.clone(), *next_global_idx, ty.clone());
283 crate::codegen::next_index(next_global_idx)?;
284 }
285 };
286 Ok(())
287}
288
289fn declared_wrapper_abi(
310 symbols: &SymbolTable,
311 mangled: &crate::MangledName,
312 sig: &crate::MethodSig,
313 has_receiver: bool,
314) -> Result<symbol_table::MethodSlotAbi, crate::compiler_error::CompilerFailure> {
315 let receiver_offset = usize::from(has_receiver);
316 if let Some(imported) = symbols.top_level_fn(mangled)
317 && sig.params.len().checked_add(receiver_offset) == Some(imported.params.len())
318 {
319 return Ok(symbol_table::MethodSlotAbi {
320 params: imported
321 .params
322 .iter()
323 .skip(receiver_offset)
324 .map(|ty| symbols.value_type(ty))
325 .collect::<Result<_, _>>()?,
326 ret: if symbols.resultless_functions.contains(&imported.wasm_idx) {
327 None
328 } else {
329 symbols.wasm_result(&imported.ret)?.first().copied()
330 },
331 });
332 }
333 Ok(symbol_table::MethodSlotAbi {
334 params: sig
335 .params
336 .iter()
337 .map(|p| symbols.value_type(&p.ty))
338 .collect::<Result<_, _>>()?,
339 ret: if is_resultless(mangled.as_str(), &sig.ret) {
340 None
341 } else {
342 symbols.wasm_result(&sig.ret)?.first().copied()
343 },
344 })
345}
346
347#[allow(clippy::too_many_arguments)]
348fn import_json_host_function(
349 name: &str,
350 intrinsics: &intrinsics::IntrinsicTypeIndices,
351 next_type_idx: &mut u32,
352 next_func_idx: &mut u32,
353 types: &mut wasm_encoder::TypeSection,
354 import_section: &mut wasm_encoder::ImportSection,
355 symbols: &mut SymbolTable,
356) -> Result<(), crate::compiler_error::CompilerFailure> {
357 let mangled = crate::mangle::host(crate::runtime::JSON_MODULE_NAME, name);
358 if symbols.func_idx(&mangled).is_some() {
359 return Ok(());
360 }
361
362 let sig_idx = *next_type_idx;
363 crate::codegen::next_index(next_type_idx)?;
364 let (param_types, result_types) = json_host_signature(name, &[], symbols, intrinsics)?;
365 types.ty().function(param_types, result_types);
366 import_section.import(
367 crate::runtime::JSON_MODULE_NAME,
368 mangled.as_str(),
369 EntityType::Function(sig_idx),
370 );
371 symbols.record_func(mangled, *next_func_idx);
372 crate::codegen::next_index(next_func_idx)?;
373
374 Ok(())
375}
376
377pub use symbol_table::SymbolTable;
378
379#[derive(Clone, Debug, PartialEq, Eq)]
380pub struct CodegenError {
381 message: &'static str,
382 span: crate::Span,
383}
384
385impl std::fmt::Display for CodegenError {
386 fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
387 write!(out, "internal code generation error: {}", self.message)
388 }
389}
390
391impl std::error::Error for CodegenError {}
392
393impl From<CodegenError> for CompilerFailure {
394 fn from(error: CodegenError) -> Self {
395 Self::Internal {
396 stage: CompilerStage::Codegen,
397 span: Some(error.span),
398 message: error.message.to_owned(),
399 }
400 }
401}
402
403impl CodegenError {
404 pub fn diagnostic(self) -> crate::Diagnostic {
405 crate::Diagnostic {
406 severity: crate::Severity::Error,
407 span: self.span,
408 message: self.to_string(),
409 help: vec!["report this compiler error with the source program".into()],
410 notes: Vec::new(),
411 }
412 }
413}
414
415pub struct CodegenCtx<'a> {
416 pub ta: &'a TypedAst,
417 pub strings: &'a StringPool,
418 pub bigints: &'a BigIntPool,
419 pub symbols: &'a SymbolTable,
420 pub source: &'a str,
421 pub line_index: &'a LineIndex,
422 pub file: crate::FileId,
425 pub validator_bodies: &'a recursive_validators::ValidatorBodies,
427 pub type_info: &'a crate::TypeInfoTable,
428 pub package_string_global_idx: Option<u32>,
429 failure: std::cell::Cell<Option<CompilerFailure>>,
430 validator_steps_left: std::cell::Cell<u64>,
433 validator_root: std::cell::Cell<Option<Type>>,
436 check_is_standalone: std::cell::Cell<bool>,
438}
439
440impl CodegenCtx<'_> {
441 fn require<T>(&self, value: Option<T>, message: &'static str) -> Option<T> {
444 if value.is_none() {
445 self.fail(message);
446 }
447 value
448 }
449
450 fn latch<T>(&self, result: Result<T, CompilerFailure>) -> Option<T> {
454 match result {
455 Ok(value) => Some(value),
456 Err(failure) => {
457 self.record_failure(failure);
458 None
459 }
460 }
461 }
462
463 fn check_failure(&self) -> Result<(), CompilerFailure> {
464 match self.failure.take() {
465 Some(error) => Err(error),
466 None => Ok(()),
467 }
468 }
469
470 fn fail(&self, message: &'static str) {
471 self.record_failure(
472 CodegenError {
473 message,
474 span: crate::Span::at(self.file),
475 }
476 .into(),
477 );
478 }
479
480 fn charge_validator_step(&self, emitter: &mut FunctionEmitter) -> Result<(), CompilerFailure> {
486 self.charge_validator_steps(emitter, 1)
487 }
488
489 fn charge_validator_steps(
492 &self,
493 emitter: &mut FunctionEmitter,
494 steps: u64,
495 ) -> Result<(), CompilerFailure> {
496 use crate::compiler_limits::MAX_INLINE_VALIDATOR_STEPS;
497 self.check_function_limits(emitter)?;
498 let Some(left) = self.validator_steps_left.get().checked_sub(steps) else {
499 return Err(self.validator_limit(
500 emitter.last_source_span(),
501 format!("exceeds the compiler limit of {MAX_INLINE_VALIDATOR_STEPS} steps"),
502 "check against a smaller type, or restructure the interfaces so members do not repeat the same nested interface",
503 ));
504 };
505 self.validator_steps_left.set(left);
506 Ok(())
507 }
508
509 fn check_function_limits(&self, emitter: &mut FunctionEmitter) -> Result<(), CompilerFailure> {
519 let Some(limit) = FunctionLimit::exceeded(emitter.body_size(), emitter.local_count())
520 else {
521 return Ok(());
522 };
523 let check_has_emitted_code =
526 self.validator_steps_left.get() < crate::compiler_limits::MAX_INLINE_VALIDATOR_STEPS;
527 if !check_has_emitted_code {
528 let span = match limit {
529 FunctionLimit::BodyBytes => emitter.body_limit_span(),
530 FunctionLimit::Locals => emitter.locals_limit_span(),
531 };
532 return Err(limit.failure(span));
533 }
534 let fix = if self.check_is_standalone.get() {
535 "check against a smaller type"
536 } else {
537 "check against a smaller type, or move some of the function's checks into another function"
538 };
539 Err(self.validator_limit(emitter.last_source_span(), limit.check_exceeds(), fix))
540 }
541
542 fn check_finished_function(&self, function: FinishedFunction) {
546 let Some(limit) = FunctionLimit::exceeded(function.body_bytes, function.locals) else {
547 return;
548 };
549 let span = match limit {
550 FunctionLimit::BodyBytes => function.body_limit_span,
551 FunctionLimit::Locals => function.locals_limit_span,
552 };
553 self.record_failure(limit.failure(span));
554 }
555
556 fn validator_limit(
558 &self,
559 span: Option<crate::Span>,
560 exceeded: String,
561 fix: &str,
562 ) -> CompilerFailure {
563 let root = self.validator_root.take();
564 let subject = match &root {
565 Some(ty) => format!("the runtime check of `{}`", abbreviated(ty)),
566 None => "a runtime type check".to_string(),
567 };
568 self.validator_root.set(root);
569 CompilerFailure::Limit {
570 stage: crate::compiler_error::CompilerStage::Codegen,
571 span,
572 message: format!("{subject} {exceeded}"),
573 help: vec![
574 "a runtime check of a value against an interface includes the checks of every member, so interfaces whose members reference other interfaces several times multiply its size".into(),
575 fix.into(),
576 ],
577 }
578 }
579
580 fn checking<T>(&self, ty: &Type, emit: impl FnOnce() -> T) -> T {
584 if let Some(outer) = self.validator_root.take() {
585 self.validator_root.set(Some(outer));
586 return emit();
587 }
588 self.validator_steps_left
589 .set(crate::compiler_limits::MAX_INLINE_VALIDATOR_STEPS);
590 self.validator_root.set(Some(ty.clone()));
591 let result = emit();
592 self.validator_root.set(None);
593 result
594 }
595
596 fn checking_standalone<T>(&self, ty: &Type, emit: impl FnOnce() -> T) -> T {
599 self.check_is_standalone.set(true);
600 let result = self.checking(ty, emit);
601 self.check_is_standalone.set(false);
602 result
603 }
604
605 fn record_failure(&self, failure: CompilerFailure) {
607 let first = self.failure.take().unwrap_or(failure);
608 self.failure.set(Some(first));
609 }
610}
611
612#[derive(Clone, Copy)]
614enum FunctionLimit {
615 BodyBytes,
617 Locals,
620}
621
622impl FunctionLimit {
623 fn exceeded(body_bytes: usize, locals: u32) -> Option<Self> {
627 if locals > crate::compiler_limits::MAX_FUNCTION_LOCALS {
628 Some(Self::Locals)
629 } else if !Self::body_fits(body_bytes) {
630 Some(Self::BodyBytes)
631 } else {
632 None
633 }
634 }
635
636 fn body_fits(bytes: usize) -> bool {
638 bytes <= crate::compiler_limits::MAX_FUNCTION_BODY_BYTES
639 }
640
641 fn failure(self, span: Option<crate::Span>) -> CompilerFailure {
644 CompilerFailure::Limit {
645 stage: crate::compiler_error::CompilerStage::Codegen,
646 span,
647 message: format!("the function this code compiles into {}", self.function_exceeds()),
648 help: vec![
649 "split the code into smaller functions; top-level statements compile into one function, and runtime type checks of large types are emitted inline in the function that contains them".into(),
650 ],
651 }
652 }
653
654 fn function_exceeds(self) -> String {
656 use crate::compiler_limits::{MAX_FUNCTION_BODY_BYTES, MAX_FUNCTION_LOCALS};
657 match self {
658 Self::BodyBytes => {
659 format!("is larger than the {MAX_FUNCTION_BODY_BYTES} bytes a function may have")
660 }
661 Self::Locals => {
662 format!("needs more than the {MAX_FUNCTION_LOCALS} locals a function may have")
663 }
664 }
665 }
666
667 fn check_exceeds(self) -> String {
670 use crate::compiler_limits::{MAX_FUNCTION_BODY_BYTES, MAX_FUNCTION_LOCALS};
671 match self {
672 Self::BodyBytes => format!(
673 "makes its function larger than the {MAX_FUNCTION_BODY_BYTES} bytes a function may have"
674 ),
675 Self::Locals => {
676 format!("needs more than the {MAX_FUNCTION_LOCALS} locals a function may have")
677 }
678 }
679 }
680}
681
682struct FinishedFunction {
684 body_bytes: usize,
685 body_limit_span: Option<crate::Span>,
686 locals: u32,
687 locals_limit_span: Option<crate::Span>,
688}
689
690fn abbreviated(ty: &Type) -> String {
693 const MAX_CHARS: usize = 120;
694 let text = ty.to_string();
695 if text.chars().count() <= MAX_CHARS {
696 return text;
697 }
698 let mut short: String = text.chars().take(MAX_CHARS).collect();
699 short.push('…');
700 short
701}
702
703pub fn codegen(
704 source: &str,
705 filename: &str,
706 file: crate::FileId,
707 ta: &TypedAst,
708 dependencies: &[&PackageDeclaration],
709) -> Result<Vec<u8>, CompilerFailure> {
710 Ok(codegen_with_type_info(source, filename, file, ta, dependencies)?.wasm)
711}
712
713pub fn codegen_owned_by(
721 owning_package: &str,
722 source: &str,
723 filename: &str,
724 file: crate::FileId,
725 ta: &TypedAst,
726 dependencies: &[&PackageDeclaration],
727) -> Result<GeneratedModule, CompilerFailure> {
728 codegen_inner(
729 source,
730 filename,
731 file,
732 ta,
733 dependencies,
734 owning_package,
735 None,
736 )
737}
738
739#[derive(Clone, Debug, PartialEq)]
740pub struct GeneratedModule {
741 pub wasm: Vec<u8>,
742 pub type_info: crate::TypeInfoTable,
743 pub runtime_functions: BTreeMap<crate::MangledName, crate::RuntimeFunction>,
744 pub runtime_globals: BTreeMap<crate::MangledName, Type>,
745 pub closure_caches: BTreeSet<crate::MangledName>,
746}
747
748pub fn codegen_with_type_info(
749 source: &str,
750 filename: &str,
751 file: crate::FileId,
752 ta: &TypedAst,
753 dependencies: &[&PackageDeclaration],
754) -> Result<GeneratedModule, CompilerFailure> {
755 codegen_inner(
756 source,
757 filename,
758 file,
759 ta,
760 dependencies,
761 &ta.package_name,
762 None,
763 )
764}
765
766pub fn codegen_package_with_type_info(
767 sources: &crate::Sources,
768 file: crate::FileId,
769 ta: &TypedAst,
770 dependencies: &[&PackageDeclaration],
771) -> Result<GeneratedModule, CompilerFailure> {
772 let source = sources.get(file).ok_or_else(|| {
773 crate::source::SourceError::UnknownFile { file }
774 .into_compiler_failure(CompilerStage::Codegen)
775 })?;
776 codegen_inner(
777 source.text(),
778 source.path.as_str(),
779 file,
780 ta,
781 dependencies,
782 &ta.package_name,
783 Some(sources),
784 )
785}
786
787fn codegen_inner(
788 source: &str,
789 filename: &str,
790 file: crate::FileId,
791 ta: &TypedAst,
792 dependencies: &[&PackageDeclaration],
793 owning_package: &str,
794 package_sources: Option<&crate::Sources>,
795) -> Result<GeneratedModule, CompilerFailure> {
796 tree_height::check_typed(ta, CompilerStage::Codegen)?;
797 crate::typechecker::infer::check_declaration_types(dependencies.iter().copied())
798 .map_err(|failure| failure.with_stage(CompilerStage::Codegen))?;
799 u32::try_from(source.len()).map_err(|_| CompilerFailure::Limit {
800 stage: CompilerStage::Codegen,
801 span: None,
802 message: "source exceeds the 32-bit source-offset limit".into(),
803 help: vec!["split the source into smaller modules".into()],
804 })?;
805 let line_index = LineIndex::new(source)
806 .map_err(|error| error.into_compiler_failure(CompilerStage::Codegen))?;
807 let debug_sources = dwarf::DebugSources {
808 file,
809 filename,
810 index: &line_index,
811 package: package_sources.map(|sources| dwarf::PackageSources {
812 sources,
813 name: owning_package,
814 }),
815 };
816 for id in ta
817 .expr_ids()
818 .map_err(|error| error.into_compiler_failure(CompilerStage::Codegen))?
819 {
820 let expr = ta
821 .try_expr(id)
822 .map_err(|error| error.into_compiler_failure(CompilerStage::Codegen))?;
823 debug_sources.location(expr.span)?;
824 }
825 for id in ta
826 .stmt_ids()
827 .map_err(|error| error.into_compiler_failure(CompilerStage::Codegen))?
828 {
829 let stmt = ta
830 .try_stmt(id)
831 .map_err(|error| error.into_compiler_failure(CompilerStage::Codegen))?;
832 debug_sources.location(stmt.span)?;
833 }
834 let source_main_return_ty = ta
835 .functions
836 .iter()
837 .find(|function| function.name.name == "main")
838 .map(|function| function.return_type.clone());
839 let lowered = runtime_values::lower(ta, dependencies)?;
840 let ta = &lowered;
841 let lowered_dependencies: Vec<_> = dependencies
842 .iter()
843 .map(|defs| runtime_values::lower_declaration(defs))
844 .collect::<Result<_, _>>()?;
845 let dependencies: Vec<_> = lowered_dependencies.iter().collect();
846 let dependencies = dependencies.as_slice();
847 let analysis = CodegenAnalysis::collect(ta, dependencies)?;
848 let mut pool = analysis.string_pool;
849 let bigint_pool = analysis.bigint_pool;
850 let dependency_usage = analysis.dependency_usage.finish(dependencies);
851
852 let mut module = Module::new();
853 let mut types = TypeSection::new();
854 let mut import_section = ImportSection::new();
855 let mut symbols = SymbolTable::default();
856 let mut next_type_idx: u32 = 0;
857 let mut next_func_idx: u32 = 0;
858
859 let intrinsics = intrinsics::declare_intrinsic_types(&mut types);
863 symbols.set_intrinsic_type_indices(intrinsics);
864 next_type_idx = next_type_idx
865 .checked_add(intrinsics::INTRINSIC_TYPE_COUNT)
866 .ok_or_else(|| crate::codegen::internal_failure("Wasm index count overflow"))?;
867 field_names::declare_optional_name_type(
868 &mut types,
869 &mut symbols,
870 &mut next_type_idx,
871 intrinsics,
872 )?;
873
874 let error_tag_func_type_idx = next_type_idx;
883 types.ty().function(
884 [ValType::Ref(RefType {
885 nullable: false,
886 heap_type: HeapType::Concrete(intrinsics.error),
887 })],
888 Vec::<ValType>::new(),
889 );
890 crate::codegen::next_index(&mut next_type_idx)?;
891 import_section.import(
892 crate::runtime::prelude::MODULE_NAME,
893 crate::mangle::prelude("__error_tag").as_str(),
894 EntityType::Tag(TagType {
895 kind: TagKind::Exception,
896 func_type_idx: error_tag_func_type_idx,
897 }),
898 );
899 symbols.set_error_tag_idx(0);
900
901 let dependency_types = dependency_usage.dependency_types(dependencies);
905 let validator_bodies =
906 recursive_validators::ValidatorBodies::collect(ta, dependency_types.iter());
907 let recursive_validators =
908 recursive_validators::discover(ta, dependency_types.iter(), &validator_bodies)?;
909 let mut all_shapes = ta.shapes.clone();
910 all_shapes.extend(recursive_validators.extra_shapes.iter().cloned());
911
912 let dependency_shapes = dependency_usage.dependency_shapes(dependencies);
913 let user_emitted_types: Vec<Type> =
914 user_subtypes::collect_object_shapes(dependency_shapes.iter().copied(), &all_shapes);
915 let (type_info, type_info_index) = crate::TypeInfoTable::collect_indexed(
916 ta.package_name.clone(),
917 &all_shapes,
918 &user_emitted_types,
919 );
920 let needs_host_object_to_json =
921 user_subtypes::needs_host_object_to_json_adapter(&user_emitted_types);
922
923 let closure_metas = analysis.closure_metas;
926 let adapter_metas = analysis.adapter_metas;
927 let mut mentioned_closure_sigs = analysis.mentioned_closure_sigs;
928 if !recursive_validators.descriptor_types.is_empty() {
931 mentioned_closure_sigs.push(field_guards::signature());
932 }
933 let dependency_values = dependency_usage.dependency_values(dependencies);
934 let hof_dependency_sigs = closures::collect_from_dependencies(
935 dependency_values.iter().map(|value| value.symbol),
936 dependency_shapes.iter().copied(),
937 dependency_types.iter(),
938 &dependency_usage,
939 )?;
940 let class_member_sigs = closures::class_member_sigs(ta, dependencies)?;
941 closures::emit_func_and_struct_types(
944 closure_metas
945 .iter()
946 .map(|m| closures::classify(&m.signature))
947 .chain(
948 adapter_metas
949 .iter()
950 .map(|a| closures::classify(&a.signature)),
951 )
952 .collect::<Result<Vec<_>, _>>()?
953 .into_iter()
954 .chain(hof_dependency_sigs.iter().copied())
955 .chain(class_member_sigs.iter().copied())
956 .chain(mentioned_closure_sigs.iter().copied()),
957 &mut types,
958 &mut symbols,
959 &mut next_type_idx,
960 )?;
961
962 let boxed_value_types = box_types::collect(ta, &symbols)?;
964 box_types::emit(
965 &boxed_value_types,
966 &mut types,
967 &mut symbols,
968 &mut next_type_idx,
969 );
970
971 closures::emit_env_types(&closure_metas, &mut types, &mut symbols, &mut next_type_idx)?;
973
974 let mut next_global_idx: u32 = 0;
976
977 symbols.record_rewritable_members(classes::RewritableMembers::of(ta)?);
981 let imported_class_layouts = imported_classes::reconstruct(
982 dependencies,
983 ta,
984 &dependency_usage,
985 intrinsics,
986 file,
987 &mut types,
988 &mut next_type_idx,
989 &mut import_section,
990 &mut next_func_idx,
991 &mut next_global_idx,
992 &mut symbols,
993 )?;
994
995 let mut class_plan = classes::ClassPlan::collect(ta, &imported_class_layouts)?;
998 class_plan.reserve_and_emit_types(&mut types, &mut next_type_idx, &mut symbols, intrinsics)?;
999 for value in &dependency_values {
1000 let defs = value.package;
1001 if defs.virtual_dispatch.is_some() {
1006 continue;
1007 }
1008 import_value_symbol(
1009 value.export_name.as_ref(),
1010 value.symbol,
1011 defs,
1012 &intrinsics,
1013 &mut next_type_idx,
1014 &mut next_func_idx,
1015 &mut next_global_idx,
1016 &mut types,
1017 &mut import_section,
1018 &mut symbols,
1019 )?;
1020 }
1021 function_adapters::import_shared_closure_caches(
1022 &adapter_metas,
1023 dependencies,
1024 &mut import_section,
1025 &mut symbols,
1026 &mut next_global_idx,
1027 )?;
1028 let exported_closure_caches = function_adapters::exported_closure_caches(ta);
1029
1030 let vtable_ref = ValType::Ref(RefType {
1032 nullable: false,
1033 heap_type: HeapType::Concrete(intrinsics.vtable),
1034 });
1035 for (module, name) in [
1039 (crate::runtime::prelude::MODULE_NAME, "string_vtable"),
1040 (crate::runtime::prelude::MODULE_NAME, "boxed_number_vtable"),
1041 (crate::runtime::prelude::MODULE_NAME, "boxed_boolean_vtable"),
1042 (crate::runtime::prelude::MODULE_NAME, "array_vtable"),
1043 (crate::runtime::prelude::MODULE_NAME, "closure_vtable"),
1044 (crate::runtime::prelude::MODULE_NAME, "object_vtable"),
1045 (crate::runtime::prelude::MODULE_NAME, "opaque_vtable"),
1046 ] {
1047 let mangled = crate::mangle::prelude(name);
1048 import_section.import(
1049 module,
1050 mangled.as_str(),
1051 EntityType::Global(GlobalType {
1052 val_type: vtable_ref,
1053 mutable: false,
1054 shared: false,
1055 }),
1056 );
1057 symbols.record_global(crate::mangle::prelude(name), next_global_idx);
1058 crate::codegen::next_index(&mut next_global_idx)?;
1059 }
1060 let undefined_value = crate::mangle::prelude(crate::runtime::prelude::undefined::GLOBAL_NAME);
1062 import_section.import(
1063 crate::runtime::prelude::MODULE_NAME,
1064 undefined_value.as_str(),
1065 EntityType::Global(GlobalType {
1066 val_type: ValType::Ref(RefType {
1067 nullable: false,
1068 heap_type: HeapType::Concrete(intrinsics.undefined),
1069 }),
1070 mutable: false,
1071 shared: false,
1072 }),
1073 );
1074 symbols.record_global(undefined_value, next_global_idx);
1075 crate::codegen::next_index(&mut next_global_idx)?;
1076 if dependency_usage.uses_bigint() {
1077 let mangled = crate::mangle::prelude("bigint_vtable");
1078 import_section.import(
1079 crate::runtime::prelude::MODULE_NAME,
1080 mangled.as_str(),
1081 EntityType::Global(GlobalType {
1082 val_type: vtable_ref,
1083 mutable: false,
1084 shared: false,
1085 }),
1086 );
1087 symbols.record_global(crate::mangle::prelude("bigint_vtable"), next_global_idx);
1088 crate::codegen::next_index(&mut next_global_idx)?;
1089 }
1090
1091 let raw_bigint_val_type = ValType::Ref(RefType {
1094 nullable: false,
1095 heap_type: HeapType::Concrete(intrinsics.raw_bigint),
1096 });
1097 let mut bigint_binop_sig_idx = None;
1098 let mut import_bigint_host = |name: &str, sig_idx: u32| -> Result<(), CompilerFailure> {
1099 let mangled = crate::mangle::host(crate::runtime::BIGINT_MODULE_NAME, name);
1100 import_section.import(
1101 crate::runtime::BIGINT_MODULE_NAME,
1102 mangled.as_str(),
1103 EntityType::Function(sig_idx),
1104 );
1105 symbols.record_func(mangled, next_func_idx);
1106 crate::codegen::next_index(&mut next_func_idx)?;
1107 Ok(())
1108 };
1109 for name in ["add", "sub", "mul", "div", "mod", "pow"] {
1110 if dependency_usage.is_host_value_used(crate::runtime::BIGINT_MODULE_NAME, name) {
1111 let sig_idx = if let Some(sig_idx) = bigint_binop_sig_idx {
1112 sig_idx
1113 } else {
1114 let sig_idx = next_type_idx;
1115 crate::codegen::next_index(&mut next_type_idx)?;
1116 types.ty().function(
1117 [
1118 ValType::I32,
1119 raw_bigint_val_type,
1120 ValType::I32,
1121 raw_bigint_val_type,
1122 ],
1123 [ValType::I32, raw_bigint_val_type],
1124 );
1125 bigint_binop_sig_idx = Some(sig_idx);
1126 sig_idx
1127 };
1128 import_bigint_host(name, sig_idx)?;
1129 }
1130 }
1131 if dependency_usage.is_host_value_used(crate::runtime::BIGINT_MODULE_NAME, "cmp") {
1132 let sig_idx = {
1133 let sig_idx = next_type_idx;
1134 crate::codegen::next_index(&mut next_type_idx)?;
1135 types.ty().function(
1136 [
1137 ValType::I32,
1138 raw_bigint_val_type,
1139 ValType::I32,
1140 raw_bigint_val_type,
1141 ],
1142 [ValType::I32],
1143 );
1144 sig_idx
1145 };
1146 import_bigint_host("cmp", sig_idx)?;
1147 }
1148 if dependency_usage.is_host_value_used(crate::runtime::BIGINT_MODULE_NAME, "neg") {
1149 let sig_idx = {
1150 let sig_idx = next_type_idx;
1151 crate::codegen::next_index(&mut next_type_idx)?;
1152 types.ty().function(
1153 [ValType::I32, raw_bigint_val_type],
1154 [ValType::I32, raw_bigint_val_type],
1155 );
1156 sig_idx
1157 };
1158 import_bigint_host("neg", sig_idx)?;
1159 }
1160 if dependency_usage.is_host_value_used(crate::runtime::BIGINT_MODULE_NAME, "fromNumber") {
1161 let sig_idx = {
1162 let sig_idx = next_type_idx;
1163 crate::codegen::next_index(&mut next_type_idx)?;
1164 types
1165 .ty()
1166 .function([ValType::F64], [ValType::I32, raw_bigint_val_type]);
1167 sig_idx
1168 };
1169 import_bigint_host("fromNumber", sig_idx)?;
1170 }
1171 if dependency_usage.is_host_value_used(crate::runtime::BIGINT_MODULE_NAME, "fromString") {
1172 let raw_string_val_type = ValType::Ref(RefType {
1173 nullable: false,
1174 heap_type: HeapType::Concrete(intrinsics.raw_string),
1175 });
1176 let sig_idx = {
1177 let sig_idx = next_type_idx;
1178 crate::codegen::next_index(&mut next_type_idx)?;
1179 types
1180 .ty()
1181 .function([raw_string_val_type], [ValType::I32, raw_bigint_val_type]);
1182 sig_idx
1183 };
1184 import_bigint_host("fromString", sig_idx)?;
1185 }
1186 if dependency_usage.is_host_value_used(crate::runtime::BIGINT_MODULE_NAME, "toString") {
1187 let raw_string_val_type = ValType::Ref(RefType {
1188 nullable: false,
1189 heap_type: HeapType::Concrete(intrinsics.raw_string),
1190 });
1191 let sig_idx = {
1192 let sig_idx = next_type_idx;
1193 crate::codegen::next_index(&mut next_type_idx)?;
1194 types
1195 .ty()
1196 .function([ValType::I32, raw_bigint_val_type], [raw_string_val_type]);
1197 sig_idx
1198 };
1199 import_bigint_host("toString", sig_idx)?;
1200 }
1201 if dependency_usage.is_host_value_used(crate::runtime::NUMBER_MODULE_NAME, "fromBigInt") {
1202 let sig_idx = {
1203 let sig_idx = next_type_idx;
1204 crate::codegen::next_index(&mut next_type_idx)?;
1205 types
1206 .ty()
1207 .function([ValType::I32, raw_bigint_val_type], [ValType::F64]);
1208 sig_idx
1209 };
1210 let from_bigint = crate::mangle::host(crate::runtime::NUMBER_MODULE_NAME, "fromBigInt");
1211 import_section.import(
1212 crate::runtime::NUMBER_MODULE_NAME,
1213 from_bigint.as_str(),
1214 EntityType::Function(sig_idx),
1215 );
1216 symbols.record_func(from_bigint, next_func_idx);
1217 crate::codegen::next_index(&mut next_func_idx)?;
1218 }
1219
1220 let intrinsic_string_ref = ValType::Ref(RefType {
1221 nullable: false,
1222 heap_type: HeapType::Concrete(intrinsics.string),
1223 });
1224 let intrinsic_object_ref = ValType::Ref(RefType {
1225 nullable: false,
1226 heap_type: HeapType::Concrete(intrinsics.object),
1227 });
1228 for dependency_type in &dependency_types {
1229 let defs = dependency_type.package;
1230 let iface_name = dependency_type.name;
1231 let ty_sym = dependency_type.symbol;
1232 let TypeKind::Interface {
1233 dispatch,
1234 methods,
1235 properties,
1236 ..
1237 } = &ty_sym.kind
1238 else {
1239 continue;
1240 };
1241 if *dispatch == crate::Dispatch::VTable {
1242 continue;
1243 }
1244 symbols.record_iface_dispatch(ty_sym.mangled_name.clone(), *dispatch);
1246 let intrinsic_bigint_ref = ValType::Ref(RefType {
1247 nullable: false,
1248 heap_type: HeapType::Concrete(intrinsics.bigint),
1249 });
1250 let receiver_wasm: Option<ValType> = match dispatch {
1251 crate::Dispatch::Static => None,
1252 crate::Dispatch::Direct => Some(match iface_name {
1253 "Number" => ValType::F64,
1254 "Boolean" => ValType::I32,
1255 "String" => intrinsic_string_ref,
1256 "BigInt" => intrinsic_bigint_ref,
1257 _ => intrinsic_object_ref,
1258 }),
1259 crate::Dispatch::VTable => {
1260 return Err(crate::codegen::internal_failure(
1261 "invalid compiler emission dispatch",
1262 ));
1263 }
1264 };
1265 for (method_name, sig) in methods {
1266 if !dependency_usage.is_interface_member_used(ty_sym, method_name) {
1267 continue;
1268 }
1269 let mangled = crate::mangle::extend(&ty_sym.mangled_name, method_name);
1270 let abi = declared_wrapper_abi(&symbols, &mangled, sig, receiver_wasm.is_some())?;
1274 symbols.record_iface_method_abi(mangled.clone(), abi.clone());
1275 let host_defaults = is_host_package(&defs.package_name).then(|| {
1283 std::iter::repeat_n(None, usize::from(receiver_wasm.is_some()))
1284 .chain(sig.params.iter().map(|param| param.default.clone()))
1285 .collect::<Vec<_>>()
1286 });
1287 if let Some(func_idx) = symbols.func_idx(&mangled) {
1288 if let Some(defaults) = host_defaults {
1289 symbols.record_host_parameter_defaults(func_idx, defaults);
1290 }
1291 continue;
1292 }
1293 let mut params: Vec<ValType> = Vec::new();
1294 if let Some(recv) = receiver_wasm {
1295 params.push(recv);
1296 }
1297 params.extend(abi.params.iter().copied());
1298 if defs.runtime_generics.contains(&mangled) {
1302 symbols.runtime_generic_functions.insert(mangled.clone());
1303 params.push(runtime_descriptors::environment_type(&symbols)?);
1304 }
1305 if let Some(defaults) = host_defaults {
1306 symbols.record_host_parameter_defaults(next_func_idx, defaults);
1307 }
1308 let results: Vec<ValType> = abi.ret.into_iter().collect();
1309 let sig_idx = next_type_idx;
1310 crate::codegen::next_index(&mut next_type_idx)?;
1311 types.ty().function(params, results);
1312 import_section.import(
1313 defs.package_name.as_str(),
1314 mangled.as_str(),
1315 EntityType::Function(sig_idx),
1316 );
1317 symbols.record_func(mangled, next_func_idx);
1318 crate::codegen::next_index(&mut next_func_idx)?;
1319 }
1320 for (prop_name, sig) in properties {
1321 if sig.intrinsic {
1325 symbols.record_intrinsic_member(crate::mangle::extend(
1326 &ty_sym.mangled_name,
1327 prop_name,
1328 ));
1329 continue;
1330 }
1331 if !dependency_usage.is_interface_member_used(ty_sym, prop_name) {
1332 continue;
1333 }
1334 let mangled = crate::mangle::extend(&ty_sym.mangled_name, prop_name);
1335 if symbols.func_idx(&mangled).is_some() || symbols.global_idx(&mangled).is_some() {
1339 continue;
1340 }
1341 let Some(recv) = receiver_wasm else {
1342 import_section.import(
1347 defs.package_name.as_str(),
1348 mangled.as_str(),
1349 EntityType::Global(wasm_encoder::GlobalType {
1350 val_type: symbols.value_type(&sig.ty)?,
1351 mutable: true,
1352 shared: false,
1353 }),
1354 );
1355 symbols.record_typed_global(mangled, next_global_idx, sig.ty.clone());
1356 crate::codegen::next_index(&mut next_global_idx)?;
1357 continue;
1358 };
1359 let params = vec![recv];
1360 let results = symbols.wasm_result(&sig.ty)?;
1361 let sig_idx = next_type_idx;
1362 crate::codegen::next_index(&mut next_type_idx)?;
1363 types.ty().function(params, results);
1364 import_section.import(
1365 defs.package_name.as_str(),
1366 mangled.as_str(),
1367 EntityType::Function(sig_idx),
1368 );
1369 symbols.record_func(mangled, next_func_idx);
1370 crate::codegen::next_index(&mut next_func_idx)?;
1371 }
1372 }
1373 if needs_host_object_to_json {
1374 import_json_host_function(
1375 "stringifyTypedObject",
1376 &intrinsics,
1377 &mut next_type_idx,
1378 &mut next_func_idx,
1379 &mut types,
1380 &mut import_section,
1381 &mut symbols,
1382 )?;
1383 }
1384 import_json_host_function(
1385 "parse",
1386 &intrinsics,
1387 &mut next_type_idx,
1388 &mut next_func_idx,
1389 &mut types,
1390 &mut import_section,
1391 &mut symbols,
1392 )?;
1393
1394 let start_type_idx = next_type_idx;
1396 crate::codegen::next_index(&mut next_type_idx)?;
1397 types
1398 .ty()
1399 .function(Vec::<ValType>::new(), Vec::<ValType>::new());
1400 let start_func_idx = next_func_idx;
1401 crate::codegen::next_index(&mut next_func_idx)?;
1402
1403 let mut user_funcs: Vec<UserFunc> = Vec::new();
1405 let mut user_func_type_idx: BTreeMap<MangledName, u32> = BTreeMap::new();
1406 for f in &ta.functions {
1407 let sig_idx = next_type_idx;
1408 crate::codegen::next_index(&mut next_type_idx)?;
1409 let mut params: Vec<_> = f
1410 .params
1411 .iter()
1412 .map(|p| symbols.value_type(&p.ty))
1413 .collect::<Result<_, _>>()?;
1414 if !f.generics.is_empty() {
1415 symbols
1416 .runtime_generic_functions
1417 .insert(f.mangled_name.clone());
1418 params.push(runtime_descriptors::environment_type(&symbols)?);
1419 }
1420 types
1421 .ty()
1422 .function(params, symbols.wasm_result(&f.return_type)?);
1423 let func_idx = next_func_idx;
1424 crate::codegen::next_index(&mut next_func_idx)?;
1425 let param_types: Vec<Type> = f.params.iter().map(|p| p.ty.clone()).collect();
1426 if let Some(metadata) = call_arguments::typed_metadata(&f.params) {
1427 symbols
1428 .function_argument_metadata
1429 .insert(f.mangled_name.clone(), metadata);
1430 }
1431 symbols.record_local_fn(
1432 f.mangled_name.clone(),
1433 func_idx,
1434 param_types,
1435 f.return_type.clone(),
1436 )?;
1437 user_func_type_idx.insert(f.mangled_name.clone(), sig_idx);
1438 user_funcs.push(UserFunc {
1439 name: f.name.name.clone(),
1440 decl_span: f.name.span,
1441 type_idx: sig_idx,
1442 func_idx,
1443 body: f.body,
1444 return_type: f.return_type.clone(),
1445 params: f.params.clone(),
1446 generics: f.generics.clone(),
1447 });
1448 }
1449
1450 let needs_closure_coercions = !mentioned_closure_sigs.is_empty();
1452 let closure_methods = if closure_metas.is_empty()
1453 && adapter_metas.is_empty()
1454 && class_member_sigs.is_empty()
1455 && !needs_closure_coercions
1456 {
1457 None
1458 } else {
1459 Some(closures::allocate_methods(&mut next_func_idx)?)
1460 };
1461
1462 for meta in &closure_metas {
1463 let func_idx = next_func_idx;
1464 crate::codegen::next_index(&mut next_func_idx)?;
1465 symbols.record_closure_func_idx(meta.expr_id, func_idx);
1466 }
1467
1468 for meta in &adapter_metas {
1469 let func_idx = next_func_idx;
1470 crate::codegen::next_index(&mut next_func_idx)?;
1471 symbols.record_adapter_func_idx(meta.mangled.clone(), func_idx);
1472 }
1473
1474 let closure_coercion_targets = if closure_methods.is_some() {
1475 closure_coercions::allocate(&mut symbols, &mut next_func_idx)?
1476 } else {
1477 Vec::new()
1478 };
1479
1480 let mut user_subtypes_alloc: Vec<user_subtypes::UserSubtype> = Vec::new();
1482 for ty in &user_emitted_types {
1483 if let Type::Object { .. } = ty {
1484 user_subtypes_alloc.push(user_subtypes::allocate_methods(ty, &mut next_func_idx)?);
1485 }
1486 }
1487
1488 class_plan.allocate_funcs(&mut next_func_idx, &mut symbols)?;
1490 let instance_field_guards = field_guards::allocate(ta, &mut symbols, &mut next_func_idx)?;
1491 let type_descriptors = runtime_descriptors::allocate(
1492 ta,
1493 &recursive_validators.descriptor_types,
1494 &mut symbols,
1495 &mut next_func_idx,
1496 )?;
1497 runtime_descriptors::prepare_descriptor_data(&mut pool, ta, &mut symbols);
1498
1499 let field_lookup_signature = field_lookup::allocate(
1500 &mut types,
1501 &mut symbols,
1502 &mut next_type_idx,
1503 &mut next_func_idx,
1504 )?;
1505
1506 let object_ref_null = ValType::Ref(RefType {
1509 nullable: true,
1510 heap_type: HeapType::Concrete(intrinsics.object),
1511 });
1512 let raw_array_ref = ValType::Ref(RefType {
1513 nullable: false,
1514 heap_type: HeapType::Concrete(intrinsics.raw_array),
1515 });
1516 let raw_index_array_ref = ValType::Ref(RefType {
1517 nullable: false,
1518 heap_type: HeapType::Concrete(intrinsics.raw_index_array),
1519 });
1520 let mut runtime_validator_sigs: Vec<u32> = Vec::with_capacity(recursive_validators.plans.len());
1521 for plan in &recursive_validators.plans {
1522 let sig_idx = next_type_idx;
1523 crate::codegen::next_index(&mut next_type_idx)?;
1524 types.ty().function(
1525 [
1526 object_ref_null,
1527 raw_array_ref,
1528 raw_index_array_ref,
1529 ValType::I32,
1530 runtime_descriptors::environment_type(&symbols)?,
1531 ],
1532 [ValType::I32],
1533 );
1534 let func_idx = next_func_idx;
1535 crate::codegen::next_index(&mut next_func_idx)?;
1536 symbols.record_runtime_validator(plan.key.clone(), func_idx);
1537 runtime_validator_sigs.push(sig_idx);
1538 }
1539
1540 let finalizer_argument = ta
1541 .finalizer_argument
1542 .as_ref()
1543 .map(|check| {
1544 finalizer_argument::allocate(
1545 check,
1546 &mut types,
1547 &symbols,
1548 &mut next_type_idx,
1549 &mut next_func_idx,
1550 )
1551 })
1552 .transpose()?;
1553
1554 let main_func = ta.functions.iter().find(|f| f.name.name == "main");
1562 let main_return_ty = main_func.map(|f| f.return_type.clone());
1563 let main_output_shim = match main_return_ty.as_ref().map(Type::peel) {
1564 None | Some(Type::Never | Type::Error) => None,
1565 Some(_) => {
1566 let sig_idx = next_type_idx;
1567 types.ty().function(
1568 Vec::<ValType>::new(),
1569 [ValType::Ref(RefType {
1570 nullable: true,
1571 heap_type: HeapType::Concrete(intrinsics.string),
1572 })],
1573 );
1574 let func_idx = next_func_idx;
1576 Some((sig_idx, func_idx))
1577 }
1578 };
1579
1580 let pkg_string_global_idx = if needs_host_object_to_json {
1581 let idx = next_global_idx;
1582 crate::codegen::next_index(&mut next_global_idx)?;
1583 Some(idx)
1584 } else {
1585 None
1586 };
1587
1588 let main_mangled = main_func.map(|f| f.mangled_name.clone());
1589 let main_func_idx = main_mangled
1590 .as_ref()
1591 .map(|mangled| {
1592 symbols.func_idx(mangled).ok_or_else(|| {
1593 crate::codegen::internal_failure("`main` recorded during user-function pre-pass")
1594 })
1595 })
1596 .transpose()?;
1597
1598 module.section(&types);
1599 module.section(&import_section);
1600
1601 let mut functions = FunctionSection::new();
1602 functions.function(start_type_idx);
1603 for f in &user_funcs {
1604 functions.function(f.type_idx);
1605 }
1606 if closure_methods.is_some() {
1607 closures::emit_method_function_entries(&mut functions, intrinsics);
1608 }
1609 for meta in &closure_metas {
1610 let sig_idx = symbols
1611 .closure_func_type_idx(closures::classify(&meta.signature)?)
1612 .ok_or_else(|| {
1613 crate::codegen::internal_failure(
1614 "closures::emit_func_and_struct_types registered the funcref type",
1615 )
1616 })?;
1617 functions.function(sig_idx);
1618 }
1619 for meta in &adapter_metas {
1620 let sig_idx = symbols
1621 .closure_func_type_idx(closures::classify(&meta.signature)?)
1622 .ok_or_else(|| {
1623 crate::codegen::internal_failure(
1624 "closures::emit_func_and_struct_types registered the adapter signature",
1625 )
1626 })?;
1627 functions.function(sig_idx);
1628 }
1629 closure_coercions::emit_entries(&closure_coercion_targets, &mut functions, &symbols)?;
1630 user_subtypes::emit_method_function_entries(&mut functions, &user_subtypes_alloc, intrinsics);
1631 class_plan.emit_function_entries(&mut functions, &symbols, intrinsics)?;
1632 for _ in instance_field_guards
1633 .iter()
1634 .map(|_| ())
1635 .chain(type_descriptors.iter().map(|_| ()))
1636 {
1637 functions.function(
1638 symbols
1639 .closure_func_type_idx(field_guards::signature())
1640 .ok_or_else(|| crate::codegen::internal_failure("guard signature registered"))?,
1641 );
1642 }
1643 functions.function(field_lookup_signature);
1644 for &sig_idx in &runtime_validator_sigs {
1645 functions.function(sig_idx);
1646 }
1647 if let Some(argument) = &finalizer_argument {
1648 functions.function(argument.type_idx);
1649 }
1650 if let Some((sig_idx, _)) = main_output_shim {
1651 functions.function(sig_idx);
1652 }
1653 module.section(&functions);
1654
1655 let mut globals = GlobalSection::new();
1657 let mut globals_count: u32 = 0;
1658 if pkg_string_global_idx.is_some() {
1659 globals.global(
1660 GlobalType {
1661 val_type: ValType::Ref(RefType {
1662 nullable: true,
1663 heap_type: HeapType::Concrete(intrinsics.string),
1664 }),
1665 mutable: true,
1666 shared: false,
1667 },
1668 &ConstExpr::ref_null(HeapType::Concrete(intrinsics.string)),
1669 );
1670 globals_count = globals_count
1671 .checked_add(1)
1672 .ok_or_else(|| crate::codegen::internal_failure("Wasm index count overflow"))?;
1673 }
1674 for g in &ta.globals {
1675 let val_type = global_val_type(&g.ty, &symbols)?;
1676 globals.global(
1677 GlobalType {
1678 val_type,
1679 mutable: true,
1680 shared: false,
1681 },
1682 &default_const_expr(val_type)?,
1683 );
1684 symbols.record_typed_global(g.mangled_name.clone(), next_global_idx, g.ty.clone());
1685 crate::codegen::next_index(&mut next_global_idx)?;
1686 globals_count = globals_count
1687 .checked_add(1)
1688 .ok_or_else(|| crate::codegen::internal_failure("Wasm index count overflow"))?;
1689 }
1690 let init_flags_count =
1691 init_guard::allocate_flags(ta, &mut globals, &mut symbols, &mut next_global_idx)?;
1692 globals_count = globals_count
1693 .checked_add(init_flags_count)
1694 .ok_or_else(|| crate::codegen::internal_failure("Wasm index count overflow"))?;
1695 let vtable_globals_count = wasm_u32(user_subtypes_alloc.len())?;
1696 user_subtypes::emit_vtable_globals(
1697 &mut globals,
1698 &mut user_subtypes_alloc,
1699 &mut symbols,
1700 &mut next_global_idx,
1701 intrinsics,
1702 )?;
1703 let mut field_names_shapes = field_names::collect(&user_emitted_types);
1704 for class_fields in class_plan.field_name_lists() {
1707 if !field_names_shapes.contains(&class_fields) {
1708 field_names_shapes.push(class_fields);
1709 }
1710 }
1711 let string_vtable_global_idx =
1712 symbols.prelude_global_idx("string_vtable").ok_or_else(|| {
1713 crate::codegen::internal_failure(
1714 "string_vtable imported from prelude in the hard-coded import bootstrap",
1715 )
1716 })?;
1717 let field_names_globals_count = wasm_u32(field_names_shapes.len())?;
1718 field_names::emit(
1719 &field_names_shapes,
1720 &mut globals,
1721 &mut symbols,
1722 &mut next_global_idx,
1723 intrinsics,
1724 string_vtable_global_idx,
1725 )?;
1726 let mut extra_field_names = analysis.extra_field_names;
1731 for dependency_type in &dependency_types {
1732 let ty_sym = dependency_type.symbol;
1733 if let TypeKind::Interface {
1734 methods,
1735 properties,
1736 dispatch: crate::Dispatch::VTable,
1737 ..
1738 } = &ty_sym.kind
1739 {
1740 for method_name in methods.keys() {
1741 if dependency_usage.is_interface_member_used(ty_sym, method_name) {
1742 extra_field_names.push(method_name.clone());
1743 }
1744 }
1745 for property_name in properties.keys() {
1746 if dependency_usage.is_interface_member_used(ty_sym, property_name) {
1747 extra_field_names.push(property_name.clone());
1748 }
1749 }
1750 } else if let TypeKind::Class { accessors, .. } = &ty_sym.kind {
1751 for acc in accessors {
1755 extra_field_names.push(acc.name().to_string());
1756 match acc {
1757 crate::AccessorSig::Getter { name, .. } => {
1758 extra_field_names.push(crate::codegen::classes::accessor_getter_name(name));
1759 }
1760 crate::AccessorSig::Setter { name, .. } => {
1761 extra_field_names.push(crate::codegen::classes::accessor_setter_name(name));
1762 }
1763 }
1764 }
1765 }
1766 }
1767 for class_fields in class_plan.field_name_lists() {
1770 extra_field_names.extend(class_fields.into_iter().map(|field| field.name));
1771 }
1772 extra_field_names.extend(class_plan.accessor_property_names());
1775 let field_name_strings = field_name_strings::collect(&user_emitted_types, &extra_field_names);
1776 let field_name_strings_count = wasm_u32(field_name_strings.len())?;
1777 field_name_strings::emit(
1778 &field_name_strings,
1779 &mut globals,
1780 &mut symbols,
1781 &mut next_global_idx,
1782 intrinsics,
1783 string_vtable_global_idx,
1784 )?;
1785 class_plan.emit_globals(&mut globals, &mut symbols, &mut next_global_idx, intrinsics)?;
1787 let mut closure_methods_for_emit = closure_methods;
1788 let closure_vtable_count = if let Some(methods) = closure_methods_for_emit.as_mut() {
1789 closures::emit_vtable_global(
1790 &mut globals,
1791 methods,
1792 &mut symbols,
1793 &mut next_global_idx,
1794 intrinsics,
1795 )?;
1796 1
1797 } else {
1798 0
1799 };
1800 let adapter_closure_globals_count = function_adapters::allocate_closure_globals(
1801 &adapter_metas,
1802 &exported_closure_caches,
1803 &mut globals,
1804 &mut symbols,
1805 &mut next_global_idx,
1806 )?;
1807 let descriptor_globals_count =
1808 runtime_descriptors::allocate_globals(&mut globals, &mut symbols, &mut next_global_idx)?;
1809 if [
1810 adapter_closure_globals_count,
1811 descriptor_globals_count,
1812 globals_count,
1813 vtable_globals_count,
1814 field_names_globals_count,
1815 field_name_strings_count,
1816 closure_vtable_count,
1817 ]
1818 .iter()
1819 .any(|&count| count > 0)
1820 {
1821 module.section(&globals);
1822 }
1823
1824 let mut exports = ExportSection::new();
1825 if let Some(main_func_idx) = main_func_idx {
1826 exports.export("main", WasmExportKind::Func, main_func_idx);
1827 }
1828 if let Some((_, func_idx)) = main_output_shim {
1829 exports.export("__main_output", WasmExportKind::Func, func_idx);
1830 }
1831 if let Some(argument) = &finalizer_argument {
1832 exports.export(
1833 crate::mangle::finalizer_argument(&ta.package_name).as_str(),
1834 WasmExportKind::Func,
1835 argument.func_idx,
1836 );
1837 }
1838 for entry in ta.exports.iter().chain(&ta.test_exports) {
1840 match entry.kind {
1841 crate::ExportKind::Function => {
1842 let func_idx = symbols
1843 .top_level_fn(&entry.target)
1844 .map(|f| f.wasm_idx)
1845 .ok_or_else(|| {
1846 crate::codegen::internal_failure(
1847 "checked package export function target exists",
1848 )
1849 })?;
1850 exports.export(entry.public_name.as_str(), WasmExportKind::Func, func_idx);
1851 }
1852 crate::ExportKind::Global => {
1853 let global_idx = symbols.global_idx(&entry.target).ok_or_else(|| {
1854 crate::codegen::internal_failure("checked package export global target exists")
1855 })?;
1856 exports.export(
1857 entry.public_name.as_str(),
1858 WasmExportKind::Global,
1859 global_idx,
1860 );
1861 }
1862 crate::ExportKind::Type => {}
1863 }
1864 }
1865 let exported_class_mangles: BTreeSet<&MangledName> = ta
1872 .types
1873 .iter()
1874 .filter_map(|decl| match decl {
1875 crate::TypedTypeDecl::Class(class) => Some(&class.mangled_name),
1876 _ => None,
1877 })
1878 .collect();
1879 for (name, func_idx) in
1880 class_plan.exported_funcs(&symbols, |m| exported_class_mangles.contains(m))?
1881 {
1882 exports.export(name.as_str(), WasmExportKind::Func, func_idx);
1883 }
1884 for (name, global_idx) in
1885 class_plan.exported_vtable_globals(&symbols, |m| exported_class_mangles.contains(m))?
1886 {
1887 exports.export(name.as_str(), WasmExportKind::Global, global_idx);
1888 }
1889 for (public_name, function) in &exported_closure_caches {
1890 let global_idx = symbols
1891 .adapter_closure_global_idx(function)
1892 .ok_or_else(|| crate::codegen::internal_failure("exported closure cache allocated"))?;
1893 exports.export(
1894 crate::mangle::closure_cache(public_name).as_str(),
1895 WasmExportKind::Global,
1896 global_idx,
1897 );
1898 }
1899 module.section(&exports);
1900
1901 module.section(&StartSection {
1902 function_index: start_func_idx,
1903 });
1904
1905 let mut declared = user_subtypes::declared_method_funcs(&user_subtypes_alloc);
1907 if let Some(methods) = closure_methods_for_emit {
1908 declared.extend(closures::declared_funcs(methods));
1909 }
1910 for meta in &closure_metas {
1911 let idx = symbols
1912 .closure_func_idx(meta.expr_id)
1913 .ok_or_else(|| crate::codegen::internal_failure("closure func index allocated"))?;
1914 declared.push(idx);
1915 }
1916 for meta in &adapter_metas {
1917 let idx = symbols
1918 .adapter_func_idx(&meta.mangled)
1919 .ok_or_else(|| crate::codegen::internal_failure("adapter func index allocated"))?;
1920 declared.push(idx);
1921 }
1922 declared.extend(
1923 closure_coercion_targets
1924 .iter()
1925 .map(|&sig| {
1926 symbols
1927 .closure_coercion(sig)
1928 .ok_or_else(|| crate::codegen::internal_failure("coercion allocated"))
1929 })
1930 .collect::<Result<Vec<_>, _>>()?,
1931 );
1932 declared.extend(class_plan.declared_funcs(&symbols));
1933 declared.extend(instance_field_guards.iter().map(|guard| guard.function));
1934 declared.extend(type_descriptors.iter().map(|(_, function)| *function));
1935 if !declared.is_empty() {
1936 let mut elements = wasm_encoder::ElementSection::new();
1937 elements.declared(wasm_encoder::Elements::Functions(Cow::Owned(declared)));
1938 module.section(&elements);
1939 }
1940
1941 let total_data_segments = pool
1943 .strings
1944 .len()
1945 .checked_add(bigint_pool.literals.len())
1946 .ok_or_else(|| internal_failure("data segment count overflow"))?;
1947 if total_data_segments > 0 {
1948 module.section(&DataCountSection {
1949 count: wasm_u32(total_data_segments)?,
1950 });
1951 }
1952
1953 let ctx = CodegenCtx {
1954 ta,
1955 strings: &pool,
1956 bigints: &bigint_pool,
1957 symbols: &symbols,
1958 source,
1959 line_index: &line_index,
1960 file,
1961 validator_bodies: &validator_bodies,
1962 type_info: &type_info,
1963 package_string_global_idx: pkg_string_global_idx,
1964 failure: std::cell::Cell::new(None),
1965 validator_steps_left: std::cell::Cell::new(
1966 crate::compiler_limits::MAX_INLINE_VALIDATOR_STEPS,
1967 ),
1968 validator_root: std::cell::Cell::new(None),
1969 check_is_standalone: std::cell::Cell::new(false),
1970 };
1971
1972 let mut code = CodeSection::new();
1973 let mut start_emitter = FunctionEmitter::new(&ctx, &[])?;
1974 if let Some(pkg_string_global_idx) = pkg_string_global_idx {
1975 emit_package_string_init(
1976 &mut start_emitter,
1977 ta.package_name.as_str(),
1978 pkg_string_global_idx,
1979 intrinsics.raw_string,
1980 intrinsics.string,
1981 string_vtable_global_idx,
1982 )?;
1983 }
1984 for (index, &stmt_id) in ctx.ta.top_level_statements.iter().enumerate() {
1985 init_guard::emit_mark_classes_declared(&mut start_emitter, &ctx, index);
1986 emit_statement(&mut start_emitter, &ctx, stmt_id)?;
1987 ctx.check_failure()?;
1988 }
1989 init_guard::emit_mark_classes_declared(
1990 &mut start_emitter,
1991 &ctx,
1992 ctx.ta.top_level_statements.len(),
1993 );
1994 let mut debug_functions = dwarf::DebugFunctions::default();
1995 debug_functions.write(
1996 &mut code,
1997 start_emitter.build_with_lines()?,
1998 TOP_LEVEL_FRAME_NAME.to_string(),
1999 crate::Span::at(ctx.file),
2000 )?;
2001
2002 for func in &user_funcs {
2003 let built = function_emitter::emit_function(
2004 &func.generics,
2005 &ctx,
2006 &func.params,
2007 func.body,
2008 &func.return_type,
2009 )?;
2010 ctx.check_failure()?;
2011 debug_functions.write(&mut code, built, func.name.clone(), func.decl_span)?;
2012 }
2013
2014 if let Some(methods) = closure_methods_for_emit {
2015 closures::emit_method_bodies(&mut code, methods, &symbols)?;
2016 }
2017
2018 for meta in &closure_metas {
2019 let built = function_emitter::emit_closure_function(&ctx, meta)?;
2020 ctx.check_failure()?;
2021 let (name, decl_span) = closure_frame(ta, meta)?;
2022 debug_functions.write(&mut code, built, name, decl_span)?;
2023 }
2024
2025 function_adapters::emit_bodies(&adapter_metas, &mut code, &ctx)?;
2026 closure_coercions::emit_bodies(&closure_coercion_targets, &mut code, &ctx)?;
2027
2028 user_subtypes::emit_method_bodies(
2029 &mut code,
2030 &user_subtypes_alloc,
2031 &symbols,
2032 &type_info,
2033 &type_info_index,
2034 pkg_string_global_idx,
2035 )?;
2036
2037 class_plan.emit_bodies(&mut code, &mut debug_functions, &ctx)?;
2038 ctx.check_failure()?;
2041 for guard in &instance_field_guards {
2042 code.function(&field_guards::body(&ctx, guard)?);
2043 }
2044 for (ty, _) in &type_descriptors {
2045 code.function(&runtime_descriptors::body(&ctx, ty)?);
2046 }
2047
2048 code.function(&field_lookup::body(&ctx)?);
2049 for (validator_id, plan) in recursive_validators.plans.iter().enumerate() {
2050 let validator = ctx.checking_standalone(&plan.key, || {
2051 cast_check::emit_runtime_validator_body(
2052 &ctx,
2053 &plan.key,
2054 &plan.body,
2055 plan.rejects_polymorphic_edge,
2056 i32::try_from(validator_id)
2057 .map_err(|_| internal_failure("validator id exceeds i32"))?,
2058 )
2059 })?;
2060 code.function(&validator);
2061 }
2062 if let Some(argument) = &finalizer_argument {
2063 code.function(&finalizer_argument::body(&ctx, argument)?);
2064 }
2065
2066 if let (Some(_), Some(main_func_idx), Some(main_return_ty)) =
2067 (main_output_shim, main_func_idx, main_return_ty.as_ref())
2068 {
2069 code.function(&function_emitter::json::emit_main_output_shim(
2070 &ctx,
2071 main_func_idx,
2072 main_return_ty,
2073 source_main_return_ty.as_ref().unwrap_or(main_return_ty),
2074 )?);
2075 }
2076
2077 ctx.check_failure()?;
2078 module.section(&code);
2079
2080 let vec_count_size = leb128_u32_size(code.len()) as u64;
2082 let code_content_size = vec_count_size
2083 .checked_add(u64::try_from(code.byte_len()).map_err(|_| index_space_exhausted())?)
2084 .ok_or_else(index_space_exhausted)?;
2085
2086 let funcs = debug_functions.into_debug_info(vec_count_size)?;
2087 for (sect_name, bytes) in
2088 dwarf::build_dwarf(&funcs, code_content_size, filename, &debug_sources)?
2089 {
2090 module.section(&CustomSection {
2091 name: Cow::Borrowed(sect_name),
2092 data: Cow::Owned(bytes),
2093 });
2094 }
2095
2096 {
2100 let mut names = wasm_encoder::NameSection::new();
2101 names.module(owning_package);
2102 module.section(&names);
2103 }
2104
2105 if total_data_segments > 0 {
2106 let mut data = DataSection::new();
2107 for i in 0..pool.strings.len() {
2108 data.passive(pool.utf16_le_bytes(i)?);
2109 }
2110 for i in 0..bigint_pool.literals.len() {
2112 data.passive(bigint_pool.le_bytes(i)?);
2113 }
2114 module.section(&data);
2115 }
2116
2117 Ok(GeneratedModule {
2118 wasm: module.finish(),
2119 type_info,
2120 runtime_functions: runtime_values::signatures(ta),
2121 runtime_globals: runtime_values::global_types(ta),
2122 closure_caches: exported_closure_caches.into_keys().collect(),
2123 })
2124}
2125
2126const TOP_LEVEL_FRAME_NAME: &str = "<top level>";
2130const ANONYMOUS_FRAME_NAME: &str = "<anonymous>";
2131
2132fn closure_frame(
2136 ta: &TypedAst,
2137 meta: &closures::ClosureMeta,
2138) -> Result<(String, crate::Span), CompilerFailure> {
2139 let name = meta
2140 .self_name
2141 .as_ref()
2142 .or_else(|| ta.nested_function_names.get(&meta.expr_id));
2143 if let Some(name) = name {
2144 return Ok((name.name.clone(), name.span));
2145 }
2146 let span = ta.try_expr(meta.expr_id).map_err(arena_failure)?.span;
2147 Ok((ANONYMOUS_FRAME_NAME.to_string(), span))
2148}
2149
2150struct UserFunc {
2151 name: String,
2152 decl_span: crate::Span,
2153 type_idx: u32,
2154 #[allow(dead_code)]
2155 func_idx: u32,
2156 body: StmtId,
2157 return_type: Type,
2158 params: Vec<crate::TypedParam>,
2159 generics: Vec<String>,
2160}
2161
2162fn leb128_u32_size(mut v: u32) -> usize {
2166 let mut size = 0;
2167 loop {
2168 size += 1;
2169 v >>= 7;
2170 if v == 0 {
2171 return size;
2172 }
2173 }
2174}
2175
2176fn json_host_signature(
2177 name: &str,
2178 params: &[Param],
2179 symbols: &SymbolTable,
2180 intrinsics: &intrinsics::IntrinsicTypeIndices,
2181) -> Result<
2182 (Vec<wasm_encoder::ValType>, Vec<wasm_encoder::ValType>),
2183 crate::compiler_error::CompilerFailure,
2184> {
2185 use wasm_encoder::{HeapType, RefType, ValType};
2186
2187 let raw_string_ref = ValType::Ref(RefType {
2188 nullable: false,
2189 heap_type: HeapType::Concrete(intrinsics.raw_string),
2190 });
2191 Ok(match name {
2192 "parse" => (
2194 vec![raw_string_ref],
2195 vec![ValType::Ref(RefType {
2196 nullable: true,
2197 heap_type: HeapType::Concrete(intrinsics.object),
2198 })],
2199 ),
2200 "stringify" => (vec![raw_string_ref], vec![raw_string_ref]),
2201 "stringifyTypedObject" => (
2202 vec![
2203 raw_string_ref,
2204 ValType::I32,
2205 ValType::Ref(RefType {
2206 nullable: false,
2207 heap_type: HeapType::Abstract {
2208 shared: false,
2209 ty: AbstractHeapType::Struct,
2210 },
2211 }),
2212 ],
2213 vec![raw_string_ref],
2214 ),
2215 "stringifyPrettyNumber" => (vec![raw_string_ref, ValType::F64], vec![raw_string_ref]),
2216 "stringifyPrettyString" => (vec![raw_string_ref, raw_string_ref], vec![raw_string_ref]),
2217 _ => (
2218 params
2219 .iter()
2220 .map(|p| symbols.host_value_type(&p.ty))
2221 .collect::<Result<_, _>>()?,
2222 vec![],
2223 ),
2224 })
2225}
2226
2227fn global_val_type(
2234 ty: &Type,
2235 im: &SymbolTable,
2236) -> Result<ValType, crate::compiler_error::CompilerFailure> {
2237 Ok(match im.value_type(ty)? {
2238 ValType::Ref(r) => ValType::Ref(RefType {
2239 nullable: true,
2240 ..r
2241 }),
2242 other => other,
2243 })
2244}
2245
2246fn default_const_expr(val_type: ValType) -> Result<ConstExpr, CompilerFailure> {
2248 Ok(match val_type {
2249 ValType::F64 => ConstExpr::f64_const(Ieee64::from(0.0_f64)),
2250 ValType::I32 => ConstExpr::i32_const(0),
2251 ValType::Ref(RefType { heap_type, .. }) => ConstExpr::ref_null(heap_type),
2252 other => {
2253 return Err(crate::codegen::internal_failure(format!(
2254 "unsupported default constant type {other:?}"
2255 )));
2256 }
2257 })
2258}
2259
2260fn emit_package_string_init(
2261 emitter: &mut FunctionEmitter<'_>,
2262 package_name: &str,
2263 pkg_string_global_idx: u32,
2264 raw_string_type_idx: u32,
2265 string_type_idx: u32,
2266 string_vtable_global_idx: u32,
2267) -> Result<(), CompilerFailure> {
2268 emitter.instruction(Instruction::GlobalGet(string_vtable_global_idx));
2269 for code_unit in package_name.encode_utf16() {
2270 emitter.instruction(Instruction::I32Const(code_unit as i32));
2271 }
2272 emitter.instruction(Instruction::ArrayNewFixed {
2273 array_type_index: raw_string_type_idx,
2274 array_size: wasm_u32(package_name.encode_utf16().count())?,
2275 });
2276 emitter.instruction(Instruction::I64Const(0));
2277 emitter.instruction(Instruction::StructNew(string_type_idx));
2278 emitter.instruction(Instruction::GlobalSet(pkg_string_global_idx));
2279 Ok(())
2280}
2281
2282fn arena_failure(error: crate::arena::ArenaError) -> CompilerFailure {
2283 error.into_compiler_failure(CompilerStage::Codegen)
2284}
2285
2286pub(crate) fn host_property_iface(receiver_ty: &Type) -> Option<crate::MangledName> {
2289 match receiver_ty.lowered() {
2290 Type::InterfaceRef { mangled, .. } => Some(mangled.clone()),
2291 Type::Array(_) => Some(crate::mangle::prelude("Array")),
2292 _ if receiver_ty.array_like_union_element().is_some() => {
2293 Some(crate::mangle::prelude("Array"))
2294 }
2295 _ => None,
2296 }
2297}
2298
2299pub(crate) fn may_hold_builtin_value(receiver_ty: &Type) -> bool {
2302 match receiver_ty.lowered() {
2303 Type::InterfaceRef { .. } | Type::Unknown => true,
2304 Type::Union(members) => members
2305 .iter()
2306 .any(|member| !matches!(member.lowered(), Type::Object { .. })),
2307 _ => false,
2308 }
2309}
2310
2311#[cfg(test)]
2312pub(crate) mod tests {
2313 use super::{SymbolTable, ValueSymbol, codegen, codegen_with_type_info};
2314 use crate::runtime::prelude;
2315 use crate::{
2316 Asi, ModulePath, NamespaceSymbol, ObjectField, PackageDeclaration, Sources, Token,
2317 TokenKind, Type, TypedAst, capture, check, desugar, infer, infer_package, lower_patterns,
2318 parse,
2319 };
2320 use wasmparser::{Parser, Payload};
2321
2322 #[test]
2323 fn missing_record_import_returns_an_internal_error_without_wasm() {
2324 let source = "function main(): number | undefined { const d: Record<string, number> = {}; const key: string = 'x'; return d[key]; }";
2325 let ta = type_check(source);
2326 let (prelude_defs, host_defs, internal_defs) =
2327 prelude::cached_runtime_package_declarations();
2328 let mut dependencies: Vec<_> = prelude_defs
2329 .iter()
2330 .chain(host_defs.iter())
2331 .chain(internal_defs.iter())
2332 .cloned()
2333 .collect();
2334 for declaration in &mut dependencies {
2335 declaration.values.retain(|_, value| {
2336 !value
2337 .mangled_name
2338 .as_str()
2339 .ends_with("ObjectConstructor##getField")
2340 });
2341 }
2342 let refs: Vec<_> = dependencies.iter().collect();
2343 let error = codegen_with_type_info(source, "record.ts", crate::FileId(0), &ta, &refs)
2344 .expect_err("missing internal import must not return a module");
2345 assert!(
2346 error.to_string().contains("dynamic read imported"),
2347 "{error}"
2348 );
2349 super::tests::compile("function main(): number { return 42; }");
2350 }
2351
2352 pub(crate) fn type_check(source: &str) -> TypedAst {
2353 type_check_with_packages(source, &[])
2354 }
2355
2356 fn type_check_with_packages(source: &str, packages: &[&PackageDeclaration]) -> TypedAst {
2357 let mut asi = Asi::new(source, crate::FileId(0));
2358 let mut tokens: Vec<Token> = Vec::new();
2359 loop {
2360 let tok = asi.next_token();
2361 let is_eof = matches!(tok.kind, TokenKind::Eof);
2362 tokens.push(tok);
2363 if is_eof {
2364 break;
2365 }
2366 }
2367 let lex_diags = asi.into_diagnostics();
2368 assert!(
2369 lex_diags.is_empty(),
2370 "unexpected lexer diags: {lex_diags:?}"
2371 );
2372 let (ast, parse_diags) = parse(source, tokens, crate::FileId(0));
2373 assert!(
2374 parse_diags.is_empty(),
2375 "unexpected parser diags: {parse_diags:?}"
2376 );
2377 let packages = runtime_packages(packages);
2378 let (mut ta, mut diags) = infer(source, "main", &ast, &packages);
2379 diags.extend(check(&ta).unwrap());
2380 ta = capture(ta).unwrap();
2381 ta = desugar(ta, crate::FileId(0)).unwrap();
2382 assert!(diags.is_empty(), "unexpected typecheck diags: {diags:?}");
2383 ta
2384 }
2385
2386 fn runtime_packages<'a>(packages: &[&'a PackageDeclaration]) -> Vec<&'a PackageDeclaration> {
2387 let (prelude_defs, host_defs, _) = prelude::cached_runtime_package_declarations();
2388 let mut out = Vec::with_capacity(prelude_defs.len() + host_defs.len() + packages.len());
2389 out.extend(prelude_defs.iter());
2390 out.extend(host_defs.iter());
2391 out.extend(packages.iter().copied());
2392 out
2393 }
2394
2395 fn infer_with_runtime_packages(source: &str, ast: &crate::Ast) -> Vec<crate::Diagnostic> {
2396 let packages = runtime_packages(&[]);
2397 let (_, diags) = infer(source, "main", ast, &packages);
2398 diags
2399 }
2400
2401 fn parse_module(
2402 sources: &mut Sources,
2403 module: &str,
2404 source: &str,
2405 ) -> (ModulePath, crate::FileId, crate::Ast) {
2406 let file = sources.add(module.to_string(), source).unwrap();
2407 let mut asi = Asi::new(source, file);
2408 let mut tokens: Vec<Token> = Vec::new();
2409 loop {
2410 let tok = asi.next_token();
2411 let is_eof = matches!(tok.kind, TokenKind::Eof);
2412 tokens.push(tok);
2413 if is_eof {
2414 break;
2415 }
2416 }
2417 let lex_diags = asi.into_diagnostics();
2418 assert!(
2419 lex_diags.is_empty(),
2420 "unexpected lexer diags: {lex_diags:?}"
2421 );
2422 let (mut ast, parse_diags) = parse(source, tokens, file);
2423 assert!(
2424 parse_diags.is_empty(),
2425 "unexpected parser diags: {parse_diags:?}"
2426 );
2427 ast = lower_patterns(ast).unwrap();
2428 (ModulePath::from(module), file, ast)
2429 }
2430
2431 pub(crate) fn compile_package_modules(
2432 package_name: &str,
2433 modules: &[(&str, &str)],
2434 packages: &[&PackageDeclaration],
2435 ) -> (Vec<u8>, PackageDeclaration, crate::TypeInfoTable) {
2436 let mut sources = Sources::new();
2437 let owned_modules: Vec<_> = modules
2438 .iter()
2439 .map(|(module, source)| parse_module(&mut sources, module, source))
2440 .collect();
2441 let module_refs: Vec<_> = owned_modules
2442 .iter()
2443 .map(|(module, file, ast)| (module.clone(), *file, ast))
2444 .collect();
2445 let stdlib_defs = crate::runtime::stdlib_package_declarations();
2446 let mut external_packages: std::collections::BTreeMap<String, PackageDeclaration> =
2447 stdlib_defs
2448 .into_iter()
2449 .map(|defs| (defs.package_name.clone(), defs))
2450 .collect();
2451 let (prelude_defs, host_defs, _) = prelude::cached_runtime_package_declarations();
2452 for defs in prelude_defs {
2453 external_packages.insert(defs.package_name.clone(), defs.clone());
2454 }
2455 for defs in host_defs {
2456 external_packages.insert(defs.package_name.clone(), defs.clone());
2457 }
2458 for defs in packages {
2459 external_packages.insert(defs.package_name.clone(), (*defs).clone());
2460 }
2461 let (mut ta, mut package, diags) = infer_package(
2462 package_name,
2463 ModulePath::from("lib"),
2464 module_refs,
2465 &sources,
2466 external_packages,
2467 std::collections::BTreeMap::new(),
2468 );
2469 assert!(diags.is_empty(), "unexpected package diags: {diags:?}");
2470 ta = capture(ta).unwrap();
2471 let root_file = owned_modules
2472 .iter()
2473 .find(|(module, _, _)| module.as_str() == "lib")
2474 .map(|(_, file, _)| *file)
2475 .expect("test package has lib module");
2476 ta = desugar(ta, root_file).unwrap();
2477
2478 let (prelude_defs, host_defs, internal_defs) =
2479 prelude::cached_runtime_package_declarations();
2480 let stdlib_defs = crate::runtime::stdlib_package_declarations();
2481 let mut dependencies: Vec<&PackageDeclaration> = prelude_defs.iter().collect();
2482 dependencies.extend(host_defs.iter());
2483 dependencies.extend(internal_defs.iter());
2484 dependencies.extend(stdlib_defs.iter());
2485 dependencies.extend_from_slice(packages);
2486 let generated =
2487 super::codegen_package_with_type_info(&sources, root_file, &ta, &dependencies)
2488 .expect("code generation");
2489 package.runtime_functions = generated.runtime_functions;
2490 package.runtime_globals = generated.runtime_globals;
2491 package.closure_caches = generated.closure_caches;
2492 (generated.wasm, package, generated.type_info)
2493 }
2494
2495 pub(crate) fn compile(source: &str) -> Vec<u8> {
2496 crate::compile::compile_script(source, "script.subm", crate::FileId(0), &[], &[])
2497 .expect("test-only compile expects no diagnostics")
2498 .wasm
2499 }
2500
2501 #[test]
2502 fn compiled_type_info_includes_stringify_object_shapes() {
2503 let source = r#"
2504function main(): string {
2505 const original = "{\"id\":1,\"name\":\"alice\"}";
2506 const u = JSON.parse(original) as { id: number, name: string };
2507 return JSON.stringify(u);
2508}
2509"#;
2510 let compiled =
2511 crate::compile::compile_script(source, "script.subm", crate::FileId(0), &[], &[])
2512 .expect("script should compile");
2513 let ty = Type::Object {
2514 index: None,
2515 fields: std::collections::BTreeMap::from([
2516 ("id".to_string(), ObjectField::required(Type::Number)),
2517 ("name".to_string(), ObjectField::required(Type::String)),
2518 ]),
2519 };
2520
2521 let id = compiled
2522 .type_info
2523 .object_type_id(&ty)
2524 .expect("compiled TypeInfo should contain the object stringify target");
2525 let info = compiled.type_info.get(id).expect("TypeInfo id is valid");
2526 assert!(matches!(
2527 &info.kind,
2528 crate::TypeInfoKind::Object { fields } if fields.len() == 2
2529 ));
2530
2531 let ta = type_check(source);
2532 let emitted = super::user_subtypes::collect_object_shapes(std::iter::empty(), &ta.shapes);
2533 assert!(
2534 emitted
2535 .iter()
2536 .all(|ty| compiled.type_info.object_type_id(ty).is_some()),
2537 "every emitted object subtype should have TypeInfo"
2538 );
2539 }
2540
2541 fn build_symbol_table(source: &str) -> SymbolTable {
2542 let _ta = type_check(source);
2543 let prelude_defs = prelude::prelude_package_declaration();
2544 let dependencies: [&crate::PackageDeclaration; 1] = [&prelude_defs];
2545 let mut map = SymbolTable::default();
2546 let mut next_type_idx: u32 = 0;
2547 let mut next_func_idx: u32 = 0;
2548 let mut next_global_idx: u32 = 0;
2549 map.set_intrinsic_type_indices(super::intrinsics::IntrinsicTypeIndices {
2550 raw_string: 0,
2551 vtable: 1,
2552 object: 2,
2553 undefined: 51,
2554 type_descriptor: 52,
2555 string: 3,
2556 boxed_number: 4,
2557 boxed_boolean: 5,
2558 field_names: 6,
2559 object_fields: 7,
2560 object_shape: 8,
2561 to_string_fn: 9,
2562 to_json_fn: 10,
2563 equals_fn: 11,
2564 hash_fn: 12,
2565 field_getter: 13,
2566 field_setter: 14,
2567 raw_array: 15,
2568 array: 16,
2569 raw_uint8_array: 17,
2570 uint8_array: 18,
2571 closure: 19,
2572 class_vtable: 20,
2573 error_vtable: 21,
2574 error: 22,
2575 raw_bigint: 23,
2576 bigint: 24,
2577 regex_capture_array: 25,
2578 regex_match: 26,
2579 regex: 27,
2580 regex_match_box: 28,
2581 temporal_instant: 29,
2582 temporal_duration: 30,
2583 temporal_zdt: 31,
2584 raw_index_array: 32,
2585 map: 33,
2586 set: 34,
2587 url: 35,
2588 fs_stat: 41,
2589 fs_peek: 42,
2590 fs_dir_entry: 43,
2591 fs_info: 44,
2592 fs_file_writer: 45,
2593 http_response: 46,
2594 http_download_result: 47,
2595 session_entry: 48,
2596 session_page: 49,
2597 fs_mount_info: 50,
2598
2599 temporal_plain_date: 36,
2600 temporal_plain_time: 37,
2601 temporal_plain_date_time: 38,
2602 temporal_plain_year_month: 39,
2603 temporal_plain_month_day: 40,
2604 });
2605 next_type_idx += super::intrinsics::INTRINSIC_TYPE_COUNT;
2606 let _ = next_type_idx;
2607 for defs in &dependencies {
2608 for value in defs.values.values() {
2609 record(&mut map, value, &mut next_func_idx, &mut next_global_idx);
2610 }
2611 walk_test_namespaces(
2612 &defs.namespaces,
2613 &mut map,
2614 &mut next_func_idx,
2615 &mut next_global_idx,
2616 );
2617 }
2618 map
2619 }
2620
2621 fn record(
2622 map: &mut SymbolTable,
2623 value: &ValueSymbol,
2624 next_func_idx: &mut u32,
2625 next_global_idx: &mut u32,
2626 ) {
2627 match &value.kind {
2628 crate::ValueKind::Function { .. } => {
2629 map.record_func(value.mangled_name.clone(), *next_func_idx);
2630 *next_func_idx += 1;
2631 }
2632 crate::ValueKind::Let { .. } | crate::ValueKind::Const { .. } => {
2633 map.record_global(value.mangled_name.clone(), *next_global_idx);
2634 *next_global_idx += 1;
2635 }
2636 }
2637 }
2638
2639 fn walk_test_namespaces(
2640 map_in: &std::collections::BTreeMap<String, NamespaceSymbol>,
2641 map: &mut SymbolTable,
2642 next_func_idx: &mut u32,
2643 next_global_idx: &mut u32,
2644 ) {
2645 for ns in map_in.values() {
2646 for value in ns.values.values() {
2647 record(map, value, next_func_idx, next_global_idx);
2648 }
2649 walk_test_namespaces(&ns.namespaces, map, next_func_idx, next_global_idx);
2650 }
2651 }
2652
2653 fn function_count(bytes: &[u8]) -> usize {
2654 for payload in Parser::new(0).parse_all(bytes) {
2655 if let Payload::FunctionSection(reader) = payload.expect("payload") {
2656 return reader.count() as usize;
2657 }
2658 }
2659 0
2660 }
2661
2662 fn imports(bytes: &[u8]) -> Vec<(String, String)> {
2663 let mut out = Vec::new();
2664 for payload in Parser::new(0).parse_all(bytes) {
2665 if let Payload::ImportSection(reader) = payload.expect("payload") {
2666 for entry in reader {
2667 match entry.expect("import") {
2668 wasmparser::Imports::Single(_, imp) => {
2669 out.push((imp.module.to_string(), imp.name.to_string()));
2670 }
2671 other => panic!("unexpected import form {other:?}"),
2672 }
2673 }
2674 }
2675 }
2676 out
2677 }
2678
2679 fn global_count(bytes: &[u8]) -> usize {
2680 for payload in Parser::new(0).parse_all(bytes) {
2681 if let Payload::GlobalSection(reader) = payload.expect("payload") {
2682 return reader.count() as usize;
2683 }
2684 }
2685 0
2686 }
2687
2688 fn global_decls(bytes: &[u8]) -> Vec<(bool, &'static str)> {
2689 let mut out = Vec::new();
2690 for payload in Parser::new(0).parse_all(bytes) {
2691 if let Payload::GlobalSection(reader) = payload.expect("payload") {
2692 for g in reader {
2693 let g = g.expect("global");
2694 let kind = match g.ty.content_type {
2695 wasmparser::ValType::F64 => "f64",
2696 wasmparser::ValType::I32 => "i32",
2697 wasmparser::ValType::Ref(_) => "ref",
2698 _ => "other",
2699 };
2700 out.push((g.ty.mutable, kind));
2701 }
2702 }
2703 }
2704 out
2705 }
2706
2707 fn start_function_idx(bytes: &[u8]) -> Option<u32> {
2708 for payload in Parser::new(0).parse_all(bytes) {
2709 if let Payload::StartSection { func, .. } = payload.expect("payload") {
2710 return Some(func);
2711 }
2712 }
2713 None
2714 }
2715
2716 fn data_count(bytes: &[u8]) -> Option<u32> {
2717 for payload in Parser::new(0).parse_all(bytes) {
2718 if let Payload::DataCountSection { count, .. } = payload.expect("payload") {
2719 return Some(count);
2720 }
2721 }
2722 None
2723 }
2724
2725 fn data_segments(bytes: &[u8]) -> Vec<Vec<u8>> {
2726 let mut out = Vec::new();
2727 for payload in Parser::new(0).parse_all(bytes) {
2728 if let Payload::DataSection(reader) = payload.expect("payload") {
2729 for d in reader {
2730 let d = d.expect("data segment");
2731 out.push(d.data.to_vec());
2732 }
2733 }
2734 }
2735 out
2736 }
2737
2738 fn module_name(bytes: &[u8]) -> Option<String> {
2742 let engine = crate::runtime::RuntimeConfig::default()
2743 .engine()
2744 .expect("engine");
2745 let module = wasmtime::Module::new(&engine, bytes).expect("module validates");
2746 module.name().map(str::to_string)
2747 }
2748
2749 #[test]
2750 fn package_module_is_named_for_its_package() {
2751 let (bytes, _decl, _ti) = compile_package_modules(
2752 "test:pkg",
2753 &[(
2754 "lib",
2755 "/** Identity.\n * @param x Value to return.\n * @returns `x`. */\nexport function noop(x: number): number { return x; }",
2756 )],
2757 &[],
2758 );
2759 assert_eq!(module_name(&bytes).as_deref(), Some("test:pkg"));
2760 }
2761
2762 #[test]
2763 fn script_module_is_named_main() {
2764 let bytes = compile("function main(): number { return 1; }");
2765 assert_eq!(module_name(&bytes).as_deref(), Some("main"));
2766 }
2767
2768 #[test]
2773 fn test_file_module_is_named_for_its_owning_package() {
2774 let source = "function main(): number { return 1; }";
2775 let compiled = crate::compile::compile_script_owned_by(
2776 "test:pkg",
2777 source,
2778 "tests/thing.test.ts",
2779 crate::FileId(0),
2780 &[],
2781 &[],
2782 )
2783 .expect("compiles");
2784 assert_eq!(module_name(&compiled.wasm).as_deref(), Some("test:pkg"));
2785
2786 assert!(
2787 main_export_func_idx(&compiled.wasm).is_some(),
2788 "entry point must stay exported as `main` — the module name is identity, not mangling"
2789 );
2790 }
2791
2792 struct RecordingAllowAll {
2796 seen: std::sync::Mutex<Vec<(String, String)>>,
2797 }
2798
2799 impl crate::runtime::SecurityCheck for RecordingAllowAll {
2800 fn check(
2801 &self,
2802 caller: &str,
2803 capability: &str,
2804 _context: &serde_json::Value,
2805 ) -> crate::runtime::CheckOutcome {
2806 self.seen
2807 .lock()
2808 .expect("mutex")
2809 .push((caller.to_string(), capability.to_string()));
2810 crate::runtime::CheckOutcome::Allow { rule: None }
2811 }
2812 }
2813
2814 struct FixedSecret;
2815
2816 impl crate::runtime::SecretProvider for FixedSecret {
2817 fn get<'a>(
2818 &'a self,
2819 _name: &'a str,
2820 ) -> std::pin::Pin<
2821 Box<dyn std::future::Future<Output = Result<Option<String>, String>> + Send + 'a>,
2822 > {
2823 Box::pin(async { Ok(Some("SECRET-VALUE".to_string())) })
2824 }
2825 }
2826
2827 #[tokio::test]
2836 async fn main_authored_code_keeps_main_identity_inside_a_package_call() {
2837 let (lib_bytes, lib_decl, lib_type_info) = compile_package_modules(
2838 "test:vuln",
2839 &[(
2840 "lib",
2841 r#"
2842 /**
2843 * Pass-through JSON encoder.
2844 * @param value Value to encode.
2845 * @returns `value` as JSON.
2846 */
2847 export function passthrough(value: unknown): string | undefined {
2848 return JSON.stringify(value);
2849 }
2850 "#,
2851 )],
2852 &[],
2853 );
2854
2855 let recording = std::sync::Arc::new(RecordingAllowAll {
2856 seen: std::sync::Mutex::new(Vec::new()),
2857 });
2858 let cfg = crate::runtime::RuntimeConfig::default();
2859 let engine = cfg.engine().expect("engine");
2860 let mut data = crate::runtime::StoreData::with_vfs(
2861 "test-run".into(),
2862 crate::runtime::Vfs::tempdir().expect("tempdir"),
2863 );
2864 data.install_type_info(lib_type_info);
2865 data.security_check = recording.clone();
2866 data.secret_provider = std::sync::Arc::new(FixedSecret);
2867 let mut store = cfg.store(&engine, data).expect("store");
2868 let mut linker = wasmtime::Linker::<crate::runtime::StoreData>::new(&engine);
2869 crate::runtime::install_runtime_async(&mut linker, &mut store)
2870 .await
2871 .expect("install runtime");
2872 let lib_module = wasmtime::Module::new(&engine, &lib_bytes).expect("library module");
2873 let lib_inst = linker
2874 .instantiate_async(&mut store, &lib_module)
2875 .await
2876 .expect("instantiate library");
2877 linker
2878 .instance(&mut store, "test:vuln", lib_inst)
2879 .expect("register library instance");
2880 let public_name = crate::mangle::package_symbol("test:vuln", "passthrough");
2881 let func = lib_inst
2882 .get_func(&mut store, public_name.as_str())
2883 .expect("library public export");
2884 linker
2885 .define(&mut store, "test:vuln", "passthrough", func)
2886 .expect("plain package import alias");
2887
2888 let direct = crate::compile::compile_script(
2890 r#"
2891 import { get } from "submilli:secrets";
2892 function main(): void { const _ = get("TOKEN"); }
2893 "#,
2894 "direct.subm",
2895 crate::FileId(0),
2896 &[&lib_decl],
2897 &[],
2898 )
2899 .expect("direct consumer compiles");
2900 store.data_mut().install_type_info(direct.type_info.clone());
2901 let direct_module = wasmtime::Module::new(&engine, &direct.wasm).expect("direct module");
2902 let direct_inst = linker
2903 .instantiate_async(&mut store, &direct_module)
2904 .await
2905 .expect("instantiate direct consumer");
2906 let err = crate::runtime::dispatch_main_async(&mut store, &direct_inst)
2907 .await
2908 .expect_err("direct read is denied");
2909 assert!(
2910 err.to_string().contains("permission denied"),
2911 "expected denial, got: {err}",
2912 );
2913
2914 let escalated = crate::compile::compile_script(
2916 r#"
2917 import { passthrough } from "test:vuln";
2918 import { get } from "submilli:secrets";
2919
2920 class Exfil {
2921 stolen: string;
2922 constructor() { this.stolen = "none"; }
2923 toJson(): string {
2924 const token = get("TOKEN");
2925 if (token !== undefined) { this.stolen = token; }
2926 return "\"ok\"";
2927 }
2928 }
2929
2930 function main(): string {
2931 const payload = new Exfil();
2932 const _ = passthrough(payload);
2933 return payload.stolen;
2934 }
2935 "#,
2936 "escalated.subm",
2937 crate::FileId(0),
2938 &[&lib_decl],
2939 &[],
2940 )
2941 .expect("escalating consumer compiles");
2942 store
2943 .data_mut()
2944 .install_type_info(escalated.type_info.clone());
2945 let escalated_module =
2946 wasmtime::Module::new(&engine, &escalated.wasm).expect("escalated module");
2947 let escalated_inst = linker
2948 .instantiate_async(&mut store, &escalated_module)
2949 .await
2950 .expect("instantiate escalating consumer");
2951 let result = crate::runtime::dispatch_main_async(&mut store, &escalated_inst).await;
2952 let seen = recording.seen.lock().expect("mutex").clone();
2953
2954 assert!(
2957 !seen.iter().any(|(caller, _)| caller == "test:vuln"),
2958 "main-authored code was attributed to the package it was handed to: {seen:?}",
2959 );
2960 assert!(
2961 result.is_err(),
2962 "main must not obtain a secret it is unconditionally denied; got {result:?}",
2963 );
2964 }
2965
2966 async fn attributions_for(
2971 package: &str,
2972 lib_bytes: &[u8],
2973 lib_decl: &PackageDeclaration,
2974 lib_type_info: crate::TypeInfoTable,
2975 consumer_src: &str,
2976 ) -> (Vec<(String, String)>, bool) {
2977 let recording = std::sync::Arc::new(RecordingAllowAll {
2978 seen: std::sync::Mutex::new(Vec::new()),
2979 });
2980 let cfg = crate::runtime::RuntimeConfig::default();
2981 let engine = cfg.engine().expect("engine");
2982 let mut data = crate::runtime::StoreData::with_vfs(
2983 "test-run".into(),
2984 crate::runtime::Vfs::tempdir().expect("tempdir"),
2985 );
2986 data.install_type_info(lib_type_info);
2987 data.security_check = recording.clone();
2988 data.secret_provider = std::sync::Arc::new(FixedSecret);
2989 let mut store = cfg.store(&engine, data).expect("store");
2990 let mut linker = wasmtime::Linker::<crate::runtime::StoreData>::new(&engine);
2991 crate::runtime::install_runtime_async(&mut linker, &mut store)
2992 .await
2993 .expect("install runtime");
2994 let lib_module = wasmtime::Module::new(&engine, lib_bytes).expect("library module");
2995 let lib_inst = linker
2996 .instantiate_async(&mut store, &lib_module)
2997 .await
2998 .expect("instantiate library");
2999 linker
3000 .instance(&mut store, package, lib_inst)
3001 .expect("register library instance");
3002
3003 let consumer = crate::compile::compile_script(
3004 consumer_src,
3005 "consumer.subm",
3006 crate::FileId(0),
3007 &[lib_decl],
3008 &[],
3009 )
3010 .expect("consumer compiles");
3011 store
3012 .data_mut()
3013 .install_type_info(consumer.type_info.clone());
3014 let consumer_module = wasmtime::Module::new(&engine, &consumer.wasm).expect("module");
3015 let inst = linker
3016 .instantiate_async(&mut store, &consumer_module)
3017 .await
3018 .expect("instantiate consumer");
3019 let ran_ok = crate::runtime::dispatch_main_async(&mut store, &inst)
3020 .await
3021 .is_ok();
3022 (recording.seen.lock().expect("mutex").clone(), ran_ok)
3023 }
3024
3025 #[tokio::test]
3029 async fn main_authored_tojson_inside_an_array_keeps_main_identity() {
3030 let (lib_bytes, lib_decl, lib_type_info) = compile_package_modules(
3031 "test:arr",
3032 &[(
3033 "lib",
3034 r#"
3035 /**
3036 * Pass-through JSON encoder for a collection.
3037 * @param values Values to encode.
3038 * @returns `values` as JSON.
3039 */
3040 export function passthroughAll(values: unknown[]): string {
3041 return JSON.stringify(values);
3042 }
3043 "#,
3044 )],
3045 &[],
3046 );
3047 let (seen, ran_ok) = attributions_for(
3048 "test:arr",
3049 &lib_bytes,
3050 &lib_decl,
3051 lib_type_info,
3052 r#"
3053 import { passthroughAll } from "test:arr";
3054 import { get } from "submilli:secrets";
3055
3056 class Exfil {
3057 stolen: string;
3058 constructor() { this.stolen = "none"; }
3059 toJson(): string {
3060 const t = get("TOKEN");
3061 if (t !== undefined) { this.stolen = t; }
3062 return "\"ok\"";
3063 }
3064 }
3065
3066 function main(): string {
3067 const payload = new Exfil();
3068 const _ = passthroughAll([payload]);
3069 return payload.stolen;
3070 }
3071 "#,
3072 )
3073 .await;
3074 assert!(
3075 !seen.iter().any(|(caller, _)| caller == "test:arr"),
3076 "an element's toJson is main's code wherever the package serializes it: {seen:?}",
3077 );
3078 assert!(
3079 !ran_ok,
3080 "identified as main, the read must be refused rather than completing",
3081 );
3082 }
3083
3084 #[tokio::test]
3090 async fn a_packages_exported_class_method_is_attributed_to_the_package() {
3091 let (lib_bytes, lib_decl, lib_type_info) = compile_package_modules(
3092 "test:cls",
3093 &[(
3094 "lib",
3095 r#"
3096 import { get } from "submilli:secrets";
3097
3098 /** Resolves a credential from inside a class method. */
3099 export class Client {
3100 token: string;
3101 constructor() { this.token = "none"; }
3102
3103 /** Reads the token.
3104 * @returns The credential, or "none" when it is absent.
3105 */
3106 load(): string {
3107 const t = get("TOKEN");
3108 return t === undefined ? "none" : t;
3109 }
3110 }
3111 "#,
3112 )],
3113 &[],
3114 );
3115 let (seen, ran_ok) = attributions_for(
3116 "test:cls",
3117 &lib_bytes,
3118 &lib_decl,
3119 lib_type_info,
3120 r#"
3121 import { Client } from "test:cls";
3122 function main(): string {
3123 const c = new Client();
3124 return c.load();
3125 }
3126 "#,
3127 )
3128 .await;
3129 assert!(ran_ok, "the package is permitted, so main must complete");
3130 assert_eq!(
3131 seen,
3132 vec![("test:cls".to_string(), "secrets.get".to_string())],
3133 "a class method's gated call belongs to the package that defines it",
3134 );
3135 }
3136
3137 #[tokio::test]
3142 async fn a_packages_initializer_is_attributed_to_the_package() {
3143 let (lib_bytes, lib_decl, lib_type_info) = compile_package_modules(
3144 "test:init",
3145 &[(
3146 "lib",
3147 r#"
3148 import { get } from "submilli:secrets";
3149
3150 const TOKEN: string | undefined = get("TOKEN");
3151
3152 /**
3153 * Reports whether the module-level read succeeded.
3154 * @returns Whether the read succeeded.
3155 */
3156 export function loaded(): boolean {
3157 return TOKEN !== undefined;
3158 }
3159 "#,
3160 )],
3161 &[],
3162 );
3163 let (seen, ran_ok) = attributions_for(
3164 "test:init",
3165 &lib_bytes,
3166 &lib_decl,
3167 lib_type_info,
3168 r#"
3169 import { loaded } from "test:init";
3170 function main(): boolean { return loaded(); }
3171 "#,
3172 )
3173 .await;
3174 assert!(ran_ok, "the package is permitted, so main must complete");
3175 assert_eq!(
3176 seen,
3177 vec![("test:init".to_string(), "secrets.get".to_string())],
3178 "a package initializer's gated call belongs to the package",
3179 );
3180 }
3181
3182 fn custom_section(bytes: &[u8], name: &str) -> Option<Vec<u8>> {
3183 for payload in Parser::new(0).parse_all(bytes) {
3184 if let Payload::CustomSection(reader) = payload.expect("payload")
3185 && reader.name() == name
3186 {
3187 return Some(reader.data().to_vec());
3188 }
3189 }
3190 None
3191 }
3192
3193 fn export_names(bytes: &[u8]) -> Vec<String> {
3194 let mut names = Vec::new();
3195 for payload in Parser::new(0).parse_all(bytes) {
3196 if let Payload::ExportSection(reader) = payload.expect("payload") {
3197 for export in reader {
3198 names.push(export.expect("export").name.to_string());
3199 }
3200 }
3201 }
3202 names
3203 }
3204
3205 fn export_entries(bytes: &[u8]) -> Vec<(String, wasmparser::ExternalKind, u32)> {
3206 let mut exports = Vec::new();
3207 for payload in Parser::new(0).parse_all(bytes) {
3208 if let Payload::ExportSection(reader) = payload.expect("payload") {
3209 for export in reader {
3210 let export = export.expect("export");
3211 exports.push((export.name.to_string(), export.kind, export.index));
3212 }
3213 }
3214 }
3215 exports
3216 }
3217
3218 fn imported_func_count(bytes: &[u8]) -> u32 {
3219 let mut count = 0;
3220 for payload in Parser::new(0).parse_all(bytes) {
3221 if let Payload::ImportSection(reader) = payload.expect("payload") {
3222 for entry in reader {
3223 if let wasmparser::Imports::Single(_, imp) = entry.expect("import")
3224 && matches!(imp.ty, wasmparser::TypeRef::Func(_))
3225 {
3226 count += 1;
3227 }
3228 }
3229 }
3230 }
3231 count
3232 }
3233
3234 fn imported_func_idx(bytes: &[u8], field: &str) -> u32 {
3236 let mut count = 0;
3237 for payload in Parser::new(0).parse_all(bytes) {
3238 if let Payload::ImportSection(reader) = payload.expect("payload") {
3239 for entry in reader {
3240 if let wasmparser::Imports::Single(_, imp) = entry.expect("import")
3241 && matches!(imp.ty, wasmparser::TypeRef::Func(_))
3242 {
3243 if imp.name == field {
3244 return count;
3245 }
3246 count += 1;
3247 }
3248 }
3249 }
3250 }
3251 panic!("no func import named {field}");
3252 }
3253
3254 fn body_call_targets(bytes: &[u8], body_idx: usize) -> Vec<u32> {
3255 let mut bodies_seen = 0;
3256 let mut targets = Vec::new();
3257 for payload in Parser::new(0).parse_all(bytes) {
3258 if let Payload::CodeSectionEntry(body) = payload.expect("payload") {
3259 if bodies_seen == body_idx {
3260 let mut reader = body
3261 .get_operators_reader()
3262 .expect("body operators readable");
3263 while !reader.eof() {
3264 if let wasmparser::Operator::Call { function_index } =
3265 reader.read().expect("operator")
3266 {
3267 targets.push(function_index);
3268 }
3269 }
3270 return targets;
3271 }
3272 bodies_seen += 1;
3273 }
3274 }
3275 targets
3276 }
3277
3278 fn main_body_call_targets(bytes: &[u8]) -> Vec<u32> {
3279 let main_idx = main_export_func_idx(bytes).expect("main exported");
3280 let body_idx = (main_idx - imported_func_count(bytes)) as usize;
3281 body_call_targets(bytes, body_idx)
3282 }
3283
3284 fn main_export_func_idx(bytes: &[u8]) -> Option<u32> {
3285 for payload in Parser::new(0).parse_all(bytes) {
3286 if let Payload::ExportSection(reader) = payload.expect("payload") {
3287 for export in reader {
3288 let export = export.expect("export");
3289 if export.name == "main"
3290 && matches!(export.kind, wasmparser::ExternalKind::Func)
3291 {
3292 return Some(export.index);
3293 }
3294 }
3295 }
3296 }
3297 None
3298 }
3299
3300 fn instantiate_against_prelude(consumer_bytes: &[u8]) {
3301 let _ = link_consumer(consumer_bytes);
3302 }
3303
3304 fn link_consumer(
3305 consumer_bytes: &[u8],
3306 ) -> (
3307 wasmtime::Store<crate::runtime::StoreData>,
3308 wasmtime::Instance,
3309 ) {
3310 let cfg = crate::runtime::RuntimeConfig::default();
3311 link_consumer_with(&cfg, consumer_bytes)
3312 }
3313
3314 fn link_consumer_with(
3315 cfg: &crate::runtime::RuntimeConfig,
3316 consumer_bytes: &[u8],
3317 ) -> (
3318 wasmtime::Store<crate::runtime::StoreData>,
3319 wasmtime::Instance,
3320 ) {
3321 let engine = cfg.engine().expect("engine builds with default config");
3322 let mut store = cfg
3323 .store(
3324 &engine,
3325 crate::runtime::StoreData::with_tempdir()
3326 .expect("tempdir allocates for codegen test VFS"),
3327 )
3328 .expect("store builds with default config");
3329 let consumer_module =
3330 wasmtime::Module::new(&engine, consumer_bytes).expect("consumer module");
3331 let mut linker = wasmtime::Linker::<crate::runtime::StoreData>::new(&engine);
3332 let consumer_inst = pollster::block_on(async {
3336 crate::runtime::install_runtime_async(&mut linker, &mut store)
3337 .await
3338 .expect("runtime installs the prelude + host functions");
3339 linker
3340 .instantiate_async(&mut store, &consumer_module)
3341 .await
3342 .expect("consumer instantiates against the runtime")
3343 });
3344 (store, consumer_inst)
3345 }
3346
3347 fn run_main_f64(source: &str) -> f64 {
3348 run_main_number(&compile(source))
3349 }
3350
3351 #[test]
3352 fn undefined_backend_distinguishes_nullish_values_and_absence() {
3353 assert_eq!(
3354 run_main_f64(
3355 r#"
3356 function read(value: { a?: number } | null | undefined): number | undefined {
3357 return value?.a;
3358 }
3359 function main(): number {
3360 const absent: { a?: number } = {};
3361 const present: { a?: number } = { a: undefined };
3362 assert(absent.a === undefined, "missing read");
3363 assert(!("a" in absent) && "a" in present, "presence");
3364 assert(read(null) === undefined && read(undefined) === undefined, "chain");
3365 assert((read(undefined) ?? 7) === 7, "coalesce");
3366 const value: number | undefined = undefined;
3367 assert(!value && typeof value === "undefined", "truthiness and tag");
3368 assert(typeof null === "object" && typeof 1n === "bigint", "other tags");
3369 return 1;
3370 }
3371 "#
3372 ),
3373 1.0
3374 );
3375 }
3376
3377 #[test]
3378 fn undefined_backend_preserves_void_callback_completion() {
3379 assert_eq!(
3380 run_main_f64(
3381 r#"
3382 function empty(): void {}
3383 function invoke(callback: () => void): unknown { return callback(); }
3384 function main(): number {
3385 const callback: () => void = () => 42;
3386 const result: unknown = invoke(callback);
3387 assert(result === 42, "void callback preserves value");
3388 const completion: unknown = empty();
3389 assert(completion === undefined, "fallthrough completion");
3390 assert((void 42) === undefined, "void expression");
3391 return 1;
3392 }
3393 "#
3394 ),
3395 1.0
3396 );
3397 }
3398
3399 #[test]
3400 fn undefined_backend_optional_tuple_casts_preserve_length() {
3401 assert_eq!(
3402 run_main_f64(
3403 r#"
3404 function check(value: unknown): [number, string?] {
3405 return value as [number, string?];
3406 }
3407 function main(): number {
3408 const short = check([1]);
3409 const entries: unknown[] = [1, undefined];
3410 const explicit = check(entries);
3411 assert(short.length === 1 && short[1] === undefined, "omitted tuple slot");
3412 assert(explicit.length === 2 && explicit[1] === undefined, "present tuple slot");
3413 assert(JSON.stringify(short) === "[1]", "short tuple JSON");
3414 return 1;
3415 }
3416 "#
3417 ),
3418 1.0
3419 );
3420 }
3421
3422 #[test]
3423 fn undefined_backend_native_defaults_and_constructor_properties() {
3424 assert_eq!(
3425 run_main_f64(
3426 r#"
3427 class Value {
3428 constructor(public value: number = 17) {}
3429 }
3430 function main(): number {
3431 assert(new Value(undefined).value === 17, "parameter property default");
3432 assert("abc".slice(1, undefined) === "bc", "native numeric default");
3433 assert("a".padStart(2, undefined) === " a", "native string default");
3434 return 1;
3435 }
3436 "#
3437 ),
3438 1.0
3439 );
3440 }
3441
3442 fn run_main_number(bytes: &[u8]) -> f64 {
3443 let (mut store, inst) = link_consumer(bytes);
3444 let main = inst.get_func(&mut store, "main").expect("main export");
3445 let mut results = [wasmtime::Val::F64(0)];
3446 pollster::block_on(main.call_async(&mut store, &[], &mut results))
3447 .expect("main does not trap");
3448 match &results[0] {
3449 wasmtime::Val::F64(bits) => f64::from_bits(*bits),
3450 wasmtime::Val::AnyRef(Some(value)) => {
3451 let boxed = value
3452 .as_struct(&mut store)
3453 .expect("read result")
3454 .expect("boxed number");
3455 boxed
3456 .field(&mut store, 1)
3457 .expect("number payload")
3458 .f64()
3459 .expect("f64 payload")
3460 }
3461 other => panic!("expected numeric result, got {other:?}"),
3462 }
3463 }
3464
3465 fn run_main_i32(source: &str) -> i32 {
3466 let bytes = compile(source);
3467 let (mut store, inst) = link_consumer(&bytes);
3468 let main = inst
3469 .get_typed_func::<(), i32>(&mut store, "main")
3470 .expect("main is exported as `() -> i32`");
3471 pollster::block_on(main.call_async(&mut store, ())).expect("main does not trap")
3472 }
3473
3474 fn run_main_expecting_error(source: &str) -> String {
3475 let bytes = compile(source);
3476 let (mut store, inst) = link_consumer(&bytes);
3477 let main = inst.get_func(&mut store, "main").expect("main export");
3478 let result = pollster::block_on(main.call_async(
3479 &mut store,
3480 &[],
3481 &mut [wasmtime::Val::AnyRef(None)],
3482 ));
3483 let err = result.expect_err("expected main() to trap or throw");
3486 let err = crate::runtime::exec::uncaught_error(&mut store, err);
3487 let (sources, file) = crate::Sources::single("script.subm", source).unwrap();
3488 crate::render_backtrace(&err, &sources, file, crate::BacktraceMode::Full)
3489 .expect("backtrace empty — was wasm_backtrace_details enabled?")
3490 }
3491
3492 fn dump_dwarf_dies(bytes: &[u8]) -> String {
3493 use gimli::LittleEndian;
3494 use gimli::read::{AttributeValue, DebugAbbrev, DebugInfo, DebugStr};
3495 use std::fmt::Write;
3496
3497 let info_bytes = custom_section(bytes, ".debug_info").expect(".debug_info");
3498 let abbrev_bytes = custom_section(bytes, ".debug_abbrev").expect(".debug_abbrev");
3499 let str_bytes = custom_section(bytes, ".debug_str").expect(".debug_str");
3500 let debug_info = DebugInfo::new(&info_bytes, LittleEndian);
3501 let debug_abbrev = DebugAbbrev::new(&abbrev_bytes, LittleEndian);
3502 let debug_str = DebugStr::new(&str_bytes, LittleEndian);
3503
3504 let mut out = String::new();
3505 let mut units = debug_info.units();
3506 while let Some(header) = units.next().expect("unit") {
3507 let abbreviations = header.abbreviations(&debug_abbrev).expect("abbreviations");
3508 let mut entries = header.entries(&abbreviations);
3509 while let Some(entry) = entries.next_dfs().expect("entry") {
3510 let tag = entry.tag().static_string().unwrap_or("DW_TAG_<unknown>");
3511 writeln!(out, "<{:#x}> {}", entry.offset().0, tag).unwrap();
3512 for attr in entry.attrs() {
3513 let name = attr.name().static_string().unwrap_or("DW_AT_<unknown>");
3514 let value_repr = match attr.value() {
3515 AttributeValue::Addr(a) => format!("Addr({a:#x})"),
3516 AttributeValue::Udata(u) => format!("Udata({u})"),
3517 AttributeValue::DebugStrRef(off) => {
3518 let s = debug_str.get_str(off).expect("str");
3519 format!("Str({:?})", std::str::from_utf8(s.slice()).unwrap_or("?"))
3520 }
3521 AttributeValue::Language(lang) => format!(
3522 "Language({})",
3523 lang.static_string().unwrap_or("DW_LANG_<unknown>")
3524 ),
3525 other => format!("{other:?}"),
3526 };
3527 writeln!(out, " {name} = {value_repr}").unwrap();
3528 }
3529 }
3530 }
3531 out
3532 }
3533
3534 fn dump_dwarf_lines(bytes: &[u8]) -> String {
3535 use gimli::LittleEndian;
3536 use gimli::read::{DebugAbbrev, DebugInfo, DebugLine};
3537 use std::fmt::Write;
3538
3539 let info_bytes = custom_section(bytes, ".debug_info").expect(".debug_info");
3540 let abbrev_bytes = custom_section(bytes, ".debug_abbrev").expect(".debug_abbrev");
3541 let line_bytes = custom_section(bytes, ".debug_line").expect(".debug_line");
3542 let debug_info = DebugInfo::new(&info_bytes, LittleEndian);
3543 let debug_abbrev = DebugAbbrev::new(&abbrev_bytes, LittleEndian);
3544 let debug_line = DebugLine::new(&line_bytes, LittleEndian);
3545
3546 let mut out = String::new();
3547 let header = debug_info
3548 .units()
3549 .next()
3550 .expect("units")
3551 .expect("at least one unit");
3552 let _ = header.abbreviations(&debug_abbrev).expect("abbrevs");
3553 let program = debug_line
3554 .program(gimli::DebugLineOffset(0), header.address_size(), None, None)
3555 .expect("line program");
3556 let (program, sequences) = program.sequences().expect("sequences");
3557 for seq in sequences {
3558 writeln!(out, "sequence start={:#x} end={:#x}", seq.start, seq.end,).unwrap();
3559 let cloned = program.clone();
3560 let mut state = cloned.resume_from(&seq);
3561 while let Some((header, row)) = state.next_row().expect("next_row") {
3562 let _ = header;
3563 let line = row.line().map_or(0, std::num::NonZero::get);
3564 writeln!(
3565 out,
3566 " addr={:#x} file={} line={} col={:?} stmt={}{}",
3567 row.address(),
3568 row.file_index(),
3569 line,
3570 row.column(),
3571 row.is_stmt(),
3572 if row.end_sequence() {
3573 " end_sequence"
3574 } else {
3575 ""
3576 },
3577 )
3578 .unwrap();
3579 }
3580 }
3581 out
3582 }
3583
3584 #[test]
3585 fn empty_main_links_with_prelude() {
3586 instantiate_against_prelude(&compile("function main(): void { }"));
3587 }
3588
3589 #[test]
3590 fn main_with_number_return_links_with_prelude() {
3591 instantiate_against_prelude(&compile("function main(): number { return 0; }"));
3592 }
3593
3594 #[test]
3595 fn main_with_boolean_return_links_with_prelude() {
3596 instantiate_against_prelude(&compile("function main(): boolean { return true; }"));
3597 }
3598
3599 #[test]
3600 fn string_runtime_imported_from_prelude() {
3601 let bytes = compile("function main(): void { }");
3602 assert_eq!(function_count(&bytes), 4);
3604 let stable_imports: Vec<_> = imports(&bytes)
3605 .into_iter()
3606 .filter(|(_, name)| !is_sub421_temporal_getter_import(name))
3607 .collect();
3608 assert!(
3609 stable_imports.contains(&(
3610 crate::runtime::prelude::MODULE_NAME.to_string(),
3611 crate::mangle::prelude("__error_tag").to_string(),
3612 )),
3613 "host tag import should use canonical name"
3614 );
3615 assert!(
3616 stable_imports.contains(&(
3617 crate::runtime::prelude::MODULE_NAME.to_string(),
3618 crate::mangle::prelude("string_vtable").to_string(),
3619 )),
3620 "host-owned string_vtable import should use canonical name"
3621 );
3622 assert!(
3623 !stable_imports
3624 .iter()
3625 .any(|(module, _)| module == "submilli:crypto"),
3626 "unused stdlib imports should be tree-shaken"
3627 );
3628 assert!(
3629 !stable_imports
3630 .iter()
3631 .any(|(module, name)| module == "submilli:prelude" && name == "string_vtable"),
3632 "prelude imports must not use short names"
3633 );
3634 assert_eq!(data_count(&bytes), None);
3635 assert!(data_segments(&bytes).is_empty());
3636 }
3637
3638 fn is_sub421_temporal_getter_import(name: &str) -> bool {
3639 matches!(
3640 name,
3641 "Temporal#PlainDate#monthCode"
3642 | "Temporal#PlainDate#dayOfYear"
3643 | "Temporal#PlainDate#weekOfYear"
3644 | "Temporal#PlainDate#yearOfWeek"
3645 | "Temporal#PlainDate#daysInWeek"
3646 | "Temporal#PlainDate#daysInMonth"
3647 | "Temporal#PlainDate#daysInYear"
3648 | "Temporal#PlainDate#monthsInYear"
3649 | "Temporal#PlainDate#inLeapYear"
3650 | "Temporal#PlainTime#millisecond"
3651 | "Temporal#PlainTime#microsecond"
3652 | "Temporal#PlainDateTime#dayOfWeek"
3653 | "Temporal#PlainDateTime#monthCode"
3654 | "Temporal#PlainDateTime#dayOfYear"
3655 | "Temporal#PlainDateTime#weekOfYear"
3656 | "Temporal#PlainDateTime#yearOfWeek"
3657 | "Temporal#PlainDateTime#daysInWeek"
3658 | "Temporal#PlainDateTime#daysInMonth"
3659 | "Temporal#PlainDateTime#daysInYear"
3660 | "Temporal#PlainDateTime#monthsInYear"
3661 | "Temporal#PlainDateTime#inLeapYear"
3662 | "Temporal#PlainDateTime#millisecond"
3663 | "Temporal#PlainDateTime#microsecond"
3664 | "Temporal#PlainYearMonth#monthCode"
3665 | "Temporal#PlainYearMonth#daysInMonth"
3666 | "Temporal#PlainYearMonth#daysInYear"
3667 | "Temporal#PlainYearMonth#monthsInYear"
3668 | "Temporal#PlainYearMonth#inLeapYear"
3669 | "Temporal#PlainMonthDay#monthCode"
3670 | "Temporal#ZonedDateTime#monthCode"
3671 | "Temporal#ZonedDateTime#dayOfYear"
3672 | "Temporal#ZonedDateTime#weekOfYear"
3673 | "Temporal#ZonedDateTime#yearOfWeek"
3674 | "Temporal#ZonedDateTime#daysInWeek"
3675 | "Temporal#ZonedDateTime#daysInMonth"
3676 | "Temporal#ZonedDateTime#daysInYear"
3677 | "Temporal#ZonedDateTime#monthsInYear"
3678 | "Temporal#ZonedDateTime#inLeapYear"
3679 | "Temporal#ZonedDateTime#millisecond"
3680 | "Temporal#ZonedDateTime#microsecond"
3681 | "Temporal#ZonedDateTime#nanosecond"
3682 )
3683 }
3684
3685 #[test]
3686 fn main_function_index_follows_imports() {
3687 let bytes = compile("function main(): void { }");
3688 assert_eq!(
3689 main_export_func_idx(&bytes),
3690 Some(imported_func_count(&bytes) + 1),
3691 );
3692 }
3693
3694 #[test]
3695 fn main_still_exported_uniquely() {
3696 let bytes = compile("function main(): void { }");
3697 assert_eq!(
3698 export_names(&bytes),
3699 vec!["main".to_string(), "__main_output".to_string()]
3700 );
3701 }
3702
3703 #[test]
3704 fn symbol_table_resolves_prelude_function_indices() {
3705 let map = build_symbol_table("function main(): void { }");
3706 assert_eq!(map.prelude_func_idx("isFinite"), Some(0));
3709 assert_eq!(map.prelude_func_idx("isNaN"), Some(1));
3710 assert_eq!(map.prelude_func_idx("string_cmp"), Some(2));
3711 assert_eq!(map.prelude_func_idx("string_concat"), Some(3));
3712 assert_eq!(map.prelude_func_idx("string_eq"), Some(4));
3713 assert_eq!(map.prelude_func_idx("string_index"), Some(5));
3714 assert_eq!(map.prelude_func_idx("string_length"), Some(6));
3715 assert_eq!(map.prelude_func_idx("missing"), None);
3716 }
3717
3718 #[test]
3719 fn symbol_table_resolves_prelude_string_type_index() {
3720 let map = build_symbol_table("function main(): void { }");
3721 assert_eq!(map.string_type_idx(), Some(3));
3722 assert_eq!(map.raw_string_type_idx(), Some(0));
3723 }
3724
3725 #[test]
3726 fn module_with_one_string_adds_one_data_segment() {
3727 let bytes = compile(r#"let x: string = "hello"; function main(): void { }"#);
3728 instantiate_against_prelude(&bytes);
3729 assert_eq!(data_count(&bytes), Some(1));
3730 let segments = data_segments(&bytes);
3731 assert_eq!(segments.len(), 1);
3732 assert_eq!(segments[0], b"h\0e\0l\0l\0o\0".to_vec());
3733 }
3734
3735 #[test]
3736 fn module_with_two_distinct_strings_has_two_segments() {
3737 let bytes = compile(
3738 r#"let x: string = "hello"; let y: string = "world"; function main(): void { }"#,
3739 );
3740 instantiate_against_prelude(&bytes);
3741 assert_eq!(data_count(&bytes), Some(2));
3742 let segments = data_segments(&bytes);
3743 assert_eq!(segments.len(), 2);
3744 assert_eq!(segments[0], b"h\0e\0l\0l\0o\0".to_vec());
3745 assert_eq!(segments[1], b"w\0o\0r\0l\0d\0".to_vec());
3746 }
3747
3748 #[test]
3749 fn module_with_dedup_strings_has_one_segment() {
3750 let bytes = compile(
3751 r#"let x: string = "hello"; let y: string = "hello"; function main(): void { }"#,
3752 );
3753 instantiate_against_prelude(&bytes);
3754 assert_eq!(data_count(&bytes), Some(1));
3755 assert_eq!(data_segments(&bytes).len(), 1);
3756 }
3757
3758 #[test]
3759 fn module_with_strings_keeps_runtime_imports() {
3760 let bytes = compile(r#"let x: string = "hi"; function main(): void { }"#);
3761 assert_eq!(function_count(&bytes), 4);
3763 let imp = imports(&bytes);
3764 assert!(imp.contains(&(
3765 crate::runtime::prelude::MODULE_NAME.to_string(),
3766 crate::mangle::prelude("string_vtable").to_string(),
3767 )));
3768 assert!(
3769 !imp.iter()
3770 .any(|(module, _)| module == crate::runtime::BIGINT_MODULE_NAME),
3771 "unused bigint imports should be tree-shaken",
3772 );
3773 }
3774
3775 #[test]
3776 fn cross_module_string_type_canonicalization_proves_out() {
3777 let bytes = compile(r#"let x: string = "hello"; function main(): void { }"#);
3778 instantiate_against_prelude(&bytes);
3779 }
3780
3781 #[test]
3782 fn helper_emits_function_body_but_is_not_exported() {
3783 let bytes = compile("function main(): void { } function helper(): void { }");
3784 instantiate_against_prelude(&bytes);
3785 assert_eq!(function_count(&bytes), 5);
3787 assert_eq!(
3788 export_names(&bytes),
3789 vec!["main".to_string(), "__main_output".to_string()]
3790 );
3791 }
3792
3793 #[test]
3794 fn typed_exports_emit_function_and_global_but_skip_types() {
3795 let bytes = compile(
3796 r#"
3797 export interface Public { value: number; }
3798 export const answer: number = 42;
3799 export function api(): number { return answer; }
3800 function main(): void { }
3801 "#,
3802 );
3803 instantiate_against_prelude(&bytes);
3804 let exports = export_entries(&bytes);
3805 assert!(
3806 exports.iter().any(|(name, kind, _)| {
3807 name == "main#api" && matches!(kind, wasmparser::ExternalKind::Func)
3808 }),
3809 "expected exported function entry, got {exports:?}",
3810 );
3811 assert!(
3812 exports.iter().any(|(name, kind, _)| {
3813 name == "main#answer" && matches!(kind, wasmparser::ExternalKind::Global)
3814 }),
3815 "expected exported global entry, got {exports:?}",
3816 );
3817 assert!(
3818 !exports.iter().any(|(name, _, _)| name == "main#Public"),
3819 "type-only exports must not become Wasm exports: {exports:?}",
3820 );
3821 }
3822
3823 #[test]
3824 fn package_reexport_aliases_share_one_wrapper_index() {
3825 let (bytes, package, _) = compile_package_modules(
3826 "test:lib",
3827 &[
3828 (
3829 "lib",
3830 r#"export { inner as first, inner as second } from "./util";"#,
3831 ),
3832 (
3833 "util",
3834 "/** Inner function.\n * @returns One. */\nexport function inner(): number { return 1; }",
3835 ),
3836 ],
3837 &[],
3838 );
3839 assert_eq!(
3840 package.values.keys().collect::<Vec<_>>(),
3841 vec![&"first".to_string(), &"second".to_string()],
3842 );
3843 let exports = export_entries(&bytes);
3844 let first = exports
3845 .iter()
3846 .find(|(name, kind, _)| {
3847 name == "test:lib#first" && matches!(kind, wasmparser::ExternalKind::Func)
3848 })
3849 .expect("first function export")
3850 .2;
3851 let second = exports
3852 .iter()
3853 .find(|(name, kind, _)| {
3854 name == "test:lib#second" && matches!(kind, wasmparser::ExternalKind::Func)
3855 })
3856 .expect("second function export")
3857 .2;
3858 assert_eq!(first, second, "aliases for one target share one wrapper");
3859 assert!(
3860 main_export_func_idx(&bytes).is_none(),
3861 "libraries have no main"
3862 );
3863 }
3864
3865 struct RecordingSecurity {
3866 seen: std::sync::Mutex<Vec<(String, String)>>,
3867 }
3868
3869 impl crate::runtime::SecurityCheck for RecordingSecurity {
3870 fn check(
3871 &self,
3872 caller: &str,
3873 capability: &str,
3874 _context: &serde_json::Value,
3875 ) -> crate::runtime::CheckOutcome {
3876 self.seen
3877 .lock()
3878 .expect("recording security mutex")
3879 .push((caller.to_string(), capability.to_string()));
3880 if capability == "test.denied" {
3881 crate::runtime::CheckOutcome::Deny {
3882 rule: None,
3883 reason: "blocked by test".to_string(),
3884 }
3885 } else {
3886 crate::runtime::CheckOutcome::Allow { rule: None }
3887 }
3888 }
3889 }
3890
3891 #[tokio::test]
3892 async fn a_package_api_attributes_to_the_package_on_both_outcomes() {
3893 let (lib_bytes, lib_decl, lib_type_info) = compile_package_modules(
3894 "test:lib",
3895 &[(
3896 "lib",
3897 r#"
3898 import security from "submilli:security";
3899 /**
3900 * Allowed operation.
3901 * @capability test.allowed {}
3902 */
3903 export function allowed(): void {
3904 security.check("test.allowed", {});
3905 }
3906 /**
3907 * Denied operation.
3908 * @capability test.denied {}
3909 */
3910 export function denied(): void {
3911 security.check("test.denied", {});
3912 }
3913 "#,
3914 )],
3915 &[],
3916 );
3917 let recording = std::sync::Arc::new(RecordingSecurity {
3918 seen: std::sync::Mutex::new(Vec::new()),
3919 });
3920 let cfg = crate::runtime::RuntimeConfig::default();
3921 let engine = cfg.engine().expect("engine");
3922 let mut data = crate::runtime::StoreData::with_vfs(
3923 "test-run".into(),
3924 crate::runtime::Vfs::tempdir().expect("tempdir"),
3925 );
3926 data.install_type_info(lib_type_info);
3927 data.security_check = recording.clone();
3928 let mut store = cfg.store(&engine, data).expect("store");
3929 let mut linker = wasmtime::Linker::<crate::runtime::StoreData>::new(&engine);
3930 crate::runtime::install_runtime_async(&mut linker, &mut store)
3931 .await
3932 .expect("install runtime");
3933 let lib_module = wasmtime::Module::new(&engine, &lib_bytes).expect("library module");
3934 let lib_inst = linker
3935 .instantiate_async(&mut store, &lib_module)
3936 .await
3937 .expect("instantiate library");
3938 linker
3939 .instance(&mut store, "test:lib", lib_inst)
3940 .expect("register library instance");
3941 for name in ["allowed", "denied"] {
3942 let public_name = crate::mangle::package_symbol("test:lib", name);
3943 let func = lib_inst
3944 .get_func(&mut store, public_name.as_str())
3945 .expect("library public export");
3946 linker
3947 .define(&mut store, "test:lib", name, func)
3948 .expect("plain package import alias");
3949 }
3950
3951 let allowed = crate::compile::compile_script(
3952 r#"import { allowed } from "test:lib"; function main(): void { allowed(); }"#,
3953 "consumer.subm",
3954 crate::FileId(0),
3955 &[&lib_decl],
3956 &[],
3957 )
3958 .expect("allowed consumer compiles");
3959 store
3960 .data_mut()
3961 .install_type_info(allowed.type_info.clone());
3962 let allowed_module = wasmtime::Module::new(&engine, &allowed.wasm).expect("allowed module");
3963 let allowed_inst = linker
3964 .instantiate_async(&mut store, &allowed_module)
3965 .await
3966 .expect("instantiate allowed consumer");
3967 crate::runtime::dispatch_main_async(&mut store, &allowed_inst)
3968 .await
3969 .expect("allowed consumer runs");
3970
3971 assert_eq!(
3972 recording
3973 .seen
3974 .lock()
3975 .expect("recording security mutex")
3976 .as_slice(),
3977 &[("main".to_string(), "test.allowed".to_string())],
3978 );
3979
3980 let denied = crate::compile::compile_script(
3981 r#"import { denied } from "test:lib"; function main(): void { denied(); }"#,
3982 "consumer.subm",
3983 crate::FileId(0),
3984 &[&lib_decl],
3985 &[],
3986 )
3987 .expect("denied consumer compiles");
3988 store.data_mut().install_type_info(denied.type_info.clone());
3989 let denied_module = wasmtime::Module::new(&engine, &denied.wasm).expect("denied module");
3990 let denied_inst = linker
3991 .instantiate_async(&mut store, &denied_module)
3992 .await
3993 .expect("instantiate denied consumer");
3994 let err = crate::runtime::dispatch_main_async(&mut store, &denied_inst)
3995 .await
3996 .expect_err("denied security check throws");
3997 assert!(
3998 err.to_string().contains("permission denied"),
3999 "unexpected error: {err}",
4000 );
4001 assert!(
4002 err.to_string().contains("caller=main"),
4003 "unexpected error: {err}",
4004 );
4005 }
4006
4007 #[tokio::test]
4012 async fn a_caught_throw_from_a_package_export_leaves_main_as_the_caller() {
4013 let (lib_bytes, lib_decl, lib_type_info) = compile_package_modules(
4014 "test:lib",
4015 &[(
4016 "lib",
4017 r#"
4018 /** Always throws, so the caller can catch and continue. */
4019 export function boom(): void {
4020 throw new Error("boom");
4021 }
4022 "#,
4023 )],
4024 &[],
4025 );
4026 let cfg = crate::runtime::RuntimeConfig::default();
4027 let engine = cfg.engine().expect("engine");
4028 let mut data = crate::runtime::StoreData::with_vfs(
4029 "test-run".into(),
4030 crate::runtime::Vfs::tempdir().expect("tempdir"),
4031 );
4032 data.install_type_info(lib_type_info);
4033 let mut store = cfg.store_async(&engine, data).expect("store");
4034 let mut linker = wasmtime::Linker::<crate::runtime::StoreData>::new(&engine);
4035 crate::runtime::install_runtime_async(&mut linker, &mut store)
4036 .await
4037 .expect("install runtime");
4038 let lib_module = wasmtime::Module::new(&engine, &lib_bytes).expect("library module");
4039 let lib_inst = linker
4040 .instantiate_async(&mut store, &lib_module)
4041 .await
4042 .expect("instantiate library");
4043 linker
4044 .instance(&mut store, "test:lib", lib_inst)
4045 .expect("register library instance");
4046 let public_name = crate::mangle::package_symbol("test:lib", "boom");
4047 let func = lib_inst
4048 .get_func(&mut store, public_name.as_str())
4049 .expect("library public export");
4050 linker
4051 .define(&mut store, "test:lib", "boom", func)
4052 .expect("plain package import alias");
4053
4054 let consumer = crate::compile::compile_script(
4055 r#"
4056 import { boom } from "test:lib";
4057 import { get } from "submilli:secrets";
4058
4059 function main(): void {
4060 try {
4061 boom();
4062 } catch (e: Error) {
4063 // The export's frame must come off even though it threw.
4064 }
4065 const _ = get("TOKEN");
4066 }
4067 "#,
4068 "consumer.subm",
4069 crate::FileId(0),
4070 &[&lib_decl],
4071 &[],
4072 )
4073 .expect("consumer compiles");
4074 store
4075 .data_mut()
4076 .install_type_info(consumer.type_info.clone());
4077 let consumer_module =
4078 wasmtime::Module::new(&engine, &consumer.wasm).expect("consumer module");
4079 let consumer_inst = linker
4080 .instantiate_async(&mut store, &consumer_module)
4081 .await
4082 .expect("instantiate consumer");
4083
4084 let err = crate::runtime::dispatch_main_async(&mut store, &consumer_inst)
4085 .await
4086 .expect_err("secrets.get from main is refused");
4087
4088 assert!(
4089 err.to_string().contains("caller=main"),
4090 "a leaked frame would attribute this to test:lib: {err}",
4091 );
4092 }
4093
4094 struct RecordingPerCaller {
4098 seen: std::sync::Mutex<Vec<(String, String)>>,
4099 deny_caller: Option<&'static str>,
4100 }
4101
4102 impl crate::runtime::SecurityCheck for RecordingPerCaller {
4103 fn check(
4104 &self,
4105 caller: &str,
4106 capability: &str,
4107 _context: &serde_json::Value,
4108 ) -> crate::runtime::CheckOutcome {
4109 self.seen
4110 .lock()
4111 .expect("recording security mutex")
4112 .push((caller.to_string(), capability.to_string()));
4113 if self.deny_caller == Some(caller) {
4114 crate::runtime::CheckOutcome::Deny {
4115 rule: None,
4116 reason: "blocked by test".to_string(),
4117 }
4118 } else {
4119 crate::runtime::CheckOutcome::Allow { rule: None }
4120 }
4121 }
4122 }
4123
4124 const PACKAGE_WITH_GATED_INITIALIZER: &str = r#"
4127 import { exists } from "submilli:fs";
4128 const _probe: boolean = exists("/init-probe");
4129 /** No-op export; the initializer is what this package is for. */
4130 export function noop(): void {}
4131 "#;
4132
4133 async fn install_package_with_gated_initializer(
4134 recording: std::sync::Arc<RecordingPerCaller>,
4135 ) -> (
4136 wasmtime::Store<crate::runtime::StoreData>,
4137 wasmtime::Result<()>,
4138 ) {
4139 let (lib_bytes, lib_decl, lib_type_info) =
4140 compile_package_modules("test:lib", &[("lib", PACKAGE_WITH_GATED_INITIALIZER)], &[]);
4141 let cfg = crate::runtime::RuntimeConfig::default();
4142 let engine = cfg.engine().expect("engine");
4143 let mut data = crate::runtime::StoreData::with_vfs(
4144 "test-run".into(),
4145 crate::runtime::Vfs::tempdir().expect("tempdir"),
4146 );
4147 data.security_check = recording;
4148 let mut store = cfg.store(&engine, data).expect("store");
4149 let mut linker = wasmtime::Linker::<crate::runtime::StoreData>::new(&engine);
4150 crate::runtime::install_runtime_async(&mut linker, &mut store)
4151 .await
4152 .expect("install runtime");
4153 let lib_module = wasmtime::Module::new(&engine, &lib_bytes).expect("library module");
4154 let outcome = crate::runtime::install_package_modules_async(
4155 &mut linker,
4156 &mut store,
4157 &[crate::runtime::LinkedPackageModule {
4158 module: &lib_module,
4159 declaration: &lib_decl,
4160 type_info: &lib_type_info,
4161 }],
4162 )
4163 .await;
4164 (store, outcome)
4165 }
4166
4167 #[tokio::test]
4168 async fn package_initializers_are_attributed_to_the_package() {
4169 let recording = std::sync::Arc::new(RecordingPerCaller {
4170 seen: std::sync::Mutex::new(Vec::new()),
4171 deny_caller: None,
4172 });
4173 let (_store, outcome) = install_package_with_gated_initializer(recording.clone()).await;
4174 outcome.expect("package instantiates");
4175
4176 assert_eq!(
4177 recording
4178 .seen
4179 .lock()
4180 .expect("recording security mutex")
4181 .as_slice(),
4182 &[("test:lib".to_string(), "fs.stat".to_string())],
4183 );
4184 }
4185
4186 #[tokio::test]
4190 async fn package_initializer_denied_to_the_package_fails_and_restores_the_stack() {
4191 let recording = std::sync::Arc::new(RecordingPerCaller {
4192 seen: std::sync::Mutex::new(Vec::new()),
4193 deny_caller: Some("test:lib"),
4194 });
4195 let (_store, outcome) = install_package_with_gated_initializer(recording.clone()).await;
4196 let err = outcome.expect_err("denied initializer traps");
4197
4198 let message = err.to_string();
4203 assert!(
4204 message.contains("package `test:lib` failed to initialize"),
4205 "the failure must name the package: {message}",
4206 );
4207 assert!(
4208 message.contains("fs.stat") && message.contains("caller=test:lib"),
4209 "the failure must name the capability and caller: {message}",
4210 );
4211
4212 assert_eq!(
4213 recording
4214 .seen
4215 .lock()
4216 .expect("recording security mutex")
4217 .as_slice(),
4218 &[("test:lib".to_string(), "fs.stat".to_string())],
4219 );
4220 }
4221
4222 fn fake_globals_definitions() -> crate::PackageDeclaration {
4223 use crate::{PackageDeclaration, Span, Type, ValueKind, ValueSymbol};
4224 let mut defs = PackageDeclaration::with_package("test:globals");
4225 defs.values.insert(
4226 "counter".to_string(),
4227 ValueSymbol {
4228 name: "counter".to_string(),
4229 mangled_name: crate::mangle::package_symbol("test:globals", "counter"),
4230 declaration_span: Span::at(crate::FileId(0)),
4231 kind: ValueKind::Let {
4232 ty: Type::Number,
4233 doc: None,
4234 },
4235 },
4236 );
4237 defs.values.insert(
4238 "max_iterations".to_string(),
4239 ValueSymbol {
4240 name: "max_iterations".to_string(),
4241 mangled_name: crate::mangle::package_symbol("test:globals", "max_iterations"),
4242 declaration_span: Span::at(crate::FileId(0)),
4243 kind: ValueKind::Const {
4244 ty: Type::Number,
4245 doc: None,
4246 },
4247 },
4248 );
4249 defs
4250 }
4251
4252 fn compile_with_package_imports<'a>(
4253 source: &str,
4254 packages: &'a [&'a crate::PackageDeclaration],
4255 ) -> Vec<u8> {
4256 let ta = type_check_with_packages(source, packages);
4257 let (prelude_defs, host_defs, internal_defs) =
4258 prelude::cached_runtime_package_declarations();
4259 let mut dependencies: Vec<&crate::PackageDeclaration> = prelude_defs.iter().collect();
4260 dependencies.extend(host_defs.iter());
4261 dependencies.extend(internal_defs.iter());
4262 dependencies.extend_from_slice(packages);
4263 codegen(source, "script.subm", crate::FileId(0), &ta, &dependencies)
4264 .expect("code generation")
4265 }
4266
4267 fn imports_with_kind(bytes: &[u8]) -> Vec<(String, String, &'static str, bool)> {
4268 let mut out = Vec::new();
4269 for payload in Parser::new(0).parse_all(bytes) {
4270 if let Payload::ImportSection(reader) = payload.expect("payload") {
4271 for entry in reader {
4272 match entry.expect("import") {
4273 wasmparser::Imports::Single(_, imp) => {
4274 let (kind, mutable) = match imp.ty {
4275 wasmparser::TypeRef::Func(_) => ("func", false),
4276 wasmparser::TypeRef::Global(g) => ("global", g.mutable),
4277 _ => ("other", false),
4278 };
4279 out.push((imp.module.to_string(), imp.name.to_string(), kind, mutable));
4280 }
4281 other => panic!("unexpected import form {other:?}"),
4282 }
4283 }
4284 }
4285 }
4286 out
4287 }
4288
4289 #[test]
4290 fn let_and_const_become_global_imports() {
4291 let defs = fake_globals_definitions();
4292 let bytes = compile_with_package_imports(
4293 r#"import { counter, max_iterations } from "test:globals";
4294function main(): number { return counter + max_iterations; }"#,
4295 &[&defs],
4296 );
4297 let imp = imports_with_kind(&bytes);
4298 let counter_name = crate::mangle::package_symbol("test:globals", "counter");
4299 let counter = imp
4300 .iter()
4301 .find(|(_, n, _, _)| n == counter_name.as_str())
4302 .expect("counter import");
4303 assert_eq!(counter.0, "test:globals");
4304 assert_eq!(counter.2, "global");
4305 assert!(counter.3, "let imports as mutable global");
4306
4307 let max_iterations_name = crate::mangle::package_symbol("test:globals", "max_iterations");
4308 let max_it = imp
4309 .iter()
4310 .find(|(_, n, _, _)| n == max_iterations_name.as_str())
4311 .expect("max_iterations import");
4312 assert_eq!(max_it.0, "test:globals");
4313 assert_eq!(max_it.2, "global");
4314 assert!(
4318 max_it.3,
4319 "const imports as mutable global to match the exporter"
4320 );
4321 }
4322
4323 #[test]
4324 fn ported_methods_route_to_prelude_host_not_wasm_wrapper() {
4325 let src = "function main(): void { \
4326 [1, 2].forEach((x: number) => { }); \
4327 const r: string = \"ab\".repeat(2); \
4328 assert(r.length === 4); \
4329 const s = new Set<number>(); \
4330 s.add(1); \
4331 s.delete(1); \
4332 s.add(2); \
4333 assert(s.size === 1 && s.has(2)); \
4334 const f: string = (3.14).toFixed(1); \
4335 const b: string = true.toString(); \
4336 assert(Number.isInteger(4) && Number.parseInt(\"10\", 10) === 10); \
4337 assert(Number.EPSILON > 0); \
4338 assert(Math.PI > 3 && Math.abs(-2) === 2 && Math.imul(2, 3) === 6 && Math.min(3, 1, 2) === 1 && Math.random() >= 0); \
4339 const u = Uint8Array.fromHex(\"01ff\"); \
4340 u.fill(7); \
4341 assert(u.length === 2 && u.toHex() === \"0707\"); \
4342 const enc = new TextEncoder().encode(\"hi\"); \
4343 const dec: string = new TextDecoder().decode(enc); \
4344 assert(dec === \"hi\"); \
4345 const pd = Temporal.PlainDate.from(\"2024-03-09\"); \
4346 const pd2 = pd.add({ days: 1 }).subtract({ days: 1 }).with({ day: 10 }); \
4347 const pd3 = pd.toPlainYearMonth().toPlainDate({ day: 9 }); \
4348 const pd4 = pd.toPlainMonthDay().toPlainDate({ year: 2024 }); \
4349 const pdtFromDate = pd.toPlainDateTime(); \
4350 const zFromDate = pd.toZonedDateTime(\"UTC\"); \
4351 assert(pd.until(pd2).days === 1 && pd2.since(pd).days === 1); \
4352 assert(pd.year === 2024 && pd.toString() === \"2024-03-09\" && pd.toJSON() === \"2024-03-09\" && pd2.day === 10 && pd3.day === 9 && pd4.year === 2024 && pdtFromDate.hour === 0 && zFromDate.year === 2024); \
4353 const pt = Temporal.PlainTime.from(\"15:30:45.123456789\"); \
4354 const pt2 = pt.add({ hours: 1 }).with({ minute: 0 }); \
4355 assert(pt.until(pt2).hours === 1 && pt.hour === 15 && pt.nanosecond === 123456789 && pt2.minute === 0); \
4356 const pdt = Temporal.PlainDateTime.from(\"2024-03-09T15:30:45\"); \
4357 const pdt2 = pdt.subtract({ hours: 1 }).with({ second: 0 }); \
4358 const zFromDateTime = pdt.toZonedDateTime(\"UTC\"); \
4359 assert(pdt.since(pdt2).hours === 1 && pdt.toPlainDate().day === 9 && pdt.toPlainTime().hour === 15 && pdt.toPlainYearMonth().month === 3 && pdt.toPlainMonthDay().day === 9); \
4360 assert(pdt.month === 3 && pdt2.second === 0 && zFromDateTime.month === 3 && Temporal.PlainDateTime.compare(pdt, pdt) === 0); \
4361 const ym = Temporal.PlainYearMonth.from(\"2024-03\"); \
4362 const ym2 = ym.add({ months: 1 }).with({ month: 5 }); \
4363 assert(ym.until(ym2).months === 2 && ym.monthCode === \"M03\" && ym2.month === 5); \
4364 const md = Temporal.PlainMonthDay.from(\"03-09\"); \
4365 const md2 = md.with({ day: 10 }); \
4366 assert(md.day === 9 && md2.day === 10); \
4367 const i0 = Temporal.Instant.from(\"2024-03-09T15:30:45.123456789Z\"); \
4368 const i1 = Temporal.Instant.fromEpochMilliseconds(i0.epochMilliseconds); \
4369 const i2 = Temporal.Instant.fromEpochNanoseconds(i0.epochNanoseconds); \
4370 const i3 = i0.add({ hours: 1 }).subtract({ hours: 1 }).round({ smallestUnit: \"second\" }); \
4371 const id0 = i0.until(i1); const id1 = i1.since(i0); \
4372 const iz = i0.toZonedDateTimeISO(\"UTC\"); \
4373 assert(Temporal.Instant.compare(i0, i2) === 0 && i0.equals(i2) && i0.toString().length > 0 && i0.toJSON().length > 0 && i3.epochMilliseconds > 0 && id0.seconds === id1.seconds && iz.year === 2024); \
4374 const dur0 = new Temporal.Duration({ hours: 1 }); const dur1 = Temporal.Duration.from(\"PT1H\"); \
4375 assert(Temporal.Duration.compare(dur0, dur1) === 0 && Temporal.ZonedDateTime.compare(iz, iz) === 0); \
4376 const ni = Temporal.Now.instant(); const ntz = Temporal.Now.timeZoneId(); const nz = Temporal.Now.zonedDateTimeISO(\"UTC\"); \
4377 assert(ni.epochMilliseconds > 0 && ntz.length > 0 && nz.timeZoneId === \"UTC\"); \
4378 const re: RegExp = new RegExp(\"a\", \"g\"); \
4379 assert(re.test(\"a\") && re.global && re.source === \"a\" && re.flags === \"g\" && re.lastIndex >= 0); \
4380 const rmatch: RegExpExecArray | null = re.exec(\"a\"); \
4381 assert(rmatch !== null); \
4382 if (rmatch !== null) { const gg = rmatch.groups; \
4383 assert(rmatch[0] === \"a\" && rmatch.index === 0 && rmatch.input === \"a\" && gg === undefined && rmatch.length >= 1); } \
4384 const sm: RegExpMatchArray | null = \"a\".match(re); \
4385 const marr = \"aa\".matchAll(re); \
4386 const rep: string = \"a\".replace(\"a\", \"b\"); \
4387 const repall: string = \"aa\".replaceAll(\"a\", \"b\"); \
4388 const sp = \"a,b\".split(\",\"); \
4389 assert(sm === sm && \"a\".search(re) >= -1 && marr.next().done === false && rep.length >= 0 && repall.length >= 0 && sp.length === 2); \
4390 assert(isNaN(NaN) && !isFinite(Infinity)); \
4391 const oo = { a: 1 }; \
4392 assert(Object.keys(oo).length === 1 && Object.values(oo).length === 1); \
4393 assert(Object.entries(oo).length === 1 && Object.hasOwn(oo, \"a\") && Object.is(1, 1)); }";
4394 let ta = type_check(src);
4395 let (prelude_defs, host_defs, internal_defs) =
4396 prelude::cached_runtime_package_declarations();
4397 let mut dependencies: Vec<&crate::PackageDeclaration> = prelude_defs.iter().collect();
4398 dependencies.extend(host_defs.iter());
4399 dependencies.extend(internal_defs.iter());
4400 let bytes = codegen(src, "script.subm", crate::FileId(0), &ta, &dependencies)
4401 .expect("code generation");
4402 let imp = imports_with_kind(&bytes);
4403
4404 for (iface, method) in [
4409 ("String", "repeat"),
4410 ("Array", "forEach"),
4411 ("SetConstructor", "new"),
4412 ("Set", "add"),
4413 ("Set", "has"),
4414 ("Set", "delete"),
4415 ("Set", "size"),
4416 ("Number", "toFixed"),
4417 ("Boolean", "toString"),
4418 ("NumberConstructor", "isInteger"),
4419 ("NumberConstructor", "parseInt"),
4420 ("NumberConstructor", "EPSILON"),
4422 ("Uint8Array", "fill"),
4424 ("Uint8Array", "length"),
4425 ("Uint8Array", "toHex"),
4426 ("Uint8ArrayConstructor", "fromHex"),
4427 ("TextEncoder", "encode"),
4429 ("TextDecoder", "decode"),
4430 ("TextEncoderConstructor", "new"),
4431 ("TextDecoderConstructor", "new"),
4432 ("RegExpConstructor", "new"),
4434 ("RegExp", "test"),
4435 ("RegExp", "exec"),
4436 ("RegExp", "source"),
4437 ("RegExp", "flags"),
4438 ("RegExp", "lastIndex"),
4439 ("RegExp", "global"),
4440 ("RegExpExecArray", "index"),
4441 ("RegExpExecArray", "input"),
4442 ("RegExpExecArray", "groups"),
4443 ("String", "match"),
4445 ("String", "search"),
4446 ("String", "matchAll"),
4447 ("String", "replace"),
4448 ("String", "replaceAll"),
4449 ("String", "split"),
4450 ("ObjectConstructor", "keys"),
4452 ("ObjectConstructor", "values"),
4453 ("ObjectConstructor", "entries"),
4454 ("ObjectConstructor", "hasOwn"),
4455 ("ObjectConstructor", "is"),
4456 ] {
4457 let key = crate::mangle::extend(&crate::mangle::prelude(iface), method);
4458 assert!(
4459 imp.iter()
4460 .any(|(m, n, _, _)| m == crate::runtime::prelude::MODULE_NAME
4461 && n == key.as_str()),
4462 "{iface}#{method} should import from the prelude host module"
4463 );
4464 }
4465
4466 for name in ["PI", "abs", "imul", "min", "random"] {
4467 let key = crate::runtime::prelude::math::math_key(name);
4468 assert!(
4469 imp.iter()
4470 .any(|(m, n, _, _)| m == crate::runtime::prelude::MODULE_NAME
4471 && n == key.as_str()),
4472 "Math#{name} should import from the prelude host module"
4473 );
4474 }
4475
4476 for (iface, method) in [
4479 ("InstantConstructor", "from"),
4480 ("InstantConstructor", "fromEpochMilliseconds"),
4481 ("InstantConstructor", "fromEpochNanoseconds"),
4482 ("InstantConstructor", "compare"),
4483 ("Instant", "epochMilliseconds"),
4484 ("Instant", "epochNanoseconds"),
4485 ("Instant", "add"),
4486 ("Instant", "subtract"),
4487 ("Instant", "until"),
4488 ("Instant", "since"),
4489 ("Instant", "round"),
4490 ("Instant", "equals"),
4491 ("Instant", "toString"),
4492 ("Instant", "toJSON"),
4493 ("Instant", "toZonedDateTimeISO"),
4494 ("Now", "instant"),
4495 ("Now", "timeZoneId"),
4496 ("Now", "zonedDateTimeISO"),
4497 ("DurationConstructor", "new"),
4498 ("DurationConstructor", "from"),
4499 ("DurationConstructor", "compare"),
4500 ("Duration", "years"),
4501 ("Duration", "months"),
4502 ("Duration", "weeks"),
4503 ("Duration", "days"),
4504 ("Duration", "hours"),
4505 ("Duration", "minutes"),
4506 ("Duration", "seconds"),
4507 ("Duration", "milliseconds"),
4508 ("Duration", "microseconds"),
4509 ("Duration", "nanoseconds"),
4510 ("Duration", "sign"),
4511 ("Duration", "blank"),
4512 ("Duration", "add"),
4513 ("Duration", "subtract"),
4514 ("Duration", "negated"),
4515 ("Duration", "abs"),
4516 ("Duration", "with"),
4517 ("Duration", "round"),
4518 ("Duration", "total"),
4519 ("Duration", "toString"),
4520 ("Duration", "toJSON"),
4521 ("ZonedDateTimeConstructor", "from"),
4522 ("ZonedDateTimeConstructor", "compare"),
4523 ("ZonedDateTime", "timeZoneId"),
4524 ("ZonedDateTime", "offset"),
4525 ("ZonedDateTime", "offsetNanoseconds"),
4526 ("ZonedDateTime", "epochMilliseconds"),
4527 ("ZonedDateTime", "epochNanoseconds"),
4528 ("ZonedDateTime", "hoursInDay"),
4529 ("ZonedDateTime", "year"),
4530 ("ZonedDateTime", "monthCode"),
4531 ("ZonedDateTime", "dayOfWeek"),
4532 ("ZonedDateTime", "inLeapYear"),
4533 ("ZonedDateTime", "nanosecond"),
4534 ("ZonedDateTime", "add"),
4535 ("ZonedDateTime", "subtract"),
4536 ("ZonedDateTime", "until"),
4537 ("ZonedDateTime", "since"),
4538 ("ZonedDateTime", "with"),
4539 ("ZonedDateTime", "withTimeZone"),
4540 ("ZonedDateTime", "round"),
4541 ("ZonedDateTime", "startOfDay"),
4542 ("ZonedDateTime", "toInstant"),
4543 ("ZonedDateTime", "toPlainDate"),
4544 ("ZonedDateTime", "toPlainTime"),
4545 ("ZonedDateTime", "toPlainDateTime"),
4546 ("ZonedDateTime", "equals"),
4547 ("ZonedDateTime", "toString"),
4548 ("ZonedDateTime", "toJSON"),
4549 ("PlainDateConstructor", "from"),
4550 ("PlainDate", "year"),
4551 ("PlainDate", "toString"),
4552 ("PlainDate", "toJSON"),
4553 ("PlainDate", "add"),
4554 ("PlainDate", "subtract"),
4555 ("PlainDate", "until"),
4556 ("PlainDate", "since"),
4557 ("PlainDate", "with"),
4558 ("PlainDate", "toPlainYearMonth"),
4559 ("PlainDate", "toPlainMonthDay"),
4560 ("PlainDate", "toPlainDateTime"),
4561 ("PlainDate", "toZonedDateTime"),
4562 ("PlainTimeConstructor", "from"),
4563 ("PlainTime", "hour"),
4564 ("PlainTime", "nanosecond"),
4565 ("PlainTime", "add"),
4566 ("PlainTime", "until"),
4567 ("PlainTime", "with"),
4568 ("PlainDateTimeConstructor", "from"),
4569 ("PlainDateTime", "month"),
4570 ("PlainDateTime", "subtract"),
4571 ("PlainDateTime", "since"),
4572 ("PlainDateTime", "with"),
4573 ("PlainDateTime", "toPlainDate"),
4574 ("PlainDateTime", "toPlainTime"),
4575 ("PlainDateTime", "toPlainYearMonth"),
4576 ("PlainDateTime", "toPlainMonthDay"),
4577 ("PlainDateTime", "toZonedDateTime"),
4578 ("PlainDateTimeConstructor", "compare"),
4579 ("PlainYearMonthConstructor", "from"),
4580 ("PlainYearMonth", "monthCode"),
4581 ("PlainYearMonth", "add"),
4582 ("PlainYearMonth", "until"),
4583 ("PlainYearMonth", "with"),
4584 ("PlainYearMonth", "toPlainDate"),
4585 ("PlainMonthDayConstructor", "from"),
4586 ("PlainMonthDay", "day"),
4587 ("PlainMonthDay", "with"),
4588 ("PlainMonthDay", "toPlainDate"),
4589 ] {
4590 let key = crate::mangle::extend(
4591 &crate::mangle::extend(&crate::mangle::prelude("Temporal"), iface),
4592 method,
4593 );
4594 assert!(
4595 imp.iter()
4596 .any(|(m, n, _, _)| m == crate::runtime::prelude::MODULE_NAME
4597 && n == key.as_str()),
4598 "Temporal#{iface}#{method} should import from the prelude host module"
4599 );
4600 }
4601
4602 for name in ["boxed_number_vtable", "boxed_boolean_vtable"] {
4604 let key = crate::mangle::prelude(name);
4605 assert!(
4606 imp.iter()
4607 .any(|(m, n, _, _)| m == crate::runtime::prelude::MODULE_NAME
4608 && n == key.as_str()),
4609 "{name} should import from the prelude host module"
4610 );
4611 }
4612
4613 for name in ["isNaN", "isFinite", "Infinity"] {
4616 let key = crate::mangle::prelude(name);
4617 assert!(
4618 imp.iter()
4619 .any(|(m, n, _, _)| m == crate::runtime::prelude::MODULE_NAME
4620 && n == key.as_str()),
4621 "globalThis {name} should import from the prelude host module"
4622 );
4623 }
4624 let unused = crate::mangle::extend(&crate::mangle::prelude("String"), "charAt");
4625 assert!(
4626 !imp.iter().any(|(_, n, _, _)| n == unused.as_str()),
4627 "unused ported methods should be tree-shaken",
4628 );
4629 }
4630
4631 #[test]
4632 fn global_imports_assigned_indices_starting_from_zero() {
4633 let defs = fake_globals_definitions();
4634 let ta = type_check("function main(): void { }");
4635 let prelude_defs = prelude::prelude_package_declaration();
4636 let prelude_host_defs = crate::runtime::prelude::package_declaration();
4637 let dependencies: [&crate::PackageDeclaration; 3] =
4638 [&prelude_defs, &prelude_host_defs, &defs];
4639 let _ = codegen(
4640 "function main(): void { }",
4641 "script.subm",
4642 crate::FileId(0),
4643 &ta,
4644 &dependencies,
4645 );
4646
4647 let mut map = SymbolTable::default();
4648 let mut next_global_idx: u32 = 0;
4649 for defs in [&prelude_defs, &defs] {
4650 for value in defs.values.values() {
4651 let ty = match &value.kind {
4652 crate::ValueKind::Let { ty, .. } | crate::ValueKind::Const { ty, .. } => {
4653 Some(ty)
4654 }
4655 _ => None,
4656 };
4657 if let Some(ty) = ty {
4658 if matches!(ty, crate::Type::InterfaceRef { .. }) {
4659 continue;
4660 }
4661 map.record_global(value.mangled_name.clone(), next_global_idx);
4662 next_global_idx += 1;
4663 }
4664 }
4665 }
4666 assert_eq!(map.prelude_global_idx("Infinity"), Some(0));
4667 assert_eq!(map.prelude_global_idx("NaN"), Some(1));
4668 assert_eq!(map.prelude_global_idx("string_comma"), Some(2));
4669 assert_eq!(map.prelude_global_idx("string_false"), Some(3));
4670 assert_eq!(map.prelude_global_idx("string_null"), Some(4));
4671 assert_eq!(map.prelude_global_idx("string_object_function"), Some(5));
4672 assert_eq!(map.prelude_global_idx("string_object_object"), Some(6));
4673 assert_eq!(map.prelude_global_idx("string_true"), Some(7));
4674 assert_eq!(
4675 map.global_idx(&crate::mangle::package_symbol("test:globals", "counter")),
4676 Some(8),
4677 );
4678 assert_eq!(
4679 map.global_idx(&crate::mangle::package_symbol(
4680 "test:globals",
4681 "max_iterations"
4682 )),
4683 Some(9),
4684 );
4685 assert_eq!(
4686 map.global_idx(&crate::mangle::package_symbol("test:globals", "missing")),
4687 None,
4688 );
4689 }
4690
4691 #[test]
4692 fn module_with_global_imports_validates() {
4693 let defs = fake_globals_definitions();
4694 let bytes = compile_with_package_imports("function main(): void { }", &[&defs]);
4695 wasmparser::Validator::new()
4696 .validate_all(&bytes)
4697 .expect("module with global imports validates");
4698 }
4699
4700 #[test]
4701 fn no_top_level_globals_still_emits_empty_start() {
4702 let bytes = compile("function main(): void { }");
4703 assert_eq!(
4704 start_function_idx(&bytes),
4705 Some(imported_func_count(&bytes))
4706 );
4707 assert_eq!(function_count(&bytes), 4);
4709 }
4710
4711 #[test]
4712 fn top_level_string_let_declares_one_mutable_ref_global() {
4713 let bytes = compile(r#"let x: string = "hi"; function main(): void { }"#);
4714 let baseline = compile("function main(): void { }");
4715 assert_eq!(global_count(&bytes), global_count(&baseline) + 1);
4716 assert!(
4717 global_decls(&bytes).contains(&(true, "ref")),
4718 "top-level string let should declare a mutable ref global",
4719 );
4720 }
4721
4722 #[test]
4723 fn top_level_const_also_declared_mutable_at_wasm_level() {
4724 let bytes = compile(r#"const x: string = "hi"; function main(): void { }"#);
4725 let baseline = compile("function main(): void { }");
4726 assert_eq!(global_count(&bytes), global_count(&baseline) + 1);
4727 assert!(
4728 global_decls(&bytes).contains(&(true, "ref")),
4729 "top-level string const should declare a mutable ref global",
4730 );
4731 }
4732
4733 #[test]
4734 fn top_level_let_emits_start_section_pointing_at_first_local_func() {
4735 let bytes = compile(r#"let x: string = "hi"; function main(): void { }"#);
4736 let first_local_func = imported_func_count(&bytes);
4737 assert_eq!(start_function_idx(&bytes), Some(first_local_func));
4738 assert_eq!(main_export_func_idx(&bytes), Some(first_local_func + 1));
4739 }
4740
4741 #[test]
4742 fn top_level_string_let_validates_and_links_with_prelude() {
4743 let bytes = compile(r#"let x: string = "hello"; function main(): void { }"#);
4744 instantiate_against_prelude(&bytes);
4745 }
4746
4747 #[test]
4748 fn arithmetic_precedence_returns_seven() {
4749 assert_eq!(
4750 run_main_f64("function main(): number { return 1 + 2 * 3; }"),
4751 7.0
4752 );
4753 }
4754
4755 #[test]
4756 fn arithmetic_subtraction_and_division() {
4757 assert_eq!(
4758 run_main_f64("function main(): number { return 10 - 4 / 2; }"),
4759 8.0
4760 );
4761 }
4762
4763 #[test]
4764 fn modulo_truncates_toward_zero() {
4765 assert_eq!(
4767 run_main_f64("function main(): number { return 10 % 3; }"),
4768 1.0
4769 );
4770 assert_eq!(
4771 run_main_f64("function main(): number { return -10 % 3; }"),
4772 -1.0
4773 );
4774 assert_eq!(
4775 run_main_f64("function main(): number { return 10 % -3; }"),
4776 1.0
4777 );
4778 }
4779
4780 #[test]
4781 fn division_by_zero_yields_infinity() {
4782 let r = run_main_f64("function main(): number { return 1 / 0; }");
4784 assert!(r.is_infinite() && r.is_sign_positive());
4785 }
4786
4787 #[test]
4788 fn zero_div_zero_yields_nan() {
4789 let r = run_main_f64("function main(): number { return 0 / 0; }");
4790 assert!(r.is_nan());
4791 }
4792
4793 #[test]
4794 fn unary_neg_negates_grouped_addition() {
4795 assert_eq!(
4796 run_main_f64("function main(): number { return -(2 + 3); }"),
4797 -5.0
4798 );
4799 }
4800
4801 #[test]
4802 fn unary_pos_is_identity() {
4803 assert_eq!(run_main_f64("function main(): number { return +5; }"), 5.0);
4804 }
4805
4806 #[test]
4807 fn nested_arithmetic_round_trip() {
4808 assert_eq!(
4809 run_main_f64("function main(): number { return (1 + 2) * (4 - 1); }"),
4810 9.0
4811 );
4812 }
4813
4814 #[test]
4815 fn top_level_let_referenced_from_main_body() {
4816 assert_eq!(
4817 run_main_f64("let x: number = 7; function main(): number { return x + 1; }"),
4818 8.0
4819 );
4820 }
4821
4822 #[test]
4823 fn while_counter_loop_sums_three_iterations() {
4824 let src = "function main(): number {\n let i = 0;\n let n = 0;\n while (i < 3) {\n n = n + i;\n i = i + 1;\n }\n return n;\n}";
4825 assert_eq!(run_main_f64(src), 3.0);
4826 }
4827
4828 #[test]
4829 fn while_zero_iterations_skips_body() {
4830 let src = "function main(): number {\n let i = 0;\n while (i < 0) {\n i = i + 1;\n }\n return i;\n}";
4831 assert_eq!(run_main_f64(src), 0.0);
4832 }
4833
4834 #[test]
4835 fn while_with_nested_if_counts_evens() {
4836 let src = "function main(): number {\n let i = 0;\n let evens = 0;\n while (i < 5) {\n if (i % 2 === 0) {\n evens = evens + 1;\n }\n i = i + 1;\n }\n return evens;\n}";
4837 assert_eq!(run_main_f64(src), 3.0);
4838 }
4839
4840 #[test]
4841 fn nested_while_loops_multiply_iterations() {
4842 let src = "function main(): number {\n let total = 0;\n let i = 0;\n while (i < 3) {\n let j = 0;\n while (j < 4) {\n total = total + 1;\n j = j + 1;\n }\n i = i + 1;\n }\n return total;\n}";
4843 assert_eq!(run_main_f64(src), 12.0);
4844 }
4845
4846 #[test]
4847 fn while_drives_top_level_global() {
4848 let src = "let g = 0; function main(): number {\n let i = 0;\n while (i < 4) {\n g = g + 1;\n i = i + 1;\n }\n return g;\n}";
4849 assert_eq!(run_main_f64(src), 4.0);
4850 }
4851
4852 #[test]
4853 fn if_true_takes_then_branch() {
4854 assert_eq!(
4855 run_main_f64("function main(): number { if (true) { return 1; } return 2; }"),
4856 1.0
4857 );
4858 }
4859
4860 #[test]
4861 fn if_false_skips_then_branch() {
4862 assert_eq!(
4863 run_main_f64("function main(): number { if (false) { return 1; } return 2; }"),
4864 2.0
4865 );
4866 }
4867
4868 #[test]
4869 fn if_else_picks_branch() {
4870 assert_eq!(
4871 run_main_f64("function main(): number { if (false) { return 1; } else { return 2; } }"),
4872 2.0
4873 );
4874 assert_eq!(
4875 run_main_f64("function main(): number { if (true) { return 1; } else { return 2; } }"),
4876 1.0
4877 );
4878 }
4879
4880 #[test]
4881 fn if_combines_with_assignment() {
4882 assert_eq!(
4883 run_main_f64(
4884 "function main(): number { let x: number = 0; if (1 < 2) { x = 1; } else { x = 2; } return x; }"
4885 ),
4886 1.0
4887 );
4888 }
4889
4890 #[test]
4891 fn else_if_chain_terminal_else() {
4892 let src = "function main(): number { let x: number = 3; if (x === 1) { return 10; } else if (x === 2) { return 20; } else { return 30; } }";
4893 assert_eq!(run_main_f64(src), 30.0);
4894 }
4895
4896 #[test]
4897 fn else_if_chain_middle_branch() {
4898 let src = "function main(): number { let x: number = 2; if (x === 1) { return 10; } else if (x === 2) { return 20; } else { return 30; } }";
4899 assert_eq!(run_main_f64(src), 20.0);
4900 }
4901
4902 #[test]
4903 fn nested_if_inside_if() {
4904 let src = "function main(): number { let x: number = 3; if (x > 0) { if (x > 2) { return 99; } return 11; } return 0; }";
4905 assert_eq!(run_main_f64(src), 99.0);
4906 }
4907
4908 #[test]
4909 fn function_local_let_round_trips_via_local() {
4910 assert_eq!(
4911 run_main_f64("function main(): number { let x: number = 1; return x; }"),
4912 1.0
4913 );
4914 }
4915
4916 #[test]
4917 fn function_local_assignment_overwrites_local() {
4918 assert_eq!(
4919 run_main_f64("function main(): number { let x: number = 1; x = 2; return x; }"),
4920 2.0
4921 );
4922 }
4923
4924 #[test]
4925 fn function_local_let_inferred_from_initializer() {
4926 assert_eq!(
4927 run_main_f64("function main(): number { let x = 5; return x + 1; }"),
4928 6.0
4929 );
4930 }
4931
4932 #[test]
4933 fn function_local_const_round_trips() {
4934 assert_eq!(
4935 run_main_f64("function main(): number { const c = 5; return c; }"),
4936 5.0
4937 );
4938 }
4939
4940 #[test]
4941 fn assignment_to_top_level_let_is_global_set() {
4942 assert_eq!(
4943 run_main_f64("let g = 1; function main(): number { g = 2; return g; }"),
4944 2.0
4945 );
4946 }
4947
4948 #[test]
4949 fn assignment_uses_self_reference_on_rhs() {
4950 assert_eq!(
4951 run_main_f64("function main(): number { let i = 0; i = i + 1; i = i + 1; return i; }"),
4952 2.0
4953 );
4954 }
4955
4956 #[test]
4957 fn const_reassignment_is_compile_error() {
4958 let mut asi = Asi::new(
4959 "function main(): number { const c = 5; c = 6; return c; }",
4960 crate::FileId(0),
4961 );
4962 let mut tokens: Vec<Token> = Vec::new();
4963 loop {
4964 let tok = asi.next_token();
4965 let is_eof = matches!(tok.kind, TokenKind::Eof);
4966 tokens.push(tok);
4967 if is_eof {
4968 break;
4969 }
4970 }
4971 let (ast, _) = parse(
4972 "function main(): number { const c = 5; c = 6; return c; }",
4973 tokens,
4974 crate::FileId(0),
4975 );
4976 let diags = infer_with_runtime_packages(
4977 "function main(): number { const c = 5; c = 6; return c; }",
4978 &ast,
4979 );
4980 assert!(
4981 diags
4982 .iter()
4983 .any(|d| d.message == "cannot assign to const binding `c`"),
4984 "expected const-reassignment diagnostic, got {diags:?}",
4985 );
4986 }
4987
4988 #[test]
4989 fn assignment_type_mismatch_is_compile_error() {
4990 let src = "function main(): number { let x: number = 1; x = 2; return x; }";
4991 let _ = compile(src);
4992
4993 let bad = r#"function main(): number { let x: number = 1; x = "hi"; return x; }"#;
4994 let mut asi = Asi::new(bad, crate::FileId(0));
4995 let mut tokens: Vec<Token> = Vec::new();
4996 loop {
4997 let tok = asi.next_token();
4998 let is_eof = matches!(tok.kind, TokenKind::Eof);
4999 tokens.push(tok);
5000 if is_eof {
5001 break;
5002 }
5003 }
5004 let (ast, _) = parse(bad, tokens, crate::FileId(0));
5005 let diags = infer_with_runtime_packages(bad, &ast);
5006 assert!(
5007 diags
5008 .iter()
5009 .any(|d| d.message.contains("expected `number`")),
5010 "expected type-mismatch diagnostic, got {diags:?}",
5011 );
5012 }
5013
5014 #[test]
5015 fn assignment_to_function_is_compile_error() {
5016 let bad = "function helper(): void { } function main(): void { helper = 1; }";
5017 let mut asi = Asi::new(bad, crate::FileId(0));
5018 let mut tokens: Vec<Token> = Vec::new();
5019 loop {
5020 let tok = asi.next_token();
5021 let is_eof = matches!(tok.kind, TokenKind::Eof);
5022 tokens.push(tok);
5023 if is_eof {
5024 break;
5025 }
5026 }
5027 let (ast, _) = parse(bad, tokens, crate::FileId(0));
5028 let diags = infer_with_runtime_packages(bad, &ast);
5029 assert!(
5030 diags
5031 .iter()
5032 .any(|d| d.message == "cannot assign to function `helper`"),
5033 "expected function-reassignment diagnostic, got {diags:?}",
5034 );
5035 }
5036
5037 #[test]
5038 fn boolean_literal_round_trips() {
5039 assert_eq!(run_main_i32("function main(): boolean { return true; }"), 1);
5040 assert_eq!(
5041 run_main_i32("function main(): boolean { return false; }"),
5042 0
5043 );
5044 }
5045
5046 #[test]
5047 fn numeric_comparisons() {
5048 assert_eq!(
5049 run_main_i32("function main(): boolean { return 1 < 2; }"),
5050 1
5051 );
5052 assert_eq!(
5053 run_main_i32("function main(): boolean { return 2 < 1; }"),
5054 0
5055 );
5056 assert_eq!(
5057 run_main_i32("function main(): boolean { return 2 > 1; }"),
5058 1
5059 );
5060 assert_eq!(
5061 run_main_i32("function main(): boolean { return 2 <= 2; }"),
5062 1
5063 );
5064 assert_eq!(
5065 run_main_i32("function main(): boolean { return 2 >= 3; }"),
5066 0
5067 );
5068 }
5069
5070 #[test]
5071 fn numeric_equality() {
5072 assert_eq!(
5073 run_main_i32("function main(): boolean { return 1 === 1; }"),
5074 1
5075 );
5076 assert_eq!(
5077 run_main_i32("function main(): boolean { const two: number = 2; return 1 !== two; }"),
5078 1
5079 );
5080 assert_eq!(
5081 run_main_i32("function main(): boolean { return 1 == 1; }"),
5082 1
5083 );
5084 assert_eq!(
5085 run_main_i32("function main(): boolean { return 1 != 1; }"),
5086 0
5087 );
5088 }
5089
5090 #[test]
5091 fn boolean_equality() {
5092 assert_eq!(
5093 run_main_i32("function main(): boolean { return true === true; }"),
5094 1
5095 );
5096 assert_eq!(
5097 run_main_i32(
5098 "function no(): boolean { return false; } function main(): boolean { return true !== no(); }"
5099 ),
5100 1
5101 );
5102 }
5103
5104 #[test]
5105 fn string_equality_via_runtime() {
5106 assert_eq!(
5107 run_main_i32(r#"function main(): boolean { return ("a" + "b") === "ab"; }"#),
5108 1
5109 );
5110 assert_eq!(
5111 run_main_i32(
5112 r#"function main(): boolean { const ac: string = "ac"; return "ab" !== ac; }"#
5113 ),
5114 1
5115 );
5116 assert_eq!(
5117 run_main_i32(
5118 r#"function main(): boolean { const ac: string = "ac"; return "ab" === ac; }"#
5119 ),
5120 0
5121 );
5122 }
5123
5124 #[test]
5125 fn unary_not() {
5126 assert_eq!(
5127 run_main_i32("function main(): boolean { return !true; }"),
5128 0
5129 );
5130 assert_eq!(
5131 run_main_i32("function main(): boolean { return !false; }"),
5132 1
5133 );
5134 assert_eq!(
5135 run_main_i32("function main(): boolean { return !!true; }"),
5136 1
5137 );
5138 }
5139
5140 #[test]
5141 fn logical_and_or_results() {
5142 assert_eq!(
5143 run_main_i32("function main(): boolean { return true && true; }"),
5144 1
5145 );
5146 assert_eq!(
5147 run_main_i32("function main(): boolean { return true && false; }"),
5148 0
5149 );
5150 assert_eq!(
5151 run_main_i32("function main(): boolean { return false || true; }"),
5152 1
5153 );
5154 assert_eq!(
5155 run_main_i32("function main(): boolean { return false || false; }"),
5156 0
5157 );
5158 }
5159
5160 #[test]
5161 fn logical_and_short_circuits_structurally() {
5162 let bytes = compile("function main(): boolean { return false && true; }");
5163 instantiate_against_prelude(&bytes);
5164 let main_idx = main_export_func_idx(&bytes).expect("main exported");
5165 let body_idx = (main_idx - imported_func_count(&bytes)) as usize;
5166 let mut bodies_seen = 0;
5167 let mut saw_if = false;
5168 for payload in Parser::new(0).parse_all(&bytes) {
5169 if let Payload::CodeSectionEntry(body) = payload.expect("payload") {
5170 if bodies_seen == body_idx {
5171 let mut reader = body
5172 .get_operators_reader()
5173 .expect("body operators readable");
5174 while !reader.eof() {
5175 if matches!(
5176 reader.read().expect("operator"),
5177 wasmparser::Operator::If { .. }
5178 ) {
5179 saw_if = true;
5180 break;
5181 }
5182 }
5183 break;
5184 }
5185 bodies_seen += 1;
5186 }
5187 }
5188 assert!(saw_if, "logical && should compile to a branchy `if` block");
5189 }
5190
5191 #[test]
5192 fn string_concat_compiles_and_links() {
5193 let bytes = compile(r#"function main(): string { return "a" + "b"; }"#);
5194 instantiate_against_prelude(&bytes);
5195
5196 let segments = data_segments(&bytes);
5197 assert_eq!(segments.len(), 2);
5198 assert_eq!(segments[0], b"a\0".to_vec());
5199 assert_eq!(segments[1], b"b\0".to_vec());
5200
5201 let concat_idx = imported_func_idx(&bytes, "submilli:prelude#string_concat");
5202 let calls = main_body_call_targets(&bytes);
5203 assert_eq!(
5204 calls,
5205 vec![concat_idx],
5206 "main should call string_concat exactly once",
5207 );
5208 }
5209
5210 #[test]
5211 fn string_concat_with_mixed_length_literals() {
5212 let bytes = compile(r#"function main(): string { return "Hello, " + "world"; }"#);
5213 instantiate_against_prelude(&bytes);
5214 assert_eq!(data_segments(&bytes).len(), 2);
5215 let concat_idx = imported_func_idx(&bytes, "submilli:prelude#string_concat");
5216 assert_eq!(main_body_call_targets(&bytes), vec![concat_idx]);
5217 }
5218
5219 #[test]
5220 fn string_concat_chained_emits_two_calls() {
5221 let bytes = compile(r#"function main(): string { return "a" + "b" + "c"; }"#);
5222 instantiate_against_prelude(&bytes);
5223 let concat_idx = imported_func_idx(&bytes, "submilli:prelude#string_concat");
5224 assert_eq!(main_body_call_targets(&bytes), vec![concat_idx, concat_idx]);
5225 }
5226
5227 #[test]
5228 fn string_concat_uses_top_level_global() {
5229 let bytes = compile(
5230 r#"let greeting: string = "Hello, "; function main(): string { return greeting + "world"; }"#,
5231 );
5232 instantiate_against_prelude(&bytes);
5233 let concat_idx = imported_func_idx(&bytes, "submilli:prelude#string_concat");
5234 assert_eq!(main_body_call_targets(&bytes), vec![concat_idx]);
5235 }
5236
5237 #[test]
5238 fn user_global_idx_resolves_for_consumer_locals() {
5239 let mut map = SymbolTable::default();
5240 map.record_global(crate::mangle::package_symbol("main", "x"), 0);
5241 map.record_global(crate::mangle::package_symbol("main", "y"), 1);
5242 assert_eq!(
5243 map.global_idx(&crate::mangle::package_symbol("main", "x")),
5244 Some(0)
5245 );
5246 assert_eq!(
5247 map.global_idx(&crate::mangle::package_symbol("main", "y")),
5248 Some(1)
5249 );
5250 assert_eq!(
5251 map.global_idx(&crate::mangle::package_symbol("main", "missing")),
5252 None
5253 );
5254 }
5255
5256 #[test]
5257 fn dwarf_sections_emitted_and_validate() {
5258 let bytes = compile("function main(): void { }");
5259 assert!(!custom_section(&bytes, ".debug_info").unwrap().is_empty());
5260 assert!(!custom_section(&bytes, ".debug_abbrev").unwrap().is_empty());
5261 assert!(!custom_section(&bytes, ".debug_str").unwrap().is_empty());
5262 instantiate_against_prelude(&bytes);
5263 }
5264
5265 #[test]
5266 fn dwarf_subprogram_carries_main_name() {
5267 let bytes = compile("function main(): void { }");
5268 let strs = custom_section(&bytes, ".debug_str").unwrap();
5269 assert!(
5270 strs.windows(4).any(|w| w == b"main"),
5271 "expected `main` in .debug_str, got {strs:?}",
5272 );
5273 }
5274
5275 #[test]
5276 fn dwarf_names_the_start_function_as_the_top_level() {
5277 let bytes = compile("function main(): void { }");
5278 let strs = custom_section(&bytes, ".debug_str").unwrap();
5279 assert!(
5280 strs.windows(11).any(|w| w == b"<top level>"),
5281 "expected `<top level>` in .debug_str, got {strs:?}",
5282 );
5283 assert!(
5284 !strs.windows(6).any(|w| w == b"_start"),
5285 "_start should not appear in .debug_str",
5286 );
5287 }
5288
5289 #[test]
5290 fn dwarf_die_snapshot() {
5291 let bytes = compile("function main(): void { }");
5292 let dump = dump_dwarf_dies(&bytes);
5293 insta::assert_snapshot!(dump);
5294 }
5295
5296 #[test]
5297 fn forward_reference_call_resolves() {
5298 assert_eq!(
5299 run_main_f64(
5300 "function main(): number { return helper(); } \
5301 function helper(): number { return 42; }",
5302 ),
5303 42.0,
5304 );
5305 }
5306
5307 #[test]
5308 fn function_with_args_passes_through() {
5309 assert_eq!(
5310 run_main_f64(
5311 "function double(n: number): number { return n * 2; } \
5312 function main(): number { return double(21); }",
5313 ),
5314 42.0,
5315 );
5316 }
5317
5318 #[test]
5319 fn function_with_multiple_args_in_order() {
5320 assert_eq!(
5321 run_main_f64(
5322 "function sub(a: number, b: number): number { return a - b; } \
5323 function main(): number { return sub(10, 3); }",
5324 ),
5325 7.0,
5326 );
5327 }
5328
5329 #[test]
5330 fn nested_call_evaluates_inner_first() {
5331 assert_eq!(
5332 run_main_f64(
5333 "function inner(n: number): number { return n + 1; } \
5334 function outer(n: number): number { return n * 10; } \
5335 function main(): number { return outer(inner(5)); }",
5336 ),
5337 60.0,
5338 );
5339 }
5340
5341 #[test]
5342 fn recursion_works_via_self_call() {
5343 assert_eq!(
5344 run_main_f64(
5345 "function fact(n: number): number { \
5346 if (n <= 1) { return 1; } else { return n * fact(n - 1); } \
5347 } \
5348 function main(): number { return fact(5); }",
5349 ),
5350 120.0,
5351 );
5352 }
5353
5354 #[test]
5355 fn mvp_target_greeting_self_check() {
5356 let src = "\
5357function greet(name: string): string {\n\
5358 return \"Hello, \" + name;\n\
5359}\n\
5360function main(): boolean {\n\
5361 return greet(\"world\") === \"Hello, world\";\n\
5362}\n";
5363 assert_eq!(run_main_i32(src), 1);
5364 }
5365
5366 #[test]
5367 fn top_level_let_initializer_calls_user_function() {
5368 let src = "\
5369function compute(): number { return 7; }\n\
5370let cached: number = compute();\n\
5371function main(): number { return cached + 1; }\n";
5372 assert_eq!(run_main_f64(src), 8.0);
5373 }
5374
5375 #[test]
5376 fn dwarf_subprograms_for_each_user_function() {
5377 let bytes = compile("function main(): void { } function helper(): void { }");
5378 let dump = dump_dwarf_dies(&bytes);
5379 insta::assert_snapshot!(dump);
5380 }
5381
5382 #[test]
5383 fn dwarf_line_section_emitted() {
5384 let bytes = compile("function main(): void { }");
5385 assert!(
5386 !custom_section(&bytes, ".debug_line").unwrap().is_empty(),
5387 ".debug_line should be emitted",
5388 );
5389 instantiate_against_prelude(&bytes);
5390 }
5391
5392 #[test]
5393 fn dwarf_line_program_snapshot() {
5394 let src = "\
5395function main(): number {
5396 let x: number = 1;
5397 let y: number = 2;
5398 return x + y;
5399}
5400";
5401 let bytes = compile(src);
5402 let dump = dump_dwarf_lines(&bytes);
5403 insta::assert_snapshot!(dump);
5404 }
5405
5406 #[test]
5407 fn dwarf_line_program_covers_helper() {
5408 let src = "\
5409function main(): number {
5410 return helper();
5411}
5412function helper(): number {
5413 return 42;
5414}
5415";
5416 let bytes = compile(src);
5417 let dump = dump_dwarf_lines(&bytes);
5418 insta::assert_snapshot!(dump);
5419 }
5420
5421 #[test]
5422 fn assert_true_is_no_op() {
5423 assert_eq!(
5424 run_main_f64("function main(): number { assert(true, \"ok\"); return 1; }",),
5425 1.0,
5426 );
5427 }
5428
5429 #[test]
5430 fn assert_typechecks_with_string_message() {
5431 run_main_f64(
5432 "function main(): number { let m: string = \"ok\"; assert(true, m); return 0; }",
5433 );
5434 }
5435
5436 #[test]
5437 fn assert_evaluates_msg_for_side_effects() {
5438 assert_eq!(
5439 run_main_f64("function main(): number { assert(true, \"a\" + \"b\"); return 1; }",),
5440 1.0,
5441 );
5442 }
5443
5444 #[test]
5445 fn user_function_named_assert_is_rejected() {
5446 let src = "function assert(): void { } function main(): void { }";
5447 let mut asi = Asi::new(src, crate::FileId(0));
5448 let mut tokens: Vec<Token> = Vec::new();
5449 loop {
5450 let tok = asi.next_token();
5451 let is_eof = matches!(tok.kind, TokenKind::Eof);
5452 tokens.push(tok);
5453 if is_eof {
5454 break;
5455 }
5456 }
5457 let (ast, _) = parse(src, tokens, crate::FileId(0));
5458 let diags = infer_with_runtime_packages(src, &ast);
5459 assert!(
5460 diags.iter().any(|d| d.message
5461 == "`assert` is a reserved compiler intrinsic and cannot be redeclared"),
5462 "expected reserved-intrinsic diagnostic, got {diags:?}",
5463 );
5464 }
5465
5466 #[test]
5467 fn assert_false_throws_with_backtrace() {
5468 let dump = run_main_expecting_error("function main(): void { assert(false, \"oops\"); }");
5469 insta::assert_snapshot!(dump);
5470 }
5471
5472 #[test]
5473 fn multi_frame_backtrace() {
5474 let src = "\
5475function inner(): void { assert(false, \"x\"); }
5476function main(): void { inner(); }
5477";
5478 let dump = run_main_expecting_error(src);
5479 insta::assert_snapshot!(dump);
5480 }
5481
5482 #[test]
5483 fn three_frame_backtrace_labels_caller_in_middle() {
5484 let src = "\
5485function deepest(): void { assert(false, \"x\"); }
5486function middle(): void { deepest(); }
5487function main(): void { middle(); }
5488";
5489 let dump = run_main_expecting_error(src);
5490 insta::assert_snapshot!(dump);
5491 }
5492
5493 #[test]
5494 fn closure_method_accessor_and_constructor_frames_are_named() {
5495 let src = "\
5496class Refused { constructor() { assert(false, \"x\"); } }
5497class Holder {
5498 get refused(): Refused { return new Refused(); }
5499 read(): Refused { return this.refused; }
5500}
5501function main(): void {
5502 const named = function outer(): void {
5503 [1].forEach((n: number): void => { new Holder().read(); });
5504 };
5505 named();
5506}
5507";
5508 let dump = run_main_expecting_error(src);
5509 insta::assert_snapshot!(dump);
5510 }
5511
5512 #[test]
5513 fn implicit_constructor_frame_points_at_its_own_class() {
5514 let src = "\
5518class Base {
5519 constructor(n: number) { assert(n > 5, \"small\"); }
5520 describe(): string { return \"unrelated\"; }
5521}
5522class Derived extends Base {}
5523function main(): void { new Derived(1); }
5524";
5525 let dump = run_main_expecting_error(src);
5526 assert!(
5527 dump.contains(" at new Derived (script.subm:5:7) [caller]\n"),
5528 "{dump}"
5529 );
5530 }
5531
5532 #[test]
5533 fn a_method_named_like_an_accessor_is_not_framed_as_one() {
5534 let src = "\
5535class C { \"get x\"(): void { assert(false, \"x\"); } }
5536function main(): void { new C()[\"get x\"](); }
5537";
5538 let dump = run_main_expecting_error(src);
5539 assert!(dump.contains(" at C.get x (script.subm:1:"), "{dump}");
5540 }
5541
5542 #[test]
5543 fn a_method_key_with_control_characters_compiles_and_frames_escaped() {
5544 let src = "\
5545class C { \"a\\u0000\\nb\"(): void { assert(false, \"x\"); } }
5546function main(): void { new C()[\"a\\u0000\\nb\"](); }
5547";
5548 let dump = run_main_expecting_error(src);
5549 assert!(
5550 dump.contains(" at C.a\\u{0}\\nb (script.subm:1:"),
5551 "{dump}"
5552 );
5553 }
5554
5555 #[test]
5556 fn failed_cast_backtrace_points_at_the_cast() {
5557 let src = "\
5558interface P { a: number }
5559function g(n: number, p: P): number { return n; }
5560function main(): void {
5561 const r = g(1,
5562 JSON.parse(\"{}\") as P);
5563}
5564";
5565 let dump = run_main_expecting_error(src);
5566 insta::assert_snapshot!(dump);
5567 }
5568
5569 #[test]
5570 fn code_after_a_cast_keeps_its_backtrace_location() {
5571 let src = "\
5572interface P { a: number }
5573function g(n: number, p: P): number { assert(false, \"x\"); return n; }
5574function main(): void {
5575 const r = g(1, JSON.parse(\"{\\\"a\\\": 1}\") as P);
5576}
5577";
5578 let dump = run_main_expecting_error(src);
5579 insta::assert_snapshot!(dump);
5580 }
5581
5582 #[test]
5583 fn render_backtrace_returns_none_for_non_trap_error() {
5584 #[derive(Debug)]
5585 struct Plain;
5586 impl std::fmt::Display for Plain {
5587 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5588 f.write_str("plain error")
5589 }
5590 }
5591 impl std::error::Error for Plain {}
5592 let err: wasmtime::Error = wasmtime::Error::new(Plain);
5593 let (sources, file) = crate::Sources::single("script.subm", "").unwrap();
5594 assert!(
5595 crate::render_backtrace(&err, &sources, file, crate::BacktraceMode::Full).is_none()
5596 );
5597 }
5598
5599 fn run_main_with_cfg_expecting_trap(
5600 cfg: &crate::runtime::RuntimeConfig,
5601 source: &str,
5602 ) -> String {
5603 let bytes = compile(source);
5604 let (mut store, inst) = link_consumer_with(cfg, &bytes);
5605 let main = inst
5606 .get_func(&mut store, "main")
5607 .expect("main signature is `() -> void` in expecting-trap fixtures");
5608 let err = pollster::block_on(main.call_async(
5609 &mut store,
5610 &[],
5611 &mut [wasmtime::Val::AnyRef(None)],
5612 ))
5613 .expect_err("expected main() to trap");
5614 let (sources, file) = crate::Sources::single("script.subm", source).unwrap();
5615 crate::render_backtrace(&err, &sources, file, crate::BacktraceMode::Full)
5616 .expect("backtrace empty — was wasm_backtrace_details enabled?")
5617 }
5618
5619 #[test]
5620 fn raw_trap_renders_an_error_header() {
5621 let cfg = crate::runtime::RuntimeConfig {
5622 max_wasm_stack: 64 * 1024,
5623 ..Default::default()
5624 };
5625 let dump = run_main_with_cfg_expecting_trap(
5626 &cfg,
5627 "function rec(): void { rec(); } function main(): void { rec(); }",
5628 );
5629 assert!(
5630 dump.starts_with("error: call stack exhausted\n"),
5631 "expected an `error:` header naming the trap, got:\n{dump}",
5632 );
5633 }
5634
5635 #[test]
5636 fn fuel_trap_header_does_not_leak_the_engine() {
5637 let cfg = crate::runtime::RuntimeConfig {
5640 fuel: 1024,
5641 ..Default::default()
5642 };
5643 let dump =
5644 run_main_with_cfg_expecting_trap(&cfg, "function main(): void { while (true) { } }");
5645 assert!(
5646 dump.starts_with("error: fuel exhausted\n"),
5647 "expected a curated `error:` header, got:\n{dump}",
5648 );
5649 assert!(
5650 !dump.contains("WebAssembly"),
5651 "engine wording leaked into the header:\n{dump}",
5652 );
5653 }
5654
5655 #[test]
5656 fn fuel_exhaustion_traps_with_label() {
5657 let cfg = crate::runtime::RuntimeConfig {
5658 fuel: 1024,
5659 ..Default::default()
5660 };
5661 let dump =
5662 run_main_with_cfg_expecting_trap(&cfg, "function main(): void { while (true) { } }");
5663 assert!(
5664 dump.contains("[fuel exhausted]"),
5665 "expected fuel-exhausted label, got:\n{dump}",
5666 );
5667 }
5668
5669 #[test]
5670 fn stack_overflow_traps_with_label() {
5671 let cfg = crate::runtime::RuntimeConfig {
5672 max_wasm_stack: 64 * 1024,
5673 ..Default::default()
5674 };
5675 let dump = run_main_with_cfg_expecting_trap(
5676 &cfg,
5677 "function rec(): void { rec(); } \
5678 function main(): void { rec(); }",
5679 );
5680 assert!(
5681 dump.contains("[stack overflow]"),
5682 "expected stack-overflow label, got:\n{dump}",
5683 );
5684 }
5685
5686 fn run_main_capturing_console(source: &str) -> String {
5687 use std::sync::{Arc, Mutex};
5688 struct Shared(Arc<Mutex<Vec<u8>>>);
5689 impl std::io::Write for Shared {
5690 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
5691 self.0.lock().unwrap().write(buf)
5692 }
5693 fn flush(&mut self) -> std::io::Result<()> {
5694 Ok(())
5695 }
5696 }
5697 let buf = Arc::new(Mutex::new(Vec::new()));
5698 let mut data = crate::runtime::StoreData::with_vfs(
5699 "test-run".into(),
5700 crate::runtime::Vfs::tempdir().expect("tempdir for codegen test VFS"),
5701 );
5702 data.console = Box::new(Shared(Arc::clone(&buf)));
5703 let cfg = crate::runtime::RuntimeConfig::default();
5704 let bytes = compile(source);
5705 let engine = cfg.engine().expect("engine");
5706 let mut store = cfg.store(&engine, data).expect("store");
5707 let consumer_module = wasmtime::Module::new(&engine, &bytes).expect("consumer module");
5708 let mut linker = wasmtime::Linker::<crate::runtime::StoreData>::new(&engine);
5709 let inst = pollster::block_on(async {
5710 crate::runtime::install_runtime_async(&mut linker, &mut store)
5711 .await
5712 .expect("runtime");
5713 linker
5714 .instantiate_async(&mut store, &consumer_module)
5715 .await
5716 .expect("consumer instantiates")
5717 });
5718 let main = inst.get_func(&mut store, "main").expect("main () -> void");
5719 pollster::block_on(main.call_async(&mut store, &[], &mut [wasmtime::Val::AnyRef(None)]))
5720 .expect("main does not trap");
5721 let captured = buf.lock().unwrap().clone();
5722 String::from_utf8(captured).expect("utf8")
5723 }
5724
5725 #[test]
5726 fn console_log_writes_message_with_newline() {
5727 let out = run_main_capturing_console("function main(): void { console.log(\"hi\"); }");
5728 assert_eq!(out, "hi\n");
5729 }
5730
5731 #[test]
5732 fn console_log_uses_string_concat() {
5733 let out = run_main_capturing_console(
5734 "function main(): void { console.log(\"Hello, \" + \"world\"); }",
5735 );
5736 assert_eq!(out, "Hello, world\n");
5737 }
5738
5739 #[test]
5740 fn console_log_joins_multiple_primitive_args() {
5741 let out =
5742 run_main_capturing_console("function main(): void { console.log(\"a\", 1, true); }");
5743 assert_eq!(out, "a 1 true\n");
5744 }
5745
5746 #[test]
5747 fn console_log_coerces_array_and_object_args() {
5748 let out =
5749 run_main_capturing_console("function main(): void { console.log([1, 2], { x: 1 }); }");
5750 assert_eq!(out, "1,2 [object Object]\n");
5751 }
5752
5753 #[test]
5754 fn console_log_accepts_single_non_string_arg() {
5755 let out = run_main_capturing_console(
5756 "function main(): void { const items = [1, 2, 3]; console.log(items.length); }",
5757 );
5758 assert_eq!(out, "3\n");
5759 }
5760
5761 #[test]
5762 fn console_log_multiple_calls_appends() {
5763 let out = run_main_capturing_console(
5764 "function main(): void { console.log(\"a\"); console.log(\"b\"); }",
5765 );
5766 assert_eq!(out, "a\nb\n");
5767 }
5768
5769 #[test]
5770 fn console_log_inside_function_call() {
5771 let out = run_main_capturing_console(
5772 "function shout(s: string): void { console.log(s); } \
5773 function main(): void { shout(\"hello\"); }",
5774 );
5775 assert_eq!(out, "hello\n");
5776 }
5777
5778 #[test]
5779 fn user_function_named_console_is_rejected() {
5780 let src = "function console(): void { } function main(): void { }";
5781 let mut asi = Asi::new(src, crate::FileId(0));
5782 let mut tokens: Vec<Token> = Vec::new();
5783 loop {
5784 let tok = asi.next_token();
5785 let is_eof = matches!(tok.kind, TokenKind::Eof);
5786 tokens.push(tok);
5787 if is_eof {
5788 break;
5789 }
5790 }
5791 let (ast, _) = parse(src, tokens, crate::FileId(0));
5792 let diags = infer_with_runtime_packages(src, &ast);
5793 assert!(
5794 diags
5795 .iter()
5796 .any(|d| d.message.contains("duplicate declaration of `console`")),
5797 "expected duplicate-declaration diagnostic, got {diags:?}",
5798 );
5799 }
5800
5801 #[test]
5802 fn object_literal_round_trip() {
5803 let result =
5804 run_main_f64("function main(): number { let p = { x: 1, y: 2 }; return p.x + p.y; }");
5805 assert_eq!(result, 3.0);
5806 }
5807
5808 #[test]
5809 fn object_literal_field_order_independent() {
5810 let result =
5811 run_main_f64("function main(): number { let p = { y: 10, x: 5 }; return p.y; }");
5812 assert_eq!(result, 10.0);
5813 }
5814
5815 #[test]
5816 fn object_with_string_field_round_trip() {
5817 let bytes = compile(
5818 r#"function main(): string { let p = { name: "hi", age: 30 }; return p.name; }"#,
5819 );
5820 let cfg = crate::runtime::RuntimeConfig::default();
5821 let result = pollster::block_on(cfg.run(&bytes)).expect("run");
5822 assert_eq!(result.value.as_deref(), Some("hi"));
5823 }
5824
5825 #[test]
5826 fn object_passed_through_function_signature() {
5827 let result = run_main_f64(
5828 "function take(p: { v: number }): number { return p.v; } function main(): number { return take({ v: 7 }); }",
5829 );
5830 assert_eq!(result, 7.0);
5831 }
5832
5833 #[test]
5834 fn array_of_numbers_round_trip() {
5835 let result = run_main_f64("function main(): number { let a = [1, 2, 3]; return a[1]; }");
5836 assert_eq!(result, 2.0);
5837 }
5838
5839 #[test]
5840 fn array_of_numbers_annotated() {
5841 let result =
5842 run_main_f64("function main(): number { let a: number[] = [42]; return a[0]; }");
5843 assert_eq!(result, 42.0);
5844 }
5845
5846 #[test]
5847 fn array_of_strings_no_boxing() {
5848 let bytes = compile(r#"function main(): string { let a = ["a", "b", "c"]; return a[2]; }"#);
5849 let cfg = crate::runtime::RuntimeConfig::default();
5850 let result = pollster::block_on(cfg.run(&bytes)).expect("run");
5851 assert_eq!(result.value.as_deref(), Some("c"));
5852 }
5853
5854 #[test]
5855 fn array_of_objects_field_access() {
5856 let result = run_main_f64(
5857 "function main(): number { let xs: { v: number }[] = [{ v: 7 }]; return xs[0].v; }",
5858 );
5859 assert_eq!(result, 7.0);
5860 }
5861
5862 #[test]
5863 fn array_index_out_of_bounds_throws_catchable() {
5864 let bytes = compile(
5867 r#"function main(): string {
5868 let a = [1, 2, 3];
5869 try {
5870 let _ = a[10];
5871 return "no throw";
5872 } catch (e: Error) {
5873 return e.message;
5874 }
5875 }"#,
5876 );
5877 let cfg = crate::runtime::RuntimeConfig::default();
5878 let result =
5879 pollster::block_on(cfg.run(&bytes)).expect("OOB throw is catchable, not a trap");
5880 assert_eq!(result.value.as_deref(), Some("index out of range"));
5881 }
5882
5883 #[test]
5884 fn array_index_out_of_bounds_uncaught_is_not_a_trap() {
5885 let bytes = compile("function main(): number { let a = [1, 2, 3]; return a[10]; }");
5886 let cfg = crate::runtime::RuntimeConfig::default();
5887 let err = pollster::block_on(cfg.run(&bytes)).expect_err("uncaught OOB throw still aborts");
5888 assert!(
5889 err.downcast_ref::<wasmtime::Trap>().is_none(),
5890 "OOB should surface as a thrown exception, not a Wasm trap: {err:#}",
5891 );
5892 }
5893
5894 use crate::codegen::function_emitter::stmt::emit_statement;
5896 use crate::{ClosureBody, ExprId, StmtId, TypedExprKind, TypedStmtKind};
5897 use wasm_encoder::HeapType;
5898 use wasm_encoder::Ieee64;
5899 use wasm_encoder::Instruction;
5900 use wasm_encoder::{
5901 CodeSection, ConstExpr, ExportKind, ExportSection, Function, FunctionSection,
5902 GlobalSection, ImportSection, Module, RefType, TypeSection, ValType,
5903 };
5904
5905 fn boxed_typed_ast(source: &str) -> TypedAst {
5906 let mut asi = Asi::new(source, crate::FileId(0));
5907 let mut tokens: Vec<Token> = Vec::new();
5908 loop {
5909 let tok = asi.next_token();
5910 let is_eof = matches!(tok.kind, TokenKind::Eof);
5911 tokens.push(tok);
5912 if is_eof {
5913 break;
5914 }
5915 }
5916 let lex_diags = asi.into_diagnostics();
5917 assert!(lex_diags.is_empty(), "{lex_diags:?}");
5918 let (ast, parse_diags) = parse(source, tokens, crate::FileId(0));
5919 assert!(parse_diags.is_empty(), "{parse_diags:?}");
5920 let packages = runtime_packages(&[]);
5921 let (mut ta, infer_diags) = infer(source, "main", &ast, &packages);
5922 assert!(infer_diags.is_empty(), "{infer_diags:?}");
5923 let chk = check(&ta).unwrap();
5924 assert!(chk.is_empty(), "{chk:?}");
5925 ta = capture(ta).unwrap();
5926 ta = desugar(ta, crate::FileId(0)).unwrap();
5927 ta
5928 }
5929
5930 fn box_named(ta: &mut TypedAst, names: &[&str]) {
5931 let func_meta: Vec<(usize, Vec<String>, StmtId)> = ta
5932 .functions
5933 .iter()
5934 .enumerate()
5935 .map(|(i, f)| {
5936 let names = f.params.iter().map(|p| p.name.name.clone()).collect();
5937 (i, names, f.body)
5938 })
5939 .collect();
5940 for (fi, param_names, body) in func_meta {
5941 for (pi, pname) in param_names.iter().enumerate() {
5942 if names.contains(&pname.as_str()) {
5943 ta.functions[fi].params[pi].boxed = true;
5944 }
5945 }
5946 box_walk_stmt(ta, body, names);
5947 }
5948 let stmt_ids: Vec<StmtId> = ta.top_level_statements.clone();
5949 for sid in stmt_ids {
5950 box_walk_stmt(ta, sid, names);
5951 }
5952 }
5953
5954 fn box_walk_stmt(ta: &mut TypedAst, id: StmtId, names: &[&str]) {
5955 let kind = ta.try_stmt(id).unwrap().kind.clone();
5956 match kind {
5957 TypedStmtKind::Block(stmts) => {
5958 for s in stmts {
5959 box_walk_stmt(ta, s, names);
5960 }
5961 }
5962 TypedStmtKind::Let { name, value, .. } => {
5963 if names.iter().any(|n| *n == name.name)
5964 && let TypedStmtKind::Let { boxed, .. } = &mut ta.try_stmt_mut(id).unwrap().kind
5965 {
5966 *boxed = true;
5967 }
5968 box_walk_expr(ta, value, names);
5969 }
5970 TypedStmtKind::Const { value, .. } => box_walk_expr(ta, value, names),
5971 TypedStmtKind::If {
5972 condition,
5973 then_block,
5974 else_block,
5975 } => {
5976 box_walk_expr(ta, condition, names);
5977 box_walk_stmt(ta, then_block, names);
5978 if let Some(eb) = else_block {
5979 box_walk_stmt(ta, eb, names);
5980 }
5981 }
5982 TypedStmtKind::While { condition, body } => {
5983 box_walk_expr(ta, condition, names);
5984 box_walk_stmt(ta, body, names);
5985 }
5986 TypedStmtKind::For {
5987 init,
5988 condition,
5989 update,
5990 body,
5991 } => {
5992 for i in init {
5993 box_walk_stmt(ta, i, names);
5994 }
5995 if let Some(c) = condition {
5996 box_walk_expr(ta, c, names);
5997 }
5998 if let Some(u) = update {
5999 box_walk_stmt(ta, u, names);
6000 }
6001 box_walk_stmt(ta, body, names);
6002 }
6003 TypedStmtKind::ForOf { iter, body, .. } => {
6004 box_walk_expr(ta, iter, names);
6005 box_walk_stmt(ta, body, names);
6006 }
6007 TypedStmtKind::DoWhile { body, condition } => {
6008 box_walk_stmt(ta, body, names);
6009 box_walk_expr(ta, condition, names);
6010 }
6011 TypedStmtKind::Switch {
6012 discriminant,
6013 cases,
6014 default,
6015 ..
6016 } => {
6017 box_walk_expr(ta, discriminant, names);
6018 for comparison in cases
6019 .iter()
6020 .flat_map(crate::TypedSwitchCase::label_comparisons)
6021 {
6022 box_walk_expr(ta, comparison, names);
6023 }
6024 for case in cases {
6025 box_walk_stmt(ta, case.body, names);
6026 }
6027 if let Some(d) = default {
6028 box_walk_stmt(ta, d, names);
6029 }
6030 }
6031 TypedStmtKind::Break | TypedStmtKind::Continue | TypedStmtKind::ReboxLocal { .. } => {}
6032 TypedStmtKind::Return(value) => {
6033 if let Some(v) = value {
6034 box_walk_expr(ta, v, names);
6035 }
6036 }
6037 TypedStmtKind::Expr(e) => box_walk_expr(ta, e, names),
6038 TypedStmtKind::AssignLocal { ident, value, .. } => {
6039 if names.iter().any(|n| *n == ident.name)
6040 && let TypedStmtKind::AssignLocal { boxed, .. } =
6041 &mut ta.try_stmt_mut(id).unwrap().kind
6042 {
6043 *boxed = true;
6044 }
6045 box_walk_expr(ta, value, names);
6046 }
6047 TypedStmtKind::AssignGlobal { value, .. } => {
6048 box_walk_expr(ta, value, names);
6049 }
6050 TypedStmtKind::AssignField {
6051 receiver, value, ..
6052 } => {
6053 box_walk_expr(ta, receiver, names);
6054 box_walk_expr(ta, value, names);
6055 }
6056 TypedStmtKind::AssignIndex {
6057 receiver,
6058 index,
6059 value,
6060 ..
6061 } => {
6062 box_walk_expr(ta, receiver, names);
6063 box_walk_expr(ta, index, names);
6064 box_walk_expr(ta, value, names);
6065 }
6066 TypedStmtKind::NarrowRegion { source, body, .. } => {
6067 box_walk_expr(ta, source, names);
6068 box_walk_stmt(ta, body, names);
6069 }
6070 TypedStmtKind::Throw { value } => box_walk_expr(ta, value, names),
6071 TypedStmtKind::Try {
6072 body,
6073 catches,
6074 finally,
6075 } => {
6076 box_walk_stmt(ta, body, names);
6077 for (i, c) in catches.iter().enumerate() {
6078 if names.iter().any(|n| *n == c.binding.name)
6079 && let TypedStmtKind::Try { catches: cs, .. } =
6080 &mut ta.try_stmt_mut(id).unwrap().kind
6081 {
6082 cs[i].boxed = true;
6083 }
6084 box_walk_stmt(ta, c.body, names);
6085 }
6086 if let Some(f) = finally {
6087 box_walk_stmt(ta, f, names);
6088 }
6089 }
6090 }
6091 }
6092
6093 fn box_walk_expr(ta: &mut TypedAst, id: ExprId, names: &[&str]) {
6094 let kind = ta.try_expr(id).unwrap().kind.clone();
6095 match kind {
6096 TypedExprKind::LocalRef { ident, .. } => {
6097 if names.iter().any(|n| *n == ident.name)
6098 && let TypedExprKind::LocalRef { boxed, .. } =
6099 &mut ta.try_expr_mut(id).unwrap().kind
6100 {
6101 *boxed = true;
6102 }
6103 }
6104 TypedExprKind::LocalNarrowRef { .. } => {}
6105 TypedExprKind::Closure { params, body, .. } => {
6106 for (i, p) in params.iter().enumerate() {
6107 if names.iter().any(|n| *n == p.name.name)
6108 && let TypedExprKind::Closure { params, .. } =
6109 &mut ta.try_expr_mut(id).unwrap().kind
6110 {
6111 params[i].boxed = true;
6112 }
6113 }
6114 match body {
6115 ClosureBody::Expr(e) => box_walk_expr(ta, e, names),
6116 ClosureBody::Block(b) => box_walk_stmt(ta, b, names),
6117 }
6118 }
6119 TypedExprKind::Binary { lhs, rhs, .. } => {
6120 box_walk_expr(ta, lhs, names);
6121 box_walk_expr(ta, rhs, names);
6122 }
6123 TypedExprKind::Unary { operand, .. } => box_walk_expr(ta, operand, names),
6124 TypedExprKind::TypeofTag { value, .. } | TypedExprKind::InstanceOf { value, .. } => {
6125 box_walk_expr(ta, value, names);
6126 }
6127 TypedExprKind::Call { args, .. }
6128 | TypedExprKind::VirtualCall { args, .. }
6129 | TypedExprKind::SuperCtorCall { args, .. }
6130 | TypedExprKind::SuperMethodCall { args, .. } => {
6131 for a in args {
6132 box_walk_expr(ta, a, names);
6133 }
6134 }
6135 TypedExprKind::CallClosure { callee, args } => {
6136 box_walk_expr(ta, callee, names);
6137 for a in args {
6138 box_walk_expr(ta, a, names);
6139 }
6140 }
6141 TypedExprKind::GenericCall { args, .. } => {
6142 for a in args {
6143 box_walk_expr(ta, a.expr, names);
6144 }
6145 }
6146 TypedExprKind::MethodCall { receiver, args, .. } => {
6147 box_walk_expr(ta, receiver, names);
6148 for a in args {
6149 box_walk_expr(ta, a, names);
6150 }
6151 }
6152 TypedExprKind::GenericMethodCall { receiver, args, .. } => {
6153 box_walk_expr(ta, receiver, names);
6154 for a in args {
6155 box_walk_expr(ta, a.expr, names);
6156 }
6157 }
6158 TypedExprKind::IntrinsicCall { args, .. } => {
6159 for a in args {
6160 box_walk_expr(ta, a, names);
6161 }
6162 }
6163 TypedExprKind::ObjectLiteral { members, .. } => {
6164 for member in members {
6165 for expression in member.expressions() {
6166 box_walk_expr(ta, expression, names);
6167 }
6168 }
6169 }
6170 TypedExprKind::ArrayLiteral { elements, .. } => {
6171 for e in elements {
6172 box_walk_expr(ta, e.expr_id(), names);
6173 }
6174 }
6175 TypedExprKind::TupleLiteral { elements, .. } => {
6176 for e in elements {
6177 box_walk_expr(ta, e, names);
6178 }
6179 }
6180 TypedExprKind::FieldAccess { receiver, .. }
6181 | TypedExprKind::InterfacePropertyAccess { receiver, .. } => {
6182 box_walk_expr(ta, receiver, names);
6183 }
6184 TypedExprKind::IndexAccess { receiver, index } => {
6185 box_walk_expr(ta, receiver, names);
6186 box_walk_expr(ta, index, names);
6187 }
6188 TypedExprKind::Narrowed { source, inner, .. } => {
6189 box_walk_expr(ta, source, names);
6190 box_walk_expr(ta, inner, names);
6191 }
6192 TypedExprKind::Ternary { cond, then_, else_ } => {
6193 box_walk_expr(ta, cond, names);
6194 box_walk_expr(ta, then_, names);
6195 box_walk_expr(ta, else_, names);
6196 }
6197 TypedExprKind::NullishCoalesce { lhs, rhs } => {
6198 box_walk_expr(ta, lhs, names);
6199 box_walk_expr(ta, rhs, names);
6200 }
6201 TypedExprKind::OptionalChain { base, parts } => {
6202 box_walk_expr(ta, base, names);
6203 let to_walk: Vec<ExprId> = parts
6204 .iter()
6205 .flat_map(|p| match p {
6206 crate::TypedChainPart::Index { idx, .. } => vec![*idx],
6207 crate::TypedChainPart::Call { args, .. }
6208 | crate::TypedChainPart::MethodCall { args, .. } => args.clone(),
6209 crate::TypedChainPart::Field { .. }
6210 | crate::TypedChainPart::InterfaceProperty { .. }
6211 | crate::TypedChainPart::NonNull { .. } => Vec::new(),
6212 })
6213 .collect();
6214 for id in to_walk {
6215 box_walk_expr(ta, id, names);
6216 }
6217 }
6218 TypedExprKind::PostfixUnary { target, .. } => match target {
6219 crate::PostfixTarget::Local { ident, .. } => {
6220 if names.iter().any(|n| *n == ident.name)
6221 && let TypedExprKind::PostfixUnary {
6222 target: crate::PostfixTarget::Local { boxed, .. },
6223 ..
6224 } = &mut ta.try_expr_mut(id).unwrap().kind
6225 {
6226 *boxed = true;
6227 }
6228 }
6229 crate::PostfixTarget::Global { .. } => {}
6230 crate::PostfixTarget::Field { receiver, .. } => {
6231 box_walk_expr(ta, receiver, names);
6232 }
6233 crate::PostfixTarget::Index {
6234 receiver, index, ..
6235 } => {
6236 box_walk_expr(ta, receiver, names);
6237 box_walk_expr(ta, index, names);
6238 }
6239 },
6240 TypedExprKind::NonNullAssert { value } | TypedExprKind::Cast { value, .. } => {
6241 box_walk_expr(ta, value, names);
6242 }
6243 TypedExprKind::EffectThen { effect, result } => {
6244 box_walk_expr(ta, effect, names);
6245 box_walk_expr(ta, result, names);
6246 }
6247 TypedExprKind::Sequence { stmts, result } => {
6248 for stmt in stmts {
6249 box_walk_stmt(ta, stmt, names);
6250 }
6251 box_walk_expr(ta, result, names);
6252 }
6253 TypedExprKind::Number(_)
6254 | TypedExprKind::BigInt(_)
6255 | TypedExprKind::String(_)
6256 | TypedExprKind::Boolean(_)
6257 | TypedExprKind::Null
6258 | TypedExprKind::Undefined
6259 | TypedExprKind::This
6260 | TypedExprKind::Regex { .. }
6261 | TypedExprKind::GlobalRef { .. }
6262 | TypedExprKind::FunctionRef { .. }
6263 | TypedExprKind::NumberEnumMember { .. }
6264 | TypedExprKind::StringEnumMember { .. } => {}
6265 }
6266 }
6267
6268 fn compile_with_boxed(source: &str, names: &[&str]) -> Vec<u8> {
6269 let mut ta = boxed_typed_ast(source);
6270 box_named(&mut ta, names);
6271
6272 let (prelude_defs, host_defs, internal_defs) =
6273 prelude::cached_runtime_package_declarations();
6274 let mut dependencies: Vec<&crate::PackageDeclaration> = prelude_defs.iter().collect();
6275 dependencies.extend(host_defs.iter());
6276 dependencies.extend(internal_defs.iter());
6279 codegen(source, "script.subm", crate::FileId(0), &ta, &dependencies)
6280 .expect("code generation")
6281 }
6282
6283 fn run_main_f64_boxed(source: &str, names: &[&str]) -> f64 {
6284 run_main_number(&compile_with_boxed(source, names))
6285 }
6286
6287 fn run_main_string_boxed(source: &str, names: &[&str]) -> String {
6288 let bytes = compile_with_boxed(source, names);
6289 let cfg = crate::runtime::RuntimeConfig::default();
6290 let result = pollster::block_on(cfg.run(&bytes)).expect("main does not trap");
6291 result.value.expect("main returns a string")
6292 }
6293
6294 #[test]
6295 fn boxed_let_number_round_trip() {
6296 let source = "function main(): number { let x: number = 1; x = x + 1; return x; }";
6297 let result = run_main_f64_boxed(source, &["x"]);
6298 assert_eq!(result, 2.0);
6299 }
6300
6301 #[test]
6302 fn boxed_let_string_round_trip() {
6303 let source =
6304 r#"function main(): string { let s: string = "hello"; s = "world"; return s; }"#;
6305 let result = run_main_string_boxed(source, &["s"]);
6306 assert_eq!(result, "world");
6307 }
6308
6309 #[test]
6310 fn boxed_let_user_object_round_trip() {
6311 let source = "
6312 function main(): number {
6313 let p: { v: number } = { v: 42 };
6314 p = { v: 99 };
6315 return p.v;
6316 }
6317 ";
6318 let result = run_main_f64_boxed(source, &["p"]);
6319 assert_eq!(result, 99.0);
6320 }
6321
6322 #[test]
6323 fn boxed_param_round_trip() {
6324 let source = "
6325 function helper(p: number): number { return p + 1; }
6326 function main(): number { return helper(5); }
6327 ";
6328 let result = run_main_f64_boxed(source, &["p"]);
6329 assert_eq!(result, 6.0);
6330 }
6331
6332 #[test]
6333 fn boxed_param_with_assignment() {
6334 let source = "
6335 function helper(p: number): number { p = p + 10; return p; }
6336 function main(): number { return helper(7); }
6337 ";
6338 let result = run_main_f64_boxed(source, &["p"]);
6339 assert_eq!(result, 17.0);
6340 }
6341
6342 #[test]
6343 fn mixed_boxed_and_unboxed_locals_in_one_function() {
6344 let source = "
6345 function main(): number {
6346 let b: number = 10;
6347 let u: number = 20;
6348 b = b + 1;
6349 u = u + 2;
6350 return b + u;
6351 }
6352 ";
6353 let result = run_main_f64_boxed(source, &["b"]);
6354 assert_eq!(result, 33.0);
6355 }
6356
6357 #[test]
6358 fn multiple_distinct_box_types_coexist() {
6359 let source = r#"
6360 function main(): string {
6361 let n: number = 1;
6362 let s: string = "x";
6363 n = n + 41;
6364 s = "answer";
6365 return s;
6366 }
6367 "#;
6368 let result = run_main_string_boxed(source, &["n", "s"]);
6369 assert_eq!(result, "answer");
6370 let mut ta = boxed_typed_ast(source);
6371 box_named(&mut ta, &["n", "s"]);
6372 let collected = super::box_types::collect(&ta, &mock_symbols_with_intrinsics()).unwrap();
6373 assert_eq!(collected.len(), 2);
6374 }
6375
6376 #[test]
6377 fn no_boxed_bindings_produces_no_box_types() {
6378 let ta = boxed_typed_ast("function main(): void { let x: number = 1; }");
6379 let collected = super::box_types::collect(&ta, &mock_symbols_with_intrinsics()).unwrap();
6380 assert!(collected.is_empty(), "{collected:?}");
6381 }
6382
6383 #[test]
6384 fn record_box_type_is_idempotent_per_value_type() {
6385 use crate::Type;
6386 use wasm_encoder::ValType;
6387 let mut symbols = SymbolTable::default();
6388 symbols.record_box_type(ValType::F64, 42);
6389 assert_eq!(symbols.box_type_idx(&Type::Number).unwrap(), Some(42));
6390 symbols.record_box_type(ValType::F64, 42);
6391 assert_eq!(symbols.box_type_idx(&Type::Number).unwrap(), Some(42));
6392 }
6393
6394 #[test]
6395 fn literal_typed_and_base_share_box_wrapper() {
6396 use crate::Type;
6397 use crate::types::LiteralF64;
6398 use wasm_encoder::ValType;
6399 let mut symbols = SymbolTable::default();
6400 symbols.record_box_type(ValType::F64, 7);
6401 assert_eq!(symbols.box_type_idx(&Type::Number).unwrap(), Some(7));
6402 assert_eq!(
6403 symbols
6404 .box_type_idx(&Type::NumberLiteral(LiteralF64(42.0)))
6405 .unwrap(),
6406 Some(7),
6407 );
6408 }
6409
6410 pub(super) fn mock_symbols_with_intrinsics() -> SymbolTable {
6411 let mut map = SymbolTable::default();
6412 map.set_intrinsic_type_indices(super::intrinsics::IntrinsicTypeIndices {
6413 raw_string: 0,
6414 vtable: 1,
6415 object: 2,
6416 undefined: 51,
6417 type_descriptor: 52,
6418 string: 3,
6419 boxed_number: 4,
6420 boxed_boolean: 5,
6421 field_names: 6,
6422 object_fields: 7,
6423 object_shape: 8,
6424 to_string_fn: 9,
6425 to_json_fn: 10,
6426 equals_fn: 11,
6427 hash_fn: 12,
6428 field_getter: 13,
6429 field_setter: 14,
6430 raw_array: 15,
6431 array: 16,
6432 raw_uint8_array: 17,
6433 uint8_array: 18,
6434 closure: 19,
6435 class_vtable: 20,
6436 error_vtable: 21,
6437 error: 22,
6438 raw_bigint: 23,
6439 bigint: 24,
6440 regex_capture_array: 25,
6441 regex_match: 26,
6442 regex: 27,
6443 regex_match_box: 28,
6444 temporal_instant: 29,
6445 temporal_duration: 30,
6446 temporal_zdt: 31,
6447 raw_index_array: 32,
6448 map: 33,
6449 set: 34,
6450 url: 35,
6451 fs_stat: 41,
6452 fs_peek: 42,
6453 fs_dir_entry: 43,
6454 fs_info: 44,
6455 fs_file_writer: 45,
6456 http_response: 46,
6457 http_download_result: 47,
6458 session_entry: 48,
6459 session_page: 49,
6460 fs_mount_info: 50,
6461
6462 temporal_plain_date: 36,
6463 temporal_plain_time: 37,
6464 temporal_plain_date_time: 38,
6465 temporal_plain_year_month: 39,
6466 temporal_plain_month_day: 40,
6467 });
6468 map
6469 }
6470
6471 fn compile_with_closures(source: &str) -> Vec<u8> {
6472 compile(source)
6473 }
6474
6475 #[test]
6476 fn closure_zero_captures_validates() {
6477 let source = "function main(): void { let f = (x: number) => x * 2; }";
6478 let bytes = compile_with_closures(source);
6479 instantiate_against_prelude(&bytes);
6480 }
6481
6482 #[test]
6483 fn closure_const_capture_validates() {
6484 let source = "
6485 function main(): void {
6486 const y: number = 5;
6487 let f = (x: number) => x + y;
6488 }
6489 ";
6490 let bytes = compile_with_closures(source);
6491 instantiate_against_prelude(&bytes);
6492 }
6493
6494 #[test]
6495 fn closure_let_capture_validates_with_box() {
6496 let source = "
6497 function main(): void {
6498 let y: number = 5;
6499 let f = (x: number) => x + y;
6500 }
6501 ";
6502 let bytes = compile_with_closures(source);
6503 instantiate_against_prelude(&bytes);
6504 }
6505
6506 #[test]
6507 fn closure_param_capture_validates_with_box() {
6508 let source = "
6509 function host(p: number): void {
6510 let f = (x: number) => p + x;
6511 }
6512 function main(): void { host(7); }
6513 ";
6514 let bytes = compile_with_closures(source);
6515 instantiate_against_prelude(&bytes);
6516 }
6517
6518 #[test]
6519 fn closure_block_body_with_assignment_validates() {
6520 let source = "
6521 function main(): void {
6522 let counter: number = 0;
6523 let inc = (): void => { counter = counter + 1; };
6524 }
6525 ";
6526 let bytes = compile_with_closures(source);
6527 instantiate_against_prelude(&bytes);
6528 }
6529
6530 #[test]
6531 fn two_arrows_same_signature_share_closure_struct() {
6532 let source = "
6533 function main(): void {
6534 let f = (x: number) => x + 1;
6535 let g = (y: number) => y * 2;
6536 }
6537 ";
6538 let ta = boxed_typed_ast(source);
6539 let metas = super::analysis::CodegenAnalysis::collect(&ta, &[])
6540 .unwrap()
6541 .closure_metas;
6542 assert_eq!(metas.len(), 2);
6543 assert_eq!(metas[0].signature, metas[1].signature);
6544 let bytes = compile_with_closures(source);
6545 instantiate_against_prelude(&bytes);
6546 }
6547
6548 #[test]
6549 fn nested_closure_validates() {
6550 let source = "
6551 function main(): void {
6552 let y: number = 1;
6553 let outer = (): number => {
6554 let inner = (x: number) => x + y;
6555 return 0;
6556 };
6557 }
6558 ";
6559 let bytes = compile_with_closures(source);
6560 instantiate_against_prelude(&bytes);
6561 }
6562
6563 #[test]
6564 fn function_typed_binding_lowers_to_closure_struct_ref() {
6565 let source = "function main(): void { let f = (x: number) => x; }";
6566 let bytes = compile_with_closures(source);
6567 instantiate_against_prelude(&bytes);
6568 }
6569
6570 #[test]
6571 fn no_closures_no_closure_types_emitted() {
6572 let ta = boxed_typed_ast("function main(): void { let x: number = 1; }");
6573 let metas = super::analysis::CodegenAnalysis::collect(&ta, &[])
6574 .unwrap()
6575 .closure_metas;
6576 assert!(metas.is_empty(), "{metas:?}");
6577 }
6578
6579 #[test]
6580 fn inline_arrow_call_returns_value() {
6581 let result = run_main_f64("function main(): number { return ((x: number) => x + 1)(5); }");
6582 assert_eq!(result, 6.0);
6583 }
6584
6585 #[test]
6586 fn let_stored_arrow_call_returns_value() {
6587 let result =
6588 run_main_f64("function main(): number { let f = (x: number) => x + 1; return f(5); }");
6589 assert_eq!(result, 6.0);
6590 }
6591
6592 #[test]
6593 fn closure_call_with_multiple_args() {
6594 let result = run_main_f64(
6595 "function main(): number { let add = (a: number, b: number) => a + b; return add(2, 3); }",
6596 );
6597 assert_eq!(result, 5.0);
6598 }
6599
6600 #[test]
6601 fn captured_let_counter_returns_three() {
6602 let result = run_main_f64(
6603 "function main(): number {
6604 let count: number = 0;
6605 let inc = (): number => { count = count + 1; return count; };
6606 inc();
6607 inc();
6608 return inc();
6609 }",
6610 );
6611 assert_eq!(result, 3.0);
6612 }
6613
6614 #[test]
6615 fn higher_order_function_calls_callback() {
6616 let result = run_main_f64(
6617 "function apply(f: (x: number) => number, x: number): number { return f(x); }
6618 function main(): number { return apply((n: number) => n + 10, 5); }",
6619 );
6620 assert_eq!(result, 15.0);
6621 }
6622
6623 #[test]
6624 fn function_as_value_let_bound_call_returns_underlying_result() {
6625 let result = run_main_f64(
6626 "function greet(x: number): number { return x + 1; }
6627 function main(): number { let f = greet; return f(2); }",
6628 );
6629 assert_eq!(result, 3.0);
6630 }
6631
6632 #[test]
6633 fn function_as_value_passed_to_hof_invokes_underlying() {
6634 let result = run_main_f64(
6635 "function greet(x: number): number { return x + 1; }
6636 function apply(f: (n: number) => number, n: number): number { return f(n); }
6637 function main(): number { return apply(greet, 5); }",
6638 );
6639 assert_eq!(result, 6.0);
6640 }
6641
6642 #[test]
6643 fn function_as_value_flows_through_multiple_bindings() {
6644 let result = run_main_f64(
6645 "function greet(x: number): number { return x + 10; }
6646 function main(): number { let f = greet; let g = f; return g(2); }",
6647 );
6648 assert_eq!(result, 12.0);
6649 }
6650
6651 #[test]
6652 fn direct_call_path_unchanged_when_function_also_used_as_value() {
6653 let result = run_main_f64(
6654 "function greet(x: number): number { return x + 1; }
6655 function apply(f: (n: number) => number, n: number): number { return f(n); }
6656 function main(): number {
6657 let direct = greet(10);
6658 let indirect = apply(greet, 20);
6659 return direct + indirect;
6660 }",
6661 );
6662 assert_eq!(result, 11.0 + 21.0);
6663 }
6664
6665 #[test]
6666 fn no_function_as_value_no_adapters_emitted() {
6667 let ta = boxed_typed_ast(
6668 "function greet(x: number): number { return x + 1; }
6669 function main(): void { greet(5); }",
6670 );
6671 let metas = super::analysis::CodegenAnalysis::collect(&ta, &[])
6672 .unwrap()
6673 .adapter_metas;
6674 assert!(metas.is_empty(), "{metas:?}");
6675 }
6676
6677 #[test]
6678 fn direct_callees_in_nested_positions_are_not_collected_as_adapters() {
6679 let ta = boxed_typed_ast(
6680 "function greet(x: number): number { return x + 1; }
6681 function nested(x: number): number { return x; }
6682 function inner(): number { return 1; }
6683 function outer(x: number): number { return x; }
6684 function main(): void {
6685 greet(5);
6686 nested(greet(5));
6687 outer(inner());
6688 }",
6689 );
6690 let metas = super::analysis::CodegenAnalysis::collect(&ta, &[])
6691 .unwrap()
6692 .adapter_metas;
6693 assert!(
6694 metas.is_empty(),
6695 "direct-only callsites should produce no adapters, got {metas:?}",
6696 );
6697 }
6698
6699 #[test]
6700 fn function_as_value_collects_one_adapter_per_distinct_function() {
6701 let ta = boxed_typed_ast(
6702 "function f1(x: number): number { return x + 1; }
6703 function f2(x: number): number { return x + 2; }
6704 function apply(g: (n: number) => number, n: number): number { return g(n); }
6705 function main(): void {
6706 let a = f1;
6707 let b = f1;
6708 apply(f2, 0);
6709 }",
6710 );
6711 let metas = super::analysis::CodegenAnalysis::collect(&ta, &[])
6712 .unwrap()
6713 .adapter_metas;
6714 assert_eq!(metas.len(), 2, "{metas:?}");
6715 let names: Vec<&str> = metas.iter().map(|m| m.name.as_str()).collect();
6716 assert!(names.contains(&"f1"));
6717 assert!(names.contains(&"f2"));
6718 }
6719
6720 #[test]
6721 fn higher_order_function_with_capture() {
6722 let result = run_main_f64(
6723 "function apply(f: (x: number) => number, x: number): number { return f(x); }
6724 function main(): number {
6725 let bias: number = 100;
6726 return apply((n: number) => n + bias, 7);
6727 }",
6728 );
6729 assert_eq!(result, 107.0);
6730 }
6731
6732 #[allow(dead_code)]
6733 fn _silence_unused() {
6734 let _ = (
6735 CodeSection::new(),
6736 ConstExpr::i32_const(0),
6737 ExportKind::Func,
6738 ExportSection::new(),
6739 FunctionSection::new(),
6740 GlobalSection::new(),
6741 ImportSection::new(),
6742 Module::new(),
6743 RefType {
6744 nullable: false,
6745 heap_type: HeapType::Concrete(0),
6746 },
6747 TypeSection::new(),
6748 ValType::F64,
6749 Function::new(vec![]),
6750 Ieee64::from(0.0_f64),
6751 Instruction::Nop,
6752 emit_statement,
6753 );
6754 }
6755}