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