Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(runtime): add marshalling of value structs #93

Merged
merged 6 commits into from
Mar 7, 2020
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
feat(runtime): add marshalling of value structs
Wodann committed Mar 7, 2020
commit 3a716add8fe7dd31ba0c4aceaf4a4085362d7f5a
7 changes: 5 additions & 2 deletions crates/mun/src/main.rs
Original file line number Diff line number Diff line change
@@ -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),
3 changes: 3 additions & 0 deletions crates/mun_codegen/src/intrinsics.rs
Original file line number Diff line number Diff line change
@@ -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;
}
2 changes: 1 addition & 1 deletion crates/mun_codegen/src/intrinsics/macros.rs
Original file line number Diff line number Diff line change
@@ -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 [<Intrinsic $name>];
8 changes: 8 additions & 0 deletions crates/mun_codegen/src/ir.rs
Original file line number Diff line number Diff line change
@@ -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<S: BasicType, T: IsIrType<Type = S>> 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<S: BasicType, T: IsIrType<Type = S>> IsPointerType for *mut T {
fn ir_type(context: &Context) -> PointerType {
T::ir_type(context).ptr_type(AddressSpace::Generic)
9 changes: 9 additions & 0 deletions crates/mun_codegen/src/ir/dispatch_table.rs
Original file line number Diff line number Diff line change
@@ -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)
}
4 changes: 2 additions & 2 deletions crates/mun_codegen/src/snapshots/test__field_crash.snap
Original file line number Diff line number Diff line change
@@ -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
6 changes: 3 additions & 3 deletions crates/mun_codegen/src/snapshots/test__field_expr.snap
Original file line number Diff line number Diff line change
@@ -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:
4 changes: 2 additions & 2 deletions crates/mun_codegen/src/snapshots/test__gc_struct.snap
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions crates/mun_codegen/src/snapshots/test__struct_test.snap
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions crates/mun_codegen/src/type_info.rs
Original file line number Diff line number Diff line change
@@ -116,6 +116,13 @@ impl<T: HasStaticTypeInfo> 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<TypeInfo>;
7 changes: 5 additions & 2 deletions crates/mun_runtime/examples/hot_reloading.rs
Original file line number Diff line number Diff line change
@@ -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();
}
}
30 changes: 26 additions & 4 deletions crates/mun_runtime/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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)?;
73 changes: 31 additions & 42 deletions crates/mun_runtime/src/macros.rs
Original file line number Diff line number Diff line change
@@ -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<core::cell::RefCell<Runtime>>,
function_name: &'s str,
$($Arg: $T,)*
output: core::marker::PhantomData<Output>,
}

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<core::cell::RefCell<Runtime>>, function_name: &'s str, $($Arg: $T),*) -> Self {
Self {
msg: err_msg,
runtime,
@@ -56,18 +56,18 @@ macro_rules! invoke_fn_impl {
}
}

impl<'r, 's, $($T: ArgumentReflection,)* Output: ReturnTypeReflection> $crate::RetryResultExt for core::result::Result<Output, $ErrName<'r, 's, $($T,)* Output>> {
impl<'s, $($T: ArgumentReflection,)* Output: ReturnTypeReflection> $crate::RetryResultExt for core::result::Result<Output, $ErrName<'s, $($T,)* Output>> {
type Output = Output;

fn retry(self) -> Self {
match self {
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<core::cell::RefCell<Runtime>>,
function_name: &'s str,
$($Arg: $T,)*
) -> core::result::Result<Output, $ErrName<'r, 's, $($T,)* Output>> {
) -> core::result::Result<Output, $ErrName<'s, $($T,)* Output>> {
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,
)
};
}
38 changes: 33 additions & 5 deletions crates/mun_runtime/src/marshal.rs
Original file line number Diff line number Diff line change
@@ -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<T>: Sized {
/// Performs the conversion.
fn marshal_into(self, type_info: Option<&TypeInfo>) -> T;
pub trait Marshal<T>: Sized {
/// Marshals itself into a `T`.
fn marshal_value(self, runtime: Rc<RefCell<Runtime>>, type_info: Option<&TypeInfo>) -> T;

/// Marshals the value at memory location `ptr` into a `T`.
fn marshal_from_ptr(
ptr: NonNull<Self>,
runtime: Rc<RefCell<Runtime>>,
type_info: Option<&TypeInfo>,
) -> T;

/// Marshals `value` to memory location `ptr`.
fn marshal_to_ptr(value: Self, ptr: NonNull<Self>, type_info: Option<&TypeInfo>);
}

impl<T> MarshalInto<T> for T {
fn marshal_into(self, _type_info: Option<&TypeInfo>) -> T {
impl<T> Marshal<T> for T {
fn marshal_value(self, _runtime: Rc<RefCell<Runtime>>, _type_info: Option<&TypeInfo>) -> T {
self
}

fn marshal_from_ptr<'r>(
ptr: NonNull<Self>,
_runtime: Rc<RefCell<Runtime>>,
_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<Self>, _type_info: Option<&TypeInfo>) {
unsafe { *ptr.as_mut() = value };
}
}
62 changes: 57 additions & 5 deletions crates/mun_runtime/src/reflection.rs
Original file line number Diff line number Diff line change
@@ -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<T: ReturnTypeReflection>(
}
}
abi::TypeGroup::StructTypes => {
if <Struct as ReturnTypeReflection>::type_guid() != T::type_guid() {
if <StructRef as ReturnTypeReflection>::type_guid() != T::type_guid() {
return Err(("struct", T::type_name()));
}
}
@@ -34,9 +34,9 @@ pub fn equals_return_type<T: ReturnTypeReflection>(
}

/// 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<Self>;
type Marshalled: Marshal<Self>;

/// 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<Self>;
type Marshalled: Marshal<Self>;

/// 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 {
<Self as ReturnTypeReflection>::type_name()
}

fn marshal(self) -> Self::Marshalled {
self
}
}

impl ArgumentReflection for *mut u8 {
type Marshalled = Self;

fn type_name(&self) -> &str {
<Self as ReturnTypeReflection>::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"
}
}
125 changes: 84 additions & 41 deletions crates/mun_runtime/src/struct.rs
Original file line number Diff line number Diff line change
@@ -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<RefCell<Runtime>>,
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<RefCell<Runtime>>, 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<T>(&self, field_idx: usize) -> NonNull<T> {
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::<T>()
}

/// Retrieves the value of the field corresponding to the specified `field_name`.
pub fn get<T: ReturnTypeReflection>(&self, field_name: &str) -> Result<T, String> {
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<Marshalled: Clone>`
self.raw
.0
.add(offset as usize)
.cast::<T::Marshalled>()
.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::<T::Marshalled>(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::<T::Marshalled>()
};
mem::swap(&mut marshalled, ptr);
Ok(marshalled.marshal_into(Some(*field_type)))
let field_ptr = unsafe { self.offset_unchecked::<T::Marshalled>(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::<T::Marshalled>() = value.marshal();
}
let field_ptr = unsafe { self.offset_unchecked::<T::Marshalled>(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,17 +140,56 @@ impl ArgumentReflection for Struct {
}
}

impl ReturnTypeReflection for Struct {
impl ReturnTypeReflection for StructRef {
type Marshalled = RawStruct;

fn type_name() -> &'static str {
"struct"
}
}

impl MarshalInto<Struct> for RawStruct {
fn marshal_into(self, type_info: Option<&TypeInfo>) -> Struct {
impl Marshal<StructRef> for RawStruct {
fn marshal_value(
self,
runtime: Rc<RefCell<Runtime>>,
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<Self>,
runtime: Rc<RefCell<Runtime>>,
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::<u8>().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<Self>, 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::<u8>().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 };
}
}
}
205 changes: 128 additions & 77 deletions crates/mun_runtime/src/test.rs
Original file line number Diff line number Diff line change
@@ -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<RefCell<Runtime>>,
}

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,87 +382,140 @@ 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::<i64>("a"));
assert_eq!(Ok(b), foo.get::<bool>("b"));
assert_eq!(Ok(c), foo.get::<f64>("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::<i64>("a"));
assert_eq!(Ok(e), foo.get::<bool>("b"));
assert_eq!(Ok(f), foo.get::<f64>("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::<i64>("a"));
assert_eq!(Ok(b), foo.get::<bool>("b"));
assert_eq!(Ok(c), foo.get::<f64>("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::<Struct>("0").unwrap();
assert_eq!(Ok(a), foo2.get::<i64>("a"));
assert_eq!(foo2.get::<bool>("b"), foo.get::<bool>("b"));
assert_eq!(foo2.get::<f64>("c"), foo.get::<f64>("c"));
struct TestData<T>(T, T);

fn test_field<
T: Copy + std::fmt::Debug + PartialEq + ArgumentReflection + ReturnTypeReflection,
>(
s: &mut StructRef,
data: &TestData<T>,
field_name: &str,
) {
assert_eq!(Ok(data.0), s.get::<T>(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::<T>(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<String> = c1.info().field_names().map(|n| n.to_string()).collect();

let int_value = c2.get::<i64>(&field_names[0]);
let bool_value = c2.get::<bool>(&field_names[1]);
s.set("0", c2).unwrap();

let c2 = s.get::<StructRef>("0").unwrap();
assert_eq!(c2.get::<i64>(&field_names[0]), int_value);
assert_eq!(c2.get::<bool>(&field_names[1]), bool_value);

let int_value = c1.get::<i64>(&field_names[0]);
let bool_value = c1.get::<bool>(&field_names[1]);
s.replace("0", c1).unwrap();

let c1 = s.get::<StructRef>("0").unwrap();
assert_eq!(c1.get::<i64>(&field_names[0]), int_value);
assert_eq!(c1.get::<bool>(&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<T>,
field_name: &str,
) {
assert_eq!(s1.get::<T>(field_name), s2.get::<T>(field_name));
s1.set(field_name, data.1).unwrap();
assert_ne!(s1.get::<T>(field_name), s2.get::<T>(field_name));
s1.replace(field_name, data.0).unwrap();
assert_eq!(s1.get::<T>(field_name), s2.get::<T>(field_name));
}

// Verify that StructRef::get makes a shallow copy of a struct
let mut foo = baz.get::<StructRef>("0").unwrap();
let foo2 = baz.get::<StructRef>("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::<StructRef>("0").unwrap();
let bar2 = qux.get::<StructRef>("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::<i64>("0");
let bar_err = bar.get::<f64>("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<i64, _> = invoke_fn!(driver.runtime, "bar_new", foo);
let bar_err: Result<i64, _> = invoke_fn!(driver.runtime, "baz_new", foo);
assert!(bar_err.is_err());

// Pass invalid struct type
let bar_err: Result<Struct, _> = invoke_fn!(driver.runtime, "bar_new", bar);
let bar_err: Result<StructRef, _> = invoke_fn!(driver.runtime, "baz_new", bar);
assert!(bar_err.is_err());
}

#[test]
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,
}