1
0
Fork 0
forked from fun/fun

Rust doc fixes. No code changes. (0.38.5)

This commit is contained in:
Johannes Findeisen 2026-01-29 15:17:46 +01:00
commit c03ac29e40

View file

@ -1,197 +1,205 @@
# Writing Rust-backed opcodes for Fun VM # Writing Rust-backed opcodes for Fun VM
This guide explains how to implement VM opcodes in Rust, wire them into the C VM, and use them from Fun scripts. This guide explains how to implement VM opcodes in Rust, wire them into the C VM, and use them from Fun scripts.
It assumes you are comfortable with basic Rust and C and have a working Fun checkout. It assumes you are comfortable with basic Rust and C and have a working Fun checkout.
## Overview ## Overview
Funs VM is written in C, but you can implement opcode handlers in Rust and call them via FFI. The typical flow is: Funs VM is written in C, but you can implement opcode handlers in Rust and call them via FFI. The typical flow is:
1) Write a Rust function with a stable C ABI (extern "C", #[no_mangle]) that takes a pointer to the VM and returns an int status code. 1) Write a Rust function with a stable C ABI (extern "C", #[no_mangle]) that takes a pointer to the VM and returns an int status code.
2) Use VM stack helpers (exposed to Rust via FFI) to pop arguments and push results. 2) Use VM stack helpers (exposed to Rust via FFI) to pop arguments and push results.
3) Expose that Rust function to the C VM by calling it from the opcode dispatch (a case in the VMs opcode switch or a small shim under src/vm/rust/). 3) Expose that Rust function to the C VM by calling it from the opcode dispatch (a case in the VMs opcode switch or a small shim under src/vm/rust/).
4) Add or reuse a Fun builtin that maps to your opcode, then call it from Fun code. 4) Add or reuse a Fun builtin that maps to your opcode, then call it from Fun code.
## Project layout (relevant parts) ## Project layout (relevant parts)
- src/rust/src/lib.rs — Rust library with exported opcode functions and FFI helpers. - src/rust/src/lib.rs — Rust library with exported opcode functions and FFI helpers.
- src/vm/rust/ — C-side wiring examples and small opcode cases calling into Rust. - src/vm/rust/ — C-side wiring examples and small opcode cases calling into Rust.
- examples/rust_hello.fun — Example Fun script using a Rust-backed opcode. - examples/rust_hello.fun — Example Fun script using a Rust-backed opcode.
- docs/opcodes.md — General overview of many built-in opcodes (mostly C-based). - docs/opcodes.md — General overview of many built-in opcodes (mostly C-based).
## Enabling Rust in the build ## Enabling Rust in the build
Rust integration is optional and gated by a CMake flag. Default builds usually have it OFF. Rust integration is optional and gated by a CMake flag. Default builds usually have it OFF.
Enable it for a configured profile (Debug or Release): Enable it for a configured profile (Debug or Release):
- Debug example: - Debug example:
cmake -S . -B build_debug -DFUN_WITH_RUST=ON cmake -S . -B build_debug -DFUN_WITH_RUST=ON
cmake --build build_debug --target fun cmake --build build_debug --target fun
- Release example: - Release example:
cmake -S . -B build_release -DFUN_WITH_RUST=ON cmake -S . -B build_release -DFUN_WITH_RUST=ON
cmake --build build_release --target fun cmake --build build_release --target fun
Useful targets in this repository include: Useful targets in this repository include:
- fun — the main executable - fun — the main executable
- rust_ops_build — helps build/link Rust ops when enabled - rust_ops_build — helps build/link Rust ops when enabled
- test_opcodes — test executable (if you want to extend tests) - test_opcodes — test executable (if you want to extend tests)
Note: In CLion, prefer building with one of the provided CMake profiles (Debug/Release) and avoid creating custom build directories. Note: In CLion, prefer building with one of the provided CMake profiles (Debug/Release) and avoid creating custom build directories.
## Writing an opcode in Rust ## Writing an opcode in Rust
The Rust side is a no_std static library exposing C ABI functions that the VM can call. See src/rust/src/lib.rs for examples already in the tree. The Rust side is a no_std static library exposing C ABI functions that the VM can call. See src/rust/src/lib.rs for examples already in the tree.
Key points: Key points:
- Use extern "C" and #[no_mangle] to fix the symbol name. - Use extern "C" and #[no_mangle] to fix the symbol name.
- Take a raw pointer to the VM as *mut Vm; return i32 status (0 for success). - Take a raw pointer to the VM as *mut Vm; return i32 status (0 for success).
- Interact with the VM stack via helper FFI functions declared as externs. - Interact with the VM stack via helper FFI functions declared as externs.
- Provide a minimal panic handler (no_std) as shown in lib.rs. - Provide a minimal panic handler (no_std) as shown in lib.rs.
Example: integer addition opcode implemented in Rust. Example: integer addition opcode implemented in Rust.
In src/rust/src/lib.rs: In src/rust/src/lib.rs:
#![no_std] ```
#![no_std]
#[repr(C)]
pub struct Vm; #[repr(C)]
pub struct Vm;
extern "C" {
fn vm_pop_i64(vm: *mut Vm) -> i64; extern "C" {
fn vm_push_i64(vm: *mut Vm, v: i64); fn vm_pop_i64(vm: *mut Vm) -> i64;
} fn vm_push_i64(vm: *mut Vm, v: i64);
}
#[no_mangle]
pub extern "C" fn fun_op_radd(vm: *mut Vm) -> i32 { #[no_mangle]
unsafe { pub extern "C" fn fun_op_radd(vm: *mut Vm) -> i32 {
let b = vm_pop_i64(vm); unsafe {
let a = vm_pop_i64(vm); let b = vm_pop_i64(vm);
vm_push_i64(vm, a + b); let a = vm_pop_i64(vm);
} vm_push_i64(vm, a + b);
0 }
} 0
}
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} } #[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} }
What this does: ```
- Pops two 64-bit integers from the VM stack.
- Pushes back their sum. What this does:
- Returns 0 to indicate success to the VM. - Pops two 64-bit integers from the VM stack.
- Pushes back their sum.
You can add more extern helpers (e.g., for strings, arrays, maps) once they are exposed by the C VM. The repository already includes a simple string example returning a const char* from Rust, see fun_rust_get_string() usage below. - Returns 0 to indicate success to the VM.
## Wiring the opcode in C You can add more extern helpers (e.g., for strings, arrays, maps) once they are exposed by the C VM. The repository already includes a simple string example returning a const char* from Rust, see fun_rust_get_string() usage below.
To make the VM call your Rust opcode, add a small C-side case that invokes the exported Rust symbol. A minimal pattern lives under src/vm/rust/. ## Wiring the opcode in C
String demo wiring (already present): src/vm/rust/hello.c To make the VM call your Rust opcode, add a small C-side case that invokes the exported Rust symbol. A minimal pattern lives under src/vm/rust/.
case OP_RUST_HELLO: { String demo wiring (already present): src/vm/rust/hello.c
#ifdef FUN_WITH_RUST
const char *s = fun_rust_get_string(); ```
if (!s) s = ""; case OP_RUST_HELLO: {
push_value(vm, make_string(s)); #ifdef FUN_WITH_RUST
#else const char *s = fun_rust_get_string();
vm_raise_error(vm, "RUST_HELLO requires FUN_WITH_RUST=ON at build time"); if (!s) s = "";
push_value(vm, make_nil()); push_value(vm, make_string(s));
#endif #else
break; vm_raise_error(vm, "RUST_HELLO requires FUN_WITH_RUST=ON at build time");
} push_value(vm, make_nil());
#endif
For a stack-based math opcode (like fun_op_radd), you would declare and call the Rust function similarly: break;
}
#ifdef FUN_WITH_RUST ```
extern int fun_op_radd(void* vm); // or use the proper VM type if available
#endif For a stack-based math opcode (like fun_op_radd), you would declare and call the Rust function similarly:
case OP_RADD: { ```
#ifdef FUN_WITH_RUST #ifdef FUN_WITH_RUST
(void)fun_op_radd(vm); extern int fun_op_radd(void* vm); // or use the proper VM type if available
#else #endif
vm_raise_error(vm, "RADD requires FUN_WITH_RUST=ON at build time");
push_value(vm, make_nil()); // or follow your opcodes error convention case OP_RADD: {
#endif #ifdef FUN_WITH_RUST
break; (void)fun_op_radd(vm);
} #else
vm_raise_error(vm, "RADD requires FUN_WITH_RUST=ON at build time");
Notes: push_value(vm, make_nil()); // or follow your opcodes error convention
- Follow the existing opcode conventions for your module (core, math, strings, etc.). #endif
- If your build puts the Rust symbol into a static library, make sure the VM target links it when FUN_WITH_RUST is ON (the top-level CMake already does this for the examples provided). break;
}
## Using Rust-backed opcodes from Fun ```
Once wired, expose the opcode via a builtin function or directly in bytecode. The repository includes a demo builtin rust_hello() that returns a Rust-generated string. Notes:
Run the example: - Follow the existing opcode conventions for your module (core, math, strings, etc.).
- If your build puts the Rust symbol into a static library, make sure the VM target links it when FUN_WITH_RUST is ON (the top-level CMake already does this for the examples provided).
1) Build with Rust enabled (Debug):
cmake -S . -B build_debug -DFUN_WITH_RUST=ON ## Using Rust-backed opcodes from Fun
cmake --build build_debug --target fun
Once wired, expose the opcode via a builtin function or directly in bytecode. The repository includes a demo builtin rust_hello() that returns a Rust-generated string.
2) Execute the script:
build_debug/fun examples/rust_hello.fun Run the example:
Expected output: 1) Build with Rust enabled (Debug):
Hello from Rust ops! cmake -S . -B build_debug -DFUN_WITH_RUST=ON
cmake --build build_debug --target fun
If you build without Rust, calling rust_hello() raises a runtime error indicating that Rust integration is disabled.
2) Execute the script:
## Stack discipline and error handling build_debug/fun examples/rust_hello.fun
- Always pop exactly the arguments you expect and push exactly the results your opcode promises. Mismatch leads to stack corruption and hard-to-debug failures. Expected output:
- Return an int status to the VM (0 for success). If your project uses a different convention for some opcodes, match it consistently. Hello from Rust ops!
- Validate types where appropriate (e.g., ensure values are integers before arithmetic). If a check fails, use the VMs error mechanism (e.g., vm_raise_error) and follow the modules convention on what to push after errors.
If you build without Rust, calling rust_hello() raises a runtime error indicating that Rust integration is disabled.
## Data types and FFI surface
## Stack discipline and error handling
The minimal helpers shown cover 64-bit integers and simple strings. Extending the Rust<->C bridge usually involves:
- Declaring additional extern "C" functions in Rust that the C VM implements (to read/write values on the stack, construct arrays/maps/strings, etc.). - Always pop exactly the arguments you expect and push exactly the results your opcode promises. Mismatch leads to stack corruption and hard-to-debug failures.
- Ensuring all pointers and lifetimes are well-defined: strings pushed to the VM should be copied or allocated using VM facilities so they remain valid after the call. - Return an int status to the VM (0 for success). If your project uses a different convention for some opcodes, match it consistently.
- Keeping Rust no_std unless you add an allocator and link setup to support std. - Validate types where appropriate (e.g., ensure values are integers before arithmetic). If a check fails, use the VMs error mechanism (e.g., vm_raise_error) and follow the modules convention on what to push after errors.
## Troubleshooting ## Data types and FFI surface
- Link errors: Make sure FUN_WITH_RUST=ON for your build directory and that the Rust library is compiled before linking the VM. Use the rust_ops_build target if provided by your profile. The minimal helpers shown cover 64-bit integers and simple strings. Extending the Rust<->C bridge usually involves:
- Missing symbol at runtime: Confirm #[no_mangle] and extern "C" on the Rust function and that C sees the correct prototype.
- Wrong or garbled values: Double-check stack order (Fun uses a stack VM; many ops pop in reverse order: first b, then a). - Declaring additional extern "C" functions in Rust that the C VM implements (to read/write values on the stack, construct arrays/maps/strings, etc.).
- No output from rust_hello(): Ensure you run a binary built with FUN_WITH_RUST=ON; otherwise the VM deliberately raises an error and returns Nil for that call. - Ensuring all pointers and lifetimes are well-defined: strings pushed to the VM should be copied or allocated using VM facilities so they remain valid after the call.
- Keeping Rust no_std unless you add an allocator and link setup to support std.
## Small end-to-end checklist
## Troubleshooting
1) Write the Rust function in src/rust/src/lib.rs with extern "C", #[no_mangle].
2) Use FFI helpers to pop arguments and push results. - Link errors: Make sure FUN_WITH_RUST=ON for your build directory and that the Rust library is compiled before linking the VM. Use the rust_ops_build target if provided by your profile.
3) Add a C-side case under src/vm/... (or src/vm/rust/...) that calls your Rust function when the opcode executes. - Missing symbol at runtime: Confirm #[no_mangle] and extern "C" on the Rust function and that C sees the correct prototype.
4) Ensure the build links Rust code when FUN_WITH_RUST=ON. - Wrong or garbled values: Double-check stack order (Fun uses a stack VM; many ops pop in reverse order: first b, then a).
5) Add or reuse a builtin in the parser/runtime to surface your opcode to Fun code. - No output from rust_hello(): Ensure you run a binary built with FUN_WITH_RUST=ON; otherwise the VM deliberately raises an error and returns Nil for that call.
6) Build and run a small .fun example to validate behavior.
## Small end-to-end checklist
## References in this repo
1) Write the Rust function in src/rust/src/lib.rs with extern "C", #[no_mangle].
- Rust lib with examples: src/rust/src/lib.rs 2) Use FFI helpers to pop arguments and push results.
- C-side hello wiring: src/vm/rust/hello.c 3) Add a C-side case under src/vm/... (or src/vm/rust/...) that calls your Rust function when the opcode executes.
- Demo script: examples/rust_hello.fun 4) Ensure the build links Rust code when FUN_WITH_RUST=ON.
- General opcode reference: docs/opcodes.md 5) Add or reuse a builtin in the parser/runtime to surface your opcode to Fun code.
6) Build and run a small .fun example to validate behavior.
## Links
## References in this repo
Authoritative and practical resources on exposing Rust to C (FFI) and maintaining a C-compatible API:
- Rust lib with examples: src/rust/src/lib.rs
- Rustonomicon: FFI overview and best practices - C-side hello wiring: src/vm/rust/hello.c
https://doc.rust-lang.org/nomicon/ffi.html - Demo script: examples/rust_hello.fun
- Rustonomicon: Calling Rust code from C - General opcode reference: docs/opcodes.md
https://doc.rust-lang.org/nomicon/ffi.html#calling-rust-code-from-c
- The Rust Book: Unsafe and FFI (extern, #[no_mangle], calling Rust from other languages) ## Links
https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#calling-rust-functions-from-other-languages
- Rust Reference: extern blocks, ABIs, and linkage Authoritative and practical resources on exposing Rust to C (FFI) and maintaining a C-compatible API:
https://doc.rust-lang.org/reference/items/external-blocks.html
- Rust FFI Omnibus (examples for many patterns, including Rust ↔ C) - Rustonomicon: FFI overview and best practices
https://github.com/shepmaster/rust-ffi-omnibus https://doc.rust-lang.org/nomicon/ffi.html
- cbindgen (generate C headers from Rust libraries) - Rustonomicon: Calling Rust code from C
https://cbindgen.github.io/cbindgen/ https://doc.rust-lang.org/nomicon/ffi.html#calling-rust-code-from-c
- bindgen (generate Rust bindings to existing C headers; useful when mixing C and Rust) - The Rust Book: Unsafe and FFI (extern, #[no_mangle], calling Rust from other languages)
https://github.com/rust-lang/rust-bindgen https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#calling-rust-functions-from-other-languages
- Rust Reference: extern blocks, ABIs, and linkage
https://doc.rust-lang.org/reference/items/external-blocks.html
- Rust FFI Omnibus (examples for many patterns, including Rust ↔ C)
https://github.com/shepmaster/rust-ffi-omnibus
- cbindgen (generate C headers from Rust libraries)
https://cbindgen.github.io/cbindgen/
- bindgen (generate Rust bindings to existing C headers; useful when mixing C and Rust)
https://github.com/rust-lang/rust-bindgen