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

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

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

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

            
63
7739
    Ok(DolistSpec {
64
7739
        var_name,
65
7739
        list_expr: &var_spec[1],
66
7739
        result_expr: var_spec.get(2),
67
7739
        body: &args[1..],
68
7739
    })
69
8165
}
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
1136
fn const_list_elements(list_value: &Expr) -> Result<Vec<Expr>> {
74
1136
    let to_list = |inner: &Expr| match inner {
75
994
        Expr::List(elems) => Ok(elems.clone()),
76
71
        Expr::Nil => Ok(vec![]),
77
71
        _ => Err(Error::Compile(
78
71
            "DOLIST: list expression must evaluate to a list".to_string(),
79
71
        )),
80
1136
    };
81
1136
    match list_value {
82
994
        Expr::Quote(inner) => to_list(inner),
83
142
        other => to_list(other),
84
    }
85
1136
}
86

            
87
3692
fn restore_loop_var(symbols: &mut SymbolTable, var_name: &str, saved: Option<Symbol>) {
88
3692
    match saved {
89
        Some(s) => symbols.define(s),
90
3692
        None => {
91
3692
            symbols.remove(var_name);
92
3692
        }
93
    }
94
3692
}
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
2627
fn emit_dolist_runtime_loop(
101
2627
    ctx: &mut CompileContext,
102
2627
    emit: &mut FunctionEmitter,
103
2627
    symbols: &mut SymbolTable,
104
2627
    var_name: &str,
105
2627
    list_expr: &Expr,
106
2627
    body: &[Expr],
107
2627
    elem: PairElement,
108
2627
) -> 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
2627
    let pair_local = ctx.alloc_local(WasmType::PairRef(elem))?;
114
2627
    let saved_loop_var = symbols.lookup(var_name).cloned();
115
2627
    let var_ty = elem.as_wasm_type();
116
2627
    let var_local = ctx.alloc_local(var_ty)?;
117
2627
    symbols.define(
118
2627
        Symbol::new(var_name, SymbolKind::Variable).with_value(Expr::WasmLocal(var_local, var_ty)),
119
    );
120

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

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

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

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

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

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

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

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

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

            
157
2627
    restore_loop_var(symbols, var_name, saved_loop_var);
158
2627
    Ok(())
159
2627
}
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
710
fn emit_dolist_const_loop(
165
710
    ctx: &mut CompileContext,
166
710
    emit: &mut FunctionEmitter,
167
710
    symbols: &mut SymbolTable,
168
710
    var_name: &str,
169
710
    elements: Vec<Expr>,
170
710
    body: &[Expr],
171
710
) -> Result<()> {
172
710
    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
710
    for (target, rhs) in collect_setf_targets(body) {
180
568
        if target != var_name {
181
568
            promote_to_wasm_local(ctx, emit, symbols, &target, rhs.as_ref())?;
182
        }
183
    }
184

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

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

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

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

            
246
781
pub(super) fn compile_dolist_for_stack(
247
781
    ctx: &mut CompileContext,
248
781
    emit: &mut FunctionEmitter,
249
781
    symbols: &mut SymbolTable,
250
781
    args: &[Expr],
251
781
) -> Result<WasmType> {
252
781
    let spec = parse_dolist(args)?;
253
781
    emit_dolist_loop(ctx, emit, symbols, &spec)?;
254
781
    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
781
            emit.i32_const(0);
261
781
            Ok(WasmType::Bool)
262
        }
263
    }
264
781
}
265

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

            
270
4402
    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
3976
        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
3976
        return match spec.result_expr {
282
            Some(result) => eval_value(symbols, result),
283
3976
            None => Ok(Expr::Nil),
284
        };
285
426
    }
286

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

            
297
355
    match spec.result_expr {
298
71
        Some(result) => eval_value(symbols, result),
299
284
        None => Ok(Expr::Nil),
300
    }
301
4686
}