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