1
//! Stack-position codegen.
2
//!
3
//! `compile_for_stack` is the entry — it emits an expression as a
4
//! single wasm stack value and returns the `WasmType` that landed on
5
//! the stack. The two numeric refinements (`compile_for_stack_ratio`,
6
//! `compile_for_stack_index`) wrap it for the Scalar / Index dispatch
7
//! in `compiler::native`. Function calls in value position
8
//! route through `compile_call_for_stack` and the symbol-call
9
//! dispatcher below.
10

            
11
use crate::ast::{Expr, WasmType};
12
use crate::compiler::context::CompileContext;
13
use crate::compiler::emit::FunctionEmitter;
14
use crate::error::{Error, Result};
15
use crate::runtime::{SymbolKind, SymbolTable};
16

            
17
use super::atoms::{emit_nil_default, push_ratio};
18
use super::call::{compile_call_ref, compile_lambda_call_for_stack, try_compile_runtime_call};
19
use super::effect::compile_for_effect;
20
use super::eval::{call, eval_value, expand_macro_then, resolve_arg};
21
use super::format::format_expr;
22
use super::quasiquote::expand_quasiquote;
23

            
24
415940
pub(in crate::compiler) fn compile_for_stack(
25
415940
    ctx: &mut CompileContext,
26
415940
    emit: &mut FunctionEmitter,
27
415940
    symbols: &mut SymbolTable,
28
415940
    expr: &Expr,
29
415940
) -> Result<WasmType> {
30
13756
    match expr {
31
13756
        Expr::Number(n) if *n.denom() == 1 => {
32
            // ADR-0028: a dimensionless integer literal defaults to Index (I32).
33
            // Operator/binding sites that need a Scalar coerce it explicitly.
34
12531
            emit.i32_const(i32::try_from(*n.numer()).map_err(|_| {
35
                Error::Compile(format!("integer literal {} exceeds i32 range", n.numer()))
36
            })?);
37
12531
            Ok(WasmType::I32)
38
        }
39
1225
        Expr::Number(n) => {
40
1225
            push_ratio(ctx, emit, *n.numer(), *n.denom());
41
1225
            Ok(WasmType::Ratio)
42
        }
43
1158
        Expr::Bool(b) => {
44
1158
            emit.i32_const(i32::from(*b));
45
1158
            Ok(WasmType::Bool)
46
        }
47
        Expr::Nil => {
48
1361
            emit.i32_const(0);
49
1361
            Ok(WasmType::Bool)
50
        }
51
        Expr::WasmRuntime(_) => Err(Error::Compile(
52
            "internal: compile_for_stack saw an Expr::WasmRuntime placeholder. \
53
             A binder failed to promote the runtime value into a WasmLocal \
54
             (allocate a wasm local, emit the producer expression, local.set) \
55
             before the symbol was referenced. Check the let / let* / dolist \
56
             / do / lambda-arg / defvar / defparameter call sites."
57
                .to_string(),
58
        )),
59
62915
        Expr::WasmLocal(idx, ty) => {
60
62915
            emit.local_get(*idx);
61
62915
            Ok(*ty)
62
        }
63
19318
        Expr::String(s) => {
64
19318
            let data_idx = ctx.add_data(s.as_bytes())?;
65
19318
            emit.i32_const(0);
66
19318
            emit.i32_const(s.len() as i32);
67
19318
            emit.array_new_data(ctx.ids.ty_i8_array, data_idx);
68
19318
            Ok(WasmType::StringRef)
69
        }
70
        Expr::Symbol(_) => {
71
61342
            let resolved = resolve_arg(symbols, expr)?;
72
61138
            compile_for_stack(ctx, emit, symbols, &resolved)
73
        }
74
256022
        Expr::List(elems) if elems.is_empty() => {
75
            emit.i32_const(0);
76
            Ok(WasmType::I32)
77
        }
78
256022
        Expr::List(elems) => compile_call_for_stack(ctx, emit, symbols, elems),
79
        Expr::Quasiquote(inner) => {
80
            let expanded = expand_quasiquote(symbols, inner)?;
81
            compile_for_stack(ctx, emit, symbols, &expanded)
82
        }
83
68
        _ => Err(Error::Compile(format!(
84
68
            "cannot compile to WASM stack value: {}",
85
68
            format_expr(expr)
86
68
        ))),
87
    }
88
415940
}
89

            
90
/// The `WasmType` that [`compile_for_stack`] pushes for a literal or
91
/// already-resolved runtime `expr`, or `None` for a shape whose stack type is
92
/// only known by actually evaluating/emitting it (a call/list, a quote, a
93
/// lambda, …). This is the SINGLE SOURCE OF TRUTH for the compile-time
94
/// "stack-type mirrors" — the eval-side functions that PREDICT a form's stack
95
/// type to size let/do/binding locals or unify branch results. Every such
96
/// mirror routes through this so the prediction can't drift from what codegen
97
/// emits (the class of bug that produced the i32/Bool, list-element, and
98
/// recursion-type mismatches). The classified arms match `compile_for_stack`
99
/// exactly: a numeric literal is always a `Ratio` (never a count), bool/nil are
100
/// `Bool`, a string literal is `StringRef`, a runtime placeholder/local carries
101
/// its own type. NOTE the empty-list literal is intentionally `None` (not
102
/// `compile_for_stack`'s degenerate `()`→I32): a mirror that sees a bare
103
/// `Expr::List(vec![])` reaches it only as a non-value-position fallback and
104
/// keeps its own default — the reader emits `()` as `Nil`, so this case is
105
/// internal-only and never the actual stack value of a binding.
106
#[must_use]
107
35905
pub(in crate::compiler) fn classify_stack_type(expr: &Expr) -> Option<WasmType> {
108
22683
    match expr {
109
9874
        Expr::WasmRuntime(ty) | Expr::WasmLocal(_, ty) => Some(*ty),
110
        // ADR-0028: an integer literal (denom == 1) defaults to Index (I32);
111
        // a fractional literal is a dimensionless Scalar (Ratio). Mirrors the
112
        // `denom() == 1` split in `compile_for_stack`.
113
22683
        Expr::Number(n) if *n.denom() == 1 => Some(WasmType::I32),
114
547
        Expr::Number(_) => Some(WasmType::Ratio),
115
2525
        Expr::Bool(_) | Expr::Nil => Some(WasmType::Bool),
116
683
        Expr::String(_) => Some(WasmType::StringRef),
117
140
        _ => None,
118
    }
119
35905
}
120

            
121
20340
pub(in crate::compiler) fn compile_for_stack_ratio(
122
20340
    ctx: &mut CompileContext,
123
20340
    emit: &mut FunctionEmitter,
124
20340
    symbols: &mut SymbolTable,
125
20340
    expr: &Expr,
126
20340
) -> Result<()> {
127
    // ADR-0028: a numeric literal is a dimension-flexible token — coerce it to
128
    // Scalar (the sanctioned Index↔Scalar crossing). A RUNTIME index never
129
    // coerces; it must bridge explicitly via `(index->scalar …)`. A non-literal
130
    // operand that *resolves* to a number still emits its effects on the live
131
    // table first (the probe runs on a clone, so it applies none).
132
20340
    if let Expr::Number(n) = eval_value(&mut symbols.clone(), expr)? {
133
10000
        if !matches!(expr, Expr::Number(_)) {
134
408
            compile_for_effect(ctx, emit, symbols, expr)?;
135
9592
        }
136
10000
        push_ratio(ctx, emit, *n.numer(), *n.denom());
137
10000
        return Ok(());
138
10340
    }
139
10340
    let ty = compile_for_stack(ctx, emit, symbols, expr)?;
140
10340
    match ty {
141
10340
        WasmType::Ratio => Ok(()),
142
        WasmType::Commodity => Err(Error::Compile(
143
            "commodity-bearing values cannot mix with pure-rational arithmetic; \
144
             use `(convert-commodity ...)` to bridge"
145
                .to_string(),
146
        )),
147
        WasmType::I32 => Err(Error::Compile(
148
            "a runtime index (count) cannot be used as a scalar; \
149
             bridge explicitly with `(index->scalar ...)`"
150
                .to_string(),
151
        )),
152
        WasmType::Bool
153
        | WasmType::PairRef(_)
154
        | WasmType::StringRef
155
        | WasmType::EntityRef(_)
156
        | WasmType::Closure(_)
157
        | WasmType::AnyRef => Err(Error::Compile(
158
            "arithmetic requires ratio values".to_string(),
159
        )),
160
    }
161
20340
}
162

            
163
/// Compiles `expr` as a raw i32 Index value: a runtime `I32` (a count/length/
164
/// index) or an integer literal. A fractional literal is a Scalar, and a
165
/// runtime Ratio/Commodity/ref is not an Index — all rejected (ADR-0028:
166
/// Index combines only with Index + integer literals).
167
43940
pub(in crate::compiler) fn compile_for_stack_index(
168
43940
    ctx: &mut CompileContext,
169
43940
    emit: &mut FunctionEmitter,
170
43940
    symbols: &mut SymbolTable,
171
43940
    expr: &Expr,
172
43940
) -> Result<()> {
173
43940
    if let Expr::Number(n) = eval_value(&mut symbols.clone(), expr)? {
174
17784
        if !matches!(expr, Expr::Number(_)) {
175
2944
            compile_for_effect(ctx, emit, symbols, expr)?;
176
14840
        }
177
17784
        if *n.denom() == 1 {
178
17648
            emit.i32_const(i32::try_from(*n.numer()).map_err(|_| {
179
68
                Error::Compile(format!("integer literal {} exceeds i32 range", n.numer()))
180
68
            })?);
181
17580
            return Ok(());
182
136
        }
183
136
        return Err(Error::Compile(
184
136
            "a fractional literal is a scalar, not an index".to_string(),
185
136
        ));
186
26156
    }
187
26156
    let ty = compile_for_stack(ctx, emit, symbols, expr)?;
188
26156
    match ty {
189
26156
        WasmType::I32 => Ok(()),
190
        WasmType::Ratio | WasmType::Commodity => Err(Error::Compile(
191
            "a scalar/money value cannot be used as an index; \
192
             bridge explicitly with `(scalar->index ...)`"
193
                .to_string(),
194
        )),
195
        WasmType::Bool
196
        | WasmType::PairRef(_)
197
        | WasmType::StringRef
198
        | WasmType::EntityRef(_)
199
        | WasmType::Closure(_)
200
        | WasmType::AnyRef => Err(Error::Compile(format!(
201
            "index arithmetic requires an integer count, got {ty}"
202
        ))),
203
    }
204
43940
}
205

            
206
/// Emits `expr` coerced to the `target` wasm type — the type-directed boundary
207
/// primitive for closure args, host-fn args, and accumulator seeds. A nil
208
/// resolves to the target's typed default; a numeric literal coerces across the
209
/// sanctioned Index↔Scalar boundary; everything else must already match. The
210
/// nil/literal probe runs on a clone, so a non-literal that resolves to one
211
/// emits its effects on the live table first.
212
82060
pub(in crate::compiler) fn compile_for_stack_as(
213
82060
    ctx: &mut CompileContext,
214
82060
    emit: &mut FunctionEmitter,
215
82060
    symbols: &mut SymbolTable,
216
82060
    expr: &Expr,
217
82060
    target: WasmType,
218
82060
) -> Result<()> {
219
82060
    if matches!(eval_value(&mut symbols.clone(), expr)?, Expr::Nil) {
220
2260
        if !matches!(expr, Expr::Nil) {
221
68
            compile_for_effect(ctx, emit, symbols, expr)?;
222
2192
        }
223
2260
        return emit_nil_default(ctx, emit, target);
224
79800
    }
225
79800
    match target {
226
20340
        WasmType::Ratio => compile_for_stack_ratio(ctx, emit, symbols, expr),
227
43940
        WasmType::I32 => compile_for_stack_index(ctx, emit, symbols, expr),
228
        _ => {
229
15520
            let ty = compile_for_stack(ctx, emit, symbols, expr)?;
230
15520
            if ty == target {
231
15316
                Ok(())
232
            } else {
233
204
                Err(Error::Compile(format!(
234
204
                    "type mismatch: expected {target}, got {ty}"
235
204
                )))
236
            }
237
        }
238
    }
239
82060
}
240

            
241
261870
pub(in crate::compiler) fn compile_call_for_stack(
242
261870
    ctx: &mut CompileContext,
243
261870
    emit: &mut FunctionEmitter,
244
261870
    symbols: &mut SymbolTable,
245
261870
    elems: &[Expr],
246
261870
) -> Result<WasmType> {
247
261870
    let (head, args) = elems
248
261870
        .split_first()
249
261870
        .ok_or_else(|| Error::Compile("empty function call".to_string()))?;
250
261870
    match head {
251
258606
        Expr::Symbol(name) => compile_symbol_call_for_stack(ctx, emit, symbols, name, args),
252
952
        Expr::Lambda(params, body) => {
253
952
            compile_lambda_call_for_stack(ctx, emit, symbols, params, body, args)
254
        }
255
2312
        Expr::List(inner) => {
256
2312
            let resolved = call(symbols, inner)?;
257
2312
            match resolved {
258
2312
                Expr::Lambda(params, body) => {
259
2312
                    compile_lambda_call_for_stack(ctx, emit, symbols, &params, &body, args)
260
                }
261
                _ => Err(Error::Compile("not callable".to_string())),
262
            }
263
        }
264
        _ => {
265
            let result = call(symbols, elems)?;
266
            compile_for_stack(ctx, emit, symbols, &result)
267
        }
268
    }
269
261870
}
270

            
271
258606
fn compile_symbol_call_for_stack(
272
258606
    ctx: &mut CompileContext,
273
258606
    emit: &mut FunctionEmitter,
274
258606
    symbols: &mut SymbolTable,
275
258606
    name: &str,
276
258606
    args: &[Expr],
277
258606
) -> Result<WasmType> {
278
258130
    let (func, kind, value) = {
279
258606
        let sym = symbols
280
258606
            .lookup(name)
281
258606
            .ok_or_else(|| Error::UndefinedSymbol(name.to_string()))?;
282
258130
        (sym.function().cloned(), sym.kind(), sym.value().cloned())
283
    };
284
258130
    if kind == SymbolKind::Macro
285
204
        && let Some(Expr::Lambda(params, body)) = func
286
    {
287
204
        return expand_macro_then(symbols, &params, &body, args, |symbols, code| {
288
204
            compile_for_stack(ctx, emit, symbols, &code)
289
204
        });
290
257926
    }
291
7310
    if let Some(Expr::Lambda(params, body)) = func {
292
7310
        if let Some(ty) = try_compile_runtime_call(ctx, emit, symbols, name, &params, &body, args)?
293
        {
294
1564
            return Ok(ty);
295
5746
        }
296
5746
        ctx.push_inlining_frame(name)?;
297
5746
        let result = compile_lambda_call_for_stack(ctx, emit, symbols, &params, &body, args);
298
5746
        ctx.pop_inlining_frame(name);
299
5746
        return result;
300
250616
    }
301
2584
    if let Some(Expr::WasmLocal(idx, WasmType::Closure(sig))) = value {
302
2584
        return compile_call_ref(ctx, emit, symbols, idx, sig, args);
303
248032
    }
304
248032
    match kind {
305
        SymbolKind::Native | SymbolKind::Operator => {
306
219944
            crate::compiler::native::compile_for_stack(ctx, emit, symbols, name, args)
307
        }
308
        SymbolKind::SpecialForm => {
309
28088
            crate::compiler::special::compile_for_stack(ctx, emit, symbols, name, args)
310
        }
311
        _ => Err(Error::Compile(format!(
312
            "symbol '{name}' is not callable for stack value"
313
        ))),
314
    }
315
258606
}
316

            
317
#[cfg(test)]
318
mod tests {
319
    use super::*;
320
    use crate::ast::{ClosureSigId, EntityKind, Fraction, PairElement};
321
    use crate::compiler::context::CompileContext;
322
    use crate::runtime::SymbolTable;
323

            
324
    /// The contract `classify_stack_type` exists to uphold: for every literal /
325
    /// already-resolved runtime `Expr` it classifies (returns `Some`), the
326
    /// prediction equals the `WasmType` `compile_for_stack` actually pushes.
327
    /// Any drift here is the class of bug (#56/#57/#58/#59) that motivated the
328
    /// single source of truth, so this test compiles each shape for real and
329
    /// compares. (`None`-classified shapes — empty list, calls, quotes — are
330
    /// not value literals and aren't asserted here.)
331
    #[test]
332
1
    fn classify_stack_type_matches_compile_for_stack() {
333
1
        let runtime = [
334
1
            WasmType::I32,
335
1
            WasmType::Bool,
336
1
            WasmType::Ratio,
337
1
            WasmType::Commodity,
338
1
            WasmType::StringRef,
339
1
            WasmType::PairRef(PairElement::Bool),
340
1
            WasmType::EntityRef(EntityKind::Account),
341
1
            WasmType::Closure(ClosureSigId(0)),
342
1
            WasmType::AnyRef,
343
1
        ];
344
1
        let mut cases: Vec<Expr> = vec![
345
1
            Expr::Number(Fraction::from_integer(7)),
346
1
            Expr::Number(Fraction::new(1, 2)),
347
1
            Expr::Bool(true),
348
1
            Expr::Bool(false),
349
1
            Expr::Nil,
350
1
            Expr::String("hi".to_string()),
351
        ];
352
        // A `WasmLocal` of every wasm type — `compile_for_stack` emits
353
        // `local.get` and returns the local's declared type unchanged.
354
9
        cases.extend(runtime.iter().map(|ty| Expr::WasmLocal(0, *ty)));
355

            
356
15
        for expr in &cases {
357
15
            let predicted = classify_stack_type(expr)
358
15
                .unwrap_or_else(|| panic!("classify_stack_type returned None for {expr:?}"));
359
15
            let mut ctx = CompileContext::new().expect("ctx");
360
15
            let mut emit = FunctionEmitter::new();
361
15
            let mut symbols = SymbolTable::with_builtins_for_wasm();
362
15
            let emitted = compile_for_stack(&mut ctx, &mut emit, &mut symbols, expr)
363
15
                .unwrap_or_else(|e| panic!("compile_for_stack {expr:?}: {e}"));
364
15
            assert_eq!(
365
                predicted, emitted,
366
                "classify_stack_type disagrees with compile_for_stack for {expr:?}"
367
            );
368
        }
369
1
    }
370

            
371
    /// Shapes that aren't a directly-emittable literal/runtime value (they need
372
    /// a call/list walk or aren't stack values) classify as `None`.
373
    #[test]
374
1
    fn classify_stack_type_is_none_for_non_literal_shapes() {
375
1
        assert_eq!(classify_stack_type(&Expr::Symbol("X".to_string())), None);
376
        // A namespaced symbol (ADR-0029) is still just a Symbol — its canonical
377
        // `NS:NAME` key must not be mistaken for any typed literal shape.
378
1
        assert_eq!(
379
1
            classify_stack_type(&Expr::Symbol("FOO:BAR".to_string())),
380
            None
381
        );
382
1
        assert_eq!(classify_stack_type(&Expr::List(vec![])), None);
383
1
        assert_eq!(classify_stack_type(&Expr::Quote(Box::new(Expr::Nil))), None);
384
1
        assert_eq!(
385
1
            classify_stack_type(&Expr::WasmRuntime(WasmType::Ratio)),
386
            Some(WasmType::Ratio)
387
        );
388
1
    }
389
}