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
161526
pub(in crate::compiler) fn compile_host_fn_for_stack(
18
161526
    ctx: &mut CompileContext,
19
161526
    emit: &mut FunctionEmitter,
20
161526
    symbols: &mut SymbolTable,
21
161526
    name: &str,
22
161526
    args: &[Expr],
23
161526
) -> Result<WasmType> {
24
161526
    let entry = expect_entry(ctx, name)?;
25
161526
    let result = entry.result.ok_or_else(|| {
26
142
        Error::Compile(format!(
27
142
            "host fn '{name}' has no return type and cannot produce a stack value"
28
142
        ))
29
142
    })?;
30
161384
    let func_idx = entry.func_idx;
31
161384
    let params = entry.params;
32
161384
    push_args(ctx, emit, symbols, name, &params, args)?;
33
161313
    symbols.mark_native_referenced(name);
34
161313
    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
161313
    emit_concrete_cast(ctx, emit, result);
41
161313
    Ok(result)
42
161526
}
43

            
44
426
pub(in crate::compiler) fn compile_host_fn_for_effect(
45
426
    ctx: &mut CompileContext,
46
426
    emit: &mut FunctionEmitter,
47
426
    symbols: &mut SymbolTable,
48
426
    name: &str,
49
426
    args: &[Expr],
50
426
) -> Result<()> {
51
426
    let entry = expect_entry(ctx, name)?;
52
426
    let result = entry.result;
53
426
    let func_idx = entry.func_idx;
54
426
    let params = entry.params;
55
426
    push_args(ctx, emit, symbols, name, &params, args)?;
56
426
    symbols.mark_native_referenced(name);
57
426
    emit.call(func_idx);
58
426
    if let Some(ty) = result {
59
355
        emit_concrete_cast(ctx, emit, ty);
60
355
        emit.drop_value();
61
355
    }
62
426
    Ok(())
63
426
}
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
161668
fn emit_concrete_cast(ctx: &CompileContext, emit: &mut FunctionEmitter, ty: WasmType) {
73
161668
    match ty {
74
41180
        WasmType::I32 | WasmType::Bool => {}
75
284
        WasmType::Ratio => emit.ref_cast_nullable(ctx.ids.ty_ratio),
76
2840
        WasmType::Commodity => emit.ref_cast_nullable(ctx.ids.ty_commodity),
77
61415
        WasmType::StringRef => emit.ref_cast_nullable(ctx.ids.ty_i8_array),
78
53890
        WasmType::PairRef(_) => emit.ref_cast_nullable(ctx.ids.ty_pair),
79
2059
        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
161668
}
84

            
85
161952
fn expect_entry(ctx: &CompileContext, name: &str) -> Result<HostFnSnapshot> {
86
161952
    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
161952
    Ok(HostFnSnapshot {
92
161952
        func_idx: entry.func_idx,
93
161952
        params: entry.params.clone(),
94
161952
        result: entry.result,
95
161952
    })
96
161952
}
97

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

            
104
161810
fn push_args(
105
161810
    ctx: &mut CompileContext,
106
161810
    emit: &mut FunctionEmitter,
107
161810
    symbols: &mut SymbolTable,
108
161810
    name: &str,
109
161810
    params: &[WasmType],
110
161810
    args: &[Expr],
111
161810
) -> Result<()> {
112
161810
    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
161810
    }
119
161810
    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
27122
        compile_for_stack_as(ctx, emit, symbols, arg, *expected_ty).map_err(|_| Error::Type {
124
71
            expected: format!("{expected_ty} for argument {idx} of '{name}'"),
125
71
            actual: "an incompatible value".to_string(),
126
71
        })?;
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
27051
        if *expected_ty == WasmType::Commodity {
133
426
            emit.call(ctx.ids.commodity_assert_atomic);
134
26625
        }
135
    }
136
161739
    Ok(())
137
161810
}