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