1
#[cfg(test)]
2
mod capture_free_tests;
3
mod captures;
4
mod emit;
5
pub(in crate::compiler) mod monomorph;
6
mod param_infer;
7

            
8
use crate::ast::Expr;
9
use crate::error::{Error, Result};
10
use crate::runtime::{SymbolKind, SymbolTable};
11

            
12
use super::super::context::CompileContext;
13
use super::super::emit::FunctionEmitter;
14
use crate::ast::WasmType;
15

            
16
use super::super::expr::{
17
    compile_call, compile_call_for_stack, compile_string, eval_value, format_expr,
18
    serialize_stack_to_output,
19
};
20
use super::SpecialFormSpec;
21
use super::binding::parse_lambda_params;
22
use crate::ast::LambdaParams;
23

            
24
pub(super) const FORMS: &[SpecialFormSpec] = &[
25
    SpecialFormSpec {
26
        name: "LAMBDA",
27
        eval: eval_lambda,
28
        compile: spec_compile_lambda,
29
        stack: None,
30
        effect: None,
31
    },
32
    SpecialFormSpec {
33
        name: "FUNCTION",
34
        eval: function_form,
35
        compile: compile_function_form,
36
        stack: None,
37
        effect: None,
38
    },
39
    SpecialFormSpec {
40
        name: "FUNCALL",
41
        eval: funcall,
42
        compile: compile_funcall,
43
        stack: Some(compile_funcall_for_stack),
44
        effect: None,
45
    },
46
    SpecialFormSpec {
47
        name: "APPLY",
48
        eval: apply,
49
        compile: compile_apply_form,
50
        stack: Some(compile_apply_form_for_stack),
51
        effect: None,
52
    },
53
];
54

            
55
21580
fn eval_lambda(_symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
56
21580
    lambda(args)
57
21580
}
58

            
59
664
fn spec_compile_lambda(
60
664
    ctx: &mut CompileContext,
61
664
    emit: &mut FunctionEmitter,
62
664
    symbols: &mut SymbolTable,
63
664
    args: &[Expr],
64
664
) -> Result<()> {
65
664
    compile_lambda_form(ctx, emit, symbols, args)
66
664
}
67

            
68
664
pub(super) fn compile_lambda_form(
69
664
    ctx: &mut CompileContext,
70
664
    emit: &mut FunctionEmitter,
71
664
    symbols: &SymbolTable,
72
664
    args: &[Expr],
73
664
) -> Result<()> {
74
664
    let result = lambda(args)?;
75
498
    emit_closure_or_stringify(ctx, emit, symbols, &result)
76
664
}
77

            
78
332
pub(super) fn compile_function_form(
79
332
    ctx: &mut CompileContext,
80
332
    emit: &mut FunctionEmitter,
81
332
    symbols: &mut SymbolTable,
82
332
    args: &[Expr],
83
332
) -> Result<()> {
84
332
    let result = function_form(symbols, args)?;
85
166
    emit_closure_or_stringify(ctx, emit, symbols, &result)
86
332
}
87

            
88
/// Lower a value-position lambda/function expression. If the result is
89
/// a `Lambda` form that fits the Tier 1.5 v1 emit slice, emit it as a
90
/// real wasm closure value and serialize via the closure debug writer.
91
/// Otherwise (out-of-scope params/captures, or non-`Lambda` body returned
92
/// by `function_form`) fall back to stringification so the existing
93
/// inline path keeps working.
94
664
fn emit_closure_or_stringify(
95
664
    ctx: &mut CompileContext,
96
664
    emit: &mut FunctionEmitter,
97
664
    symbols: &SymbolTable,
98
664
    result: &Expr,
99
664
) -> Result<()> {
100
664
    if let Expr::Lambda(params, body) = result
101
664
        && let Some(sig) = try_emit_lambda_for_value(ctx, emit, symbols, params, body)?
102
    {
103
498
        return serialize_stack_to_output(ctx, emit, crate::ast::WasmType::Closure(sig));
104
166
    }
105
166
    compile_string(ctx, emit, &format_expr(result))
106
664
}
107

            
108
/// Stack-position emit: if `params`/`body` fit the Tier 1.5 v1 slice
109
/// the closure value is constructed on the wasm stack and the
110
/// `ClosureSigId` returned. The caller decides whether to serialize it
111
/// (value position via `compile_expr`) or leave it on the stack
112
/// (stack position via `compile_for_stack`). Returns `Ok(None)` when
113
/// the lambda is out of v1 scope so the caller can fall back.
114
/// Whether `body` is safe to INLINE at a different site (a HOF call): it must
115
/// reference NO free outer variable. A captured free var — whether a runtime
116
/// `WasmLocal` OR a compile-time CONST (`(let ((x 1)) (lambda … x) …)`) — would
117
/// resolve to its CREATION-site value through the closure's env / const fold,
118
/// but an inline at the call site resolves it THERE (possibly shadowed or a
119
/// different const), producing a wrong value. So the check is stricter than
120
/// `compute_captures` (which only tracks WasmLocal captures for env building):
121
/// any free symbol that resolves to a `Variable` (let-/param-bound) OR carries a
122
/// `function` binding (a user `defun` / redefined builtin) makes the body
123
/// inline-unsafe — both can resolve differently at a later inline site. Bare
124
/// operators / natives (no `function`) and special forms resolve identically
125
/// everywhere and are fine. Re-running this at the inline site catches a global
126
/// the body uses that an inner binder has since shadowed or `defun`-redefined.
127
/// Conservative: a body that merely shadows an outer name with its own inner
128
/// binder, or calls a stable user function, may be rejected, costing precision
129
/// (not correctness).
130
///
131
/// Only a SIMPLE (required-params-only) lambda is an inline candidate — an
132
/// optional / key / aux default expr or a `&rest` binder would each need its
133
/// own capture analysis, so a lambda carrying any stays on the `call_ref` path.
134
4324
pub(in crate::compiler) fn is_capture_free(
135
4324
    symbols: &SymbolTable,
136
4324
    params: &LambdaParams,
137
4324
    body: &Expr,
138
4324
) -> bool {
139
4324
    if !params.optional.is_empty()
140
4323
        || params.rest.is_some()
141
4323
        || !params.key.is_empty()
142
4323
        || !params.aux.is_empty()
143
    {
144
1
        return false;
145
4323
    }
146
4323
    references_no_free_variable(symbols, &params.required, body)
147
4324
}
148

            
149
/// `bound` are names lexically bound by the closure (its required params) — a
150
/// reference to one is never a creation-site capture. The walk is conservative
151
/// for nested binders: it does NOT extend `bound` with an inner lambda's params,
152
/// so a shadowing inner name that collides with a creation-site `Variable` is
153
/// rejected (precision, not soundness).
154
17871
fn references_no_free_variable(symbols: &SymbolTable, bound: &[String], expr: &Expr) -> bool {
155
4737
    match expr {
156
        // A bound param is never a capture. A free symbol is inline-safe only if
157
        // it resolves to a STABLE global: not a `Variable` (a let/param binding
158
        // an inner scope could shadow) AND carrying no `function` binding. A
159
        // symbol with a `function` is a user `defun` — or a builtin operator a
160
        // `defun` has REDEFINED in place (kind stays `Operator`, `function` set).
161
        // Either way its meaning can differ between the closure's creation scope
162
        // and a later inline site, so reject it; the closure keeps `call_ref`,
163
        // which invokes the value compiled at creation. Bare operators / natives
164
        // (no `function`) resolve identically everywhere and stay inlinable.
165
10470
        Expr::Symbol(name) => {
166
13790
            bound.iter().any(|p| p == name)
167
5239
                || (!resolves_to_variable(symbols, name) && !has_function_binding(symbols, name))
168
        }
169
        // QUOTE's argument is data, not evaluated — it captures nothing.
170
1
        Expr::Quote(_) => true,
171
4737
        Expr::List(elems) if matches!(elems.first(), Some(Expr::Symbol(h)) if h.eq_ignore_ascii_case("quote")) => {
172
            true
173
        }
174
        // A macro call is opaque pre-expansion: it can expand to a creation-scope
175
        // variable, which an inline would re-expand (and re-resolve) at the call
176
        // site. Conservatively treat any macro-headed form as a capture.
177
4737
        Expr::List(elems) if matches!(elems.first(), Some(Expr::Symbol(h)) if resolves_to_macro(symbols, h)) => {
178
1
            false
179
        }
180
        // QUASIQUOTE evaluates its UNQUOTE subforms against the surrounding
181
        // scope, so it can capture; walk inner conservatively (template symbols
182
        // count as potential captures — precision cost, never a soundness hole).
183
2
        Expr::Quasiquote(inner) | Expr::Unquote(inner) | Expr::UnquoteSplicing(inner) => {
184
4
            references_no_free_variable(symbols, bound, inner)
185
        }
186
        // A nested lambda's body (and any param default) still evaluates against
187
        // the creation scope when the outer body runs, so it must be walked too;
188
        // never short-circuit it to capture-free.
189
1
        Expr::Lambda(inner_params, inner_body) => {
190
1
            param_default_exprs(inner_params)
191
1
                .all(|d| references_no_free_variable(symbols, bound, d))
192
1
                && references_no_free_variable(symbols, bound, inner_body)
193
        }
194
4736
        Expr::List(elems) => elems
195
4736
            .iter()
196
13542
            .all(|e| references_no_free_variable(symbols, bound, e)),
197
        // A dotted pair can hide an unquoted free symbol (`(,x . nil)`); both
198
        // arms evaluate when the surrounding quasiquote expands, so walk both.
199
1
        Expr::Cons(car, cdr) => {
200
1
            references_no_free_variable(symbols, bound, car)
201
                && references_no_free_variable(symbols, bound, cdr)
202
        }
203
2657
        _ => true,
204
    }
205
17871
}
206

            
207
/// The default-value expressions carried by a lambda's optional / key / aux
208
/// params (each evaluated at closure creation, so each can capture).
209
1
fn param_default_exprs(params: &LambdaParams) -> impl Iterator<Item = &Expr> {
210
1
    params
211
1
        .optional
212
1
        .iter()
213
1
        .chain(&params.key)
214
1
        .chain(&params.aux)
215
1
        .filter_map(|(_, default)| default.as_ref())
216
1
}
217

            
218
5239
fn resolves_to_variable(symbols: &SymbolTable, name: &str) -> bool {
219
4571
    matches!(
220
5239
        symbols.lookup(name).map(|sym| sym.kind()),
221
        Some(SymbolKind::Variable)
222
    )
223
5239
}
224

            
225
4737
fn resolves_to_macro(symbols: &SymbolTable, name: &str) -> bool {
226
4736
    matches!(
227
4737
        symbols.lookup(name).map(|sym| sym.kind()),
228
        Some(SymbolKind::Macro)
229
    )
230
4737
}
231

            
232
/// Whether `name` resolves to a symbol carrying a `function` body — a user
233
/// `defun` (or a builtin a `defun` redefined in place), whose meaning can change
234
/// between a closure's creation scope and a later inline site.
235
4571
fn has_function_binding(symbols: &SymbolTable, name: &str) -> bool {
236
4571
    symbols
237
4571
        .lookup(name)
238
4571
        .is_some_and(|sym| sym.function().is_some())
239
4571
}
240

            
241
4233
pub(in crate::compiler) fn try_emit_lambda_for_value(
242
4233
    ctx: &mut CompileContext,
243
4233
    emit: &mut FunctionEmitter,
244
4233
    symbols: &SymbolTable,
245
4233
    params: &LambdaParams,
246
4233
    body: &Expr,
247
4233
) -> Result<Option<crate::ast::ClosureSigId>> {
248
4233
    if !emit::is_v1_eligible(symbols, params, body) {
249
166
        return Ok(None);
250
4067
    }
251
4067
    let sig = emit::emit_lambda_value(ctx, emit, symbols, params, body)?;
252
4067
    Ok(Some(sig))
253
4233
}
254

            
255
/// Variant for host-iteration call sites (CATCH-EACH and similar). The
256
/// host fn passes per-element items as raw anyref, so the closure body
257
/// fn is wired with anyref params and downcasts to the declared user
258
/// types in its prologue. The resulting `ClosureSigId` describes the
259
/// wire-level signature `(anyref...) -> anyref` — the caller emits the
260
/// closure value, then funcref+env are extracted and handed to the
261
/// host fn for per-element invocation.
262
1577
pub(in crate::compiler) fn try_emit_lambda_for_host_iter(
263
1577
    ctx: &mut CompileContext,
264
1577
    emit: &mut FunctionEmitter,
265
1577
    symbols: &SymbolTable,
266
1577
    params: &LambdaParams,
267
1577
    body: &Expr,
268
1577
    user_param_types: &[WasmType],
269
1577
) -> Result<Option<crate::ast::ClosureSigId>> {
270
1577
    if !emit::is_v1_eligible(symbols, params, body) {
271
        return Ok(None);
272
1577
    }
273
1577
    let sig = emit::emit_lambda_value_typed(
274
1577
        ctx,
275
1577
        emit,
276
1577
        symbols,
277
1577
        params,
278
1577
        body,
279
1577
        user_param_types,
280
1577
        emit::CallingConvention::HostAnyref,
281
581
    )?;
282
996
    Ok(Some(sig))
283
1577
}
284

            
285
1328
pub(super) fn compile_funcall(
286
1328
    ctx: &mut CompileContext,
287
1328
    emit: &mut FunctionEmitter,
288
1328
    symbols: &mut SymbolTable,
289
1328
    args: &[Expr],
290
1328
) -> Result<()> {
291
1328
    if args.is_empty() {
292
83
        return Err(Error::Compile(
293
83
            "funcall requires at least a function argument".to_string(),
294
83
        ));
295
1245
    }
296
1245
    compile_call(ctx, emit, symbols, args)
297
1328
}
298

            
299
9296
pub(super) fn compile_funcall_for_stack(
300
9296
    ctx: &mut CompileContext,
301
9296
    emit: &mut FunctionEmitter,
302
9296
    symbols: &mut SymbolTable,
303
9296
    args: &[Expr],
304
9296
) -> Result<WasmType> {
305
9296
    if args.is_empty() {
306
        return Err(Error::Compile(
307
            "funcall requires at least a function argument".to_string(),
308
        ));
309
9296
    }
310
9296
    compile_call_for_stack(ctx, emit, symbols, args)
311
9296
}
312

            
313
996
pub(super) fn compile_apply_form(
314
996
    ctx: &mut CompileContext,
315
996
    emit: &mut FunctionEmitter,
316
996
    symbols: &mut SymbolTable,
317
996
    args: &[Expr],
318
996
) -> Result<()> {
319
996
    let all_args = build_apply_call(symbols, args)?;
320
498
    compile_call(ctx, emit, symbols, &all_args)
321
996
}
322

            
323
pub(super) fn compile_apply_form_for_stack(
324
    ctx: &mut CompileContext,
325
    emit: &mut FunctionEmitter,
326
    symbols: &mut SymbolTable,
327
    args: &[Expr],
328
) -> Result<WasmType> {
329
    let all_args = build_apply_call(symbols, args)?;
330
    compile_call_for_stack(ctx, emit, symbols, &all_args)
331
}
332

            
333
1079
fn build_apply_call(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Vec<Expr>> {
334
1079
    if args.len() < 2 {
335
166
        return Err(Error::Compile(
336
166
            "apply requires at least a function and one argument".to_string(),
337
166
        ));
338
913
    }
339
913
    let leading = &args[..args.len() - 1];
340
    // Evaluate the designator + leading args BEFORE deciding the last-arg
341
    // spread: a leading arg can rebind `LIST` (left-to-right eval order), and
342
    // `spread_apply_last_arg`'s builtin-`LIST` check must see that rebinding.
343
913
    let mut all_args: Vec<Expr> = Vec::with_capacity(leading.len() + 1);
344
913
    all_args.push(resolve_apply_designator(symbols, &leading[0])?);
345
913
    for arg in &leading[1..] {
346
249
        all_args.push(eval_value(symbols, arg)?);
347
    }
348
913
    all_args.extend(spread_apply_last_arg(symbols, &args[args.len() - 1])?);
349
581
    Ok(all_args)
350
1079
}
351

            
352
/// Resolve APPLY's last argument into the list of argument expressions to
353
/// spread. A syntactic `(LIST e1 e2 …)` spreads its element EXPRESSIONS
354
/// directly (`(apply f (list a b c))` ≡ `(f a b c)`) — this is the only form
355
/// that carries genuinely runtime elements, since a runtime `(list …)` no
356
/// longer eval-folds to a quoted list (it builds a `$pair` chain). A quoted
357
/// literal list spreads its constant elements; `nil` spreads nothing.
358
/// Anything else (a bare runtime pair, a non-list value) is an error: APPLY
359
/// can't statically know a runtime list's arity.
360
913
fn spread_apply_last_arg(symbols: &mut SymbolTable, arg: &Expr) -> Result<Vec<Expr>> {
361
913
    if let Expr::List(elems) = arg
362
498
        && let Some(Expr::Symbol(head)) = elems.first()
363
498
        && head.eq_ignore_ascii_case("LIST")
364
498
        && resolves_to_native_list(symbols, head)
365
    {
366
332
        return Ok(elems[1..].to_vec());
367
581
    }
368
581
    match eval_value(symbols, arg)? {
369
249
        Expr::Quote(inner) => match *inner {
370
166
            Expr::List(elems) => Ok(elems),
371
            Expr::Nil => Ok(vec![]),
372
83
            _ => Err(apply_last_arg_error()),
373
        },
374
83
        Expr::Nil => Ok(vec![]),
375
249
        _ => Err(apply_last_arg_error()),
376
    }
377
913
}
378

            
379
/// Whether `head` still names the builtin `LIST` constructor (not a user
380
/// `(defun list …)` / `(let ((list …)))` that shadows it). The call dispatcher
381
/// gives a user `function()` binding priority over the native, so the APPLY
382
/// fast-path must only fire when no such shadow exists — otherwise it would
383
/// spread element expressions while the program meant to call the user's
384
/// `list`.
385
498
fn resolves_to_native_list(symbols: &SymbolTable, head: &str) -> bool {
386
498
    match symbols.lookup(head) {
387
498
        Some(sym) => sym.function().is_none() && sym.value().is_none(),
388
        None => false,
389
    }
390
498
}
391

            
392
332
fn apply_last_arg_error() -> Error {
393
332
    Error::Compile(
394
332
        "apply: last argument must be a literal list — `(list …)`, a quoted \
395
332
         list, or nil (a runtime list's arity isn't known at compile time)"
396
332
            .to_string(),
397
332
    )
398
332
}
399

            
400
22410
pub(super) fn lambda(args: &[Expr]) -> Result<Expr> {
401
22410
    if args.len() < 2 {
402
166
        return Err(Error::Compile(
403
166
            "LAMBDA requires a parameter list and body".to_string(),
404
166
        ));
405
22244
    }
406
22244
    let params = parse_lambda_params("LAMBDA", &args[0])?;
407
22244
    let body = if args.len() == 2 {
408
21663
        args[1].clone()
409
    } else {
410
581
        let mut forms = Vec::with_capacity(args.len());
411
581
        forms.push(Expr::Symbol("BEGIN".to_string()));
412
581
        forms.extend_from_slice(&args[1..]);
413
581
        Expr::List(forms)
414
    };
415
22244
    Ok(Expr::Lambda(params, Box::new(body)))
416
22410
}
417

            
418
3735
pub(super) fn function_form(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
419
3735
    if args.len() != 1 {
420
        return Err(Error::Arity {
421
            name: "FUNCTION".to_string(),
422
            expected: 1,
423
            actual: args.len(),
424
        });
425
3735
    }
426
3735
    match &args[0] {
427
3486
        Expr::Symbol(name) => {
428
3486
            let func = symbols
429
3486
                .lookup(name)
430
3486
                .and_then(|s| s.function().cloned())
431
3486
                .ok_or_else(|| Error::Compile(format!("'{name}' has no function definition")))?;
432
3403
            Ok(func)
433
        }
434
166
        Expr::List(elems) if matches!(elems.first(), Some(Expr::Symbol(s)) if s == "LAMBDA") => {
435
166
            lambda(&elems[1..])
436
        }
437
83
        _ => Err(Error::Compile(
438
83
            "FUNCTION: argument must be a symbol or lambda expression".to_string(),
439
83
        )),
440
    }
441
3735
}
442

            
443
2573
pub(super) fn funcall(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
444
2573
    if args.is_empty() {
445
        return Err(Error::Compile(
446
            "funcall requires at least a function argument".to_string(),
447
        ));
448
2573
    }
449
2573
    tracing::debug!(args = args.len(), "compiling funcall");
450
2573
    super::super::expr::call(symbols, args)
451
2573
}
452

            
453
83
pub(super) fn apply(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
454
83
    let all_args = build_apply_call(symbols, args)?;
455
83
    super::super::expr::call(symbols, &all_args)
456
83
}
457

            
458
913
pub(super) fn resolve_apply_designator(symbols: &mut SymbolTable, arg: &Expr) -> Result<Expr> {
459
913
    match arg {
460
166
        Expr::Symbol(name) => {
461
166
            let sym = symbols
462
166
                .lookup(name)
463
166
                .ok_or_else(|| Error::UndefinedSymbol(name.clone()))?;
464
166
            if sym.value().is_some() {
465
                eval_value(symbols, arg)
466
            } else {
467
166
                Ok(Expr::Symbol(name.clone()))
468
            }
469
        }
470
747
        _ => eval_value(symbols, arg),
471
    }
472
913
}