1
//! Function call dispatch.
2
//!
3
//! Three call paths coexist; per ADR-0027 the inline path stays as the
4
//! const-fold fast path and the runtime-call / call_ref paths are
5
//! additive:
6
//!
7
//! - **Inline path** ([`compile_lambda_call`] /
8
//!   [`compile_lambda_call_for_stack`] /
9
//!   [`compile_and_bind_lambda_params`]) — clones the symbol table,
10
//!   binds each arg into the local scope (constants flow through as
11
//!   values; runtime args get an outer-scope local), then walks the
12
//!   body via `compile_expr` / `compile_for_stack`. This is the only
13
//!   path that can const-fold a fully-known argument list down to a
14
//!   single value, and it's the path the test framework, macro
15
//!   expansion, and the commodity-mismatch invariant all sit on.
16
//! - **Runtime-call path** ([`try_compile_runtime_call`]) — fires when
17
//!   the inline walk would diverge (recursion plus a runtime arg). The
18
//!   defun body is emitted once per call-site signature into a
19
//!   monomorph helper fn; each matching call site lowers to
20
//!   `call $monomorph_idx`. See `compiler/special/lambda/monomorph.rs`.
21
//! - **Closure call_ref path** ([`compile_call_ref`]) — fires when the
22
//!   call head resolves to a `WasmLocal` of a `Closure(sig)` value
23
//!   (e.g. a `let`-bound result of `(lambda ...)` or a closure passed
24
//!   through MAP/FOLD). Loads `funcref` + env from the struct and
25
//!   `call_ref`s through the typed signature.
26
//!
27
//! Entry points:
28
//! - [`compile_call`] — effect-position call from `compile_expr`'s list
29
//!   branch. Dispatches on the call head: symbol → `compile_symbol_call`,
30
//!   inline lambda → inline path, list head → recurse after `eval`.
31
//! - [`compile_symbol_call`] (private) — macro-expand if applicable,
32
//!   then dispatch in order: runtime-call → inline → closure call_ref →
33
//!   native → special form.
34
//! - The stack-position mirrors live in [`super::stack`].
35

            
36
use crate::ast::{ClosureSigId, Expr, LambdaParams, WasmType};
37
use crate::compiler::context::CompileContext;
38
use crate::compiler::emit::FunctionEmitter;
39
use crate::compiler::special::lookup_or_emit_monomorph;
40
use crate::error::{Error, Result};
41
use crate::runtime::{Symbol, SymbolKind, SymbolTable};
42

            
43
use super::compile::compile_expr;
44
use super::eval::{call, eval_value, expand_macro_then};
45
use super::stack::{compile_for_stack, compile_for_stack_as};
46

            
47
112339
pub(in crate::compiler) fn compile_call(
48
112339
    ctx: &mut CompileContext,
49
112339
    emit: &mut FunctionEmitter,
50
112339
    symbols: &mut SymbolTable,
51
112339
    elems: &[Expr],
52
112339
) -> Result<()> {
53
112339
    let (head, args) = elems
54
112339
        .split_first()
55
112339
        .ok_or_else(|| Error::Compile("empty function call".to_string()))?;
56
112339
    match head {
57
111183
        Expr::Symbol(name) => compile_symbol_call(ctx, emit, symbols, name, args),
58
544
        Expr::Quote(inner) => match inner.as_ref() {
59
544
            Expr::Symbol(name) => compile_symbol_call(ctx, emit, symbols, name, args),
60
            _ => Err(Error::Compile(format!("not callable: {head:?}"))),
61
        },
62
272
        Expr::Lambda(params, body) => compile_lambda_call(ctx, emit, symbols, params, body, args),
63
340
        Expr::List(inner) => {
64
340
            let resolved = call(symbols, inner)?;
65
340
            match resolved {
66
340
                Expr::Lambda(params, body) => {
67
340
                    compile_lambda_call(ctx, emit, symbols, &params, &body, args)
68
                }
69
                _ => Err(Error::Compile("not callable".to_string())),
70
            }
71
        }
72
        _ => Err(Error::Compile(format!("not callable: {head:?}"))),
73
    }
74
112339
}
75

            
76
111727
fn compile_symbol_call(
77
111727
    ctx: &mut CompileContext,
78
111727
    emit: &mut FunctionEmitter,
79
111727
    symbols: &mut SymbolTable,
80
111727
    name: &str,
81
111727
    args: &[Expr],
82
111727
) -> Result<()> {
83
111387
    let (func, kind, value) = {
84
111727
        let sym = symbols
85
111727
            .lookup(name)
86
111727
            .ok_or_else(|| Error::UndefinedSymbol(name.to_string()))?;
87
111387
        (sym.function().cloned(), sym.kind(), sym.value().cloned())
88
    };
89
111387
    if kind == SymbolKind::Macro
90
5780
        && let Some(Expr::Lambda(params, body)) = func
91
    {
92
5780
        return expand_macro_then(symbols, &params, &body, args, |symbols, code| {
93
5576
            compile_expr(ctx, emit, symbols, &code)
94
5576
        });
95
105607
    }
96
7004
    if let Some(Expr::Lambda(params, body)) = func {
97
7004
        if let Some(ty) = try_compile_runtime_call(ctx, emit, symbols, name, &params, &body, args)?
98
        {
99
204
            return crate::compiler::expr::serialize_stack_to_output(ctx, emit, ty);
100
6800
        }
101
6800
        ctx.push_inlining_frame(name)?;
102
6732
        let result = compile_lambda_call(ctx, emit, symbols, &params, &body, args);
103
6732
        ctx.pop_inlining_frame(name);
104
6732
        return result;
105
98603
    }
106
612
    if let Some(Expr::WasmLocal(idx, WasmType::Closure(sig))) = value {
107
612
        let ty = compile_call_ref(ctx, emit, symbols, idx, sig, args)?;
108
544
        return crate::compiler::expr::serialize_stack_to_output(ctx, emit, ty);
109
97991
    }
110
97991
    match kind {
111
        SymbolKind::Native | SymbolKind::Operator => {
112
28629
            crate::compiler::native::compile(ctx, emit, symbols, name, args)
113
        }
114
        SymbolKind::SpecialForm => {
115
69362
            crate::compiler::special::compile(ctx, emit, symbols, name, args)
116
        }
117
        _ => Err(Error::Compile(format!("symbol '{name}' is not callable"))),
118
    }
119
111727
}
120

            
121
/// Lowers a call against a runtime closure value held in `local idx`.
122
/// Stack discipline mirrors `$fn_<sig>`'s declared signature
123
/// `(env, args...)`: load env from the closure, push each arg, then
124
/// load the funcref and `call_ref`.
125
3196
pub(in crate::compiler) fn compile_call_ref(
126
3196
    ctx: &mut CompileContext,
127
3196
    emit: &mut FunctionEmitter,
128
3196
    symbols: &mut SymbolTable,
129
3196
    closure_idx: u32,
130
3196
    sig: ClosureSigId,
131
3196
    args: &[Expr],
132
3196
) -> Result<WasmType> {
133
3196
    let (closure_type_idx, fn_type_idx, expected_params, result_ty) = {
134
3196
        let entry = ctx.closure_sig(sig);
135
3196
        (
136
3196
            entry.closure_type_idx,
137
3196
            entry.fn_type_idx,
138
3196
            entry.params.clone(),
139
3196
            entry.result,
140
3196
        )
141
3196
    };
142
3196
    if args.len() != expected_params.len() {
143
68
        return Err(Error::Arity {
144
68
            name: "closure".to_string(),
145
68
            expected: expected_params.len(),
146
68
            actual: args.len(),
147
68
        });
148
3128
    }
149
    // Snapshot the closure value before compiling arguments: an argument that
150
    // reassigns the callee local (e.g. `(begin (setf f g) 1)`) must not let the
151
    // env be read from the old closure and the funcref from the new one.
152
3128
    let saved = ctx.alloc_local(WasmType::Closure(sig))?;
153
3128
    emit.local_get(closure_idx);
154
3128
    emit.local_set(saved);
155
3128
    emit.local_get(saved);
156
3128
    emit.struct_get(closure_type_idx, 1);
157
3808
    for (arg, &expected) in args.iter().zip(expected_params.iter()) {
158
        // Coerce each argument to the closure's declared parameter type: a nil
159
        // becomes the typed default, an integer/fractional literal crosses the
160
        // sanctioned Index↔Scalar boundary, and a runtime value must match.
161
3808
        compile_for_stack_as(ctx, emit, symbols, arg, expected).map_err(|_| {
162
            Error::Compile(format!(
163
                "closure argument type mismatch: expected {expected:?}"
164
            ))
165
        })?;
166
    }
167
3128
    emit.local_get(saved);
168
3128
    emit.struct_get(closure_type_idx, 0);
169
3128
    emit.call_ref(fn_type_idx);
170
3128
    Ok(result_ty)
171
3196
}
172

            
173
/// Tier 1.5 Gap B runtime-call dispatch. If the inline const-fold walk
174
/// would diverge — `name` is recursive AND at least one arg resolves to
175
/// a runtime value — we lower the call through a real wasm fn instead.
176
/// The body is emitted once per call-site signature (cached) and each
177
/// matching call site emits `call $monomorph_idx`. Returns `None` to
178
/// signal "stay on the inline path"; `Some(ret_ty)` means the runtime
179
/// stack now holds the call's result.
180
14382
pub(super) fn try_compile_runtime_call(
181
14382
    ctx: &mut CompileContext,
182
14382
    emit: &mut FunctionEmitter,
183
14382
    symbols: &mut SymbolTable,
184
14382
    name: &str,
185
14382
    params: &LambdaParams,
186
14382
    body: &Expr,
187
14382
    args: &[Expr],
188
14382
) -> Result<Option<WasmType>> {
189
14382
    if !needs_runtime_call(ctx, symbols, name, body, args) {
190
12546
        return Ok(None);
191
1836
    }
192
1836
    let arg_types = infer_arg_types(symbols, args)?;
193
1836
    let entry = lookup_or_emit_monomorph(ctx, symbols, name, params, body, &arg_types)?;
194
1836
    emit_runtime_call(ctx, emit, symbols, args, &arg_types, entry.func_idx)?;
195
1836
    Ok(Some(entry.ret_ty))
196
14382
}
197

            
198
14382
fn needs_runtime_call(
199
14382
    ctx: &CompileContext,
200
14382
    symbols: &mut SymbolTable,
201
14382
    self_name: &str,
202
14382
    body: &Expr,
203
14382
    args: &[Expr],
204
14382
) -> bool {
205
15198
    let any_runtime = args.iter().any(|arg| arg_resolves_runtime(symbols, arg));
206
14382
    any_runtime && body_calls_inlining_or_self(ctx, self_name, body)
207
14382
}
208

            
209
15196
fn arg_resolves_runtime(symbols: &mut SymbolTable, arg: &Expr) -> bool {
210
8432
    matches!(
211
15196
        eval_value(symbols, arg),
212
        Ok(Expr::WasmRuntime(_) | Expr::WasmLocal(_, _))
213
    )
214
15196
}
215

            
216
1836
fn infer_arg_types(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Vec<WasmType>> {
217
1836
    args.iter()
218
1972
        .map(|arg| infer_arg_type(symbols, arg))
219
1836
        .collect()
220
1836
}
221

            
222
1972
fn infer_arg_type(symbols: &mut SymbolTable, arg: &Expr) -> Result<WasmType> {
223
1972
    let resolved = eval_value(symbols, arg)?;
224
    // The monomorph parameter slot type is exactly the stack type the arg
225
    // lowers to — the shared classifier keeps the signature, the eval-path
226
    // recursive-call placeholder, and `compile_for_stack` in agreement.
227
1972
    crate::compiler::expr::classify_stack_type(&resolved).ok_or_else(|| {
228
        Error::Compile(format!(
229
            "runtime-call lowering can't classify argument type for {resolved:?}; \
230
             rewrite the call so each argument is a numeric or runtime value"
231
        ))
232
    })
233
1972
}
234

            
235
1836
fn emit_runtime_call(
236
1836
    ctx: &mut CompileContext,
237
1836
    emit: &mut FunctionEmitter,
238
1836
    symbols: &mut SymbolTable,
239
1836
    args: &[Expr],
240
1836
    arg_types: &[WasmType],
241
1836
    func_idx: u32,
242
1836
) -> Result<()> {
243
1972
    for (arg, &expected) in args.iter().zip(arg_types.iter()) {
244
1972
        let actual = compile_for_stack(ctx, emit, symbols, arg)?;
245
1972
        if actual != expected {
246
            return Err(Error::Compile(format!(
247
                "runtime-call argument type mismatch: expected {expected:?}, got {actual:?}"
248
            )));
249
1972
        }
250
    }
251
1836
    emit.call(func_idx);
252
1836
    Ok(())
253
1836
}
254

            
255
144220
fn body_calls_inlining_or_self(ctx: &CompileContext, self_name: &str, expr: &Expr) -> bool {
256
144220
    match expr {
257
55332
        Expr::List(elems) => {
258
55332
            if let Some(Expr::Symbol(name)) = elems.first()
259
49570
                && (name == self_name || ctx.is_inlining(name))
260
            {
261
1836
                return true;
262
53496
            }
263
53496
            elems
264
53496
                .iter()
265
137456
                .any(|e| body_calls_inlining_or_self(ctx, self_name, e))
266
        }
267
        Expr::Quasiquote(inner) | Expr::Unquote(inner) | Expr::UnquoteSplicing(inner) => {
268
            body_calls_inlining_or_self(ctx, self_name, inner)
269
        }
270
        Expr::Cons(car, cdr) => {
271
            body_calls_inlining_or_self(ctx, self_name, car)
272
                || body_calls_inlining_or_self(ctx, self_name, cdr)
273
        }
274
        Expr::Lambda(_, body) => body_calls_inlining_or_self(ctx, self_name, body),
275
88888
        _ => false,
276
    }
277
144220
}
278

            
279
7344
fn compile_lambda_call(
280
7344
    ctx: &mut CompileContext,
281
7344
    emit: &mut FunctionEmitter,
282
7344
    symbols: &mut SymbolTable,
283
7344
    params: &LambdaParams,
284
7344
    body: &Expr,
285
7344
    args: &[Expr],
286
7344
) -> Result<()> {
287
7344
    let mut local = compile_and_bind_lambda_params(ctx, emit, symbols, params, args)?;
288
7276
    compile_expr(ctx, emit, &mut local, body)
289
7344
}
290

            
291
9010
pub(in crate::compiler) fn compile_lambda_call_for_stack(
292
9010
    ctx: &mut CompileContext,
293
9010
    emit: &mut FunctionEmitter,
294
9010
    symbols: &mut SymbolTable,
295
9010
    params: &LambdaParams,
296
9010
    body: &Expr,
297
9010
    args: &[Expr],
298
9010
) -> Result<WasmType> {
299
9010
    let mut local = compile_and_bind_lambda_params(ctx, emit, symbols, params, args)?;
300
8942
    compile_for_stack(ctx, emit, &mut local, body)
301
9010
}
302

            
303
/// Codegen-aware analog of the eval-only lambda-param binder. For
304
/// each required / optional / rest / key / aux parameter whose
305
/// argument resolves to a runtime value (host fn call result, prior
306
/// `WasmLocal`, etc.), this emits the wasm to compute the value once,
307
/// stashes it in a fresh local, and binds the parameter symbol to
308
/// `Expr::WasmLocal(idx, ty)` so subsequent body references emit
309
/// `local.get N`. Constant args (`Number` / `Bool` / `Nil` / `String`
310
/// / `Bytes` / `Quote(_)` / etc) bind directly to the resolved value
311
/// — no wasm emitted, body continues to const-fold against the
312
/// value.
313
///
314
/// The runtime/constant split is what lets the inline path remain the
315
/// const-fold fast path: a defun whose entire arg list resolves at
316
/// compile time walks the body without emitting a single wasm
317
/// instruction; introducing one runtime arg promotes only that arg
318
/// into a local, leaving the rest to fold.
319
16354
pub(in crate::compiler) fn compile_and_bind_lambda_params(
320
16354
    ctx: &mut CompileContext,
321
16354
    emit: &mut FunctionEmitter,
322
16354
    symbols: &mut SymbolTable,
323
16354
    params: &LambdaParams,
324
16354
    args: &[Expr],
325
16354
) -> Result<SymbolTable> {
326
16354
    let min_args = params.required.len();
327
16354
    let max_args = if params.rest.is_some() || !params.key.is_empty() {
328
136
        None
329
    } else {
330
16218
        Some(min_args + params.optional.len())
331
    };
332

            
333
16354
    if args.len() < min_args {
334
136
        return Err(Error::Arity {
335
136
            name: "lambda".to_string(),
336
136
            expected: min_args,
337
136
            actual: args.len(),
338
136
        });
339
16218
    }
340
16218
    if let Some(max) = max_args
341
16082
        && args.len() > max
342
    {
343
        return Err(Error::Arity {
344
            name: "lambda".to_string(),
345
            expected: max,
346
            actual: args.len(),
347
        });
348
16218
    }
349

            
350
16218
    let mut local = symbols.clone();
351
16218
    let mut arg_idx = 0;
352

            
353
21270
    for param in &params.required {
354
21270
        let bound = compile_arg_for_param(ctx, emit, symbols, &args[arg_idx])?;
355
21270
        local.define(Symbol::new(param, SymbolKind::Variable).with_value(bound));
356
21270
        arg_idx += 1;
357
    }
358

            
359
16218
    for (param, default) in &params.optional {
360
68
        let bound = if arg_idx < args.len() {
361
            let v = compile_arg_for_param(ctx, emit, symbols, &args[arg_idx])?;
362
            arg_idx += 1;
363
            v
364
68
        } else if let Some(default_expr) = default {
365
68
            eval_value(symbols, default_expr)?
366
        } else {
367
            Expr::Nil
368
        };
369
68
        local.define(Symbol::new(param, SymbolKind::Variable).with_value(bound));
370
    }
371

            
372
16218
    if let Some(rest_param) = &params.rest {
373
68
        let rest_args: Vec<Expr> = args[arg_idx..]
374
68
            .iter()
375
204
            .map(|arg| eval_value(symbols, arg))
376
68
            .collect::<Result<_>>()?;
377
68
        let rest_list = if rest_args.is_empty() {
378
            Expr::Nil
379
        } else {
380
68
            Expr::List(rest_args)
381
        };
382
68
        local.define(Symbol::new(rest_param, SymbolKind::Variable).with_value(rest_list));
383
16150
    }
384

            
385
16218
    if !params.key.is_empty() {
386
68
        let remaining_args = &args[arg_idx..];
387
68
        for (param, default) in &params.key {
388
68
            let keyword = Expr::Keyword(param.to_uppercase());
389
68
            let mut found_value = None;
390

            
391
68
            if remaining_args.len() >= 2 {
392
68
                for i in (0..remaining_args.len() - 1).step_by(2) {
393
68
                    if remaining_args[i] == keyword {
394
68
                        found_value = Some(eval_value(symbols, &remaining_args[i + 1])?);
395
68
                        break;
396
                    }
397
                }
398
            }
399

            
400
68
            let value = if let Some(val) = found_value {
401
68
                val
402
            } else if let Some(default_expr) = default {
403
                eval_value(symbols, default_expr)?
404
            } else {
405
                Expr::Nil
406
            };
407

            
408
68
            local.define(Symbol::new(param, SymbolKind::Variable).with_value(value));
409
        }
410
16150
    }
411

            
412
16218
    for (param, init) in &params.aux {
413
        let value = if let Some(init_expr) = init {
414
            eval_value(&mut local, init_expr)?
415
        } else {
416
            Expr::Nil
417
        };
418
        local.define(Symbol::new(param, SymbolKind::Variable).with_value(value));
419
    }
420

            
421
16218
    Ok(local)
422
16354
}
423

            
424
/// Per-argument binding helper. Runtime values (`Expr::WasmRuntime` /
425
/// `Expr::WasmLocal`) get emitted onto the stack and stashed in a fresh
426
/// local; everything else (constants, quoted forms, lambdas) binds
427
/// directly to the resolved value.
428
21270
fn compile_arg_for_param(
429
21270
    ctx: &mut CompileContext,
430
21270
    emit: &mut FunctionEmitter,
431
21270
    symbols: &mut SymbolTable,
432
21270
    arg: &Expr,
433
21270
) -> Result<Expr> {
434
21270
    let resolved = eval_value(symbols, arg)?;
435
21270
    match &resolved {
436
544
        Expr::WasmRuntime(ty) => {
437
544
            compile_for_stack(ctx, emit, symbols, arg)?;
438
544
            let idx = ctx.alloc_local(*ty)?;
439
544
            emit.local_set(idx);
440
544
            Ok(Expr::WasmLocal(idx, *ty))
441
        }
442
        // Already a local — reusing the index keeps the body's
443
        // local.get emissions pointed at the original storage.
444
8468
        Expr::WasmLocal(_, _) => Ok(resolved),
445
12258
        _ => Ok(resolved),
446
    }
447
21270
}