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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
297
340
    match spec.result_expr {
298
68
        Some(result) => eval_value(symbols, result),
299
272
        None => Ok(Expr::Nil),
300
    }
301
4516
}