From 3a716add8fe7dd31ba0c4aceaf4a4085362d7f5a Mon Sep 17 00:00:00 2001 From: Wodann Date: Thu, 5 Mar 2020 16:09:30 +0100 Subject: [PATCH] feat(runtime): add marshalling of value structs --- crates/mun/src/main.rs | 7 +- crates/mun_codegen/src/intrinsics.rs | 3 + crates/mun_codegen/src/intrinsics/macros.rs | 2 +- crates/mun_codegen/src/ir.rs | 8 + crates/mun_codegen/src/ir/dispatch_table.rs | 9 + .../src/snapshots/test__field_crash.snap | 4 +- .../src/snapshots/test__field_expr.snap | 6 +- .../src/snapshots/test__gc_struct.snap | 4 +- .../src/snapshots/test__struct_test.snap | 3 + crates/mun_codegen/src/type_info.rs | 7 + crates/mun_runtime/examples/hot_reloading.rs | 7 +- crates/mun_runtime/src/lib.rs | 30 ++- crates/mun_runtime/src/macros.rs | 73 +++---- crates/mun_runtime/src/marshal.rs | 38 +++- crates/mun_runtime/src/reflection.rs | 62 +++++- crates/mun_runtime/src/struct.rs | 125 +++++++---- crates/mun_runtime/src/test.rs | 205 +++++++++++------- 17 files changed, 407 insertions(+), 186 deletions(-) diff --git a/crates/mun/src/main.rs b/crates/mun/src/main.rs index 3266db3dc..e8343b005 100644 --- a/crates/mun/src/main.rs +++ b/crates/mun/src/main.rs @@ -1,6 +1,8 @@ #[macro_use] extern crate failure; +use std::cell::RefCell; +use std::rc::Rc; use std::time::Duration; use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; @@ -84,10 +86,11 @@ fn build(matches: &ArgMatches) -> Result<(), failure::Error> { /// Starts the runtime with the specified library and invokes function `entry`. fn start(matches: &ArgMatches) -> Result<(), failure::Error> { - let mut runtime = runtime(matches)?; + let runtime = Rc::new(RefCell::new(runtime(matches)?)); + let borrowed = runtime.borrow(); let entry_point = matches.value_of("entry").unwrap_or("main"); - let fn_info = runtime.get_function_info(entry_point).ok_or_else(|| { + let fn_info = borrowed.get_function_info(entry_point).ok_or_else(|| { std::io::Error::new( std::io::ErrorKind::InvalidInput, format!("Failed to obtain entry point '{}'", entry_point), diff --git a/crates/mun_codegen/src/intrinsics.rs b/crates/mun_codegen/src/intrinsics.rs index bbe2d6131..d4106231d 100644 --- a/crates/mun_codegen/src/intrinsics.rs +++ b/crates/mun_codegen/src/intrinsics.rs @@ -1,4 +1,5 @@ use crate::ir::dispatch_table::FunctionPrototype; +use crate::type_info::TypeInfo; use inkwell::context::Context; use inkwell::types::FunctionType; @@ -18,4 +19,6 @@ pub trait Intrinsic: Sync { intrinsics! { /// Allocates memory from the runtime to use in code. pub fn malloc(size: u64, alignment: u64) -> *mut u8; + /// Allocates memory for and clones the specified type located at `src` into it. + pub fn clone(src: *const u8, ty: *const TypeInfo) -> *mut u8; } diff --git a/crates/mun_codegen/src/intrinsics/macros.rs b/crates/mun_codegen/src/intrinsics/macros.rs index 72839a69a..dc49d3ac0 100644 --- a/crates/mun_codegen/src/intrinsics/macros.rs +++ b/crates/mun_codegen/src/intrinsics/macros.rs @@ -1,5 +1,5 @@ macro_rules! intrinsics{ - ($($(#[$attr:meta])* pub fn $name:ident($($arg_name:ident:$arg:ty),*) -> $ret:ty;);*) => { + ($($(#[$attr:meta])* pub fn $name:ident($($arg_name:ident:$arg:ty),+) -> $ret:ty;)+) => { $( paste::item! { pub struct []; diff --git a/crates/mun_codegen/src/ir.rs b/crates/mun_codegen/src/ir.rs index 6ff1b9284..fab6ed00e 100644 --- a/crates/mun_codegen/src/ir.rs +++ b/crates/mun_codegen/src/ir.rs @@ -1,3 +1,4 @@ +use crate::type_info::TypeInfo; use inkwell::context::Context; use inkwell::types::{ AnyType, AnyTypeEnum, BasicType, BasicTypeEnum, FunctionType, IntType, PointerType, @@ -183,6 +184,13 @@ impl> IsPointerType for *const T { } } +// HACK: Manually add `*const TypeInfo` +impl IsPointerType for *const TypeInfo { + fn ir_type(context: &Context) -> PointerType { + context.i8_type().ptr_type(AddressSpace::Const) + } +} + impl> IsPointerType for *mut T { fn ir_type(context: &Context) -> PointerType { T::ir_type(context).ptr_type(AddressSpace::Generic) diff --git a/crates/mun_codegen/src/ir/dispatch_table.rs b/crates/mun_codegen/src/ir/dispatch_table.rs index baaef9f7f..d01f76389 100644 --- a/crates/mun_codegen/src/ir/dispatch_table.rs +++ b/crates/mun_codegen/src/ir/dispatch_table.rs @@ -181,6 +181,9 @@ impl<'a, D: IrDatabase> DispatchTableBuilder<'a, D> { match infer[*callee].as_callable_def() { Some(hir::CallableDef::Function(def)) => self.collect_fn_def(def), Some(hir::CallableDef::Struct(s)) => { + // self.collect_intrinsic(&intrinsics::new); + self.collect_intrinsic(&intrinsics::clone); + // self.collect_intrinsic(&intrinsics::drop); if s.data(self.db).memory_kind == hir::StructMemoryKind::GC { self.collect_intrinsic(&intrinsics::malloc) } @@ -192,6 +195,9 @@ impl<'a, D: IrDatabase> DispatchTableBuilder<'a, D> { if let Expr::RecordLit { .. } = expr { let struct_ty = infer[expr_id].clone(); let hir_struct = struct_ty.as_struct().unwrap(); // Can only really get here if the type is a struct + // self.collect_intrinsic(&intrinsics::new); + self.collect_intrinsic(&intrinsics::clone); + // self.collect_intrinsic(&intrinsics::drop); if hir_struct.data(self.db).memory_kind == hir::StructMemoryKind::GC { self.collect_intrinsic(&intrinsics::malloc) } @@ -205,6 +211,9 @@ impl<'a, D: IrDatabase> DispatchTableBuilder<'a, D> { .expect("unknown path"); if let hir::Resolution::Def(hir::ModuleDef::Struct(s)) = resolution { + // self.collect_intrinsic(&intrinsics::new); + self.collect_intrinsic(&intrinsics::clone); + // self.collect_intrinsic(&intrinsics::drop); if s.data(self.db).memory_kind == hir::StructMemoryKind::GC { self.collect_intrinsic(&intrinsics::malloc) } diff --git a/crates/mun_codegen/src/snapshots/test__field_crash.snap b/crates/mun_codegen/src/snapshots/test__field_crash.snap index fa9b982cc..5cf383f7e 100644 --- a/crates/mun_codegen/src/snapshots/test__field_crash.snap +++ b/crates/mun_codegen/src/snapshots/test__field_crash.snap @@ -5,7 +5,7 @@ expression: "struct(gc) Foo { a: int };\n\nfn main(c:int):int {\n let b = Foo ; ModuleID = 'main.mun' source_filename = "main.mun" -%DispatchTable = type { i8* (i64, i64)* } +%DispatchTable = type { i8* (i8 addrspace(4)*, i8 addrspace(4)*)*, i8* (i64, i64)* } %Foo = type { i64 } @dispatchTable = global %DispatchTable zeroinitializer @@ -18,7 +18,7 @@ body: %c1 = load i64, i64* %c %add = add i64 %c1, 5 %init = insertvalue %Foo undef, i64 %add, 0 - %malloc_ptr = load i8* (i64, i64)*, i8* (i64, i64)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 0) + %malloc_ptr = load i8* (i64, i64)*, i8* (i64, i64)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 1) %malloc = call i8* %malloc_ptr(i64 ptrtoint (i64* getelementptr (i64, i64* null, i32 1) to i64), i64 ptrtoint (i64* getelementptr ({ i1, i64 }, { i1, i64 }* null, i64 0, i32 1) to i64)) %Foo = bitcast i8* %malloc to %Foo* store %Foo %init, %Foo* %Foo diff --git a/crates/mun_codegen/src/snapshots/test__field_expr.snap b/crates/mun_codegen/src/snapshots/test__field_expr.snap index 8668bcf45..9f8ea630d 100644 --- a/crates/mun_codegen/src/snapshots/test__field_expr.snap +++ b/crates/mun_codegen/src/snapshots/test__field_expr.snap @@ -1,15 +1,15 @@ --- source: crates/mun_codegen/src/test.rs -expression: "struct Bar(float, Foo);\nstruct Foo { a: int };\n\nfn bar_0(bar: Bar): float {\n bar.0\n}\n\nfn bar_1(bar: Bar): Foo {\n bar.1\n}\n\nfn bar_1_a(bar: Bar): int {\n bar.1.a\n}\n\nfn foo_a(foo: Foo): int {\n foo.a\n}\n\nfn bar_1_foo_a(bar: Bar): int {\n foo_a(bar_1(bar))\n}\n\nfn main(): int {\n let a: Foo = Foo { a: 5 };\n let b: Bar = Bar(1.23, a);\n let aa_lhs = a.a + 2;\n let aa_rhs = 2 + a.a;\n aa_lhs + aa_rhs\n}" +expression: "struct(value) Bar(float, Foo);\nstruct(value) Foo { a: int };\n\nfn bar_0(bar: Bar): float {\n bar.0\n}\n\nfn bar_1(bar: Bar): Foo {\n bar.1\n}\n\nfn bar_1_a(bar: Bar): int {\n bar.1.a\n}\n\nfn foo_a(foo: Foo): int {\n foo.a\n}\n\nfn bar_1_foo_a(bar: Bar): int {\n foo_a(bar_1(bar))\n}\n\nfn main(): int {\n let a: Foo = Foo { a: 5 };\n let b: Bar = Bar(1.23, a);\n let aa_lhs = a.a + 2;\n let aa_rhs = 2 + a.a;\n aa_lhs + aa_rhs\n}" --- ; ModuleID = 'main.mun' source_filename = "main.mun" -%DispatchTable = type { i64 (%Foo)*, %Foo (%Bar)* } +%DispatchTable = type { i64 (%Foo)*, %Foo (%Bar)*, i8* (i8 addrspace(4)*, i8 addrspace(4)*)* } %Foo = type { i64 } %Bar = type { double, %Foo } -@dispatchTable = global %DispatchTable { i64 (%Foo)* @foo_a, %Foo (%Bar)* @bar_1 } +@dispatchTable = global %DispatchTable { i64 (%Foo)* @foo_a, %Foo (%Bar)* @bar_1, i8* (i8 addrspace(4)*, i8 addrspace(4)*)* null } define double @bar_0(%Bar) { body: diff --git a/crates/mun_codegen/src/snapshots/test__gc_struct.snap b/crates/mun_codegen/src/snapshots/test__gc_struct.snap index 20846593c..0806e5f06 100644 --- a/crates/mun_codegen/src/snapshots/test__gc_struct.snap +++ b/crates/mun_codegen/src/snapshots/test__gc_struct.snap @@ -5,7 +5,7 @@ expression: "struct(gc) Foo { a: int, b: int };\n\nfn foo() {\n let a = Foo { ; ModuleID = 'main.mun' source_filename = "main.mun" -%DispatchTable = type { i8* (i64, i64)* } +%DispatchTable = type { i8* (i8 addrspace(4)*, i8 addrspace(4)*)*, i8* (i64, i64)* } %Foo = type { i64, i64 } @dispatchTable = global %DispatchTable zeroinitializer @@ -14,7 +14,7 @@ define void @foo() { body: %b4 = alloca %Foo* %a = alloca %Foo* - %malloc_ptr = load i8* (i64, i64)*, i8* (i64, i64)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 0) + %malloc_ptr = load i8* (i64, i64)*, i8* (i64, i64)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 1) %malloc = call i8* %malloc_ptr(i64 mul nuw (i64 ptrtoint (i64* getelementptr (i64, i64* null, i32 1) to i64), i64 2), i64 ptrtoint (i64* getelementptr ({ i1, i64 }, { i1, i64 }* null, i64 0, i32 1) to i64)) %Foo = bitcast i8* %malloc to %Foo* store %Foo { i64 3, i64 4 }, %Foo* %Foo diff --git a/crates/mun_codegen/src/snapshots/test__struct_test.snap b/crates/mun_codegen/src/snapshots/test__struct_test.snap index ec0552775..7c12ba243 100644 --- a/crates/mun_codegen/src/snapshots/test__struct_test.snap +++ b/crates/mun_codegen/src/snapshots/test__struct_test.snap @@ -5,10 +5,13 @@ expression: "struct(value) Bar(float, int, bool, Foo);\nstruct(value) Foo { a: i ; ModuleID = 'main.mun' source_filename = "main.mun" +%DispatchTable = type { i8* (i8 addrspace(4)*, i8 addrspace(4)*)* } %Baz = type {} %Bar = type { double, i64, i1, %Foo } %Foo = type { i64 } +@dispatchTable = global %DispatchTable zeroinitializer + define void @foo() { body: %c = alloca %Baz diff --git a/crates/mun_codegen/src/type_info.rs b/crates/mun_codegen/src/type_info.rs index b56db2521..f088559d2 100644 --- a/crates/mun_codegen/src/type_info.rs +++ b/crates/mun_codegen/src/type_info.rs @@ -116,6 +116,13 @@ impl HasStaticTypeInfo for *const T { } } +// HACK: Manually add `*const TypeInfo` +impl HasStaticTypeInfo for *const TypeInfo { + fn type_info() -> TypeInfo { + TypeInfo::new("*const TypeInfo", TypeGroup::FundamentalTypes) + } +} + /// A trait that statically defines that a type can be used as a return type for a function. pub trait HasStaticReturnTypeInfo { fn return_type_info() -> Option; diff --git a/crates/mun_runtime/examples/hot_reloading.rs b/crates/mun_runtime/examples/hot_reloading.rs index 7f544d14e..cc0c63e7a 100644 --- a/crates/mun_runtime/examples/hot_reloading.rs +++ b/crates/mun_runtime/examples/hot_reloading.rs @@ -1,5 +1,7 @@ use mun_runtime::{invoke_fn, RetryResultExt, RuntimeBuilder}; +use std::cell::RefCell; use std::env; +use std::rc::Rc; // How to run? // 1. On the CLI, navigate to the `crates/mun_runtime/examples` directory. @@ -9,14 +11,15 @@ fn main() { let lib_dir = env::args().nth(1).expect("Expected path to a Mun library."); println!("lib: {}", lib_dir); - let mut runtime = RuntimeBuilder::new(lib_dir) + let runtime = RuntimeBuilder::new(lib_dir) .spawn() .expect("Failed to spawn Runtime"); + let runtime = Rc::new(RefCell::new(runtime)); loop { let n: i64 = invoke_fn!(runtime, "nth").wait(); let result: i64 = invoke_fn!(runtime, "fibonacci", n).wait(); println!("fibonacci({}) = {}", n, result); - runtime.update(); + runtime.borrow_mut().update(); } } diff --git a/crates/mun_runtime/src/lib.rs b/crates/mun_runtime/src/lib.rs index 14bea37ac..564909f93 100644 --- a/crates/mun_runtime/src/lib.rs +++ b/crates/mun_runtime/src/lib.rs @@ -19,19 +19,20 @@ use std::alloc::Layout; use std::collections::HashMap; use std::io; use std::path::{Path, PathBuf}; +use std::ptr; use std::sync::mpsc::{channel, Receiver}; use std::time::Duration; -use abi::{FunctionInfo, Privacy}; +use abi::{FunctionInfo, Privacy, TypeInfo}; use failure::Error; use function::FunctionInfoStorage; use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher}; -pub use crate::marshal::MarshalInto; +pub use crate::marshal::Marshal; pub use crate::reflection::{ArgumentReflection, ReturnTypeReflection}; pub use crate::assembly::Assembly; -pub use crate::r#struct::Struct; +pub use crate::r#struct::StructRef; /// Options for the construction of a [`Runtime`]. #[derive(Clone, Debug)] @@ -116,6 +117,18 @@ extern "C" fn malloc(size: u64, alignment: u64) -> *mut u8 { } } +extern "C" fn clone(src: *const u8, ty: *const TypeInfo) -> *mut u8 { + let type_info = unsafe { ty.as_ref().unwrap() }; + let struct_info = type_info.as_struct().unwrap(); + let size = struct_info.field_offsets().last().cloned().unwrap_or(0) + + struct_info.field_sizes().last().cloned().unwrap_or(0); + let alignment = 8; + + let dest = malloc(size as u64, alignment); + unsafe { ptr::copy_nonoverlapping(src, dest, size as usize) }; + dest +} + impl Runtime { /// Constructs a new `Runtime` that loads the library at `library_path` and its /// dependencies. The `Runtime` contains a file watcher that is triggered with an interval @@ -131,8 +144,17 @@ impl Runtime { malloc as *const std::ffi::c_void, ); + let (clone_info, clone_storage) = FunctionInfoStorage::new_function( + "clone", + &["*const core::u8".to_string(), "*const TypeInfo".to_string()], + Some("*mut core::u8".to_string()), + Privacy::Public, + clone as *const std::ffi::c_void, + ); + let mut dispatch_table = DispatchTable::default(); dispatch_table.insert_fn("malloc", malloc_info); + dispatch_table.insert_fn("clone", clone_info); let watcher: RecommendedWatcher = Watcher::new(tx, options.delay)?; let mut runtime = Runtime { @@ -141,7 +163,7 @@ impl Runtime { watcher, watcher_rx: rx, - _local_fn_storage: vec![malloc_storage], + _local_fn_storage: vec![malloc_storage, clone_storage], }; runtime.add_assembly(&options.library_path)?; diff --git a/crates/mun_runtime/src/macros.rs b/crates/mun_runtime/src/macros.rs index d286dfa37..41d9c5deb 100644 --- a/crates/mun_runtime/src/macros.rs +++ b/crates/mun_runtime/src/macros.rs @@ -16,36 +16,36 @@ macro_rules! invoke_fn_impl { /// An invocation error that contains the function name, a mutable reference to the /// runtime, passed arguments, and the output type. This allows the caller to retry /// the function invocation using the `Retriable` trait. - pub struct $ErrName<'r, 's, $($T: ArgumentReflection,)* Output:ReturnTypeReflection> { + pub struct $ErrName<'s, $($T: ArgumentReflection,)* Output: ReturnTypeReflection> { msg: String, - runtime: &'r mut Runtime, + runtime: std::rc::Rc>, function_name: &'s str, $($Arg: $T,)* output: core::marker::PhantomData, } - impl<'r, 's, $($T: ArgumentReflection,)* Output: ReturnTypeReflection> core::fmt::Debug for $ErrName<'r, 's, $($T,)* Output> { + impl<'s, $($T: ArgumentReflection,)* Output: ReturnTypeReflection> core::fmt::Debug for $ErrName<'s, $($T,)* Output> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{}", &self.msg) } } - impl<'r, 's, $($T: ArgumentReflection,)* Output: ReturnTypeReflection> core::fmt::Display for $ErrName<'r, 's, $($T,)* Output> { + impl<'s, $($T: ArgumentReflection,)* Output: ReturnTypeReflection> core::fmt::Display for $ErrName<'s, $($T,)* Output> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{}", &self.msg) } } - impl<'r, 's, $($T: ArgumentReflection,)* Output: ReturnTypeReflection> std::error::Error for $ErrName<'r, 's, $($T,)* Output> { + impl<'s, $($T: ArgumentReflection,)* Output: ReturnTypeReflection> std::error::Error for $ErrName<'s, $($T,)* Output> { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } } - impl<'r, 's, $($T: ArgumentReflection,)* Output: ReturnTypeReflection> $ErrName<'r, 's, $($T,)* Output> { + impl<'s, $($T: ArgumentReflection,)* Output: ReturnTypeReflection> $ErrName<'s, $($T,)* Output> { /// Constructs a new invocation error. #[allow(clippy::too_many_arguments)] - pub fn new(err_msg: String, runtime: &'r mut Runtime, function_name: &'s str, $($Arg: $T),*) -> Self { + pub fn new(err_msg: String, runtime: std::rc::Rc>, function_name: &'s str, $($Arg: $T),*) -> Self { Self { msg: err_msg, runtime, @@ -56,7 +56,7 @@ macro_rules! invoke_fn_impl { } } - impl<'r, 's, $($T: ArgumentReflection,)* Output: ReturnTypeReflection> $crate::RetryResultExt for core::result::Result> { + impl<'s, $($T: ArgumentReflection,)* Output: ReturnTypeReflection> $crate::RetryResultExt for core::result::Result> { type Output = Output; fn retry(self) -> Self { @@ -64,10 +64,10 @@ macro_rules! invoke_fn_impl { Ok(output) => Ok(output), Err(err) => { eprintln!("{}", err.msg); - while !err.runtime.update() { + while !err.runtime.borrow_mut().update() { // Wait until there has been an update that might fix the error } - $crate::Runtime::$FnName(err.runtime, err.function_name, $(err.$Arg,)*) + $crate::Runtime::$FnName(&err.runtime, err.function_name, $(err.$Arg,)*) } } } @@ -76,8 +76,9 @@ macro_rules! invoke_fn_impl { loop { if let Ok(output) = self { return output; + } else { + self = self.retry(); } - self = self.retry(); } } } @@ -89,12 +90,13 @@ macro_rules! invoke_fn_impl { /// If an error occurs when invoking the method, an error message is logged. The /// runtime continues looping until the cause of the error has been resolved. #[allow(clippy::too_many_arguments, unused_assignments)] - pub fn $FnName<'r, 's, $($T: ArgumentReflection,)* Output: ReturnTypeReflection>( - runtime: &'r mut Runtime, + pub fn $FnName<'s, $($T: ArgumentReflection,)* Output: ReturnTypeReflection>( + runtime: &std::rc::Rc>, function_name: &'s str, $($Arg: $T,)* - ) -> core::result::Result> { + ) -> core::result::Result> { match runtime + .borrow() .get_function_info(function_name) .ok_or(format!("Failed to obtain function '{}'", function_name)) .and_then(|function_info| { @@ -148,9 +150,9 @@ macro_rules! invoke_fn_impl { let result = function($($Arg.marshal()),*); // Marshall the result - Ok(result.marshal_into(function_info.signature.return_type())) + return Ok(result.marshal_value(runtime.clone(), function_info.signature.return_type())) } - Err(e) => Err($ErrName::new(e, runtime, function_name, $($Arg),*)) + Err(e) => Err($ErrName::new(e, runtime.clone(), function_name, $($Arg),*)) } } } @@ -169,59 +171,46 @@ macro_rules! invoke_fn_impl { #[macro_export] macro_rules! invoke_fn { ($Runtime:expr, $FnName:expr) => { - $crate::Runtime::invoke_fn0(&mut $Runtime, $FnName) + $crate::Runtime::invoke_fn0(&$Runtime, $FnName) }; ($Runtime:expr, $FnName:expr, $A:expr) => { - $crate::Runtime::invoke_fn1(&mut $Runtime, $FnName, $A) + $crate::Runtime::invoke_fn1(&$Runtime, $FnName, $A) }; ($Runtime:expr, $FnName:expr, $A:expr, $B:expr) => { - $crate::Runtime::invoke_fn2(&mut $Runtime, $FnName, $A, $B) + $crate::Runtime::invoke_fn2(&$Runtime, $FnName, $A, $B) }; ($Runtime:expr, $FnName:expr, $A:expr, $B:expr, $C:expr) => { - $crate::Runtime::invoke_fn3(&mut $Runtime, $FnName, $A, $B, $C) + $crate::Runtime::invoke_fn3(&$Runtime, $FnName, $A, $B, $C) }; ($Runtime:expr, $FnName:expr, $A:expr, $B:expr, $C:expr, $D:expr) => { - $crate::Runtime::invoke_fn4(&mut $Runtime, $FnName, $A, $B, $C, $D) + $crate::Runtime::invoke_fn4(&$Runtime, $FnName, $A, $B, $C, $D) }; ($Runtime:expr, $FnName:expr, $A:expr, $B:expr, $C:expr, $D:expr, $E:expr) => { - $crate::Runtime::invoke_fn5(&mut $Runtime, $FnName, $A, $B, $C, $D, $E) + $crate::Runtime::invoke_fn5(&$Runtime, $FnName, $A, $B, $C, $D, $E) }; ($Runtime:expr, $FnName:expr, $A:expr, $B:expr, $C:expr, $D:expr, $E:expr, $F:expr) => { - $crate::Runtime::invoke_fn6(&mut $Runtime, $FnName, $A, $B, $C, $D, $E, $F) + $crate::Runtime::invoke_fn6(&$Runtime, $FnName, $A, $B, $C, $D, $E, $F) }; ($Runtime:expr, $FnName:expr, $A:expr, $B:expr, $C:expr, $D:expr, $E:expr, $F:expr, $G:expr) => { - $crate::Runtime::invoke_fn7(&mut $Runtime, $FnName, $A, $B, $C, $D, $E, $F, $G) + $crate::Runtime::invoke_fn7(&$Runtime, $FnName, $A, $B, $C, $D, $E, $F, $G) }; ($Runtime:expr, $FnName:expr, $A:expr, $B:expr, $C:expr, $D:expr, $E:expr, $F:expr, $G:expr, $H:expr) => { - $crate::Runtime::invoke_fn8(&mut $Runtime, $FnName, $A, $B, $C, $D, $E, $F, $G, $H) + $crate::Runtime::invoke_fn8(&$Runtime, $FnName, $A, $B, $C, $D, $E, $F, $G, $H) }; ($Runtime:expr, $FnName:expr, $A:expr, $B:expr, $C:expr, $D:expr, $E:expr, $F:expr, $G:expr, $H:expr, $I:expr) => { - $crate::Runtime::invoke_fn9(&mut $Runtime, $FnName, $A, $B, $C, $D, $E, $F, $G, $H, $I) + $crate::Runtime::invoke_fn9(&$Runtime, $FnName, $A, $B, $C, $D, $E, $F, $G, $H, $I) }; ($Runtime:expr, $FnName:expr, $A:expr, $B:expr, $C:expr, $D:expr, $E:expr, $F:expr, $G:expr, $H:expr, $I:expr, $J:expr) => { - $crate::Runtime::invoke_fn10( - &mut $Runtime, - $FnName, - $A, - $B, - $C, - $D, - $E, - $F, - $G, - $H, - $I, - $J, - ) + $crate::Runtime::invoke_fn10(&$Runtime, $FnName, $A, $B, $C, $D, $E, $F, $G, $H, $I, $J) }; ($Runtime:expr, $FnName:expr, $A:expr, $B:expr, $C:expr, $D:expr, $E:expr, $F:expr, $G:expr, $H:expr, $I:expr, $J:expr, $K:expr) => { $crate::Runtime::invoke_fn11( - $Runtime, $FnName, $A, $B, $C, $D, $E, $F, $G, $H, $I, $J, $K, + &$Runtime, $FnName, $A, $B, $C, $D, $E, $F, $G, $H, $I, $J, $K, ) }; ($Runtime:expr, $FnName:expr, $A:expr, $B:expr, $C:expr, $D:expr, $E:expr, $F:expr, $G:expr, $H:expr, $I:expr, $J:expr, $K:expr, $L:expr) => { $crate::Runtime::invoke_fn12( - $Runtime, $FnName, $A, $B, $C, $D, $E, $F, $G, $H, $I, $J, $K, $L, + &$Runtime, $FnName, $A, $B, $C, $D, $E, $F, $G, $H, $I, $J, $K, $L, ) }; } diff --git a/crates/mun_runtime/src/marshal.rs b/crates/mun_runtime/src/marshal.rs index 889fa42cd..15b2f5c5f 100644 --- a/crates/mun_runtime/src/marshal.rs +++ b/crates/mun_runtime/src/marshal.rs @@ -1,16 +1,44 @@ +use crate::Runtime; use abi::TypeInfo; +use std::cell::RefCell; +use std::ptr::NonNull; +use std::rc::Rc; /// Used to do value-to-value conversions that require runtime type information while consuming the /// input value. /// /// If no `TypeInfo` is provided, the type is `()`. -pub trait MarshalInto: Sized { - /// Performs the conversion. - fn marshal_into(self, type_info: Option<&TypeInfo>) -> T; +pub trait Marshal: Sized { + /// Marshals itself into a `T`. + fn marshal_value(self, runtime: Rc>, type_info: Option<&TypeInfo>) -> T; + + /// Marshals the value at memory location `ptr` into a `T`. + fn marshal_from_ptr( + ptr: NonNull, + runtime: Rc>, + type_info: Option<&TypeInfo>, + ) -> T; + + /// Marshals `value` to memory location `ptr`. + fn marshal_to_ptr(value: Self, ptr: NonNull, type_info: Option<&TypeInfo>); } -impl MarshalInto for T { - fn marshal_into(self, _type_info: Option<&TypeInfo>) -> T { +impl Marshal for T { + fn marshal_value(self, _runtime: Rc>, _type_info: Option<&TypeInfo>) -> T { self } + + fn marshal_from_ptr<'r>( + ptr: NonNull, + _runtime: Rc>, + _type_info: Option<&TypeInfo>, + ) -> T { + // TODO: Avoid unsafe `read` fn by using adding `Clone` trait to T. + // This also requires changes to the `impl Struct` + unsafe { ptr.as_ptr().read() } + } + + fn marshal_to_ptr(value: T, mut ptr: NonNull, _type_info: Option<&TypeInfo>) { + unsafe { *ptr.as_mut() = value }; + } } diff --git a/crates/mun_runtime/src/reflection.rs b/crates/mun_runtime/src/reflection.rs index b5d1a8e25..1402c819a 100644 --- a/crates/mun_runtime/src/reflection.rs +++ b/crates/mun_runtime/src/reflection.rs @@ -1,4 +1,4 @@ -use crate::{marshal::MarshalInto, Struct}; +use crate::{marshal::Marshal, StructRef}; use abi::{Guid, TypeInfo}; use md5; @@ -25,7 +25,7 @@ pub fn equals_return_type( } } abi::TypeGroup::StructTypes => { - if ::type_guid() != T::type_guid() { + if ::type_guid() != T::type_guid() { return Err(("struct", T::type_name())); } } @@ -34,9 +34,9 @@ pub fn equals_return_type( } /// A type to emulate dynamic typing across compilation units for static types. -pub trait ReturnTypeReflection: Sized + 'static { +pub trait ReturnTypeReflection: Sized { /// The resulting type after marshaling. - type Marshalled: MarshalInto; + type Marshalled: Marshal; /// Retrieves the type's `Guid`. fn type_guid() -> Guid { @@ -52,7 +52,7 @@ pub trait ReturnTypeReflection: Sized + 'static { /// A type to emulate dynamic typing across compilation units for statically typed values. pub trait ArgumentReflection: Sized { /// The resulting type after dereferencing. - type Marshalled: MarshalInto; + type Marshalled: Marshal; /// Retrieves the `Guid` of the value's type. fn type_guid(&self) -> Guid { @@ -116,6 +116,42 @@ impl ArgumentReflection for () { } } +impl ArgumentReflection for *const u8 { + type Marshalled = Self; + + fn type_name(&self) -> &str { + ::type_name() + } + + fn marshal(self) -> Self::Marshalled { + self + } +} + +impl ArgumentReflection for *mut u8 { + type Marshalled = Self; + + fn type_name(&self) -> &str { + ::type_name() + } + + fn marshal(self) -> Self::Marshalled { + self + } +} + +impl ArgumentReflection for *const TypeInfo { + type Marshalled = Self; + + fn type_name(&self) -> &str { + "*const TypeInfo" + } + + fn marshal(self) -> Self::Marshalled { + self + } +} + impl ReturnTypeReflection for f64 { type Marshalled = f64; @@ -147,3 +183,19 @@ impl ReturnTypeReflection for () { "core::empty" } } + +impl ReturnTypeReflection for *const u8 { + type Marshalled = Self; + + fn type_name() -> &'static str { + "*const core::u8" + } +} + +impl ReturnTypeReflection for *mut u8 { + type Marshalled = Self; + + fn type_name() -> &'static str { + "*mut core::u8" + } +} diff --git a/crates/mun_runtime/src/struct.rs b/crates/mun_runtime/src/struct.rs index 33fafb6b2..dbdfd1fe8 100644 --- a/crates/mun_runtime/src/struct.rs +++ b/crates/mun_runtime/src/struct.rs @@ -1,11 +1,14 @@ use crate::{ - marshal::MarshalInto, + marshal::Marshal, reflection::{ equals_argument_type, equals_return_type, ArgumentReflection, ReturnTypeReflection, }, + Runtime, }; -use abi::{StructInfo, TypeInfo}; -use std::mem; +use abi::{StructInfo, StructMemoryKind, TypeInfo}; +use std::cell::RefCell; +use std::ptr::{self, NonNull}; +use std::rc::Rc; /// Represents a Mun struct pointer. /// @@ -16,20 +19,21 @@ pub struct RawStruct(*mut u8); /// Type-agnostic wrapper for interoperability with a Mun struct. /// TODO: Handle destruction of `struct(value)` -#[derive(Clone)] -pub struct Struct { +pub struct StructRef { + runtime: Rc>, raw: RawStruct, info: StructInfo, } -impl Struct { +impl StructRef { /// Creates a struct that wraps a raw Mun struct. /// /// The provided [`TypeInfo`] must be for a struct type. - fn new(type_info: &TypeInfo, raw: RawStruct) -> Self { + fn new(runtime: Rc>, type_info: &TypeInfo, raw: RawStruct) -> StructRef { assert!(type_info.group.is_struct()); Self { + runtime, raw, info: type_info.as_struct().unwrap().clone(), } @@ -40,6 +44,22 @@ impl Struct { self.raw } + /// Retrieves its struct information. + pub fn info(&self) -> &StructInfo { + &self.info + } + + /// + /// + /// # Safety + /// + /// + unsafe fn offset_unchecked(&self, field_idx: usize) -> NonNull { + let offset = *self.info.field_offsets().get_unchecked(field_idx); + // self.raw is never null + NonNull::new_unchecked(self.raw.0.add(offset as usize)).cast::() + } + /// Retrieves the value of the field corresponding to the specified `field_name`. pub fn get(&self, field_name: &str) -> Result { let field_idx = StructInfo::find_field_index(&self.info, field_name)?; @@ -54,20 +74,13 @@ impl Struct { ) })?; - let field_value = unsafe { - // If we found the `field_idx`, we are guaranteed to also have the `field_offset` - let offset = *self.info.field_offsets().get_unchecked(field_idx); - // self.ptr is never null - // TODO: The unsafe `read` fn could be avoided by adding the `Clone` bound on - // `T::Marshalled`, but its only available on nightly: - // `ReturnTypeReflection` - self.raw - .0 - .add(offset as usize) - .cast::() - .read() - }; - Ok(field_value.marshal_into(Some(*field_type))) + // If we found the `field_idx`, we are guaranteed to also have the `field_offset` + let field_ptr = unsafe { self.offset_unchecked::(field_idx) }; + Ok(Marshal::marshal_from_ptr( + field_ptr, + self.runtime.clone(), + Some(*field_type), + )) } /// Replaces the value of the field corresponding to the specified `field_name` and returns the @@ -89,15 +102,10 @@ impl Struct { ) })?; - let mut marshalled: T::Marshalled = value.marshal(); - let ptr = unsafe { - // If we found the `field_idx`, we are guaranteed to also have the `field_offset` - let offset = *self.info.field_offsets().get_unchecked(field_idx); - // self.ptr is never null - &mut *self.raw.0.add(offset as usize).cast::() - }; - mem::swap(&mut marshalled, ptr); - Ok(marshalled.marshal_into(Some(*field_type))) + let field_ptr = unsafe { self.offset_unchecked::(field_idx) }; + let old = Marshal::marshal_from_ptr(field_ptr, self.runtime.clone(), Some(*field_type)); + Marshal::marshal_to_ptr(value.marshal(), field_ptr, Some(*field_type)); + Ok(old) } /// Sets the value of the field corresponding to the specified `field_name`. @@ -114,17 +122,13 @@ impl Struct { ) })?; - unsafe { - // If we found the `field_idx`, we are guaranteed to also have the `field_offset` - let offset = *self.info.field_offsets().get_unchecked(field_idx); - // self.ptr is never null - *self.raw.0.add(offset as usize).cast::() = value.marshal(); - } + let field_ptr = unsafe { self.offset_unchecked::(field_idx) }; + Marshal::marshal_to_ptr(value.marshal(), field_ptr, Some(*field_type)); Ok(()) } } -impl ArgumentReflection for Struct { +impl ArgumentReflection for StructRef { type Marshalled = RawStruct; fn type_name(&self) -> &str { @@ -136,7 +140,7 @@ impl ArgumentReflection for Struct { } } -impl ReturnTypeReflection for Struct { +impl ReturnTypeReflection for StructRef { type Marshalled = RawStruct; fn type_name() -> &'static str { @@ -144,9 +148,48 @@ impl ReturnTypeReflection for Struct { } } -impl MarshalInto for RawStruct { - fn marshal_into(self, type_info: Option<&TypeInfo>) -> Struct { +impl Marshal for RawStruct { + fn marshal_value( + self, + runtime: Rc>, + type_info: Option<&TypeInfo>, + ) -> StructRef { + // `type_info` is only `None` for the `()` type + StructRef::new(runtime, type_info.unwrap(), self) + } + + fn marshal_from_ptr( + ptr: NonNull, + runtime: Rc>, + type_info: Option<&TypeInfo>, + ) -> StructRef { // `type_info` is only `None` for the `()` type - Struct::new(type_info.unwrap(), self) + let type_info = type_info.unwrap(); + + let struct_info = type_info.as_struct().unwrap(); + let ptr = if struct_info.memory_kind == StructMemoryKind::Value { + ptr.cast::().as_ptr() as *const _ + } else { + unsafe { ptr.as_ref() }.0 as *const _ + }; + + // Clone the struct using the runtime's intrinsic + let cloned_ptr = invoke_fn!(runtime.clone(), "clone", ptr, type_info as *const _).unwrap(); + StructRef::new(runtime, type_info, RawStruct(cloned_ptr)) + } + + fn marshal_to_ptr(value: RawStruct, mut ptr: NonNull, type_info: Option<&TypeInfo>) { + // `type_info` is only `None` for the `()` type + let type_info = type_info.unwrap(); + + let struct_info = type_info.as_struct().unwrap(); + if struct_info.memory_kind == StructMemoryKind::Value { + let dest = ptr.cast::().as_ptr(); + let size = struct_info.field_offsets().last().cloned().unwrap_or(0) + + struct_info.field_sizes().last().cloned().unwrap_or(0); + unsafe { ptr::copy_nonoverlapping(value.0, dest, size as usize) }; + } else { + unsafe { *ptr.as_mut() = value }; + } } } diff --git a/crates/mun_runtime/src/test.rs b/crates/mun_runtime/src/test.rs index 2366d1f11..28218a5c1 100644 --- a/crates/mun_runtime/src/test.rs +++ b/crates/mun_runtime/src/test.rs @@ -1,6 +1,8 @@ -use crate::{Runtime, RuntimeBuilder, Struct}; +use crate::{ArgumentReflection, ReturnTypeReflection, Runtime, RuntimeBuilder, StructRef}; use mun_compiler::{ColorChoice, Config, Driver, FileId, PathOrInline, RelativePathBuf}; +use std::cell::RefCell; use std::path::PathBuf; +use std::rc::Rc; use std::thread::sleep; use std::time::Duration; @@ -11,7 +13,7 @@ struct TestDriver { out_path: PathBuf, file_id: FileId, driver: Driver, - runtime: Runtime, + runtime: Rc>, } impl TestDriver { @@ -38,7 +40,7 @@ impl TestDriver { driver, out_path, file_id, - runtime, + runtime: Rc::new(RefCell::new(runtime)), } } @@ -51,7 +53,7 @@ impl TestDriver { "recompiling did not result in the same assembly" ); let start_time = std::time::Instant::now(); - while !self.runtime.update() { + while !self.runtime.borrow_mut().update() { let now = std::time::Instant::now(); if now - start_time > std::time::Duration::from_secs(10) { panic!("runtime did not update after recompilation within 10secs"); @@ -60,23 +62,18 @@ impl TestDriver { } } } - - /// Returns the `Runtime` used by this instance - fn runtime_mut(&mut self) -> &mut Runtime { - &mut self.runtime - } } macro_rules! assert_invoke_eq { ($ExpectedType:ty, $ExpectedResult:expr, $Driver:expr, $($Arg:tt)+) => { - let result: $ExpectedType = invoke_fn!($Driver.runtime_mut(), $($Arg)*).unwrap(); + let result: $ExpectedType = invoke_fn!($Driver.runtime, $($Arg)*).unwrap(); assert_eq!(result, $ExpectedResult, "{} == {:?}", stringify!(invoke_fn!($Driver.runtime_mut(), $($Arg)*).unwrap()), $ExpectedResult); } } #[test] fn compile_and_run() { - let mut driver = TestDriver::new( + let driver = TestDriver::new( r" pub fn main() {} ", @@ -86,7 +83,7 @@ fn compile_and_run() { #[test] fn return_value() { - let mut driver = TestDriver::new( + let driver = TestDriver::new( r" pub fn main():int { 3 } ", @@ -96,7 +93,7 @@ fn return_value() { #[test] fn arguments() { - let mut driver = TestDriver::new( + let driver = TestDriver::new( r" pub fn main(a:int, b:int):int { a+b } ", @@ -108,7 +105,7 @@ fn arguments() { #[test] fn dispatch_table() { - let mut driver = TestDriver::new( + let driver = TestDriver::new( r" pub fn add(a:int, b:int):int { a+b } pub fn main(a:int, b:int):int { add(a,b) } @@ -126,7 +123,7 @@ fn dispatch_table() { #[test] fn booleans() { - let mut driver = TestDriver::new( + let driver = TestDriver::new( r#" pub fn equal(a:int, b:int):bool { a==b } pub fn equalf(a:float, b:float):bool { a==b } @@ -170,7 +167,7 @@ fn booleans() { #[test] fn fibonacci() { - let mut driver = TestDriver::new( + let driver = TestDriver::new( r#" pub fn fibonacci(n:int):int { if n <= 1 { @@ -189,7 +186,7 @@ fn fibonacci() { #[test] fn fibonacci_loop() { - let mut driver = TestDriver::new( + let driver = TestDriver::new( r#" pub fn fibonacci(n:int):int { let a = 0; @@ -216,7 +213,7 @@ fn fibonacci_loop() { #[test] fn fibonacci_loop_break() { - let mut driver = TestDriver::new( + let driver = TestDriver::new( r#" pub fn fibonacci(n:int):int { let a = 0; @@ -243,7 +240,7 @@ fn fibonacci_loop_break() { #[test] fn fibonacci_while() { - let mut driver = TestDriver::new( + let driver = TestDriver::new( r#" pub fn fibonacci(n:int):int { let a = 0; @@ -268,7 +265,7 @@ fn fibonacci_while() { #[test] fn true_is_true() { - let mut driver = TestDriver::new( + let driver = TestDriver::new( r#" pub fn test_true():bool { true @@ -314,7 +311,8 @@ fn compiler_valid_utf8() { "#, ); - let foo_func = driver.runtime.get_function_info("foo").unwrap(); + let borrowed = driver.runtime.borrow(); + let foo_func = borrowed.get_function_info("foo").unwrap(); assert_eq!( unsafe { CStr::from_ptr(foo_func.signature.name) } .to_str() @@ -352,7 +350,7 @@ fn compiler_valid_utf8() { #[test] fn fields() { - let mut driver = TestDriver::new( + let driver = TestDriver::new( r#" struct(gc) Foo { a:int, b:int }; pub fn main(foo:int):bool { @@ -369,7 +367,7 @@ fn fields() { #[test] fn field_crash() { - let mut driver = TestDriver::new( + let driver = TestDriver::new( r#" struct(gc) Foo { a: int }; @@ -384,79 +382,132 @@ fn field_crash() { #[test] fn marshal_struct() { - let mut driver = TestDriver::new( + let driver = TestDriver::new( r#" - struct(gc) Foo { a: int, b: bool, c: float, }; - struct Bar(Foo); + struct(value) Foo { a: int, b: bool }; + struct Bar(int, bool); + struct(value) Baz(Foo); + struct(gc) Qux(Bar); - pub fn foo_new(a: int, b: bool, c: float): Foo { - Foo { a, b, c, } + pub fn foo_new(a: int, b: bool): Foo { + Foo { a, b, } } - pub fn bar_new(foo: Foo): Bar { - Bar(foo) + pub fn bar_new(a: int, b: bool): Bar { + Bar(a, b) + } + pub fn baz_new(foo: Foo): Baz { + Baz(foo) + } + pub fn qux_new(bar: Bar): Qux { + Qux(bar) } - - pub fn foo_a(foo: Foo):int { foo.a } - pub fn foo_b(foo: Foo):bool { foo.b } - pub fn foo_c(foo: Foo):float { foo.c } "#, ); - let a = 3i64; - let b = true; - let c = 1.23f64; - let mut foo: Struct = invoke_fn!(driver.runtime, "foo_new", a, b, c).unwrap(); - assert_eq!(Ok(a), foo.get::("a")); - assert_eq!(Ok(b), foo.get::("b")); - assert_eq!(Ok(c), foo.get::("c")); - - let d = 6i64; - let e = false; - let f = 4.56f64; - foo.set("a", d).unwrap(); - foo.set("b", e).unwrap(); - foo.set("c", f).unwrap(); - - assert_eq!(Ok(d), foo.get::("a")); - assert_eq!(Ok(e), foo.get::("b")); - assert_eq!(Ok(f), foo.get::("c")); - - assert_eq!(Ok(d), foo.replace("a", a)); - assert_eq!(Ok(e), foo.replace("b", b)); - assert_eq!(Ok(f), foo.replace("c", c)); - - assert_eq!(Ok(a), foo.get::("a")); - assert_eq!(Ok(b), foo.get::("b")); - assert_eq!(Ok(c), foo.get::("c")); - - assert_invoke_eq!(i64, a, driver, "foo_a", foo.clone()); - assert_invoke_eq!(bool, b, driver, "foo_b", foo.clone()); - assert_invoke_eq!(f64, c, driver, "foo_c", foo.clone()); - - let mut bar: Struct = invoke_fn!(driver.runtime, "bar_new", foo.clone()).unwrap(); - let foo2 = bar.get::("0").unwrap(); - assert_eq!(Ok(a), foo2.get::("a")); - assert_eq!(foo2.get::("b"), foo.get::("b")); - assert_eq!(foo2.get::("c"), foo.get::("c")); + struct TestData(T, T); + + fn test_field< + T: Copy + std::fmt::Debug + PartialEq + ArgumentReflection + ReturnTypeReflection, + >( + s: &mut StructRef, + data: &TestData, + field_name: &str, + ) { + assert_eq!(Ok(data.0), s.get::(field_name)); + s.set(field_name, data.1).unwrap(); + assert_eq!(Ok(data.1), s.replace(field_name, data.0)); + assert_eq!(Ok(data.0), s.get::(field_name)); + } + + let int_data = TestData(3i64, 6i64); + let bool_data = TestData(true, false); + + // Verify that struct marshalling works for fundamental types + let mut foo: StructRef = + invoke_fn!(driver.runtime, "foo_new", int_data.0, bool_data.0).unwrap(); + test_field(&mut foo, &int_data, "a"); + test_field(&mut foo, &bool_data, "b"); + + let mut bar: StructRef = + invoke_fn!(driver.runtime, "bar_new", int_data.0, bool_data.0).unwrap(); + test_field(&mut bar, &int_data, "0"); + test_field(&mut bar, &bool_data, "1"); + + fn test_struct(s: &mut StructRef, c1: StructRef, c2: StructRef) { + let field_names: Vec = c1.info().field_names().map(|n| n.to_string()).collect(); + + let int_value = c2.get::(&field_names[0]); + let bool_value = c2.get::(&field_names[1]); + s.set("0", c2).unwrap(); + + let c2 = s.get::("0").unwrap(); + assert_eq!(c2.get::(&field_names[0]), int_value); + assert_eq!(c2.get::(&field_names[1]), bool_value); + + let int_value = c1.get::(&field_names[0]); + let bool_value = c1.get::(&field_names[1]); + s.replace("0", c1).unwrap(); + + let c1 = s.get::("0").unwrap(); + assert_eq!(c1.get::(&field_names[0]), int_value); + assert_eq!(c1.get::(&field_names[1]), bool_value); + } + + // Verify that struct marshalling works for struct types + let mut baz: StructRef = invoke_fn!(driver.runtime, "baz_new", foo).unwrap(); + let c1: StructRef = invoke_fn!(driver.runtime, "foo_new", int_data.0, bool_data.0).unwrap(); + let c2: StructRef = invoke_fn!(driver.runtime, "foo_new", int_data.1, bool_data.1).unwrap(); + test_struct(&mut baz, c1, c2); + + let mut qux: StructRef = invoke_fn!(driver.runtime, "qux_new", bar).unwrap(); + let c1: StructRef = invoke_fn!(driver.runtime, "bar_new", int_data.0, bool_data.0).unwrap(); + let c2: StructRef = invoke_fn!(driver.runtime, "bar_new", int_data.1, bool_data.1).unwrap(); + test_struct(&mut qux, c1, c2); + + fn test_shallow_copy< + T: Copy + std::fmt::Debug + PartialEq + ArgumentReflection + ReturnTypeReflection, + >( + s1: &mut StructRef, + s2: &StructRef, + data: &TestData, + field_name: &str, + ) { + assert_eq!(s1.get::(field_name), s2.get::(field_name)); + s1.set(field_name, data.1).unwrap(); + assert_ne!(s1.get::(field_name), s2.get::(field_name)); + s1.replace(field_name, data.0).unwrap(); + assert_eq!(s1.get::(field_name), s2.get::(field_name)); + } + + // Verify that StructRef::get makes a shallow copy of a struct + let mut foo = baz.get::("0").unwrap(); + let foo2 = baz.get::("0").unwrap(); + test_shallow_copy(&mut foo, &foo2, &int_data, "a"); + test_shallow_copy(&mut foo, &foo2, &bool_data, "b"); + + let mut bar = qux.get::("0").unwrap(); + let bar2 = qux.get::("0").unwrap(); + test_shallow_copy(&mut bar, &bar2, &int_data, "0"); + test_shallow_copy(&mut bar, &bar2, &bool_data, "1"); // Specify invalid return type - let bar_err = bar.get::("0"); + let bar_err = bar.get::("0"); assert!(bar_err.is_err()); // Specify invalid argument type - let bar_err = bar.replace("0", 1i64); + let bar_err = bar.replace("0", 1f64); assert!(bar_err.is_err()); // Specify invalid argument type - let bar_err = bar.set("0", 1i64); + let bar_err = bar.set("0", 1f64); assert!(bar_err.is_err()); // Specify invalid return type - let bar_err: Result = invoke_fn!(driver.runtime, "bar_new", foo); + let bar_err: Result = invoke_fn!(driver.runtime, "baz_new", foo); assert!(bar_err.is_err()); // Pass invalid struct type - let bar_err: Result = invoke_fn!(driver.runtime, "bar_new", bar); + let bar_err: Result = invoke_fn!(driver.runtime, "baz_new", bar); assert!(bar_err.is_err()); } @@ -464,7 +515,7 @@ fn marshal_struct() { fn hotreload_struct_decl() { let mut driver = TestDriver::new( r#" - struct(value) Args { + struct(gc) Args { n: int, foo: Bar, } @@ -480,7 +531,7 @@ fn hotreload_struct_decl() { ); driver.update( r#" - struct(value) Args { + struct(gc) Args { n: int, foo: Bar, }