1
//! Codegen for the runtime path of `DO` / `DO*`.
2
//!
3
//! `compile_do_runtime_loop` emits the actual wasm `block / loop / br_if`
4
//! prelude + body + step sequence; the three wrapper functions
5
//! (`compile_do_runtime` / `_for_effect` / `_for_stack`) differ only
6
//! in how they consume the result-forms after the loop exits — value
7
//! position, effect-only, or push-to-stack respectively.
8
//!
9
//! Both `DO` and `DO*` parallel/sequential semantics are encoded as
10
//! `DoLoop.sequential`. The step assignment block here is the only
11
//! place that distinction surfaces in the runtime codegen.
12

            
13
use crate::ast::{Expr, WasmType};
14
use crate::compiler::context::CompileContext;
15
use crate::compiler::emit::FunctionEmitter;
16
use crate::compiler::expr::{
17
    compile_body, compile_for_effect, compile_for_stack, compile_nil, emit_nil_default, eval_value,
18
};
19
use crate::error::Result;
20
use crate::runtime::{Symbol, SymbolKind, SymbolTable};
21

            
22
use super::common::{DoLoop, collect_setf_targets, infer_wasm_type, promote_to_wasm_local};
23

            
24
3824
fn compile_do_runtime_loop(
25
3824
    ctx: &mut CompileContext,
26
3824
    emit: &mut FunctionEmitter,
27
3824
    local: &mut SymbolTable,
28
3824
    dl: &DoLoop<'_>,
29
3824
) -> Result<()> {
30
    // Declare the DO-vars before promoting body setf targets so the
31
    // setf-target type inference (which peeks at CONS step expressions)
32
    // can resolve references to the loop variables. Otherwise an
33
    // accumulator like `(setf result (cons i result))` would be allocated
34
    // with a default element type before `i`'s real type is known.
35
3824
    let mut wasm_vars: Vec<(String, u32, WasmType, Option<Expr>)> = Vec::new();
36
4980
    for v in dl.vars {
37
4980
        let init_expr = v.init.clone().unwrap_or(Expr::Nil);
38
        // Probe on a CLONE: the value is only used to size the local and detect a
39
        // nil init; the live emission below (compile_for_effect / compile_for_stack)
40
        // is the single application of any seed side effects.
41
4980
        let init_val = eval_value(&mut local.clone(), &init_expr)?;
42
4980
        let ty = infer_wasm_type(&init_val, v.step.as_ref(), local);
43
4980
        let idx = ctx.alloc_local(ty)?;
44
4980
        if matches!(init_val, Expr::Nil) {
45
            // A non-literal init that *resolves* to nil (e.g. `(progn (side-effect) nil)`)
46
            // must still emit its effects before the typed nil seed.
47
544
            if !matches!(init_expr, Expr::Nil) {
48
68
                compile_for_effect(ctx, emit, local, &init_expr)?;
49
476
            }
50
544
            emit_nil_default(ctx, emit, ty)?;
51
        } else {
52
4436
            compile_for_stack(ctx, emit, local, &init_expr)?;
53
        }
54
4980
        emit.local_set(idx);
55
4980
        local.define(
56
4980
            Symbol::new(&v.name, SymbolKind::Variable).with_value(Expr::WasmLocal(idx, ty)),
57
        );
58
4980
        wasm_vars.push((v.name.clone(), idx, ty, v.step.clone()));
59
    }
60

            
61
4980
    let do_var_names: Vec<_> = dl.vars.iter().map(|v| v.name.clone()).collect();
62
3824
    for (target, rhs) in collect_setf_targets(dl.body) {
63
1988
        if !do_var_names.contains(&target) {
64
1988
            promote_to_wasm_local(ctx, emit, local, &target, rhs.as_ref())?;
65
        }
66
    }
67

            
68
    // block $exit
69
3824
    emit.block_start();
70
    // loop $continue
71
3824
    emit.loop_start();
72

            
73
3824
    compile_for_stack(ctx, emit, local, dl.end_test)?;
74
3824
    emit.br_if(1);
75

            
76
3824
    for expr in dl.body {
77
2056
        compile_for_effect(ctx, emit, local, expr)?;
78
    }
79

            
80
3824
    if dl.sequential {
81
748
        for (_, idx, _, step) in &wasm_vars {
82
748
            if let Some(step_expr) = step {
83
680
                compile_for_stack(ctx, emit, local, step_expr)?;
84
680
                emit.local_set(*idx);
85
68
            }
86
        }
87
    } else {
88
3416
        let step_vars: Vec<_> = wasm_vars
89
3416
            .iter()
90
4232
            .filter(|(_, _, _, step)| step.is_some())
91
3416
            .collect();
92
4164
        for (_, _, _, step) in &step_vars {
93
4164
            compile_for_stack(ctx, emit, local, step.as_ref().unwrap())?;
94
        }
95
4028
        for (_, idx, _, _) in step_vars.iter().rev() {
96
4028
            emit.local_set(*idx);
97
4028
        }
98
    }
99

            
100
3756
    emit.br(0);
101
3756
    emit.block_end(); // end loop
102
3756
    emit.block_end(); // end block
103

            
104
3756
    Ok(())
105
3824
}
106

            
107
1428
pub(super) fn compile_do_runtime(
108
1428
    ctx: &mut CompileContext,
109
1428
    emit: &mut FunctionEmitter,
110
1428
    symbols: &mut SymbolTable,
111
1428
    dl: &DoLoop<'_>,
112
1428
) -> Result<()> {
113
1428
    let mut local = symbols.clone();
114
1428
    compile_do_runtime_loop(ctx, emit, &mut local, dl)?;
115
1360
    if dl.result_forms.is_empty() {
116
136
        compile_nil(ctx, emit);
117
136
    } else {
118
1224
        compile_body(ctx, emit, &mut local, dl.result_forms)?;
119
    }
120
1360
    Ok(())
121
1428
}
122

            
123
pub(super) fn compile_do_runtime_for_effect(
124
    ctx: &mut CompileContext,
125
    emit: &mut FunctionEmitter,
126
    symbols: &mut SymbolTable,
127
    dl: &DoLoop<'_>,
128
) -> Result<()> {
129
    let mut local = symbols.clone();
130
    compile_do_runtime_loop(ctx, emit, &mut local, dl)?;
131
    for expr in dl.result_forms {
132
        compile_for_effect(ctx, emit, &mut local, expr)?;
133
    }
134
    Ok(())
135
}
136

            
137
2396
pub(super) fn compile_do_runtime_for_stack(
138
2396
    ctx: &mut CompileContext,
139
2396
    emit: &mut FunctionEmitter,
140
2396
    symbols: &mut SymbolTable,
141
2396
    dl: &DoLoop<'_>,
142
2396
) -> Result<WasmType> {
143
2396
    let mut local = symbols.clone();
144
2396
    compile_do_runtime_loop(ctx, emit, &mut local, dl)?;
145
2396
    if dl.result_forms.is_empty() {
146
        // No result forms ≡ nil — falsy i31, typed `Bool` so it serializes as
147
        // Nil and agrees with the eval mirror's `Expr::Nil`.
148
        emit.i32_const(0);
149
        Ok(WasmType::Bool)
150
    } else {
151
2396
        let last = dl.result_forms.last().unwrap();
152
2396
        for expr in &dl.result_forms[..dl.result_forms.len() - 1] {
153
            compile_for_effect(ctx, emit, &mut local, expr)?;
154
        }
155
2396
        compile_for_stack(ctx, emit, &mut local, last)
156
    }
157
2396
}