RFD-0006 — Three compute forms — expression, inline body, opaque FFI
Question
Domain-specific computation in Argon (capital-gain calculation, depreciation schedule, lease-term accrual, etc.) can be expressed at varying levels of complexity. How many implementation forms does compute admit, and what’s the tradeoff for each?
Decision
compute admits three implementation forms:
-
Expression form —
compute f(args) = expr. The body is a single expression. Suitable for one-shot calculations; lowers to a Core IR expression node. -
Inline body form —
compute f(args) { input { … } out { … } ensure { … } body { … } }. The body is a constraint-sublanguage block expressed in Argon’s own grammar (RFD-0005). Lowers toCoreComputeBody::Inline. The inline interpreter (a tree walker overKernelExpr) dispatches inline bodies throughDomainComputationviaInlineComputeAdapter. Tier-classified. Dynamically loadable per-tenant without a Rust rebuild. -
Opaque Rust FFI form —
compute f(args) { … impl rust("path::to::fn") }. The body is an opaque pointer to a registered Rust function. Lowers toCoreComputeBody::Rust. Requires a Rust rebuild; offers maximum flexibility and access to native libraries.
Form 2 dynamic loading uses an overlay mechanism: the kernel loads the inline-form body into a per-tenant registry at hydration time, and the inline adapter minter wires up dispatch. Per-tenant custom math ships without a code release.
Rationale
Three forms cover the cost-flexibility frontier. Expression form is easy to write, easy to read, easy to verify, but limited. Inline body form is more flexible (multi-statement, type-checked against Argon’s own expression grammar) and dynamically loadable, but tier-bounded. Rust FFI is unbounded but requires Rust expertise and a release cycle.
Form 2 is the load-bearing form for tenant customization. Tax law differs per jurisdiction. Tenants need to author math their own way without coordinating release cycles. The inline-body form lets that happen against Argon’s own grammar, with the same tier guarantees and the same provenance-tracking the language’s structural rules get.
Why not just two (expression + Rust). The middle ground matters: tenants are not Rust programmers. Inline body form gives them a mid-tier escape hatch — more than one expression, less than full Rust — that’s still tier-classified and provenance-tracked.
Why a tree walker, not a JIT. Inline-form bodies evaluate at kernel-runtime per-fact. JIT compilation would multiply complexity for marginal latency gains in a workload dominated by I/O and saturation passes. The tree walker keeps the evaluator small, debuggable, and consistent with the rest of the inline interpreter.
Consequences
- Inline interpreter at
crates/nous/src/runtime/interpreter.rsevaluatesCoreComputeBody::Inline. mint_inline_adapters_for_overlayruns at kernel-api hydration time to wire per-tenant adapters.validate_compute_call_acyclicityrejects recursive compute-call cycles (OE0210). Arity mismatch surfaces asOE0208. Unresolved call targets surface asOE1406.- Compute-bodies compose: function calls (
path(args)) over other computes, arithmetic over Int / Decimal / Money, multi-atom negation via aux-rule hoisting, complex-scrutinee match lowering. - Justification granularity is per-leaf-read with transparent combinators. Incremental recomputation has no interpreter-level cache; correctness defers to the kernel’s IVM substrate.