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