From 7b15adfa524779913662f06a781674a3a6764f7a Mon Sep 17 00:00:00 2001 From: doinkythederp Date: Thu, 21 Nov 2024 20:30:37 -0800 Subject: [PATCH] feat: add display printing functions --- packages/runtime/armv7a-vex-v5.json | 26 --- packages/runtime/src/platform.rs | 8 + packages/runtime/src/sdk.rs | 117 +++++++++---- packages/runtime/src/teavm.rs | 233 ++++++++++++-------------- packages/wasm3/examples/wasm_print.rs | 3 +- packages/wasm3/src/environment.rs | 2 +- packages/wasm3/src/function.rs | 9 +- packages/wasm3/src/module.rs | 53 ++++-- packages/wasm3/src/store.rs | 5 +- 9 files changed, 248 insertions(+), 208 deletions(-) delete mode 100644 packages/runtime/armv7a-vex-v5.json diff --git a/packages/runtime/armv7a-vex-v5.json b/packages/runtime/armv7a-vex-v5.json deleted file mode 100644 index 1cde36c..0000000 --- a/packages/runtime/armv7a-vex-v5.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "cpu": "cortex-a9", - "arch": "arm", - "abi": "eabihf", - "os": "none", - "vendor": "vex", - "env": "v5", - "panic-strategy": "abort", - "relocation-model": "static", - "llvm-target": "armv7a-none-eabihf", - "features": "+v7,+neon,+vfp3,+thumb2", - "linker": "rust-lld", - "linker-flavor": "ld.lld", - "data-layout": "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64", - "max-atomic-width": 64, - "c-enum-min-bits": 8, - "target-pointer-width": "32", - "executables": true, - "post-link-args": { - "ld.lld": ["-Tv5.ld"] - }, - "has-thumb-interworking": true, - "default-uwtable": true, - "disable-redzone": true, - "emit-debug-gdb-scripts": false -} diff --git a/packages/runtime/src/platform.rs b/packages/runtime/src/platform.rs index e71afab..58b210e 100644 --- a/packages/runtime/src/platform.rs +++ b/packages/runtime/src/platform.rs @@ -9,3 +9,11 @@ pub fn read_user_program() -> &'static [u8] { core::slice::from_raw_parts(file_base, len as usize) } } + +pub fn flush_serial() { + while unsafe { vex_sdk::vexSerialWriteFree(1) < 2048 } { + unsafe { + vex_sdk::vexTasksRun(); + } + } +} diff --git a/packages/runtime/src/sdk.rs b/packages/runtime/src/sdk.rs index 843c2cd..bb8fe5a 100644 --- a/packages/runtime/src/sdk.rs +++ b/packages/runtime/src/sdk.rs @@ -2,22 +2,21 @@ use core::ffi::c_double; -use anyhow::Context; use vex_sdk::{ V5MotorBrakeMode, V5MotorControlMode, V5MotorEncoderUnits, V5MotorGearset, V5_ControllerId, - V5_ControllerIndex, V5_Device, V5_DeviceT, V5_DeviceType, + V5_ControllerIndex, V5_DeviceType, }; use wasm3::{store::AsContextMut, Instance, Store}; -use crate::{teavm::TeaVM, Data}; +use crate::{teavm::get_cstring, Data}; macro_rules! link { ($instance:ident, $store:ident, mod $module:literal { - $( fn $name:ident ( $($arg:ident: $arg_ty:ty $(as $wrapper:expr)? $(,)?),* ) $(-> $ret:ty $(, in .$field:tt)?)? );* $(;)? + $( fn $name:ident ( $($arg:ident: $arg_ty:ty $(as $wrapper:expr)? $(,)?),* ) $(-> $ret:ty $(, in .$field:tt)?)?; )* }) => { { $( - _ = $instance.link_closure( + $instance.link_closure( &mut *$store, $module, stringify!($name), @@ -33,7 +32,35 @@ macro_rules! link { } Ok(inner($($arg),*)) } - ).context(concat!("Unable to link ", $module, "::", stringify!($name), " function")); + )?; + )* + } + }; +} + +macro_rules! printf_style { + ($instance:ident, $store:ident, mod $module:literal { + $( fn $name:ident ( $($arg:ident: $arg_ty:ty,)* @printf@); )* + }) => { + { + $( + $instance.link_closure( + &mut *$store, + $module, + stringify!($name), + #[allow(unused_parens)] + |mut ctx, ($($arg,)* string): ($($arg_ty,)* i32)| { + let string = get_cstring(&mut ctx, string); + unsafe { + vex_sdk::$name( + $($arg,)* + c"%s".as_ptr(), + string.as_ptr(), + ); + } + Ok(()) + } + )?; )* } }; @@ -71,15 +98,6 @@ pub fn link(store: &mut Store, instance: &mut Instance) -> anyhow::R // fn vexImageBmpRead(ibuf: *const u8, oBuf: *mut v5_image, maxw: u32, maxh: u32) -> u32; // fn vexImagePngRead(ibuf: *const u8, oBuf: *mut v5_image, maxw: u32, maxh: u32, ibuflen: u32) -> u32; - // fn vexDisplayVPrintf(xpos: i32, ypos: i32, bOpaque: i32, format: *const c_char, args: VaList); - // fn vexDisplayVString(nLineNumber: i32, format: *const c_char, args: VaList); - // fn vexDisplayVStringAt(xpos: i32, ypos: i32, format: *const c_char, args: VaList); - // fn vexDisplayVBigString(nLineNumber: i32, format: *const c_char, args: VaList); - // fn vexDisplayVBigStringAt(xpos: i32, ypos: i32, format: *const c_char, args: VaList); - // fn vexDisplayVSmallStringAt(xpos: i32, ypos: i32, format: *const c_char, args: VaList); - // fn vexDisplayVCenteredString(nLineNumber: i32, format: *const c_char, args: VaList); - // fn vexDisplayVBigCenteredString(nLineNumber: i32, format: *const c_char, args: VaList); - // Controller fn vexControllerGet(id: u32 as V5_ControllerId, index: u32 as V5_ControllerIndex) -> i32; fn vexControllerConnectionStatusGet(id: u32 as V5_ControllerId) -> u32, in .0; @@ -140,26 +158,55 @@ pub fn link(store: &mut Store, instance: &mut Instance) -> anyhow::R fn vexCompetitionStatus() -> u32; }); - _ = instance - .link_closure( - &mut *store, - "vex", - "vexDeviceGetStatus", - |mut ctx, devices: i32| { - let teavm = ctx.data().teavm.clone().unwrap(); - let array_ptr = (teavm.byte_array_data)(ctx.as_context_mut(), devices).unwrap(); - let memory = ctx.memory_mut(); - let devices = &mut memory - [array_ptr as usize..(array_ptr as usize + vex_sdk::V5_MAX_DEVICE_PORTS)]; - - let devices = unsafe { - // SAFETY: V5_DeviceType is a repr(transparent) struct holding a u8 - core::mem::transmute::<*mut u8, *mut V5_DeviceType>(devices.as_mut_ptr()) - }; - Ok(unsafe { vex_sdk::vexDeviceGetStatus(devices) }) - }, - ) - .context("Unable to link vex::vexDeviceGetStatus function"); + printf_style!(instance, store, mod "vex" { + fn vexDisplayPrintf(xpos: i32, ypos: i32, bOpaque: i32, @printf@); + fn vexDisplayString(nLineNumber: i32, @printf@); + fn vexDisplayStringAt(xpos: i32, ypos: i32, @printf@); + fn vexDisplayBigString(nLineNumber: i32, @printf@); + fn vexDisplayBigStringAt(xpos: i32, ypos: i32, @printf@); + fn vexDisplaySmallStringAt(xpos: i32, ypos: i32, @printf@); + fn vexDisplayCenteredString(nLineNumber: i32, @printf@); + fn vexDisplayBigCenteredString(nLineNumber: i32, @printf@); + }); + + instance.link_closure( + &mut *store, + "vex", + "vexDeviceGetStatus", + |mut ctx, devices| { + let teavm = ctx.data().teavm.clone().unwrap(); + let array_ptr = (teavm.byte_array_data)(ctx.as_context_mut(), devices).unwrap(); + let memory = ctx.memory_mut(); + let devices = &mut memory + [array_ptr as usize..(array_ptr as usize + vex_sdk::V5_MAX_DEVICE_PORTS)]; + + let devices = unsafe { + // SAFETY: V5_DeviceType is a repr(transparent) struct holding a u8 + core::mem::transmute::<*mut u8, *mut V5_DeviceType>(devices.as_mut_ptr()) + }; + Ok(unsafe { vex_sdk::vexDeviceGetStatus(devices) }) + }, + )?; + + instance.link_closure( + &mut *store, + "vex", + "vexDisplayStringWidthGet", + |mut ctx, string: i32| { + let string = get_cstring(&mut ctx, string); + Ok(unsafe { vex_sdk::vexDisplayStringWidthGet(string.as_ptr()) }) + }, + )?; + + instance.link_closure( + &mut *store, + "vex", + "vexDisplayStringHeightGet", + |mut ctx, string: i32| { + let string = get_cstring(&mut ctx, string); + Ok(unsafe { vex_sdk::vexDisplayStringHeightGet(string.as_ptr()) }) + }, + )?; Ok(()) } diff --git a/packages/runtime/src/teavm.rs b/packages/runtime/src/teavm.rs index 2813f2e..34bc73f 100644 --- a/packages/runtime/src/teavm.rs +++ b/packages/runtime/src/teavm.rs @@ -1,166 +1,145 @@ #![allow(non_snake_case)] -use alloc::{boxed::Box, rc::Rc, string::String, sync::Arc}; +use alloc::{ffi::CString, rc::Rc, string::String}; use core::str; -use anyhow::Context; -use vexide::core::{print, println, sync::OnceLock, time::Instant}; +use anyhow::{Context, Result}; +use vexide::core::{print, println, time::Instant}; use wasm3::{ - error::Result, store::{AsContextMut, StoreContextMut}, - Function, Instance, Store, WasmArg, WasmType, + Function, Instance, Store, }; -use crate::Data; - -pub fn link_teavm(store: &mut Store, instance: &mut Instance) -> anyhow::Result<()> { - let teavm_catchException = instance - .find_function::<(), i32>(store, "teavm_catchException") - .context("teavm_catchException")?; - let teavm_allocateStringArray = wrap( - instance - .find_function::(store, "teavm_allocateStringArray") - .context("teavm_allocateStringArray")?, - teavm_catchException, - ); - let teavm_objectArrayData = wrap( - instance - .find_function::(store, "teavm_objectArrayData") - .context("teavm_objectArrayData")?, - teavm_catchException, - ); - let teavm_byteArrayData = wrap( - instance - .find_function::(store, "teavm_byteArrayData") - .context("teavm_byteArrayData")?, - teavm_catchException, - ); - let teavm_allocateString = wrap( - instance - .find_function::(store, "teavm_allocateString") - .context("teavm_allocateString")?, - teavm_catchException, - ); - let teavm_stringData = wrap( - instance - .find_function::(store, "teavm_stringData") - .context("teavm_stringData")?, - teavm_catchException, - ); - let teavm_arrayLength = wrap( - instance - .find_function::(store, "teavm_arrayLength") - .context("teavm_arrayLength")?, - teavm_catchException, - ); +use crate::{platform::flush_serial, Data}; +pub fn link_teavm(store: &mut Store, instance: &mut Instance) -> Result<()> { let teavm = TeaVM { - catch_exception: teavm_catchException, - allocate_string_array: Rc::new(teavm_allocateStringArray), - object_array_data: Rc::new(teavm_objectArrayData), - byte_array_data: Rc::new(teavm_byteArrayData), - allocate_string: Rc::new(teavm_allocateString), - string_data: Rc::new(teavm_stringData), - array_length: Rc::new(teavm_arrayLength), + catch_exception: instance + .find_function::<(), i32>(store, "teavm_catchException") + .context("finding teavm interop function")?, + allocate_string_array: wrap(&mut *store, &mut *instance, "teavm_allocateStringArray")?, + object_array_data: wrap(&mut *store, &mut *instance, "teavm_objectArrayData")?, + byte_array_data: wrap(&mut *store, &mut *instance, "teavm_byteArrayData")?, + allocate_string: wrap(&mut *store, &mut *instance, "teavm_allocateString")?, + string_data: wrap(&mut *store, &mut *instance, "teavm_stringData")?, + array_length: wrap(&mut *store, &mut *instance, "teavm_arrayLength")?, + short_array_data: wrap(&mut *store, &mut *instance, "teavm_shortArrayData")?, + char_array_data: wrap(&mut *store, &mut *instance, "teavm_charArrayData")?, + int_array_data: wrap(&mut *store, &mut *instance, "teavm_intArrayData")?, + long_array_data: wrap(&mut *store, &mut *instance, "teavm_longArrayData")?, + float_array_data: wrap(&mut *store, &mut *instance, "teavm_floatArrayData")?, + double_array_data: wrap(&mut *store, &mut *instance, "teavm_doubleArrayData")?, }; store.data_mut().teavm = Some(teavm); - _ = instance - .link_closure( - store, - "teavm", - "putwcharsOut", - |mut ctx, (chars, count): (u32, u32)| { - let mem = ctx.memory_mut(); - let string = - str::from_utf8(&mem[chars as usize..(chars + count) as usize]).unwrap(); - print!("{string}"); - Ok(()) - }, - ) - .context("putwcharsOut"); - - _ = instance - .link_closure( - store, - "teavm", - "putwcharsErr", - |mut ctx, (chars, count): (u32, u32)| { - let mem = ctx.memory_mut(); - let string = - str::from_utf8(&mem[chars as usize..(chars + count) as usize]).unwrap(); - print!("{string}"); - Ok(()) - }, - ) - .context("putwcharsErr"); + instance.link_closure( + store, + "teavm", + "putwcharsOut", + |mut ctx, (chars, count): (u32, u32)| { + let mem = ctx.memory_mut(); + let string = str::from_utf8(&mem[chars as usize..(chars + count) as usize]).unwrap(); + print!("{string}"); + Ok(()) + }, + )?; + + instance.link_closure( + store, + "teavm", + "putwcharsErr", + |mut ctx, (chars, count): (u32, u32)| { + let mem = ctx.memory_mut(); + let string = str::from_utf8(&mem[chars as usize..(chars + count) as usize]).unwrap(); + print!("{string}"); + Ok(()) + }, + )?; let epoch = Instant::now(); - _ = instance - .link_closure(store, "teavm", "currentTimeMillis", move |_ctx, ()| { - let secs = epoch.elapsed().as_secs_f64(); - Ok(secs * 1000.0) - }) - .context("currentTimeMillis"); - - _ = instance - .link_closure(store, "teavm", "logString", move |mut ctx, string: i32| { - let teavm = ctx.data().teavm.clone().unwrap(); - let array_ptr = (teavm.string_data)(ctx.as_context_mut(), string).unwrap() as usize; - let len = - (teavm.array_length)(ctx.as_context_mut(), array_ptr as i32).unwrap() as usize; - let bytes = len * size_of::(); - - let memory = ctx.memory(); - let array = &memory[array_ptr..array_ptr + bytes]; - let string = String::from_utf16_lossy(bytemuck::cast_slice(array)); + instance.link_closure(store, "teavm", "currentTimeMillis", move |_ctx, ()| { + let secs = epoch.elapsed().as_secs_f64(); + Ok(secs * 1000.0) + })?; - print!("{string}"); + instance.link_closure(store, "teavm", "logString", move |mut ctx, string: i32| { + let string = get_string(&mut ctx, string); - Ok(()) - }) - .context("logString"); + print!("{string}"); - _ = instance - .link_closure(store, "teavm", "logInt", move |_ctx, int: i32| { - print!("{int}"); - Ok(()) - }) - .context("logInt"); + Ok(()) + })?; - _ = instance - .link_closure(store, "teavm", "logOutOfMemory", move |_ctx, ()| { - println!("Out of memory"); - Ok(()) - }) - .context("logOutOfMemory"); + instance.link_closure(store, "teavm", "logInt", move |_ctx, int: i32| { + print!("{int}"); + Ok(()) + })?; + + instance.link_closure(store, "teavm", "logOutOfMemory", move |_ctx, ()| { + println!("Out of memory"); + Ok(()) + })?; Ok(()) } -fn wrap( - func: Function, - catch: Function<(), i32>, -) -> impl Fn(StoreContextMut, T) -> Result { - move |mut ctx, args| { +/// Copies a UTF16 string out of the JVM's memory and into a Rust [`String`]. +pub fn get_string(ctx: &mut wasm3::CallContext, string: i32) -> String { + let teavm = ctx.data().teavm.clone().unwrap(); + + // get pointer & length of the utf16 buffer java stores strings in + let array = (teavm.string_data)(ctx.as_context_mut(), string).unwrap(); + let len = (teavm.array_length)(ctx.as_context_mut(), array).unwrap() as usize; + let bytes = len * size_of::(); + let array_addr = (teavm.char_array_data)(ctx.as_context_mut(), array).unwrap() as usize; + + let memory = ctx.memory(); + let array = &memory[array_addr..array_addr + bytes]; + String::from_utf16_lossy(bytemuck::cast_slice(array)) +} + +/// Copies a UTF16 string out of the JVM's memory and into a Rust [`CString`]. +pub fn get_cstring(ctx: &mut wasm3::CallContext, string: i32) -> CString { + CString::new(get_string(ctx, string)).unwrap() +} + +type TeaVMDataGetter = dyn Fn(StoreContextMut, i32) -> Result; + +fn wrap( + store: &mut Store, + instance: &mut Instance, + func: &str, +) -> Result> { + let teavm_catchException = instance + .find_function::<(), i32>(store, "teavm_catchException") + .context("finding teavm interop function")?; + let func = instance + .find_function::(store, func) + .context("finding teavm interop function")?; + + Ok(Rc::new(move |mut ctx, args| { let result = func.call(&mut ctx, args)?; - let exception = catch.call(&mut ctx)?; + let exception = teavm_catchException.call(&mut ctx)?; if exception != 0 { panic!("Java code threw an exception"); } Ok(result) - } + })) } -type TeaVMDataGetter = dyn Fn(StoreContextMut, i32) -> Result; - #[derive(Clone)] pub struct TeaVM { pub catch_exception: Function<(), i32>, pub allocate_string_array: Rc, pub object_array_data: Rc, pub byte_array_data: Rc, + pub short_array_data: Rc, + pub char_array_data: Rc, + pub int_array_data: Rc, + pub long_array_data: Rc, + pub float_array_data: Rc, + pub double_array_data: Rc, pub allocate_string: Rc, pub string_data: Rc, pub array_length: Rc, @@ -198,6 +177,8 @@ pub fn teamvm_main( args_data[i] = java_arg; } + flush_serial(); + let start = instance .find_function::(store, "start") .context("getting start function")?; diff --git a/packages/wasm3/examples/wasm_print.rs b/packages/wasm3/examples/wasm_print.rs index da1462f..3e273d4 100644 --- a/packages/wasm3/examples/wasm_print.rs +++ b/packages/wasm3/examples/wasm_print.rs @@ -1,5 +1,4 @@ -use wasm3::Environment; -use wasm3::Instance; +use wasm3::{Environment, Instance}; #[cfg(feature = "wasi")] fn main() { diff --git a/packages/wasm3/src/environment.rs b/packages/wasm3/src/environment.rs index e54d97e..5cd9814 100644 --- a/packages/wasm3/src/environment.rs +++ b/packages/wasm3/src/environment.rs @@ -1,4 +1,4 @@ -use alloc::{borrow::Cow, boxed::Box, rc::Rc}; +use alloc::{borrow::Cow, rc::Rc}; use core::ptr::NonNull; use crate::{ diff --git a/packages/wasm3/src/function.rs b/packages/wasm3/src/function.rs index f2367f5..e4600cb 100644 --- a/packages/wasm3/src/function.rs +++ b/packages/wasm3/src/function.rs @@ -5,17 +5,16 @@ use core::{ ffi::{c_void, CStr}, hash::{Hash, Hasher}, marker::PhantomData, - ptr::{self, NonNull}, + ptr::NonNull, slice, str, }; use ffi::{M3Function, M3Module}; -use snafu::ensure; use crate::{ - error::{Error, Result, StoreMismatchSnafu}, - store::{AsContext, AsContextMut, Store, StoreContext, StoreContextMut, StoredData}, - Instance, WasmArg, WasmArgs, WasmType, + error::{Error, Result}, + store::{AsContext, AsContextMut, StoreContext, StoreContextMut, StoredData}, + WasmArg, WasmArgs, WasmType, }; /// Calling Context for a host function. diff --git a/packages/wasm3/src/module.rs b/packages/wasm3/src/module.rs index 887f469..65a7d01 100644 --- a/packages/wasm3/src/module.rs +++ b/packages/wasm3/src/module.rs @@ -1,4 +1,4 @@ -use alloc::{borrow::Cow, boxed::Box, ffi::CString, rc::Rc, vec::Vec}; +use alloc::{borrow::Cow, boxed::Box, ffi::CString, rc::Rc, string::String, vec::Vec}; use core::{ cell::RefCell, ffi::{c_char, c_void, CStr}, @@ -7,6 +7,7 @@ use core::{ }; use ffi::M3Module; +use snafu::{IntoError, ResultExt, Snafu}; use crate::{ environment::Environment, @@ -15,6 +16,16 @@ use crate::{ store::{AsContext, Store, StoreContext, StoredData}, }; +/// Failed to link a WASM function. +#[derive(Debug, Snafu)] +#[snafu(display("Failed to link function `{name}`"))] +pub struct ClosureLinkFailed { + /// The source of the error. + source: Error, + /// The name of the function that was being linked. + name: String, +} + #[derive(Debug)] pub(crate) struct RawModule { pub inner: NonNull, @@ -83,7 +94,7 @@ impl Instance { /// runtime parts. For easier use the [`make_func_wrapper`] should be used to create /// the unsafe facade for your function that then can be passed to this. /// - /// For a simple API see [`link_closure`] which takes a closure instead. + /// For a simple API see [`Self::link_closure`] which takes a closure instead. /// /// # Errors /// @@ -92,8 +103,6 @@ impl Instance { /// * a memory allocation failed /// * no function by the given name in the given module could be found /// * the function has been found but the signature did not match - /// - /// [`link_closure`]: #method.link_closure pub fn link_function( &mut self, store: &mut Store, @@ -136,7 +145,7 @@ impl Instance { module_name: &str, function_name: &str, closure: F, - ) -> Result<()> + ) -> core::result::Result<(), ClosureLinkFailed> where Args: crate::WasmArgs, Ret: crate::WasmType, @@ -180,8 +189,18 @@ impl Instance { result.cast() } - let module_name_cstr = CString::new(module_name)?; - let function_name_cstr = CString::new(function_name)?; + let module_name_cstr = + CString::new(module_name) + .map_err(Error::from) + .context(ClosureLinkFailedSnafu { + name: function_name, + })?; + let function_name_cstr = + CString::new(function_name) + .map_err(Error::from) + .context(ClosureLinkFailedSnafu { + name: function_name, + })?; let signature = function_signature::(); let mut closure = Box::pin(UserData { @@ -189,16 +208,30 @@ impl Instance { data: store.data_ref(), }); - unsafe { + let err = unsafe { Error::from_ffi(ffi::m3_LinkRawFunctionEx( - self.0.get(&store.as_context())?.as_ptr(), + self.0 + .get(&store.as_context()) + .context(ClosureLinkFailedSnafu { + name: function_name, + })? + .as_ptr(), module_name_cstr.as_ptr(), function_name_cstr.as_ptr(), signature.as_ptr(), Some(trampoline::), closure.as_mut().get_unchecked_mut() as *const UserData as *const c_void, - ))?; + )) + }; + + if let Err(err) = err { + if err != Error::FunctionNotFound { + Err(err).context(ClosureLinkFailedSnafu { + name: function_name, + })?; + } } + store.push_closure(closure); Ok(()) } diff --git a/packages/wasm3/src/store.rs b/packages/wasm3/src/store.rs index b5c31c9..2df4178 100644 --- a/packages/wasm3/src/store.rs +++ b/packages/wasm3/src/store.rs @@ -1,5 +1,5 @@ use alloc::{ - borrow::{Cow, ToOwned}, + borrow::Cow, boxed::Box, ffi::CString, rc::Rc, @@ -7,7 +7,7 @@ use alloc::{ vec::Vec, }; use core::{ - cell::{Ref, RefCell, RefMut, UnsafeCell}, + cell::{Ref, RefCell, RefMut}, ffi::CStr, hash::Hash, marker::PhantomData, @@ -17,7 +17,6 @@ use core::{ slice, }; -use ffi::M3ErrorInfo; use snafu::ensure; use crate::{