1
//! `DOLIST` codegen + eval handler.
2
//!
3
//! Two paths: a *runtime* path when the list expression resolves to a
4
//! `WasmType::PairRef(elem)` (typed pair chain on the wasm side), and
5
//! a *constant-fold* path when the list is a compile-time list literal.
6
//! The runtime path delegates downcast emit to
7
//! `super::super::native::emit_pair_car_downcast` so each element type
8
//! gets the right `ref.cast`.
9
//!
10
//! Three result positions share the loop emit (`emit_dolist_runtime_loop`
11
//! / `emit_dolist_const_loop`) and differ only in how the loop's result
12
//! is produced: effect drops it, the value path serializes it to the
13
//! output buffer, the stack path leaves a typed value on the operand
14
//! stack so a consumer (e.g. a `defun` body tail) can read it.
15

            
16
use crate::ast::{Expr, PairElement, WasmType};
17
use crate::compiler::context::CompileContext;
18
use crate::compiler::emit::FunctionEmitter;
19
use crate::compiler::expr::{
20
    compile_expr, compile_for_effect, compile_for_stack, compile_nil, eval_value,
21
};
22
use crate::compiler::native::emit_pair_car_downcast;
23
use crate::error::{Error, Result};
24
use crate::runtime::{Symbol, SymbolKind, SymbolTable};
25

            
26
use super::common::{collect_setf_targets, mark_runtime_setf_targets, promote_to_wasm_local};
27

            
28
/// Parsed `(dolist (var list [result]) body...)` form.
29
struct DolistSpec<'a> {
30
    var_name: &'a str,
31
    list_expr: &'a Expr,
32
    result_expr: Option<&'a Expr>,
33
    body: &'a [Expr],
34
}
35

            
36
9545
fn parse_dolist(args: &[Expr]) -> Result<DolistSpec<'_>> {
37
9545
    if args.len() < 2 {
38
249
        return Err(Error::Compile(
39
249
            "DOLIST requires a variable specification and at least one body form".to_string(),
40
249
        ));
41
9296
    }
42

            
43
9296
    let var_spec = args[0].as_list().ok_or_else(|| {
44
83
        Error::Compile(format!(
45
83
            "DOLIST: expected variable specification list, got {:?}",
46
83
            args[0]
47
83
        ))
48
83
    })?;
49

            
50
9213
    if var_spec.len() < 2 || var_spec.len() > 3 {
51
83
        return Err(Error::Compile(
52
83
            "DOLIST: variable specification must be (var list) or (var list result)".to_string(),
53
83
        ));
54
9130
    }
55

            
56
9130
    let var_name = var_spec[0].as_symbol().ok_or_else(|| {
57
83
        Error::Compile(format!(
58
83
            "DOLIST: variable must be a symbol, got {:?}",
59
83
            var_spec[0]
60
83
        ))
61
83
    })?;
62

            
63
9047
    Ok(DolistSpec {
64
9047
        var_name,
65
9047
        list_expr: &var_spec[1],
66
9047
        result_expr: var_spec.get(2),
67
9047
        body: &args[1..],
68
9047
    })
69
9545
}
70

            
71
/// Elements of a compile-time-constant list value, or an error if the
72
/// list expression didn't fold to a list.
73
1328
fn const_list_elements(list_value: &Expr) -> Result<Vec<Expr>> {
74
1328
    let to_list = |inner: &Expr| match inner {
75
1079
        Expr::List(elems) => Ok(elems.clone()),
76
166
        Expr::Nil => Ok(vec![]),
77
83
        _ => Err(Error::Compile(
78
83
            "DOLIST: list expression must evaluate to a list".to_string(),
79
83
        )),
80
1328
    };
81
1328
    match list_value {
82
1079
        Expr::Quote(inner) => to_list(inner),
83
249
        other => to_list(other),
84
    }
85
1328
}
86

            
87
4316
fn restore_loop_var(symbols: &mut SymbolTable, var_name: &str, saved: Option<Symbol>) {
88
4316
    match saved {
89
        Some(s) => symbols.define(s),
90
4316
        None => {
91
4316
            symbols.remove(var_name);
92
4316
        }
93
    }
94
4316
}
95

            
96
/// Emits the runtime DOLIST loop: walks the typed `$pair` chain, binding
97
/// each car to the loop variable and running the body for effect. Leaves
98
/// the loop variable's symbol-table entry restored and emits no result —
99
/// the caller decides effect/value/stack result handling.
100
3071
fn emit_dolist_runtime_loop(
101
3071
    ctx: &mut CompileContext,
102
3071
    emit: &mut FunctionEmitter,
103
3071
    symbols: &mut SymbolTable,
104
3071
    var_name: &str,
105
3071
    list_expr: &Expr,
106
3071
    body: &[Expr],
107
3071
    elem: PairElement,
108
3071
) -> Result<()> {
109
    // Declare the loop variable's symbol-table entry first so any body
110
    // setf-target type inference (peeking into CONS step expressions) can
111
    // see the loop var's element-derived type instead of falling back to a
112
    // default. The actual local-set for the loop var rides inside the loop.
113
3071
    let pair_local = ctx.alloc_local(WasmType::PairRef(elem))?;
114
3071
    let saved_loop_var = symbols.lookup(var_name).cloned();
115
3071
    let var_ty = elem.as_wasm_type();
116
3071
    let var_local = ctx.alloc_local(var_ty)?;
117
3071
    symbols.define(
118
3071
        Symbol::new(var_name, SymbolKind::Variable).with_value(Expr::WasmLocal(var_local, var_ty)),
119
    );
120

            
121
3071
    for (target, rhs) in collect_setf_targets(body) {
122
1660
        if target != var_name {
123
1660
            promote_to_wasm_local(ctx, emit, symbols, &target, rhs.as_ref())?;
124
        }
125
    }
126

            
127
3071
    compile_for_stack(ctx, emit, symbols, list_expr)?;
128
3071
    emit.local_set(pair_local);
129

            
130
3071
    let pair_idx = ctx.ids.ty_pair;
131

            
132
3071
    emit.block_start();
133
3071
    emit.loop_start();
134

            
135
3071
    emit.local_get(pair_local);
136
3071
    emit.ref_is_null();
137
3071
    emit.br_if(1);
138

            
139
    // Extract the car as anyref, downcast per the element type.
140
3071
    emit.local_get(pair_local);
141
3071
    emit.struct_get(pair_idx, 0);
142
3071
    emit_pair_car_downcast(ctx, emit, elem);
143
3071
    emit.local_set(var_local);
144

            
145
3320
    for expr in body {
146
3320
        compile_for_effect(ctx, emit, symbols, expr)?;
147
    }
148

            
149
3071
    emit.local_get(pair_local);
150
3071
    emit.struct_get(pair_idx, 1);
151
3071
    emit.local_set(pair_local);
152

            
153
3071
    emit.br(0);
154
3071
    emit.block_end();
155
3071
    emit.block_end();
156

            
157
3071
    restore_loop_var(symbols, var_name, saved_loop_var);
158
3071
    Ok(())
159
3071
}
160

            
161
/// Emits the constant-fold DOLIST loop: unrolls over the known elements,
162
/// binding the loop variable to each and running the body for effect.
163
/// Leaves the loop variable restored and emits no result.
164
830
fn emit_dolist_const_loop(
165
830
    ctx: &mut CompileContext,
166
830
    emit: &mut FunctionEmitter,
167
830
    symbols: &mut SymbolTable,
168
830
    var_name: &str,
169
830
    elements: Vec<Expr>,
170
830
    body: &[Expr],
171
830
) -> Result<()> {
172
830
    let saved_loop_var = symbols.lookup(var_name).cloned();
173

            
174
    // Promote any outer variable the body `setf`s to a runtime value into a
175
    // wasm local before the unroll — without this a `(setf out <runtime>)`
176
    // leaves a bare `WasmRuntime` placeholder with no stack producer. Same
177
    // pre-pass the runtime path runs; the loop var itself is excluded (it's
178
    // rebound to each constant element below).
179
830
    for (target, rhs) in collect_setf_targets(body) {
180
664
        if target != var_name {
181
664
            promote_to_wasm_local(ctx, emit, symbols, &target, rhs.as_ref())?;
182
        }
183
    }
184

            
185
1992
    for element in elements {
186
1992
        symbols.define(Symbol::new(var_name, SymbolKind::Variable).with_value(element));
187
2490
        for expr in body {
188
2490
            compile_for_effect(ctx, emit, symbols, expr)?;
189
        }
190
    }
191
830
    restore_loop_var(symbols, var_name, saved_loop_var);
192
830
    Ok(())
193
830
}
194

            
195
/// Runs the loop for effect (runtime or constant-fold), leaving no result.
196
3901
fn emit_dolist_loop(
197
3901
    ctx: &mut CompileContext,
198
3901
    emit: &mut FunctionEmitter,
199
3901
    symbols: &mut SymbolTable,
200
3901
    spec: &DolistSpec<'_>,
201
3901
) -> Result<()> {
202
3901
    let list_value = eval_value(symbols, spec.list_expr)?;
203
3901
    if let Some(WasmType::PairRef(elem)) = list_value.wasm_type() {
204
3071
        emit_dolist_runtime_loop(
205
3071
            ctx,
206
3071
            emit,
207
3071
            symbols,
208
3071
            spec.var_name,
209
3071
            spec.list_expr,
210
3071
            spec.body,
211
3071
            elem,
212
        )
213
    } else {
214
830
        let elements = const_list_elements(&list_value)?;
215
830
        emit_dolist_const_loop(ctx, emit, symbols, spec.var_name, elements, spec.body)
216
    }
217
3901
}
218

            
219
3071
pub(super) fn compile_dolist_for_effect(
220
3071
    ctx: &mut CompileContext,
221
3071
    emit: &mut FunctionEmitter,
222
3071
    symbols: &mut SymbolTable,
223
3071
    args: &[Expr],
224
3071
) -> Result<()> {
225
3071
    let spec = parse_dolist(args)?;
226
3071
    emit_dolist_loop(ctx, emit, symbols, &spec)
227
3071
}
228

            
229
332
pub(super) fn compile_dolist(
230
332
    ctx: &mut CompileContext,
231
332
    emit: &mut FunctionEmitter,
232
332
    symbols: &mut SymbolTable,
233
332
    args: &[Expr],
234
332
) -> Result<()> {
235
332
    let spec = parse_dolist(args)?;
236
166
    emit_dolist_loop(ctx, emit, symbols, &spec)?;
237
166
    match spec.result_expr {
238
83
        Some(result) => compile_expr(ctx, emit, symbols, result),
239
        None => {
240
83
            compile_nil(ctx, emit);
241
83
            Ok(())
242
        }
243
    }
244
332
}
245

            
246
664
pub(super) fn compile_dolist_for_stack(
247
664
    ctx: &mut CompileContext,
248
664
    emit: &mut FunctionEmitter,
249
664
    symbols: &mut SymbolTable,
250
664
    args: &[Expr],
251
664
) -> Result<WasmType> {
252
664
    let spec = parse_dolist(args)?;
253
664
    emit_dolist_loop(ctx, emit, symbols, &spec)?;
254
664
    match spec.result_expr {
255
        Some(result) => compile_for_stack(ctx, emit, symbols, result),
256
        None => {
257
            // No result form ≡ nil. Push the falsy i31 the stack convention
258
            // uses for nil (typed `Bool` so it serializes as Nil), matching
259
            // `compile_for_stack(Expr::Nil)` and the DO runtime stack path.
260
664
            emit.i32_const(0);
261
664
            Ok(WasmType::Bool)
262
        }
263
    }
264
664
}
265

            
266
5478
pub(super) fn dolist_form(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
267
5478
    let spec = parse_dolist(args)?;
268
5146
    let list_value = eval_value(symbols, spec.list_expr)?;
269

            
270
5146
    if let Some(WasmType::PairRef(_)) = list_value.wasm_type() {
271
        // Runtime list: the body executes at runtime. Any OUTER variable the
272
        // body `setf`s (an accumulator / counter) is mutated by that runtime
273
        // iteration; mark it runtime so a const-fold of the enclosing form (an
274
        // eval-inlined `(list-length …)` whose tail is the counter, an IF-test
275
        // classification) doesn't read the stale const init and fold the whole
276
        // branch away.
277
4648
        mark_runtime_setf_targets(symbols, spec.body, &[spec.var_name]);
278
        // The dolist's *value* is its result form (or nil), NOT the list —
279
        // mirror what `compile_dolist_for_stack` pushes so this stack-type
280
        // predictor can't drift from codegen.
281
4648
        return match spec.result_expr {
282
            Some(result) => eval_value(symbols, result),
283
4648
            None => Ok(Expr::Nil),
284
        };
285
498
    }
286

            
287
498
    let elements = const_list_elements(&list_value)?;
288
415
    let saved_loop_var = symbols.lookup(spec.var_name).cloned();
289
664
    for element in elements {
290
664
        symbols.define(Symbol::new(spec.var_name, SymbolKind::Variable).with_value(element));
291
664
        for expr in spec.body {
292
664
            eval_value(symbols, expr)?;
293
        }
294
    }
295
415
    restore_loop_var(symbols, spec.var_name, saved_loop_var);
296

            
297
415
    match spec.result_expr {
298
83
        Some(result) => eval_value(symbols, result),
299
332
        None => Ok(Expr::Nil),
300
    }
301
5478
}