1
0
Fork 0
forked from fun/fun

Some more Fun VM and Rust interaction. (0.38.2)

This commit is contained in:
Johannes Findeisen 2026-01-29 02:53:14 +01:00
commit c2e7f3869e
17 changed files with 251 additions and 46 deletions

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.10)
project(fun VERSION 0.38.1 LANGUAGES C)
project(fun VERSION 0.38.2 LANGUAGES C)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)

View file

@ -17,7 +17,6 @@ target_include_directories(fun_core PUBLIC
# Apply options to core
if(FUN_DEBUG)
message(STATUS "FUN_DEBUG enabled: building with verbose debug logging")
target_compile_definitions(fun_core PUBLIC FUN_VERSION="${PROJECT_VERSION}")
target_compile_definitions(fun_core PUBLIC FUN_DEBUG=1)
endif()

View file

@ -1,5 +1,7 @@
# Writing Rust-backed opcodes for Fun VM
!!! CAUTION !!! I am not very experienced in using Rust. This should show that it is possible to implement opcodes in Rust and integrate them with the Fun VM.
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.

View file

@ -4,7 +4,7 @@
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*

View file

@ -4,7 +4,7 @@
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*

View file

@ -0,0 +1,39 @@
#!/usr/bin/env fun
/*
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-29
*/
/*
* Rust-backed VM access demo:
* - rust_get_sp() -> reads vm.sp (stack pointer) using raw pointer + offset from Rust
* - rust_set_exit(N) -> writes vm.exit_code = N from Rust
*/
print("[Rust VM access demo]")
/* Read vm.sp via Rust and print it */
print("vm.sp (from Rust) = " + to_string(rust_get_sp()))
/* Write vm.exit_code via Rust (not directly observable without VM inspection) */
print("Setting vm.exit_code to 5 via Rust...")
rust_set_exit(42)
print("Now exiting...")
/* Expected output:
[Rust VM access demo]
vm.sp (from Rust) = 0
Setting vm.exit_code to 42 via Rust...
Now exiting...
*/
// "echo $?" will show the exit code:
// 42

View file

@ -208,6 +208,8 @@ static const char *opcode_name(OpCode op) {
case OP_FMAX: return "FMAX";
case OP_RUST_HELLO: return "RUST_HELLO";
case OP_RUST_HELLO_ARGS: return "RUST_HELLO_ARGS";
case OP_RUST_GET_SP: return "RUST_GET_SP";
case OP_RUST_SET_EXIT: return "RUST_SET_EXIT";
default: return "???";
}
}

View file

@ -266,6 +266,8 @@ typedef enum {
// Rust FFI demo opcode(s)
OP_RUST_HELLO, // pushes string returned from Rust (hello world)
OP_RUST_HELLO_ARGS, // pops message string; prints it via Rust; pushes Nil
OP_RUST_GET_SP, // pushes current VM stack pointer (via Rust reading VM memory)
OP_RUST_SET_EXIT, // pops int and sets VM exit_code (via Rust writing VM memory)
/* Notcurses TUI (optional) */
OP_NC_INIT, // initializes Notcurses; returns 1 on success, 0 on failure

View file

@ -801,6 +801,21 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
free(name);
return 1;
}
if (strcmp(name, "rust_get_sp") == 0) {
(*pos)++; /* '(' */
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "rust_get_sp expects ()"); free(name); return 0; }
bytecode_add_instruction(bc, OP_RUST_GET_SP, 0);
free(name);
return 1;
}
if (strcmp(name, "rust_set_exit") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "rust_set_exit expects (code:int)"); free(name); return 0; }
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after rust_set_exit arg"); free(name); return 0; }
bytecode_add_instruction(bc, OP_RUST_SET_EXIT, 0);
free(name);
return 1;
}
if (strcmp(name, "os_list_dir") == 0) {
(*pos)++; /* '(' */
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "os_list_dir expects (path)"); free(name); return 0; }

View file

@ -2,15 +2,13 @@
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-27
*/
#![no_std]
// Use an FFI-safe opaque handle for the VM pointer
pub type Vm = core::ffi::c_void;
@ -37,7 +35,7 @@ pub extern "C" fn fun_rust_get_string() -> *const core::ffi::c_char {
b"Hello from Rust ops!\0".as_ptr() as *const _
}
// Print a C string via libc printf to stdout (acceptable under no_std via extern)
// Print a C string via libc printf to stdout
#[no_mangle]
pub extern "C" fn fun_rust_print_string(msg: *const core::ffi::c_char) -> i32 {
unsafe {
@ -50,12 +48,4 @@ pub extern "C" fn fun_rust_print_string(msg: *const core::ffi::c_char) -> i32 {
0
}
// Provide personality symbol to satisfy some linkers in no_std builds
#[no_mangle]
pub extern "C" fn rust_eh_personality() {}
// Minimal panic handler for no_std; abort behavior requested via Cargo profile
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
// No custom panic handler; use std default

View file

@ -2,7 +2,7 @@
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
@ -16,10 +16,18 @@
//! ABI helpers declared on the C side (`vm_pop_i64`, `vm_push_i64`).
use super::Vm;
//use core::mem::size_of;
use core::ptr;
extern "C" {
fn vm_pop_i64(vm: *mut Vm) -> i64;
fn vm_push_i64(vm: *mut Vm, v: i64);
fn vm_as_mut_ptr(vm: *mut Vm) -> *mut core::ffi::c_void;
fn vm_sizeof() -> usize;
fn vm_value_sizeof() -> usize;
fn vm_offset_of_exit_code() -> usize;
fn vm_offset_of_sp() -> usize;
fn vm_offset_of_stack() -> usize;
}
/// Multiply the top two integers on the VM stack.
@ -68,3 +76,29 @@ pub extern "C" fn fun_op_rdiv(vm: *mut Vm) -> i32 {
}
0
}
/// Demo: push current stack pointer (vm.sp) using raw offset access.
#[no_mangle]
pub extern "C" fn fun_op_rget_sp(vm: *mut Vm) -> i32 {
unsafe {
let base = vm_as_mut_ptr(vm) as *mut u8;
let off_sp = vm_offset_of_sp();
let sp_ptr = base.add(off_sp) as *const i32;
let sp = ptr::read(sp_ptr);
vm_push_i64(vm, sp as i64);
}
0
}
/// Demo: set vm.exit_code to a value popped from the stack (i64 -> i32 cast).
#[no_mangle]
pub extern "C" fn fun_op_rset_exit(vm: *mut Vm) -> i32 {
unsafe {
let code = vm_pop_i64(vm) as i32;
let base = vm_as_mut_ptr(vm) as *mut u8;
let off_ec = vm_offset_of_exit_code();
let ec_ptr = base.add(off_ec) as *mut i32;
ptr::write(ec_ptr, code);
}
0
}

View file

@ -22,6 +22,7 @@
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stddef.h>
#include <time.h>
#include <math.h>
@ -488,6 +489,35 @@ void vm_push_i64(VM *vm, int64_t v) {
push_value(vm, make_int(v));
}
/* --- Extended C ABI for Rust to access VM internals (unsafe) --- */
size_t vm_sizeof(void) {
return sizeof(VM);
}
size_t vm_value_sizeof(void) {
return sizeof(Value);
}
void *vm_as_mut_ptr(VM *vm) {
return (void*)vm;
}
size_t vm_offset_of_exit_code(void) {
return offsetof(VM, exit_code);
}
size_t vm_offset_of_sp(void) {
return offsetof(VM, sp);
}
size_t vm_offset_of_stack(void) {
return offsetof(VM, stack);
}
size_t vm_offset_of_globals(void) {
return offsetof(VM, globals);
}
static void frame_init(Frame *f) {
f->fn = NULL;
f->ip = 0;
@ -758,6 +788,9 @@ void vm_run(VM *vm, Bytecode *entry) {
/* Rust FFI demo opcode(s) */
#include "vm/rust/hello.c"
#include "vm/rust/hello_args.c"
#include "vm/rust/get_sp.c"
#include "vm/rust/set_exit.c"
#include "vm/os/env.c"
#include "vm/os/env_all.c"

View file

@ -11,6 +11,7 @@
#define FUN_VM_H
#include "bytecode.h"
#include <stddef.h>
#define MAX_FRAMES 128
#define MAX_FRAME_LOCALS 64
@ -56,7 +57,7 @@ static const char *opcode_names[] = {
"TRY_PUSH","TRY_POP","THROW",
"FMIN","FMAX",
/* Rust FFI demo */
"RUST_HELLO","RUST_HELLO_ARGS",
"RUST_HELLO","RUST_HELLO_ARGS","RUST_GET_SP","RUST_SET_EXIT",
/* Notcurses TUI (optional) */
"NC_INIT","NC_SHUTDOWN","NC_CLEAR","NC_DRAW_TEXT","NC_GETCH"
};
@ -160,4 +161,23 @@ const char *fun_rust_get_string(void);
/* Rust function that prints a passed C string, returns 0 on success. */
int fun_rust_print_string(const char *msg);
/* --- Extended C ABI for Rust to access VM internals (unsafe) --- */
/* Size helpers for Rust side to compute offsets and do pointer math */
size_t vm_sizeof(void);
size_t vm_value_sizeof(void);
/* Get a mutable byte pointer to the VM object. Extremely unsafe; intended for
* low-level FFI where Rust wants parity access with C code. */
void *vm_as_mut_ptr(VM *vm);
/* Offsets of commonly accessed VM fields to avoid re-declaring the struct layout in Rust */
size_t vm_offset_of_exit_code(void);
size_t vm_offset_of_sp(void);
size_t vm_offset_of_stack(void);
size_t vm_offset_of_globals(void);
/* Demo Rust ops using the extended ABI */
int fun_op_rget_sp(VM *vm);
int fun_op_rset_exit(VM *vm);
#endif

26
src/vm/rust/get_sp.c Normal file
View file

@ -0,0 +1,26 @@
/*
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-29
*/
/*
* Rust FFI demo opcode: OP_RUST_GET_SP
* Calls into Rust to read vm.sp via raw pointer math and pushes it (int).
*/
case OP_RUST_GET_SP: {
#ifdef FUN_WITH_RUST
extern int fun_op_rget_sp(VM *vm);
int rc = fun_op_rget_sp(vm);
(void)rc; /* rc currently unused; 0 means OK */
#else
vm_raise_error(vm, "RUST_GET_SP requires FUN_WITH_RUST=ON at build time");
push_value(vm, make_int(-1));
#endif
break;
}

View file

@ -2,7 +2,7 @@
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
@ -25,29 +25,3 @@ case OP_RUST_HELLO: {
#endif
break;
}
/*
* Rust FFI demo opcode with argument: OP_RUST_HELLO_ARGS
* Pops a string from the stack and asks Rust to print it. Pushes Nil.
*/
case OP_RUST_HELLO_ARGS: {
#ifdef FUN_WITH_RUST
Value vmsg = pop_value(vm);
char *msg = value_to_string_alloc(&vmsg);
free_value(vmsg);
if (msg) {
fun_rust_print_string(msg);
free(msg);
} else {
fun_rust_print_string("");
}
push_value(vm, make_nil());
#else
/* Still pop and free the arg to keep stack sane */
Value vmsg = pop_value(vm);
free_value(vmsg);
vm_raise_error(vm, "RUST_HELLO_ARGS requires FUN_WITH_RUST=ON at build time");
push_value(vm, make_nil());
#endif
break;
}

36
src/vm/rust/hello_args.c Normal file
View file

@ -0,0 +1,36 @@
/*
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-29
*/
/*
* Rust FFI demo opcode with argument: OP_RUST_HELLO_ARGS
* Pops a string from the stack and asks Rust to print it. Pushes Nil.
*/
case OP_RUST_HELLO_ARGS: {
#ifdef FUN_WITH_RUST
Value vmsg = pop_value(vm);
char *msg = value_to_string_alloc(&vmsg);
free_value(vmsg);
if (msg) {
fun_rust_print_string(msg);
free(msg);
} else {
fun_rust_print_string("");
}
push_value(vm, make_nil());
#else
/* Still pop and free the arg to keep stack sane */
Value vmsg = pop_value(vm);
free_value(vmsg);
vm_raise_error(vm, "RUST_HELLO_ARGS requires FUN_WITH_RUST=ON at build time");
push_value(vm, make_nil());
#endif
break;
}

33
src/vm/rust/set_exit.c Normal file
View file

@ -0,0 +1,33 @@
/*
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-01-29
*/
/*
* Rust FFI demo opcode: OP_RUST_SET_EXIT
* Pops an int code and asks Rust to write it into vm.exit_code.
*/
case OP_RUST_SET_EXIT: {
#ifdef FUN_WITH_RUST
extern int fun_op_rset_exit(VM *vm);
/* Expect an integer on the stack already (produced by prior ops) */
/* fun_op_rset_exit will pop it and store into vm.exit_code */
int rc = fun_op_rset_exit(vm);
(void)rc;
/* push Nil as a conventional result */
push_value(vm, make_nil());
#else
/* pop and ignore to keep stack sane */
Value v = pop_value(vm);
free_value(v);
vm_raise_error(vm, "RUST_SET_EXIT requires FUN_WITH_RUST=ON at build time");
push_value(vm, make_nil());
#endif
break;
}