#![no_std] Runtime
Embedding Resilient on a Cortex-M class MCU.
Table of contents
What it is
Resilient ships a sibling crate at
resilient-runtime/
that carves out the value layer + core ops in a
#![no_std]-compatible form. It’s verified to cross-compile to
thumbv7em-none-eabihf (Cortex-M4F class MCU) in both feature
configs.
This is the foundation for running Resilient programs on a
microcontroller. The host build (resilient/) uses the full
interpreter / VM / JIT; the embedded build uses just this
runtime crate plus a future Program evaluator.
Feature configs
| Feature | Adds | Use when |
|---|---|---|
| (default) | Value::Int, Value::Bool, Value::Float |
Stack-only types, no allocator needed |
--features alloc |
Value::String |
When you need string values; pulls in embedded-alloc |
The alloc feature does NOT pick a #[global_allocator] — that’s
the binary’s responsibility (see below).
Build for host
cd resilient-runtime
# Default (alloc-free) — 11 unit tests
cargo build
cargo test
# With alloc — 14 unit tests (adds Float + String coverage)
cargo build --features alloc
cargo test --features alloc
Cross-compile to Cortex-M4F
rustup target add thumbv7em-none-eabihf
# Default — Cortex-M4F has native i64 instruction support, no
# compiler_builtins shim needed.
cargo build --target thumbv7em-none-eabihf
cargo clippy --target thumbv7em-none-eabihf -- -D warnings
# With --features alloc, embedded-alloc 0.5 is pulled in.
cargo build --target thumbv7em-none-eabihf --features alloc
cargo clippy --target thumbv7em-none-eabihf --features alloc -- -D warnings
Wiring an allocator (binary side)
#![no_std]
#![no_main]
extern crate alloc;
use embedded_alloc::LlffHeap as Heap;
#[global_allocator]
static HEAP: Heap = Heap::empty();
#[cortex_m_rt::entry]
fn main() -> ! {
use core::mem::MaybeUninit;
const HEAP_SIZE: usize = 4096;
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] =
[MaybeUninit::uninit(); HEAP_SIZE];
unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE); }
// Now resilient_runtime::Value::String / Float work.
use resilient_runtime::Value;
let _ = Value::Float(2.5).add(Value::Float(1.5));
loop { cortex_m::asm::nop(); }
}
A buildable example crate is planned (RES-101). Until then the sketch above is the canonical pattern.
Value semantics
The runtime mirrors the host VM’s semantics so a program runs identically on either backend:
use resilient_runtime::Value;
let r = Value::Int(2).add(Value::Int(3))?; // → Value::Int(5)
let r = Value::Int(i64::MAX).add(Value::Int(1))?; // wrapping → Value::Int(i64::MIN)
let e = Value::Int(10).div(Value::Int(0)); // → Err(RuntimeError::DivideByZero)
let e = Value::Int(1).add(Value::Bool(true)); // → Err(RuntimeError::TypeMismatch("add"))
- Int arithmetic wraps on overflow (matches the bytecode VM).
- Float follows IEEE-754 (
1.0 / 0.0 == inf, NaN equals itself for bit-equality consistency with the constant pool). - Mixed-type ops are a
TypeMismatch— promotion is the caller’s job.
Roadmap
The runtime is the foundation for the long-term plan of running Resilient programs on bare-metal MCUs. Concretely:
- RES-075/097/098 ✅ — value layer + cross-compile +
allocfeature - RES-101 (open) — buildable Cortex-M demo crate with
LlffHeapand a#[entry]function - Future — port a subset of the bytecode VM into
resilient-runtimeso embedded programs can run pre-compiled bytecode without a host toolchain - Future —
live { }block semantics with explicit snapshot/restore for embedded I/O effects
See ROADMAP.md goalpost G18 for status.