Skip to content

Commit

Permalink
feat(vm): Allow references to be passed through
Browse files Browse the repository at this point in the history
Adds the `Ref` type which ensures that the borrowed reference is alive
as long as any outstanding references to it exist. Care must be taken
that any references to the object in gluon does not outlive the scope in
which `Ref` exists for or a gluon panic will occur if it is accessed
after `Ref` has gone out of scope.
  • Loading branch information
Marwes committed Apr 20, 2019
1 parent a17fe11 commit 3a92b17
Show file tree
Hide file tree
Showing 13 changed files with 450 additions and 48 deletions.
19 changes: 16 additions & 3 deletions codegen/src/getable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,16 +179,19 @@ fn gen_impl(
use #ident::api as _gluon_api;
use #ident::thread as _gluon_thread;
use #ident::Variants as _GluonVariants;
use #ident::Result as _GluonResult;
},
attr::CrateName::GluonVm => quote! {
use api as _gluon_api;
use thread as _gluon_thread;
use Variants as _GluonVariants;
use crate::api as _gluon_api;
use crate::thread as _gluon_thread;
use crate::Variants as _GluonVariants;
use crate::Result as _GluonResult;
},
attr::CrateName::None => quote! {
use gluon::vm::api as _gluon_api;
use gluon::vm::thread as _gluon_thread;
use gluon::vm::Variants as _GluonVariants;
use gluon::vm::Result as _GluonResult;
},
};

Expand All @@ -202,6 +205,16 @@ fn gen_impl(
impl #impl_generics _gluon_api::Getable<'__vm, '__value> for #ident #ty_generics
#where_clause #(#getable_bounds,)* #(#lifetime_bounds),*
{
type Proxy = _GluonVariants<'__value>;

fn to_proxy(vm: &'__vm _gluon_thread::Thread, value: _GluonVariants<'__value>) -> _GluonResult<Self::Proxy> {
Ok(value)
}

fn from_proxy(vm: &'__vm _gluon_thread::Thread, proxy: &'__value Self::Proxy) -> Self {
Self::from_value(vm, *proxy)
}

fn from_value(vm: &'__vm _gluon_thread::Thread, variants: _GluonVariants<'__value>) -> Self {
#push_impl
}
Expand Down
3 changes: 3 additions & 0 deletions examples/marshalling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ impl<'vm, 'value> api::Pushable<'vm> for Enum {
}

impl<'vm, 'value> api::Getable<'vm, 'value> for Enum {
impl_getable_simple!();
fn from_value(thread: &'vm Thread, value: vm::Variants<'value>) -> Self {
api::de::De::from_value(thread, value).0
}
Expand Down Expand Up @@ -271,6 +272,8 @@ impl<'vm, 'value, T> Getable<'vm, 'value> for GluonUser<T>
where
T: Getable<'vm, 'value>,
{
impl_getable_simple!();

fn from_value(vm: &'vm Thread, data: Variants<'value>) -> GluonUser<T> {
// get the data, it must be a complex type
let data = match data.as_ref() {
Expand Down
2 changes: 2 additions & 0 deletions src/std_lib/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ impl<'vm> Pushable<'vm> for Headers {
}

impl<'vm, 'value> Getable<'vm, 'value> for Headers {
impl_getable_simple!();

fn from_value(vm: &'vm Thread, value: Variants<'value>) -> Self {
Headers(
Collect::from_value(vm, value)
Expand Down
116 changes: 97 additions & 19 deletions tests/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,22 @@ extern crate gluon;
#[macro_use]
extern crate gluon_vm;

use futures::future::lazy;
use futures::{Future, IntoFuture};

use gluon::base::types::{Alias, ArcType, Type};
use gluon::import::{add_extern_module, Import};
use gluon::vm::api::de::De;
use gluon::vm::api::{FunctionRef, FutureResult, OpaqueValue, RuntimeResult, Userdata, VmType, IO};
use gluon::vm::thread::{RootedThread, Thread, Traverseable};
use gluon::vm::types::VmInt;
use gluon::vm::{Error, ExternModule};
use gluon::Compiler;
use futures::{future::lazy, Future, IntoFuture};

use gluon::{
base::types::{Alias, ArcType, Type},
import::{add_extern_module, Import},
vm::{
api::{
de::De, scoped::Ref, FunctionRef, FutureResult, OpaqueValue, OwnedFunction,
RuntimeResult, Userdata, VmType, IO,
},
thread::{RootedThread, Thread, Traverseable},
types::VmInt,
Error, ExternModule,
},
Compiler,
};

fn load_script(vm: &Thread, filename: &str, input: &str) -> ::gluon::Result<()> {
Compiler::new().load_script(vm, filename, input)
Expand All @@ -34,6 +39,14 @@ fn make_vm() -> RootedThread {
vm
}

#[derive(Debug)]
struct Test(VmInt);
impl Userdata for Test {}
impl Traverseable for Test {}
impl VmType for Test {
type Type = Test;
}

#[test]
fn call_function() {
let _ = ::env_logger::try_init();
Expand All @@ -60,14 +73,6 @@ fn call_function() {
fn root_data() {
let _ = ::env_logger::try_init();

#[derive(Debug)]
struct Test(VmInt);
impl Userdata for Test {}
impl Traverseable for Test {}
impl VmType for Test {
type Type = Test;
}

let expr = r#"
let test = import! test
\x -> test x 1
Expand Down Expand Up @@ -405,3 +410,76 @@ fn runtime_result_vm_type_forwarding() {
.run_expr::<IO<()>>(&vm, "test", text)
.unwrap_or_else(|err| panic!("{}", err));
}

#[test]
fn scoped_reference_basic() {
let _ = ::env_logger::try_init();

fn function(r: &Test, i: VmInt) -> VmInt {
r.0 + i
}

let expr = r#"
let function = import! function
\x -> function x 1
"#;

let vm = make_vm();
vm.register_type::<Test>("Test", &[])
.unwrap_or_else(|_| panic!("Could not add type"));
add_extern_module(&vm, "function", |thread| {
ExternModule::new(thread, primitive!(2, function))
});

let (mut result, _) = Compiler::new()
.run_expr::<OwnedFunction<fn(_) -> VmInt>>(&vm, "<top>", expr)
.unwrap_or_else(|err| panic!("{}", err));

assert_eq!(
result
.call(&mut Ref::new(&Test(2)))
.unwrap_or_else(|err| panic!("{}", err)),
3
);
}

#[test]
fn scoped_reference_out_of_scope() {
let _ = ::env_logger::try_init();

fn recursive<'a, 'b>(
f: OwnedFunction<fn(OpaqueValue<RootedThread, Test>) -> OpaqueValue<RootedThread, Test>>,
) -> OpaqueValue<RootedThread, Test> {
let mut f: OwnedFunction<fn(_) -> OpaqueValue<RootedThread, Test>> = f.cast().unwrap();
f.call(&mut Ref::new(&Test(1))).unwrap()
}

fn function(r: &Test, i: VmInt) -> VmInt {
r.0 + i
}

let expr = r#"
let { recursive, function } = import! module
function (recursive (\t -> t)) 1
"#;

let vm = make_vm();
vm.register_type::<Test>("Test", &[])
.unwrap_or_else(|_| panic!("Could not add type"));
add_extern_module(&vm, "module", |thread| {
ExternModule::new(
thread,
record! {
recursive => primitive!(1, recursive),
function => primitive!(2, function)
},
)
});

let result = Compiler::new().run_expr::<VmInt>(&vm, "<top>", expr);
match result {
Err(gluon::Error::VM(Error::Panic(ref m, _))) if m == "Scoped pointer is invalidated" => (),
Err(err) => panic!("Wrong error: {:#?}", err),
Ok(_) => panic!("Unexpected success"),
}
}
2 changes: 2 additions & 0 deletions vm/src/api/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ where
T: VmType,
T: DeserializeOwned,
{
impl_getable_simple!();

fn from_value(thread: &'vm Thread, value: Variants<'value>) -> Self {
let typ = T::make_type(thread);
match from_value(thread, value, &typ).map(De) {
Expand Down
55 changes: 44 additions & 11 deletions vm/src/api/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ where
}

impl<'vm, 'value, F> Getable<'vm, 'value> for Function<&'vm Thread, F> {
impl_getable_simple!();

fn from_value(vm: &'vm Thread, value: Variants<'value>) -> Function<&'vm Thread, F> {
Function {
value: vm.root_value(value),
Expand All @@ -254,6 +256,8 @@ impl<'vm, 'value, F> Getable<'vm, 'value> for Function<&'vm Thread, F> {
}

impl<'vm, 'value, F> Getable<'vm, 'value> for Function<RootedThread, F> {
impl_getable_simple!();

fn from_value(vm: &'vm Thread, value: Variants<'value>) -> Self {
Function {
value: vm.root_value(value),
Expand Down Expand Up @@ -322,17 +326,25 @@ where $($args: Getable<'vm, 'vm> + 'vm,)*
let mut i = 0;
let lock;
let r = unsafe {
let ($($args,)*) = {
let stack = StackFrame::<ExternState>::current(context.stack());
$(let $args = {
let x = $args::from_value(vm, Variants::with_root(stack[i].clone(), vm));
i += 1;
x
});*;
// Lock the frame to ensure that any references to the stack stay rooted
lock = stack.into_lock();
($($args,)*)
};

let stack = StackFrame::<ExternState>::current(context.stack());
$(
let variants = Variants::with_root(stack[i].clone(), vm);
let proxy = match $args::to_proxy(vm, variants) {
Ok(x) => x,
Err(err) => {
drop(stack);
err.to_string().push(&mut context).unwrap();
return Status::Error;
}
};
// The proxy will live as along as the 'value lifetime we just created
let $args = $args::from_proxy(vm, &*(&proxy as *const _));
i += 1;
)*
// Lock the frame to ensure that any references to the stack stay rooted
lock = stack.into_lock();

drop(context);
let r = (*self)($($args),*);
context = vm.current_context();
Expand Down Expand Up @@ -499,6 +511,27 @@ make_vm_function!(A, B, C, D, E);
make_vm_function!(A, B, C, D, E, F);
make_vm_function!(A, B, C, D, E, F, G);

impl<T, F> Function<T, F>
where
T: Deref<Target = Thread>,
F: VmType,
{
pub fn cast<F2>(self) -> Result<Function<T, F2>>
where
F2: VmType,
{
let vm = self.value.vm();
if F::make_type(vm) == F2::make_type(vm) {
Ok(Function {
value: self.value,
_marker: PhantomData,
})
} else {
Err(Error::Message("Function cast is not compatible".into()))
}
}
}

pub struct TypedBytecode<T> {
id: Symbol,
args: VmIndex,
Expand Down
8 changes: 6 additions & 2 deletions vm/src/api/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ impl<'vm> crate::api::Pushable<'vm> for JsonValue {
}

impl<'vm, 'value> crate::api::Getable<'vm, 'value> for JsonValue {
impl_getable_simple!();

fn from_value(vm: &'vm Thread, value: Variants<'value>) -> Self {
JsonValue(crate::api::Getable::from_value(vm, value))
}
Expand Down Expand Up @@ -223,7 +225,8 @@ impl<'de> de::DeserializeState<'de, ActiveThread<'de>> for JsonValue {
where
E: de::Error,
{
let value = crate::api::convert_with_active_thread(self.0, value).map_err(E::custom)?;
let value =
crate::api::convert_with_active_thread(self.0, value).map_err(E::custom)?;
Ok(self.marshal(Value::String(value)))
}

Expand All @@ -232,7 +235,8 @@ impl<'de> de::DeserializeState<'de, ActiveThread<'de>> for JsonValue {
where
E: de::Error,
{
let value = crate::api::convert_with_active_thread(self.0, value).map_err(E::custom)?;
let value =
crate::api::convert_with_active_thread(self.0, value).map_err(E::custom)?;
Ok(self.marshal(Value::String(value)))
}

Expand Down
Loading

0 comments on commit 3a92b17

Please sign in to comment.