1
//! Tier 1.5 monomorph emit — closes Gap B.
2
//!
3
//! When a defun is called with a runtime arg the inline const-fold path
4
//! has no terminating walk (each step re-enters the body without
5
//! reducing toward a base case). The runtime-call path emits the body
6
//! once as a real wasm fn keyed on the call-site argument signature
7
//! and lowers each call site to `call $monomorph_idx`. The recursion
8
//! itself terminates at runtime via the same mechanism every wasm fn
9
//! uses — the wasm call stack — so the compile-time walk no longer has
10
//! to.
11
//!
12
//! Cache: [`super::super::context::monomorph::MonomorphCache`] is
13
//! keyed by `(defun_name, [arg_wasm_types])`. Distinct call-site
14
//! signatures emit distinct wasm fns; identical ones share.
15
//!
16
//! Type inference: we don't have a declared return type at the call
17
//! site. The body's return type is determined by emitting the body —
18
//! but the body may self-call, and that self-call needs a concrete
19
//! return type up front. We resolve the chicken-egg via
20
//! [`snapshot`]/[`restore`] on the [`super::super::context::CompileContext`]:
21
//! 1. Insert a placeholder cache entry whose `ret_ty` matches the
22
//!    first param's type (or `Ratio` for nullary recursion).
23
//! 2. Reserve the wasm fn slot and snapshot the wasm-section state.
24
//! 3. Emit the body with `compile_for_stack`. Self-calls inside the
25
//!    body resolve through the cache and emit `call $self_idx`
26
//!    against the placeholder return type.
27
//! 4. Compare observed return type to placeholder. If they match,
28
//!    queue the helper and we're done.
29
//! 5. If they differ, restore the snapshot (rolling back the trial
30
//!    wasm bytes) and re-emit with the observed type substituted in.
31
//!    Hard cap at 4 iterations — anything that doesn't converge is a
32
//!    structured `Error::Compile`, not an unbounded loop.
33

            
34
use crate::ast::{Expr, LambdaParams, WasmType};
35
use crate::compiler::context::CompileContext;
36
use crate::compiler::context::monomorph::{MonomorphEntry, MonomorphKey};
37
use crate::compiler::emit::FunctionEmitter;
38
use crate::compiler::expr::compile_for_stack;
39
use crate::error::{Error, Result};
40
use crate::runtime::{Symbol, SymbolKind, SymbolTable};
41

            
42
const FIXPOINT_ITER_CAP: usize = 4;
43

            
44
/// Lookup-or-emit pipeline. Returns the cache entry the caller emits
45
/// `call <entry.func_idx>` against. Body is the defun's `Expr::Lambda`
46
/// body; `params` is the defun's parameter list; `arg_types` is the
47
/// call-site argument signature (one wasm type per required param).
48
1836
pub(in crate::compiler) fn lookup_or_emit_monomorph(
49
1836
    ctx: &mut CompileContext,
50
1836
    symbols: &SymbolTable,
51
1836
    name: &str,
52
1836
    params: &LambdaParams,
53
1836
    body: &Expr,
54
1836
    arg_types: &[WasmType],
55
1836
) -> Result<MonomorphEntry> {
56
1836
    if !is_v1_signature(params) {
57
        return Err(Error::Compile(format!(
58
            "runtime-call lowering for '{name}' requires required-only \
59
             parameters; lambdas with &optional/&rest/&key/&aux still go \
60
             through the inline path"
61
        )));
62
1836
    }
63
1836
    if params.required.len() != arg_types.len() {
64
        return Err(Error::Arity {
65
            name: name.to_string(),
66
            expected: params.required.len(),
67
            actual: arg_types.len(),
68
        });
69
1836
    }
70

            
71
1836
    let key = MonomorphKey {
72
1836
        name: name.to_string(),
73
1836
        params: arg_types.to_vec(),
74
1836
    };
75
1836
    if let Some(entry) = ctx.monomorph_cache().get(&key) {
76
952
        return Ok(entry.clone());
77
884
    }
78

            
79
884
    emit_monomorph(ctx, symbols, key, params, body, arg_types)
80
1836
}
81

            
82
1836
fn is_v1_signature(params: &LambdaParams) -> bool {
83
1836
    params.optional.is_empty()
84
1836
        && params.rest.is_none()
85
1836
        && params.key.is_empty()
86
1836
        && params.aux.is_empty()
87
1836
}
88

            
89
884
fn initial_ret_guess(arg_types: &[WasmType]) -> WasmType {
90
884
    arg_types.first().copied().unwrap_or(WasmType::Ratio)
91
884
}
92

            
93
884
fn emit_monomorph(
94
884
    ctx: &mut CompileContext,
95
884
    symbols: &SymbolTable,
96
884
    key: MonomorphKey,
97
884
    params: &LambdaParams,
98
884
    body: &Expr,
99
884
    arg_types: &[WasmType],
100
884
) -> Result<MonomorphEntry> {
101
884
    let mut guess = initial_ret_guess(arg_types);
102
884
    let pre = ctx.snapshot();
103
884
    for _ in 0..FIXPOINT_ITER_CAP {
104
1088
        ctx.restore(pre.clone());
105
1088
        let entry = MonomorphEntry {
106
1088
            func_idx: u32::MAX,
107
1088
            ret_ty: guess,
108
1088
        };
109
1088
        ctx.monomorph_cache_mut().insert(key.clone(), entry.clone());
110
1088
        match try_emit_body(ctx, symbols, &key, params, body, arg_types, guess) {
111
1088
            Ok(observed) if observed.ret_ty == guess => {
112
884
                ctx.monomorph_cache_mut().insert(key, observed.clone());
113
884
                return Ok(observed);
114
            }
115
204
            Ok(observed) => {
116
204
                guess = observed.ret_ty;
117
204
            }
118
            Err(e) => {
119
                ctx.restore(pre);
120
                return Err(e);
121
            }
122
        }
123
    }
124
    ctx.restore(pre);
125
    Err(Error::Compile(format!(
126
        "type inference for '{}' did not converge within {} iterations; \
127
         the body's return type appears to depend on itself",
128
        key.name, FIXPOINT_ITER_CAP
129
    )))
130
884
}
131

            
132
1088
fn try_emit_body(
133
1088
    ctx: &mut CompileContext,
134
1088
    symbols: &SymbolTable,
135
1088
    key: &MonomorphKey,
136
1088
    params: &LambdaParams,
137
1088
    body: &Expr,
138
1088
    arg_types: &[WasmType],
139
1088
    expected_ret: WasmType,
140
1088
) -> Result<MonomorphEntry> {
141
1156
    let param_vts: Vec<_> = arg_types.iter().map(|t| ctx.wasm_val_type(*t)).collect();
142
1088
    let result_vt = ctx.wasm_val_type(expected_ret);
143
1088
    let helper_name = next_monomorph_helper_name(&key.name, arg_types);
144
1088
    let func_idx = ctx.register_function(&helper_name, &param_vts, &[result_vt])?;
145

            
146
1088
    let entry = MonomorphEntry {
147
1088
        func_idx,
148
1088
        ret_ty: expected_ret,
149
1088
    };
150
1088
    ctx.monomorph_cache_mut().insert(key.clone(), entry);
151

            
152
1088
    let param_count = u32::try_from(arg_types.len())
153
1088
        .map_err(|_| Error::Compile("monomorph param count exceeds u32 range".to_string()))?;
154

            
155
1088
    let mut local_symbols = symbols.clone();
156
1156
    for (idx, (param_name, ty)) in params.required.iter().zip(arg_types.iter()).enumerate() {
157
1156
        let local_idx = u32::try_from(idx)
158
1156
            .map_err(|_| Error::Compile("monomorph param index exceeds u32 range".to_string()))?;
159
1156
        local_symbols.define(
160
1156
            Symbol::new(param_name, SymbolKind::Variable)
161
1156
                .with_value(Expr::WasmLocal(local_idx, *ty)),
162
        );
163
    }
164

            
165
1088
    let snapshot = ctx.take_local_pool(param_count);
166

            
167
1088
    let mut helper_emit = FunctionEmitter::new();
168
1088
    if let Err(e) = ctx.push_inlining_frame(&key.name) {
169
        ctx.restore_local_pool(snapshot);
170
        return Err(e);
171
1088
    }
172
1088
    let body_result = compile_for_stack(ctx, &mut helper_emit, &mut local_symbols, body);
173
1088
    ctx.pop_inlining_frame(&key.name);
174

            
175
1088
    let observed_ret = match body_result {
176
1088
        Ok(ty) => ty,
177
        Err(e) => {
178
            ctx.restore_local_pool(snapshot);
179
            return Err(e);
180
        }
181
    };
182

            
183
1088
    helper_emit.end();
184
1088
    let helper_locals = ctx.build_helper_locals(param_count);
185
1088
    let helper_fn = helper_emit.finish(&helper_locals);
186
1088
    ctx.restore_local_pool(snapshot);
187

            
188
1088
    if observed_ret != expected_ret {
189
204
        return Ok(MonomorphEntry {
190
204
            func_idx,
191
204
            ret_ty: observed_ret,
192
204
        });
193
884
    }
194

            
195
884
    ctx.queue_helper(helper_fn);
196
884
    Ok(MonomorphEntry {
197
884
        func_idx,
198
884
        ret_ty: observed_ret,
199
884
    })
200
1088
}
201

            
202
1088
fn next_monomorph_helper_name(defun: &str, arg_types: &[WasmType]) -> String {
203
1088
    let mut name = format!("__defun_{defun}");
204
1156
    for ty in arg_types {
205
1156
        name.push('_');
206
1156
        name.push_str(&format_wasm_type(*ty));
207
1156
    }
208
1088
    name
209
1088
}
210

            
211
1156
fn format_wasm_type(ty: WasmType) -> String {
212
1156
    match ty {
213
884
        WasmType::Ratio => "ratio".to_string(),
214
        WasmType::Commodity => "commodity".to_string(),
215
204
        WasmType::I32 => "i32".to_string(),
216
68
        WasmType::Bool => "bool".to_string(),
217
        WasmType::StringRef => "string".to_string(),
218
        WasmType::PairRef(_) => "pair".to_string(),
219
        WasmType::EntityRef(kind) => format!("entity_{}", kind.type_name()),
220
        WasmType::Closure(sig) => format!("closure{}", sig.0),
221
        WasmType::AnyRef => "any".to_string(),
222
    }
223
1156
}