submilli_engine/compiler_limits.rs
1//! Compiler limits, and the native stack embedders provide for compilation.
2
3/// Native stack a thread needs to run a [`crate::compile`] entry point or a
4/// public phase API on any program within the structural limits below and in
5/// [`crate::tree_height`], including types at [`MAX_TYPE_DEPTH`]. The
6/// interpreter creates no threads: embedders run compilation on a thread of at
7/// least this size. Only touched pages are committed. The deepest programs at
8/// the limits measured about 63 MiB in unoptimized builds and 2 MiB in
9/// optimized ones; each size keeps at least twice that.
10pub const COMPILER_STACK_BYTES: usize = if cfg!(debug_assertions) {
11 128 * 1024 * 1024
12} else {
13 16 * 1024 * 1024
14};
15
16/// Number of declared argument slots in the closure ABI. Defaults keep their
17/// slots and a packed rest array occupies one slot. The environment/receiver
18/// and captured generic descriptors are carried separately.
19pub const MAX_CLOSURE_ARITY: usize = u8::MAX as usize;
20
21/// Nested type annotation and alias-body resolutions. Each alias reference
22/// costs a level, and so does each type constructor (object, array, tuple,
23/// union, function, generic type or `readonly`) wrapping the next reference in an alias
24/// body: over a primitive base, plain renames chain 254 aliases and
25/// `{ v: Previous }` bodies 127. Resolved alias bodies are inlined, so this also
26/// bounds the depth of alias-expanded types.
27pub const MAX_TYPE_RESOLUTION_DEPTH: u32 = 256;
28
29/// Nodes in one type. Instantiating an alias or generic that mentions its
30/// parameter more than once copies the argument, so nesting such instantiations
31/// doubles a type's size per level; substitution stops at this bound. An alias
32/// instance also keeps its arguments beside its substituted body, so each layer
33/// of generic alias around a type doubles it too. The largest type in the
34/// fixture and package suites has under 100 nodes. Each stored copy of a type
35/// costs memory in proportion, up to about 1 KiB per node for small objects.
36pub const MAX_TYPE_NODES: u64 = 1 << 16;
37
38/// Nesting depth of one type, counting every type constructor and alias. Every
39/// recursive walk over a type is bounded by it. Annotation resolution nests at
40/// most [`MAX_TYPE_RESOLUTION_DEPTH`] levels, but a `[]` suffix is not one of
41/// them, so a chain `type Ai = A(i-1)[]` adds two levels per alias and is cut
42/// here at 254 aliases.
43pub const MAX_TYPE_DEPTH: u32 = 512;
44
45/// Steps of type work one compiler phase may spend: each type node built by
46/// substitution (instantiating aliases, generic calls and members, and
47/// expanding interfaces) and each step comparing types. A phase can rebuild or
48/// compare types near [`MAX_TYPE_NODES`] many times over; this bounds the
49/// total. The largest inference in the fixture and package suites spends under
50/// 2 million.
51pub const MAX_TYPE_WORK: u64 = 1 << 24;
52
53/// Steps of inline code one runtime type check may emit: one per structural
54/// test of a type, union member, field or element, and one per segment of the
55/// failure path each test records. Interface checks are inlined with their
56/// members' checks, so interfaces that reference others several times multiply
57/// a check's code; this bounds the work of emitting one before the function's
58/// locals would. The largest module in the fixture and package suites emits
59/// under 4,000 tests across all its checks.
60pub const MAX_INLINE_VALIDATOR_STEPS: u64 = 1 << 15;
61
62/// Locals the Wasm engine accepts in one function (wasmparser's
63/// `MAX_WASM_FUNCTION_LOCALS`). Runtime checks add locals as they are emitted,
64/// so emitting stops with a located error before a function the engine would
65/// reject.
66pub const MAX_FUNCTION_LOCALS: u32 = 50_000;
67
68/// Bytes the Wasm engine accepts in one function body (wasmparser's
69/// `MAX_WASM_FUNCTION_SIZE`). Runtime checks stop emitting before a function
70/// reaches it, which also keeps a body far below the 4 GiB the Wasm encoder can
71/// represent.
72pub const MAX_FUNCTION_BODY_BYTES: usize = 7_654_321;
73
74/// Optional spreads that may override one field of an object literal, each
75/// over the value an earlier member supplied. Each link is a
76/// boxed fallback walked recursively; kept well below the typed-tree height
77/// limit so this limit, not the generic one, names the cause.
78pub const MAX_SPREAD_FALLBACK_CHAIN: u32 = 512;
79
80/// Classes in one inheritance chain, including the class itself and library
81/// ancestors such as `Error`. Each class struct subtypes its parent's, the root
82/// class subtypes the intrinsic object struct, and WasmGC validation rejects
83/// subtype depths above 63.
84pub const MAX_CLASS_CHAIN_LEN: usize = 62;
85
86#[derive(Clone, Copy, Debug, PartialEq, Eq)]
87pub struct UnsupportedClosureArity {
88 pub actual: usize,
89}
90
91impl std::fmt::Display for UnsupportedClosureArity {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 write!(
94 f,
95 "closure signature has {} parameter slots; the maximum is {MAX_CLOSURE_ARITY}",
96 self.actual,
97 )
98 }
99}
100
101impl std::error::Error for UnsupportedClosureArity {}
102
103/// Checks the encoded slot count, not the number of source call arguments.
104/// Callers attach their compiler stage and source context to a failure.
105pub fn checked_closure_arity(arity: usize) -> Result<u8, UnsupportedClosureArity> {
106 u8::try_from(arity).map_err(|_| UnsupportedClosureArity { actual: arity })
107}
108
109/// Limits on dependency namespace metadata before recursive compiler consumers.
110pub const MAX_NAMESPACE_DEPTH: usize = 128;
111pub const MAX_NAMESPACE_NODES: usize = 1 << 16;
112/// Total qualified namespace/type path bytes materialized during registration.
113pub const MAX_NAMESPACE_PATH_BYTES: usize = 1 << 20;
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn closure_arity_preserves_the_supported_boundary() {
121 assert_eq!(MAX_CLOSURE_ARITY, 255);
122 for arity in [0, 1, 254, 255] {
123 assert_eq!(usize::from(checked_closure_arity(arity).unwrap()), arity);
124 }
125 }
126
127 #[test]
128 fn closure_arity_rejects_excess_without_truncation() {
129 for actual in [256, 257, usize::MAX] {
130 let error = checked_closure_arity(actual).unwrap_err();
131 assert_eq!(error.actual, actual);
132 assert_eq!(
133 error.to_string(),
134 format!("closure signature has {actual} parameter slots; the maximum is 255"),
135 );
136 }
137 assert_eq!(checked_closure_arity(255), Ok(255));
138 }
139}