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