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
776335
pub(in crate::compiler) fn compile_for_stack(
25
776335
    ctx: &mut CompileContext,
26
776335
    emit: &mut FunctionEmitter,
27
776335
    symbols: &mut SymbolTable,
28
776335
    expr: &Expr,
29
776335
) -> Result<WasmType> {
30
14530
    match expr {
31
14530
        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
14031
            emit.i32_const(i32::try_from(*n.numer()).map_err(|_| {
35
                Error::Compile(format!("integer literal {} exceeds i32 range", n.numer()))
36
            })?);
37
14031
            Ok(WasmType::I32)
38
        }
39
499
        Expr::Number(n) => {
40
499
            push_ratio(ctx, emit, *n.numer(), *n.denom());
41
499
            Ok(WasmType::Ratio)
42
        }
43
        Expr::T => {
44
2076
            emit.i32_const(1);
45
2076
            Ok(WasmType::Bool)
46
        }
47
        Expr::Nil => {
48
1498
            emit.i32_const(0);
49
1498
            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
81598
        Expr::WasmLocal(idx, ty) => {
60
81598
            emit.local_get(*idx);
61
81598
            Ok(*ty)
62
        }
63
42167
        Expr::String(s) => {
64
42167
            let data_idx = ctx.add_data(s.as_bytes())?;
65
42167
            emit.i32_const(0);
66
42167
            emit.i32_const(s.len() as i32);
67
42167
            emit.array_new_data(ctx.ids.ty_i8_array, data_idx);
68
42167
            Ok(WasmType::StringRef)
69
        }
70
        Expr::Symbol(_) => {
71
78767
            let resolved = resolve_arg(symbols, expr)?;
72
78518
            compile_for_stack(ctx, emit, symbols, &resolved)
73
        }
74
553200
        Expr::List(elems) if elems.is_empty() => compile_for_stack(ctx, emit, symbols, &Expr::Nil),
75
553199
        Expr::List(elems) => compile_call_for_stack(ctx, emit, symbols, elems),
76
2499
        Expr::Quote(inner) if is_quoted_datum(expr) => render_datum_string(ctx, emit, inner),
77
3
        Expr::Quote(inner) => compile_for_stack(ctx, emit, symbols, inner),
78
        Expr::Quasiquote(inner) => {
79
            let expanded = expand_quasiquote(symbols, inner)?;
80
            compile_for_stack(ctx, emit, symbols, &expanded)
81
        }
82
        _ => Err(Error::Compile(format!(
83
            "cannot compile to WASM stack value: {}",
84
            format_expr(expr)
85
        ))),
86
    }
87
776335
}
88

            
89
/// The `WasmType` that [`compile_for_stack`] pushes for a literal or
90
/// already-resolved runtime `expr`, or `None` for a shape whose stack type is
91
/// only known by actually evaluating/emitting it (a call/list, a quote, a
92
/// lambda, …). This is the SINGLE SOURCE OF TRUTH for the compile-time
93
/// "stack-type mirrors" — the eval-side functions that PREDICT a form's stack
94
/// type to size let/do/binding locals or unify branch results. Every such
95
/// mirror routes through this so the prediction can't drift from what codegen
96
/// emits (the class of bug that produced the i32/Bool, list-element, and
97
/// recursion-type mismatches). The classified arms match `compile_for_stack`
98
/// exactly: an integer literal is an Index (`I32`), a fractional literal a
99
/// Scalar (`Ratio`), `t` / `nil` / the empty list are `Bool`, a string literal
100
/// and a quoted datum are `StringRef`, a quoted literal is the literal's type,
101
/// and a runtime placeholder/local carries its own type.
102
#[must_use]
103
45368
pub(in crate::compiler) fn classify_stack_type(expr: &Expr) -> Option<WasmType> {
104
31469
    match expr {
105
10887
        Expr::WasmRuntime(ty) | Expr::WasmLocal(_, ty) => Some(*ty),
106
        // ADR-0028: an integer literal (denom == 1) defaults to Index (I32);
107
        // a fractional literal is a dimensionless Scalar (Ratio). Mirrors the
108
        // `denom() == 1` split in `compile_for_stack`.
109
31469
        Expr::Number(n) if *n.denom() == 1 => Some(WasmType::I32),
110
1248
        Expr::Number(_) => Some(WasmType::Ratio),
111
1751
        Expr::T | Expr::Nil => Some(WasmType::Bool),
112
1
        Expr::List(elems) if elems.is_empty() => Some(WasmType::Bool),
113
1083
        Expr::String(_) => Some(WasmType::StringRef),
114
175
        Expr::Quote(_) if is_quoted_datum(expr) => Some(WasmType::StringRef),
115
3
        Expr::Quote(inner) => classify_stack_type(inner),
116
2
        _ => None,
117
    }
118
45368
}
119

            
120
/// A quoted symbol, keyword, cons, non-empty list or (un)quasiquoted form: data
121
/// with no runtime value, which lowers to its printed form. The single test for
122
/// that shape.
123
#[must_use]
124
29234
pub(in crate::compiler) fn is_quoted_datum(expr: &Expr) -> bool {
125
29234
    match expr {
126
2923
        Expr::Quote(inner) => match inner.as_ref() {
127
            Expr::Symbol(_)
128
            | Expr::Keyword(_)
129
            | Expr::Cons(_, _)
130
            | Expr::Quasiquote(_)
131
            | Expr::Unquote(_)
132
923
            | Expr::UnquoteSplicing(_) => true,
133
1994
            Expr::List(elems) => !elems.is_empty(),
134
6
            _ => false,
135
        },
136
26311
        _ => false,
137
    }
138
29234
}
139

            
140
/// Pushes the printed form of a datum as a `StringRef` from a data segment.
141
2496
fn render_datum_string(
142
2496
    ctx: &mut CompileContext,
143
2496
    emit: &mut FunctionEmitter,
144
2496
    datum: &Expr,
145
2496
) -> Result<WasmType> {
146
2496
    let rendered = format_expr(datum);
147
2496
    let data_idx = ctx.add_data(rendered.as_bytes())?;
148
2496
    emit.i32_const(0);
149
2496
    emit.i32_const(rendered.len() as i32);
150
2496
    emit.array_new_data(ctx.ids.ty_i8_array, data_idx);
151
2496
    Ok(WasmType::StringRef)
152
2496
}
153

            
154
29548
pub(in crate::compiler) fn compile_for_stack_ratio(
155
29548
    ctx: &mut CompileContext,
156
29548
    emit: &mut FunctionEmitter,
157
29548
    symbols: &mut SymbolTable,
158
29548
    expr: &Expr,
159
29548
) -> Result<()> {
160
    // ADR-0028: a numeric literal is a dimension-flexible token — coerce it to
161
    // Scalar (the sanctioned Index↔Scalar crossing). A RUNTIME index never
162
    // coerces; it must bridge explicitly via `(index->scalar …)`. A non-literal
163
    // operand that *resolves* to a number still emits its effects on the live
164
    // table first (the probe runs on a clone, so it applies none).
165
29548
    if let Expr::Number(n) = eval_value(&mut symbols.clone(), expr)? {
166
16102
        if !matches!(expr, Expr::Number(_)) {
167
664
            compile_for_effect(ctx, emit, symbols, expr)?;
168
15438
        }
169
16102
        push_ratio(ctx, emit, *n.numer(), *n.denom());
170
16102
        return Ok(());
171
13446
    }
172
13446
    let ty = compile_for_stack(ctx, emit, symbols, expr)?;
173
13446
    match ty {
174
13280
        WasmType::Ratio => Ok(()),
175
        WasmType::Commodity => Err(Error::Compile(
176
            "commodity-bearing values cannot mix with pure-rational arithmetic; \
177
             use `(convert-commodity ...)` to bridge"
178
                .to_string(),
179
        )),
180
166
        WasmType::I32 => Err(Error::Compile(
181
166
            "a runtime index (count) cannot be used as a scalar; \
182
166
             bridge explicitly with `(index->scalar ...)`"
183
166
                .to_string(),
184
166
        )),
185
        WasmType::Bool
186
        | WasmType::PairRef(_)
187
        | WasmType::StringRef
188
        | WasmType::EntityRef(_)
189
        | WasmType::Closure(_)
190
        | WasmType::AnyRef => Err(Error::Compile(
191
            "arithmetic requires ratio values".to_string(),
192
        )),
193
    }
194
29548
}
195

            
196
/// Compiles `expr` as a raw i32 Index value: a runtime `I32` (a count/length/
197
/// index) or an integer literal. A fractional literal is a Scalar, and a
198
/// runtime Ratio/Commodity/ref is not an Index — all rejected (ADR-0028:
199
/// Index combines only with Index + integer literals).
200
72542
pub(in crate::compiler) fn compile_for_stack_index(
201
72542
    ctx: &mut CompileContext,
202
72542
    emit: &mut FunctionEmitter,
203
72542
    symbols: &mut SymbolTable,
204
72542
    expr: &Expr,
205
72542
) -> Result<()> {
206
72542
    if let Expr::Number(n) = eval_value(&mut symbols.clone(), expr)? {
207
33200
        if !matches!(expr, Expr::Number(_)) {
208
3901
            compile_for_effect(ctx, emit, symbols, expr)?;
209
29299
        }
210
33200
        if *n.denom() == 1 {
211
33034
            emit.i32_const(i32::try_from(*n.numer()).map_err(|_| {
212
83
                Error::Compile(format!("integer literal {} exceeds i32 range", n.numer()))
213
83
            })?);
214
32951
            return Ok(());
215
166
        }
216
166
        return Err(Error::Compile(
217
166
            "a fractional literal is a scalar, not an index".to_string(),
218
166
        ));
219
39342
    }
220
39342
    let ty = compile_for_stack(ctx, emit, symbols, expr)?;
221
39342
    match ty {
222
39342
        WasmType::I32 => Ok(()),
223
        WasmType::Ratio | WasmType::Commodity => Err(Error::Compile(
224
            "a scalar/money value cannot be used as an index; \
225
             bridge explicitly with `(scalar->index ...)`"
226
                .to_string(),
227
        )),
228
        WasmType::Bool
229
        | WasmType::PairRef(_)
230
        | WasmType::StringRef
231
        | WasmType::EntityRef(_)
232
        | WasmType::Closure(_)
233
        | WasmType::AnyRef => Err(Error::Compile(format!(
234
            "index arithmetic requires an integer count, got {ty}"
235
        ))),
236
    }
237
72542
}
238

            
239
/// Emits `expr` coerced to the `target` wasm type — the type-directed boundary
240
/// primitive for closure args, host-fn args, and accumulator seeds. A nil
241
/// resolves to the target's typed default; a numeric literal coerces across the
242
/// sanctioned Index↔Scalar boundary; everything else must already match. The
243
/// nil/literal probe runs on a clone, so a non-literal that resolves to one
244
/// emits its effects on the live table first.
245
141266
pub(in crate::compiler) fn compile_for_stack_as(
246
141266
    ctx: &mut CompileContext,
247
141266
    emit: &mut FunctionEmitter,
248
141266
    symbols: &mut SymbolTable,
249
141266
    expr: &Expr,
250
141266
    target: WasmType,
251
141266
) -> Result<()> {
252
141266
    if matches!(eval_value(&mut symbols.clone(), expr)?, Expr::Nil) {
253
3237
        if !matches!(expr, Expr::Nil) {
254
166
            compile_for_effect(ctx, emit, symbols, expr)?;
255
3071
        }
256
3237
        return emit_nil_default(ctx, emit, target);
257
138029
    }
258
138029
    match target {
259
29548
        WasmType::Ratio => compile_for_stack_ratio(ctx, emit, symbols, expr),
260
72542
        WasmType::I32 => compile_for_stack_index(ctx, emit, symbols, expr),
261
        _ => {
262
35939
            let ty = compile_for_stack(ctx, emit, symbols, expr)?;
263
35939
            if ty == target {
264
35690
                Ok(())
265
            } else {
266
249
                Err(Error::Compile(format!(
267
249
                    "type mismatch: expected {target}, got {ty}"
268
249
                )))
269
            }
270
        }
271
    }
272
141266
}
273

            
274
562495
pub(in crate::compiler) fn compile_call_for_stack(
275
562495
    ctx: &mut CompileContext,
276
562495
    emit: &mut FunctionEmitter,
277
562495
    symbols: &mut SymbolTable,
278
562495
    elems: &[Expr],
279
562495
) -> Result<WasmType> {
280
562495
    let (head, args) = elems
281
562495
        .split_first()
282
562495
        .ok_or_else(|| Error::Compile("empty function call".to_string()))?;
283
562495
    match head {
284
557017
        Expr::Symbol(name) => compile_symbol_call_for_stack(ctx, emit, symbols, name, args),
285
1162
        Expr::Lambda(params, body) => {
286
1162
            compile_lambda_call_for_stack(ctx, emit, symbols, params, body, args)
287
        }
288
4316
        Expr::List(inner) => {
289
4316
            let resolved = call(symbols, inner)?;
290
4316
            match resolved {
291
4316
                Expr::Lambda(params, body) => {
292
4316
                    compile_lambda_call_for_stack(ctx, emit, symbols, &params, &body, args)
293
                }
294
                _ => Err(Error::Compile("not callable".to_string())),
295
            }
296
        }
297
        _ => {
298
            let result = call(symbols, elems)?;
299
            compile_for_stack(ctx, emit, symbols, &result)
300
        }
301
    }
302
562495
}
303

            
304
557017
fn compile_symbol_call_for_stack(
305
557017
    ctx: &mut CompileContext,
306
557017
    emit: &mut FunctionEmitter,
307
557017
    symbols: &mut SymbolTable,
308
557017
    name: &str,
309
557017
    args: &[Expr],
310
557017
) -> Result<WasmType> {
311
556436
    let (func, kind, value) = {
312
557017
        let sym = symbols
313
557017
            .lookup(name)
314
557017
            .ok_or_else(|| Error::UndefinedSymbol(name.to_string()))?;
315
556436
        (sym.function().cloned(), sym.kind(), sym.value().cloned())
316
    };
317
556436
    if kind == SymbolKind::Macro
318
1079
        && let Some(Expr::Lambda(params, body)) = func
319
    {
320
1079
        return expand_macro_then(symbols, &params, &body, args, |symbols, code| {
321
1079
            compile_for_stack(ctx, emit, symbols, &code)
322
1079
        });
323
555357
    }
324
9296
    if let Some(Expr::Lambda(params, body)) = func {
325
9296
        if let Some(ty) = try_compile_runtime_call(ctx, emit, symbols, name, &params, &body, args)?
326
        {
327
1909
            return Ok(ty);
328
7387
        }
329
7387
        ctx.push_inlining_frame(name)?;
330
7387
        let result = compile_lambda_call_for_stack(ctx, emit, symbols, &params, &body, args);
331
7387
        ctx.pop_inlining_frame(name);
332
7387
        return result;
333
546061
    }
334
3818
    if let Some(Expr::WasmLocal(idx, WasmType::Closure(sig))) = value {
335
3818
        return compile_call_ref(ctx, emit, symbols, idx, sig, args);
336
542243
    }
337
542243
    match kind {
338
        SymbolKind::Native | SymbolKind::Operator => {
339
499830
            crate::compiler::native::compile_for_stack(ctx, emit, symbols, name, args)
340
        }
341
        SymbolKind::SpecialForm => {
342
42413
            crate::compiler::special::compile_for_stack(ctx, emit, symbols, name, args)
343
        }
344
        _ => Err(Error::Compile(format!(
345
            "symbol '{name}' is not callable for stack value"
346
        ))),
347
    }
348
557017
}
349

            
350
#[cfg(test)]
351
mod tests {
352
    use super::*;
353
    use crate::ast::{ClosureSigId, EntityKind, Fraction, PairElement};
354
    use crate::compiler::context::CompileContext;
355
    use crate::runtime::SymbolTable;
356

            
357
    /// The contract `classify_stack_type` exists to uphold: for every literal /
358
    /// already-resolved runtime `Expr` it classifies (returns `Some`), the
359
    /// prediction equals the `WasmType` `compile_for_stack` actually pushes.
360
    /// Any drift here is the class of bug (#56/#57/#58/#59) that motivated the
361
    /// single source of truth, so this test compiles each shape for real and
362
    /// compares. (`None`-classified shapes — symbols, calls — are not value
363
    /// literals and aren't asserted here.)
364
    #[test]
365
1
    fn classify_stack_type_matches_compile_for_stack() {
366
9
        let quote = |inner: Expr| Expr::Quote(Box::new(inner));
367
6
        let sym = |name: &str| Expr::Symbol(name.to_string());
368
1
        let runtime = [
369
1
            WasmType::I32,
370
1
            WasmType::Bool,
371
1
            WasmType::Ratio,
372
1
            WasmType::Commodity,
373
1
            WasmType::StringRef,
374
1
            WasmType::PairRef(PairElement::Bool),
375
1
            WasmType::EntityRef(EntityKind::Account),
376
1
            WasmType::Closure(ClosureSigId(0)),
377
1
            WasmType::AnyRef,
378
1
        ];
379
1
        let mut cases: Vec<Expr> = vec![
380
1
            Expr::Number(Fraction::from_integer(7)),
381
1
            Expr::Number(Fraction::new(1, 2)),
382
1
            Expr::T,
383
1
            Expr::Nil,
384
1
            Expr::Nil,
385
1
            Expr::String("hi".to_string()),
386
1
            Expr::List(vec![]),
387
1
            quote(Expr::Nil),
388
1
            quote(Expr::Number(Fraction::from_integer(3))),
389
1
            quote(Expr::String("s".to_string())),
390
1
            quote(sym("A")),
391
1
            quote(Expr::Keyword("K".to_string())),
392
1
            quote(Expr::Quasiquote(Box::new(Expr::List(vec![sym("A")])))),
393
1
            quote(Expr::List(vec![sym("A"), sym("B")])),
394
1
            quote(Expr::cons(sym("A"), Expr::Nil)),
395
1
            quote(Expr::cons(
396
1
                Expr::Number(Fraction::from_integer(1)),
397
1
                sym("B"),
398
1
            )),
399
        ];
400
        // A `WasmLocal` of every wasm type — `compile_for_stack` emits
401
        // `local.get` and returns the local's declared type unchanged.
402
9
        cases.extend(runtime.iter().map(|ty| Expr::WasmLocal(0, *ty)));
403

            
404
25
        for expr in &cases {
405
25
            let predicted = classify_stack_type(expr)
406
25
                .unwrap_or_else(|| panic!("classify_stack_type returned None for {expr:?}"));
407
25
            let mut ctx = CompileContext::new().expect("ctx");
408
25
            let mut emit = FunctionEmitter::new();
409
25
            let mut symbols = SymbolTable::with_builtins_for_wasm();
410
25
            let emitted = compile_for_stack(&mut ctx, &mut emit, &mut symbols, expr)
411
25
                .unwrap_or_else(|e| panic!("compile_for_stack {expr:?}: {e}"));
412
25
            assert_eq!(
413
                predicted, emitted,
414
                "classify_stack_type disagrees with compile_for_stack for {expr:?}"
415
            );
416
        }
417
1
    }
418

            
419
    /// Shapes that aren't a directly-emittable literal/runtime value (they need
420
    /// a call/list walk or aren't stack values) classify as `None`.
421
    #[test]
422
1
    fn classify_stack_type_is_none_for_non_literal_shapes() {
423
1
        assert_eq!(classify_stack_type(&Expr::Symbol("X".to_string())), None);
424
        // A namespaced symbol (ADR-0029) is still just a Symbol — its canonical
425
        // `NS:NAME` key must not be mistaken for any typed literal shape.
426
1
        assert_eq!(
427
1
            classify_stack_type(&Expr::Symbol("FOO:BAR".to_string())),
428
            None
429
        );
430
1
        assert_eq!(
431
1
            classify_stack_type(&Expr::WasmRuntime(WasmType::Ratio)),
432
            Some(WasmType::Ratio)
433
        );
434
1
    }
435
}