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
470894
pub(in crate::compiler) fn compile_for_stack(
25
470894
    ctx: &mut CompileContext,
26
470894
    emit: &mut FunctionEmitter,
27
470894
    symbols: &mut SymbolTable,
28
470894
    expr: &Expr,
29
470894
) -> Result<WasmType> {
30
14346
    match expr {
31
14346
        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
13067
            emit.i32_const(i32::try_from(*n.numer()).map_err(|_| {
35
                Error::Compile(format!("integer literal {} exceeds i32 range", n.numer()))
36
            })?);
37
13067
            Ok(WasmType::I32)
38
        }
39
1279
        Expr::Number(n) => {
40
1279
            push_ratio(ctx, emit, *n.numer(), *n.denom());
41
1279
            Ok(WasmType::Ratio)
42
        }
43
1209
        Expr::Bool(b) => {
44
1209
            emit.i32_const(i32::from(*b));
45
1209
            Ok(WasmType::Bool)
46
        }
47
        Expr::Nil => {
48
1421
            emit.i32_const(0);
49
1421
            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
65471
        Expr::WasmLocal(idx, ty) => {
60
65471
            emit.local_get(*idx);
61
65471
            Ok(*ty)
62
        }
63
33585
        Expr::String(s) => {
64
33585
            let data_idx = ctx.add_data(s.as_bytes())?;
65
33585
            emit.i32_const(0);
66
33585
            emit.i32_const(s.len() as i32);
67
33585
            emit.array_new_data(ctx.ids.ty_i8_array, data_idx);
68
33585
            Ok(WasmType::StringRef)
69
        }
70
        Expr::Symbol(_) => {
71
63829
            let resolved = resolve_arg(symbols, expr)?;
72
63616
            compile_for_stack(ctx, emit, symbols, &resolved)
73
        }
74
290962
        Expr::List(elems) if elems.is_empty() => {
75
            emit.i32_const(0);
76
            Ok(WasmType::I32)
77
        }
78
290962
        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
71
        _ => Err(Error::Compile(format!(
84
71
            "cannot compile to WASM stack value: {}",
85
71
            format_expr(expr)
86
71
        ))),
87
    }
88
470894
}
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
37385
pub(in crate::compiler) fn classify_stack_type(expr: &Expr) -> Option<WasmType> {
108
23583
    match expr {
109
10309
        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
23583
        Expr::Number(n) if *n.denom() == 1 => Some(WasmType::I32),
114
571
        Expr::Number(_) => Some(WasmType::Ratio),
115
2634
        Expr::Bool(_) | Expr::Nil => Some(WasmType::Bool),
116
713
        Expr::String(_) => Some(WasmType::StringRef),
117
146
        _ => None,
118
    }
119
37385
}
120

            
121
21229
pub(in crate::compiler) fn compile_for_stack_ratio(
122
21229
    ctx: &mut CompileContext,
123
21229
    emit: &mut FunctionEmitter,
124
21229
    symbols: &mut SymbolTable,
125
21229
    expr: &Expr,
126
21229
) -> 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
21229
    if let Expr::Number(n) = eval_value(&mut symbols.clone(), expr)? {
133
10437
        if !matches!(expr, Expr::Number(_)) {
134
426
            compile_for_effect(ctx, emit, symbols, expr)?;
135
10011
        }
136
10437
        push_ratio(ctx, emit, *n.numer(), *n.denom());
137
10437
        return Ok(());
138
10792
    }
139
10792
    let ty = compile_for_stack(ctx, emit, symbols, expr)?;
140
10792
    match ty {
141
10792
        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
21229
}
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
45724
pub(in crate::compiler) fn compile_for_stack_index(
168
45724
    ctx: &mut CompileContext,
169
45724
    emit: &mut FunctionEmitter,
170
45724
    symbols: &mut SymbolTable,
171
45724
    expr: &Expr,
172
45724
) -> Result<()> {
173
45724
    if let Expr::Number(n) = eval_value(&mut symbols.clone(), expr)? {
174
18531
        if !matches!(expr, Expr::Number(_)) {
175
3053
            compile_for_effect(ctx, emit, symbols, expr)?;
176
15478
        }
177
18531
        if *n.denom() == 1 {
178
18389
            emit.i32_const(i32::try_from(*n.numer()).map_err(|_| {
179
71
                Error::Compile(format!("integer literal {} exceeds i32 range", n.numer()))
180
71
            })?);
181
18318
            return Ok(());
182
142
        }
183
142
        return Err(Error::Compile(
184
142
            "a fractional literal is a scalar, not an index".to_string(),
185
142
        ));
186
27193
    }
187
27193
    let ty = compile_for_stack(ctx, emit, symbols, expr)?;
188
27193
    match ty {
189
27193
        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
45724
}
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
98903
pub(in crate::compiler) fn compile_for_stack_as(
213
98903
    ctx: &mut CompileContext,
214
98903
    emit: &mut FunctionEmitter,
215
98903
    symbols: &mut SymbolTable,
216
98903
    expr: &Expr,
217
98903
    target: WasmType,
218
98903
) -> Result<()> {
219
98903
    if matches!(eval_value(&mut symbols.clone(), expr)?, Expr::Nil) {
220
2343
        if !matches!(expr, Expr::Nil) {
221
71
            compile_for_effect(ctx, emit, symbols, expr)?;
222
2272
        }
223
2343
        return emit_nil_default(ctx, emit, target);
224
96560
    }
225
96560
    match target {
226
21229
        WasmType::Ratio => compile_for_stack_ratio(ctx, emit, symbols, expr),
227
45724
        WasmType::I32 => compile_for_stack_index(ctx, emit, symbols, expr),
228
        _ => {
229
29607
            let ty = compile_for_stack(ctx, emit, symbols, expr)?;
230
29607
            if ty == target {
231
29394
                Ok(())
232
            } else {
233
213
                Err(Error::Compile(format!(
234
213
                    "type mismatch: expected {target}, got {ty}"
235
213
                )))
236
            }
237
        }
238
    }
239
98903
}
240

            
241
297068
pub(in crate::compiler) fn compile_call_for_stack(
242
297068
    ctx: &mut CompileContext,
243
297068
    emit: &mut FunctionEmitter,
244
297068
    symbols: &mut SymbolTable,
245
297068
    elems: &[Expr],
246
297068
) -> Result<WasmType> {
247
297068
    let (head, args) = elems
248
297068
        .split_first()
249
297068
        .ok_or_else(|| Error::Compile("empty function call".to_string()))?;
250
297068
    match head {
251
293660
        Expr::Symbol(name) => compile_symbol_call_for_stack(ctx, emit, symbols, name, args),
252
994
        Expr::Lambda(params, body) => {
253
994
            compile_lambda_call_for_stack(ctx, emit, symbols, params, body, args)
254
        }
255
2414
        Expr::List(inner) => {
256
2414
            let resolved = call(symbols, inner)?;
257
2414
            match resolved {
258
2414
                Expr::Lambda(params, body) => {
259
2414
                    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
297068
}
270

            
271
293660
fn compile_symbol_call_for_stack(
272
293660
    ctx: &mut CompileContext,
273
293660
    emit: &mut FunctionEmitter,
274
293660
    symbols: &mut SymbolTable,
275
293660
    name: &str,
276
293660
    args: &[Expr],
277
293660
) -> Result<WasmType> {
278
293163
    let (func, kind, value) = {
279
293660
        let sym = symbols
280
293660
            .lookup(name)
281
293660
            .ok_or_else(|| Error::UndefinedSymbol(name.to_string()))?;
282
293163
        (sym.function().cloned(), sym.kind(), sym.value().cloned())
283
    };
284
293163
    if kind == SymbolKind::Macro
285
213
        && let Some(Expr::Lambda(params, body)) = func
286
    {
287
213
        return expand_macro_then(symbols, &params, &body, args, |symbols, code| {
288
213
            compile_for_stack(ctx, emit, symbols, &code)
289
213
        });
290
292950
    }
291
7597
    if let Some(Expr::Lambda(params, body)) = func {
292
7597
        if let Some(ty) = try_compile_runtime_call(ctx, emit, symbols, name, &params, &body, args)?
293
        {
294
1633
            return Ok(ty);
295
5964
        }
296
5964
        ctx.push_inlining_frame(name)?;
297
5964
        let result = compile_lambda_call_for_stack(ctx, emit, symbols, &params, &body, args);
298
5964
        ctx.pop_inlining_frame(name);
299
5964
        return result;
300
285353
    }
301
2698
    if let Some(Expr::WasmLocal(idx, WasmType::Closure(sig))) = value {
302
2698
        return compile_call_ref(ctx, emit, symbols, idx, sig, args);
303
282655
    }
304
282655
    match kind {
305
        SymbolKind::Native | SymbolKind::Operator => {
306
253403
            crate::compiler::native::compile_for_stack(ctx, emit, symbols, name, args)
307
        }
308
        SymbolKind::SpecialForm => {
309
29252
            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
293660
}
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
}