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
4814
fn compile_do_runtime_loop(
25
4814
    ctx: &mut CompileContext,
26
4814
    emit: &mut FunctionEmitter,
27
4814
    local: &mut SymbolTable,
28
4814
    dl: &DoLoop<'_>,
29
4814
) -> 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
4814
    let mut wasm_vars: Vec<(String, u32, WasmType, Option<Expr>)> = Vec::new();
36
6142
    for v in dl.vars {
37
6142
        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
6142
        let init_val = eval_value(&mut local.clone(), &init_expr)?;
42
6142
        let ty = infer_wasm_type(&init_val, v.step.as_ref(), local);
43
6142
        let idx = ctx.alloc_local(ty)?;
44
6142
        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
664
            if !matches!(init_expr, Expr::Nil) {
48
83
                compile_for_effect(ctx, emit, local, &init_expr)?;
49
581
            }
50
664
            emit_nil_default(ctx, emit, ty)?;
51
        } else {
52
5478
            compile_for_stack(ctx, emit, local, &init_expr)?;
53
        }
54
6142
        emit.local_set(idx);
55
6142
        local.define(
56
6142
            Symbol::new(&v.name, SymbolKind::Variable).with_value(Expr::WasmLocal(idx, ty)),
57
        );
58
6142
        wasm_vars.push((v.name.clone(), idx, ty, v.step.clone()));
59
    }
60

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

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

            
73
4814
    if compile_for_stack(ctx, emit, local, dl.end_test)? == crate::ast::WasmType::I32 {
74
83
        // An index end-test is a count, never nil: the loop ends at once.
75
83
        emit.drop_value();
76
83
        emit.i32_const(1);
77
4731
    }
78
4814
    emit.br_if(1);
79

            
80
4814
    for expr in dl.body {
81
2573
        compile_for_effect(ctx, emit, local, expr)?;
82
    }
83

            
84
4814
    if dl.sequential {
85
913
        for (_, idx, _, step) in &wasm_vars {
86
913
            if let Some(step_expr) = step {
87
830
                compile_for_stack(ctx, emit, local, step_expr)?;
88
830
                emit.local_set(*idx);
89
83
            }
90
        }
91
    } else {
92
4316
        let step_vars: Vec<_> = wasm_vars
93
4316
            .iter()
94
5229
            .filter(|(_, _, _, step)| step.is_some())
95
4316
            .collect();
96
5146
        for (_, _, _, step) in &step_vars {
97
5146
            compile_for_stack(ctx, emit, local, step.as_ref().unwrap())?;
98
        }
99
4980
        for (_, idx, _, _) in step_vars.iter().rev() {
100
4980
            emit.local_set(*idx);
101
4980
        }
102
    }
103

            
104
4731
    emit.br(0);
105
4731
    emit.block_end(); // end loop
106
4731
    emit.block_end(); // end block
107

            
108
4731
    Ok(())
109
4814
}
110

            
111
1743
pub(super) fn compile_do_runtime(
112
1743
    ctx: &mut CompileContext,
113
1743
    emit: &mut FunctionEmitter,
114
1743
    symbols: &mut SymbolTable,
115
1743
    dl: &DoLoop<'_>,
116
1743
) -> Result<()> {
117
1743
    let mut local = symbols.clone();
118
1743
    compile_do_runtime_loop(ctx, emit, &mut local, dl)?;
119
1660
    if dl.result_forms.is_empty() {
120
166
        compile_nil(ctx, emit);
121
166
    } else {
122
1494
        compile_body(ctx, emit, &mut local, dl.result_forms)?;
123
    }
124
1660
    Ok(())
125
1743
}
126

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

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