1
//! Compile-time emit for caller-supplied host fn calls.
2
//!
3
//! When `(rpc-protocol-version)` (or any other registered `HostFnSpec`)
4
//! shows up in nomiscript source, the dispatcher in `super::mod`
5
//! routes here. We push each argument onto the wasm stack via
6
//! `compile_for_stack`, validate the type matches the spec, and emit
7
//! `(call $idx)` against the import index recorded by
8
//! [`CompileContext::register_host_fn`].
9

            
10
use super::CompileContext;
11
use crate::ast::{Expr, WasmType};
12
use crate::compiler::emit::FunctionEmitter;
13
use crate::compiler::expr::compile_for_stack_as;
14
use crate::error::{Error, Result};
15
use crate::runtime::SymbolTable;
16

            
17
366446
pub(in crate::compiler) fn compile_host_fn_for_stack(
18
366446
    ctx: &mut CompileContext,
19
366446
    emit: &mut FunctionEmitter,
20
366446
    symbols: &mut SymbolTable,
21
366446
    name: &str,
22
366446
    args: &[Expr],
23
366446
) -> Result<WasmType> {
24
366446
    let entry = expect_entry(ctx, name)?;
25
366446
    let result = entry.result.ok_or_else(|| {
26
166
        Error::Compile(format!(
27
166
            "host fn '{name}' has no return type and cannot produce a stack value"
28
166
        ))
29
166
    })?;
30
366280
    let func_idx = entry.func_idx;
31
366280
    let params = entry.params;
32
366280
    push_args(ctx, emit, symbols, name, &params, args)?;
33
366197
    symbols.mark_native_referenced(name);
34
366197
    emit.call(func_idx);
35
    // Host imports declare GC-ref results with the abstract heap type
36
    // (`(ref null struct)` / `(ref null array)`) because that's what
37
    // `Rooted<StructRef>` / `Rooted<ArrayRef>` report through
38
    // `WasmTy::valtype()`. Cast the result back to the concrete type
39
    // the rest of the pipeline expects.
40
366197
    emit_concrete_cast(ctx, emit, result);
41
366197
    Ok(result)
42
366446
}
43

            
44
498
pub(in crate::compiler) fn compile_host_fn_for_effect(
45
498
    ctx: &mut CompileContext,
46
498
    emit: &mut FunctionEmitter,
47
498
    symbols: &mut SymbolTable,
48
498
    name: &str,
49
498
    args: &[Expr],
50
498
) -> Result<()> {
51
498
    let entry = expect_entry(ctx, name)?;
52
498
    let result = entry.result;
53
498
    let func_idx = entry.func_idx;
54
498
    let params = entry.params;
55
498
    push_args(ctx, emit, symbols, name, &params, args)?;
56
498
    symbols.mark_native_referenced(name);
57
498
    emit.call(func_idx);
58
498
    if let Some(ty) = result {
59
415
        emit_concrete_cast(ctx, emit, ty);
60
415
        emit.drop_value();
61
415
    }
62
498
    Ok(())
63
498
}
64

            
65
/// Casts the abstract `(ref null struct)` / `(ref null array)` left on
66
/// the stack by a host import to the concrete type the result's
67
/// `WasmType` declares. Primitive returns (I32) pass through unchanged.
68
/// Nullable variant: wasmtime surfaces `Option<Rooted<…>>` returns as
69
/// `(ref null …)`, so an empty `list-accounts` / not-found
70
/// `get-account` returns null and the cast must preserve it. The
71
/// non-null `ref_cast` would trap on every nil-shaped result.
72
366612
fn emit_concrete_cast(ctx: &CompileContext, emit: &mut FunctionEmitter, ty: WasmType) {
73
366612
    match ty {
74
105493
        WasmType::I32 | WasmType::Bool => {}
75
332
        WasmType::Ratio => emit.ref_cast_nullable(ctx.ids.ty_ratio),
76
3403
        WasmType::Commodity => emit.ref_cast_nullable(ctx.ids.ty_commodity),
77
131887
        WasmType::StringRef => emit.ref_cast_nullable(ctx.ids.ty_i8_array),
78
123090
        WasmType::PairRef(_) => emit.ref_cast_nullable(ctx.ids.ty_pair),
79
2407
        WasmType::EntityRef(kind) => emit.ref_cast_nullable(ctx.ids.entity_type(kind)),
80
        WasmType::Closure(sig) => emit.ref_cast_nullable(ctx.closure_sig(sig).closure_type_idx),
81
        WasmType::AnyRef => {}
82
    }
83
366612
}
84

            
85
366944
fn expect_entry(ctx: &CompileContext, name: &str) -> Result<HostFnSnapshot> {
86
366944
    let entry = ctx.lookup_host_fn(name).ok_or_else(|| {
87
        Error::Compile(format!(
88
            "host fn dispatch reached for unregistered name '{name}'"
89
        ))
90
    })?;
91
366944
    Ok(HostFnSnapshot {
92
366944
        func_idx: entry.func_idx,
93
366944
        params: entry.params.clone(),
94
366944
        result: entry.result,
95
366944
    })
96
366944
}
97

            
98
struct HostFnSnapshot {
99
    func_idx: u32,
100
    params: Vec<WasmType>,
101
    result: Option<WasmType>,
102
}
103

            
104
366778
fn push_args(
105
366778
    ctx: &mut CompileContext,
106
366778
    emit: &mut FunctionEmitter,
107
366778
    symbols: &mut SymbolTable,
108
366778
    name: &str,
109
366778
    params: &[WasmType],
110
366778
    args: &[Expr],
111
366778
) -> Result<()> {
112
366778
    if args.len() != params.len() {
113
        return Err(Error::Arity {
114
            name: name.to_string(),
115
            expected: params.len(),
116
            actual: args.len(),
117
        });
118
366778
    }
119
366778
    for (idx, (arg, expected_ty)) in args.iter().zip(params.iter()).enumerate() {
120
        // Coerce each argument to the host fn's declared parameter type; an
121
        // integer/fractional literal crosses the sanctioned Index↔Scalar
122
        // boundary so e.g. a `Ratio` arg accepts a bare literal.
123
31623
        compile_for_stack_as(ctx, emit, symbols, arg, *expected_ty).map_err(|_| Error::Type {
124
83
            expected: format!("{expected_ty} for argument {idx} of '{name}'"),
125
83
            actual: "an incompatible value".to_string(),
126
83
        })?;
127
        // ADR-0028 E2: the wasm↔host border is unit-erased — host fns only see
128
        // ATOMIC single-currency money (fields 0-3). Guard every Commodity arg:
129
        // `commodity_assert_atomic` returns it unchanged when its unit term is
130
        // null, else throws a catchable `NON-ATOMIC-COMMODITY`, so a compound
131
        // value can never be misread as `id = 0` money.
132
31540
        if *expected_ty == WasmType::Commodity {
133
498
            emit.call(ctx.ids.commodity_assert_atomic);
134
31042
        }
135
    }
136
366695
    Ok(())
137
366778
}