From c2e7f3869ea5661c91e8cd1dd40b7f095059f501 Mon Sep 17 00:00:00 2001 From: hanez Date: Thu, 29 Jan 2026 02:53:14 +0100 Subject: [PATCH] Some more Fun VM and Rust interaction. (0.38.2) --- CMakeLists.txt | 2 +- cmake/Targets.cmake | 1 - docs/rust.md | 2 ++ examples/rust_hello.fun | 2 +- examples/rust_hello_args.fun | 2 +- examples/rust_vm_access.fun | 39 ++++++++++++++++++++++++++++++++++++ src/bytecode.c | 2 ++ src/bytecode.h | 2 ++ src/parser.c | 15 ++++++++++++++ src/rust/src/lib.rs | 16 +++------------ src/rust/src/vm/mod.rs | 36 ++++++++++++++++++++++++++++++++- src/vm.c | 33 ++++++++++++++++++++++++++++++ src/vm.h | 22 +++++++++++++++++++- src/vm/rust/get_sp.c | 26 ++++++++++++++++++++++++ src/vm/rust/hello.c | 28 +------------------------- src/vm/rust/hello_args.c | 36 +++++++++++++++++++++++++++++++++ src/vm/rust/set_exit.c | 33 ++++++++++++++++++++++++++++++ 17 files changed, 251 insertions(+), 46 deletions(-) create mode 100644 examples/rust_vm_access.fun create mode 100644 src/vm/rust/get_sp.c create mode 100644 src/vm/rust/hello_args.c create mode 100644 src/vm/rust/set_exit.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d93022..1e619dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/cmake/Targets.cmake b/cmake/Targets.cmake index dbdb7da..99146ec 100644 --- a/cmake/Targets.cmake +++ b/cmake/Targets.cmake @@ -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() diff --git a/docs/rust.md b/docs/rust.md index 9a884bc..9657c1f 100644 --- a/docs/rust.md +++ b/docs/rust.md @@ -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. diff --git a/examples/rust_hello.fun b/examples/rust_hello.fun index e5455db..434da0e 100755 --- a/examples/rust_hello.fun +++ b/examples/rust_hello.fun @@ -4,7 +4,7 @@ * This file is part of the Fun programming language. * https://fun-lang.xyz/ * - * Copyright 2025 Johannes Findeisen + * Copyright 2026 Johannes Findeisen * Licensed under the terms of the Apache-2.0 license. * https://opensource.org/license/apache-2-0 * diff --git a/examples/rust_hello_args.fun b/examples/rust_hello_args.fun index 9fd2e70..652ae37 100755 --- a/examples/rust_hello_args.fun +++ b/examples/rust_hello_args.fun @@ -4,7 +4,7 @@ * This file is part of the Fun programming language. * https://fun-lang.xyz/ * - * Copyright 2025 Johannes Findeisen + * Copyright 2026 Johannes Findeisen * Licensed under the terms of the Apache-2.0 license. * https://opensource.org/license/apache-2-0 * diff --git a/examples/rust_vm_access.fun b/examples/rust_vm_access.fun new file mode 100644 index 0000000..b4e2474 --- /dev/null +++ b/examples/rust_vm_access.fun @@ -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 + * 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 diff --git a/src/bytecode.c b/src/bytecode.c index 82d8ca1..ed55ff8 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -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 "???"; } } diff --git a/src/bytecode.h b/src/bytecode.h index 81c12c1..4fbf9ef 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -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 diff --git a/src/parser.c b/src/parser.c index bef13c9..78697a7 100644 --- a/src/parser.c +++ b/src/parser.c @@ -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; } diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index 115785b..fbd9be7 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -2,15 +2,13 @@ * This file is part of the Fun programming language. * https://fun-lang.xyz/ * - * Copyright 2025 Johannes Findeisen + * Copyright 2026 Johannes Findeisen * 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 diff --git a/src/rust/src/vm/mod.rs b/src/rust/src/vm/mod.rs index aec43a4..2816307 100644 --- a/src/rust/src/vm/mod.rs +++ b/src/rust/src/vm/mod.rs @@ -2,7 +2,7 @@ * This file is part of the Fun programming language. * https://fun-lang.xyz/ * - * Copyright 2025 Johannes Findeisen + * Copyright 2026 Johannes Findeisen * 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 +} diff --git a/src/vm.c b/src/vm.c index 4c78db9..46c451e 100644 --- a/src/vm.c +++ b/src/vm.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -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" diff --git a/src/vm.h b/src/vm.h index e746a96..1503b0d 100644 --- a/src/vm.h +++ b/src/vm.h @@ -11,6 +11,7 @@ #define FUN_VM_H #include "bytecode.h" +#include #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 diff --git a/src/vm/rust/get_sp.c b/src/vm/rust/get_sp.c new file mode 100644 index 0000000..1a08b30 --- /dev/null +++ b/src/vm/rust/get_sp.c @@ -0,0 +1,26 @@ +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * 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; +} diff --git a/src/vm/rust/hello.c b/src/vm/rust/hello.c index 6878c46..32772ca 100644 --- a/src/vm/rust/hello.c +++ b/src/vm/rust/hello.c @@ -2,7 +2,7 @@ * This file is part of the Fun programming language. * https://fun-lang.xyz/ * - * Copyright 2025 Johannes Findeisen + * Copyright 2026 Johannes Findeisen * 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; -} diff --git a/src/vm/rust/hello_args.c b/src/vm/rust/hello_args.c new file mode 100644 index 0000000..d361907 --- /dev/null +++ b/src/vm/rust/hello_args.c @@ -0,0 +1,36 @@ +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * 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; +} diff --git a/src/vm/rust/set_exit.c b/src/vm/rust/set_exit.c new file mode 100644 index 0000000..70f4314 --- /dev/null +++ b/src/vm/rust/set_exit.c @@ -0,0 +1,33 @@ +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * 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; +}