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
117295
pub(in crate::compiler) fn compile_call(
48
117295
    ctx: &mut CompileContext,
49
117295
    emit: &mut FunctionEmitter,
50
117295
    symbols: &mut SymbolTable,
51
117295
    elems: &[Expr],
52
117295
) -> Result<()> {
53
117295
    let (head, args) = elems
54
117295
        .split_first()
55
117295
        .ok_or_else(|| Error::Compile("empty function call".to_string()))?;
56
117295
    match head {
57
116088
        Expr::Symbol(name) => compile_symbol_call(ctx, emit, symbols, name, args),
58
568
        Expr::Quote(inner) => match inner.as_ref() {
59
568
            Expr::Symbol(name) => compile_symbol_call(ctx, emit, symbols, name, args),
60
            _ => Err(Error::Compile(format!("not callable: {head:?}"))),
61
        },
62
284
        Expr::Lambda(params, body) => compile_lambda_call(ctx, emit, symbols, params, body, args),
63
355
        Expr::List(inner) => {
64
355
            let resolved = call(symbols, inner)?;
65
355
            match resolved {
66
355
                Expr::Lambda(params, body) => {
67
355
                    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
117295
}
75

            
76
116656
fn compile_symbol_call(
77
116656
    ctx: &mut CompileContext,
78
116656
    emit: &mut FunctionEmitter,
79
116656
    symbols: &mut SymbolTable,
80
116656
    name: &str,
81
116656
    args: &[Expr],
82
116656
) -> Result<()> {
83
116301
    let (func, kind, value) = {
84
116656
        let sym = symbols
85
116656
            .lookup(name)
86
116656
            .ok_or_else(|| Error::UndefinedSymbol(name.to_string()))?;
87
116301
        (sym.function().cloned(), sym.kind(), sym.value().cloned())
88
    };
89
116301
    if kind == SymbolKind::Macro
90
6035
        && let Some(Expr::Lambda(params, body)) = func
91
    {
92
6035
        return expand_macro_then(symbols, &params, &body, args, |symbols, code| {
93
5822
            compile_expr(ctx, emit, symbols, &code)
94
5822
        });
95
110266
    }
96
7313
    if let Some(Expr::Lambda(params, body)) = func {
97
7313
        if let Some(ty) = try_compile_runtime_call(ctx, emit, symbols, name, &params, &body, args)?
98
        {
99
213
            return crate::compiler::expr::serialize_stack_to_output(ctx, emit, ty);
100
7100
        }
101
7100
        ctx.push_inlining_frame(name)?;
102
7029
        let result = compile_lambda_call(ctx, emit, symbols, &params, &body, args);
103
7029
        ctx.pop_inlining_frame(name);
104
7029
        return result;
105
102953
    }
106
639
    if let Some(Expr::WasmLocal(idx, WasmType::Closure(sig))) = value {
107
639
        let ty = compile_call_ref(ctx, emit, symbols, idx, sig, args)?;
108
568
        return crate::compiler::expr::serialize_stack_to_output(ctx, emit, ty);
109
102314
    }
110
102314
    match kind {
111
        SymbolKind::Native | SymbolKind::Operator => {
112
29892
            crate::compiler::native::compile(ctx, emit, symbols, name, args)
113
        }
114
        SymbolKind::SpecialForm => {
115
72422
            crate::compiler::special::compile(ctx, emit, symbols, name, args)
116
        }
117
        _ => Err(Error::Compile(format!("symbol '{name}' is not callable"))),
118
    }
119
116656
}
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
3337
pub(in crate::compiler) fn compile_call_ref(
126
3337
    ctx: &mut CompileContext,
127
3337
    emit: &mut FunctionEmitter,
128
3337
    symbols: &mut SymbolTable,
129
3337
    closure_idx: u32,
130
3337
    sig: ClosureSigId,
131
3337
    args: &[Expr],
132
3337
) -> Result<WasmType> {
133
3337
    let (closure_type_idx, fn_type_idx, expected_params, result_ty) = {
134
3337
        let entry = ctx.closure_sig(sig);
135
3337
        (
136
3337
            entry.closure_type_idx,
137
3337
            entry.fn_type_idx,
138
3337
            entry.params.clone(),
139
3337
            entry.result,
140
3337
        )
141
3337
    };
142
3337
    if args.len() != expected_params.len() {
143
71
        return Err(Error::Arity {
144
71
            name: "closure".to_string(),
145
71
            expected: expected_params.len(),
146
71
            actual: args.len(),
147
71
        });
148
3266
    }
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
3266
    let saved = ctx.alloc_local(WasmType::Closure(sig))?;
153
3266
    emit.local_get(closure_idx);
154
3266
    emit.local_set(saved);
155
3266
    emit.local_get(saved);
156
3266
    emit.struct_get(closure_type_idx, 1);
157
3976
    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
3976
        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
3266
    emit.local_get(saved);
168
3266
    emit.struct_get(closure_type_idx, 0);
169
3266
    emit.call_ref(fn_type_idx);
170
3266
    Ok(result_ty)
171
3337
}
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
14981
pub(super) fn try_compile_runtime_call(
181
14981
    ctx: &mut CompileContext,
182
14981
    emit: &mut FunctionEmitter,
183
14981
    symbols: &mut SymbolTable,
184
14981
    name: &str,
185
14981
    params: &LambdaParams,
186
14981
    body: &Expr,
187
14981
    args: &[Expr],
188
14981
) -> Result<Option<WasmType>> {
189
14981
    if !needs_runtime_call(ctx, symbols, name, body, args) {
190
13064
        return Ok(None);
191
1917
    }
192
1917
    let arg_types = infer_arg_types(symbols, args)?;
193
1917
    let entry = lookup_or_emit_monomorph(ctx, symbols, name, params, body, &arg_types)?;
194
1917
    emit_runtime_call(ctx, emit, symbols, args, &arg_types, entry.func_idx)?;
195
1917
    Ok(Some(entry.ret_ty))
196
14981
}
197

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

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

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

            
222
2059
fn infer_arg_type(symbols: &mut SymbolTable, arg: &Expr) -> Result<WasmType> {
223
2059
    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
2059
    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
2059
}
234

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

            
255
149526
fn body_calls_inlining_or_self(ctx: &CompileContext, self_name: &str, expr: &Expr) -> bool {
256
149526
    match expr {
257
57368
        Expr::List(elems) => {
258
57368
            if let Some(Expr::Symbol(name)) = elems.first()
259
51404
                && (name == self_name || ctx.is_inlining(name))
260
            {
261
1917
                return true;
262
55451
            }
263
55451
            elems
264
55451
                .iter()
265
142497
                .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
92158
        _ => false,
276
    }
277
149526
}
278

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

            
291
9372
pub(in crate::compiler) fn compile_lambda_call_for_stack(
292
9372
    ctx: &mut CompileContext,
293
9372
    emit: &mut FunctionEmitter,
294
9372
    symbols: &mut SymbolTable,
295
9372
    params: &LambdaParams,
296
9372
    body: &Expr,
297
9372
    args: &[Expr],
298
9372
) -> Result<WasmType> {
299
9372
    let mut local = compile_and_bind_lambda_params(ctx, emit, symbols, params, args)?;
300
9301
    compile_for_stack(ctx, emit, &mut local, body)
301
9372
}
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
17040
pub(in crate::compiler) fn compile_and_bind_lambda_params(
320
17040
    ctx: &mut CompileContext,
321
17040
    emit: &mut FunctionEmitter,
322
17040
    symbols: &mut SymbolTable,
323
17040
    params: &LambdaParams,
324
17040
    args: &[Expr],
325
17040
) -> Result<SymbolTable> {
326
17040
    let min_args = params.required.len();
327
17040
    let max_args = if params.rest.is_some() || !params.key.is_empty() {
328
142
        None
329
    } else {
330
16898
        Some(min_args + params.optional.len())
331
    };
332

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

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

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

            
359
16898
    for (param, default) in &params.optional {
360
71
        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
71
        } else if let Some(default_expr) = default {
365
71
            eval_value(symbols, default_expr)?
366
        } else {
367
            Expr::Nil
368
        };
369
71
        local.define(Symbol::new(param, SymbolKind::Variable).with_value(bound));
370
    }
371

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

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

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

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

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

            
412
16898
    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
16898
    Ok(local)
422
17040
}
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
22152
fn compile_arg_for_param(
429
22152
    ctx: &mut CompileContext,
430
22152
    emit: &mut FunctionEmitter,
431
22152
    symbols: &mut SymbolTable,
432
22152
    arg: &Expr,
433
22152
) -> Result<Expr> {
434
22152
    let resolved = eval_value(symbols, arg)?;
435
22152
    match &resolved {
436
568
        Expr::WasmRuntime(ty) => {
437
568
            compile_for_stack(ctx, emit, symbols, arg)?;
438
568
            let idx = ctx.alloc_local(*ty)?;
439
568
            emit.local_set(idx);
440
568
            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
8804
        Expr::WasmLocal(_, _) => Ok(resolved),
445
12780
        _ => Ok(resolved),
446
    }
447
22152
}