Skip to content

Commit

Permalink
add #[lucet_hostcall] attr macro; deprecate lucet_hostcalls!
Browse files Browse the repository at this point in the history
The attribute macro is much more flexible syntactically, and in particular allows hostcall
implementations to be properly `rustfmt`ed rather than being stuck in the invocation of a macro.
  • Loading branch information
acfoltzer committed Nov 26, 2019
1 parent 904f351 commit d4f3e3b
Show file tree
Hide file tree
Showing 18 changed files with 609 additions and 472 deletions.
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ members = [
"lucet-objdump",
"lucet-runtime",
"lucet-runtime/lucet-runtime-internals",
"lucet-runtime/lucet-runtime-macros",
"lucet-runtime/lucet-runtime-tests",
"lucet-spectest",
"lucet-validate",
Expand Down
55 changes: 27 additions & 28 deletions benchmarks/lucet-benchmarks/src/modules.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use lucet_module::lucet_signature;
use lucet_runtime::lucet_hostcalls;
use lucet_runtime::lucet_hostcall;
use lucet_runtime::vmctx::{lucet_vmctx, Vmctx};
use lucet_runtime_internals::module::{
FunctionPointer, HeapSpec, MockExportBuilder, MockModuleBuilder, Module,
Expand Down Expand Up @@ -215,33 +215,32 @@ pub fn many_args_mock() -> Arc<dyn Module> {
}

pub fn hostcalls_mock() -> Arc<dyn Module> {
lucet_hostcalls! {
#[inline(never)]
#[no_mangle]
pub unsafe extern "C" fn hostcall_wrapped(
&mut vmctx,
x1: u64,
x2: u64,
x3: u64,
x4: u64,
x5: u64,
x6: u64,
x7: u64,
x8: u64,
x9: u64,
x10: u64,
x11: u64,
x12: u64,
x13: u64,
x14: u64,
x15: u64,
x16: u64,
) -> () {
vmctx.heap_mut()[0] =
(x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 + x12 + x13 + x14 + x15 + x16)
as u8;
assert_eq!(vmctx.heap()[0], 136);
}
#[lucet_hostcall]
#[inline(never)]
#[no_mangle]
pub unsafe extern "C" fn hostcall_wrapped(
vmctx: &mut Vmctx,
x1: u64,
x2: u64,
x3: u64,
x4: u64,
x5: u64,
x6: u64,
x7: u64,
x8: u64,
x9: u64,
x10: u64,
x11: u64,
x12: u64,
x13: u64,
x14: u64,
x15: u64,
x16: u64,
) -> () {
vmctx.heap_mut()[0] =
(x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 + x12 + x13 + x14 + x15 + x16)
as u8;
assert_eq!(vmctx.heap()[0], 136);
}

#[inline(never)]
Expand Down
1 change: 1 addition & 0 deletions lucet-runtime/lucet-runtime-internals/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ edition = "2018"

[dependencies]
lucet-module = { path = "../../lucet-module", version = "0.4.1" }
lucet-runtime-macros = { path = "../lucet-runtime-macros", version = "0.4.1" }

bitflags = "1.0"
bincode = "1.1.4"
Expand Down
3 changes: 3 additions & 0 deletions lucet-runtime/lucet-runtime-internals/src/hostcall_macros.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/// The macro that surrounds definitions of Lucet hostcalls in Rust.
///
/// **Note:** this macro has been deprecated and replaced by the `#[lucet_hostcall]` attribute.
///
/// It is important to use this macro for hostcalls, rather than exporting them directly, as it
/// installs unwind protection that prevents panics from unwinding into the guest stack.
///
Expand All @@ -20,6 +22,7 @@
/// }
/// ```
#[macro_export]
#[deprecated(since = "0.4.2", note = "Use the #[lucet_hostcall] attribute instead")]
macro_rules! lucet_hostcalls {
{
$(
Expand Down
1 change: 1 addition & 0 deletions lucet-runtime/lucet-runtime-internals/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
pub mod error;
#[macro_use]
pub mod hostcall_macros;
pub use lucet_runtime_macros::lucet_hostcall;

#[macro_use]
#[cfg(test)]
Expand Down
44 changes: 23 additions & 21 deletions lucet-runtime/lucet-runtime-internals/src/vmctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,9 @@ impl VmctxInternal for Vmctx {
impl Vmctx {
/// Create a `Vmctx` from the compiler-inserted `vmctx` argument in a guest function.
///
/// This is almost certainly not what you want to use to get a `Vmctx`; instead use the `&mut
/// Vmctx` argument to a `lucet_hostcalls!`-wrapped function.
/// This is almost certainly not what you want to use to get a `Vmctx`; instead use the first
/// argument of a function with the `#[lucet_hostcall]` attribute, which must have the type
/// `&mut Vmctx`.
pub unsafe fn from_raw(vmctx: *mut lucet_vmctx) -> Vmctx {
let inst = instance_from_vmctx(vmctx);
assert!(inst.valid_magic());
Expand Down Expand Up @@ -277,29 +278,30 @@ impl Vmctx {
/// from its own context.
///
/// ```no_run
/// use lucet_runtime_internals::{lucet_hostcalls, lucet_hostcall_terminate};
/// use lucet_runtime_macros::lucet_hostcall;
/// use lucet_runtime_internals::lucet_hostcall_terminate;
/// use lucet_runtime_internals::vmctx::{lucet_vmctx, Vmctx};
///
/// lucet_hostcalls! {
/// #[no_mangle]
/// pub unsafe extern "C" fn hostcall_call_binop(
/// &mut vmctx,
/// binop_table_idx: u32,
/// binop_func_idx: u32,
/// operand1: u32,
/// operand2: u32,
/// ) -> u32 {
/// if let Ok(binop) = vmctx.get_func_from_idx(binop_table_idx, binop_func_idx) {
/// let typed_binop = std::mem::transmute::<
/// usize,
/// extern "C" fn(*mut lucet_vmctx, u32, u32) -> u32
/// >(binop.ptr.as_usize());
/// unsafe { (typed_binop)(vmctx.as_raw(), operand1, operand2) }
/// } else {
/// lucet_hostcall_terminate!("invalid function index")
/// }
/// #[lucet_hostcall]
/// #[no_mangle]
/// pub unsafe extern "C" fn hostcall_call_binop(
/// vmctx: &mut Vmctx,
/// binop_table_idx: u32,
/// binop_func_idx: u32,
/// operand1: u32,
/// operand2: u32,
/// ) -> u32 {
/// if let Ok(binop) = vmctx.get_func_from_idx(binop_table_idx, binop_func_idx) {
/// let typed_binop = std::mem::transmute::<
/// usize,
/// extern "C" fn(*mut lucet_vmctx, u32, u32) -> u32,
/// >(binop.ptr.as_usize());
/// unsafe { (typed_binop)(vmctx.as_raw(), operand1, operand2) }
/// } else {
/// lucet_hostcall_terminate!("invalid function index")
/// }
/// }
/// ```
pub fn get_func_from_idx(
&self,
table_idx: u32,
Expand Down
17 changes: 17 additions & 0 deletions lucet-runtime/lucet-runtime-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "lucet-runtime-macros"
version = "0.4.1"
description = "Macros for the Lucet WebAssembly runtime"
homepage = "https://github.com/bytecodealliance/lucet"
repository = "https://github.com/bytecodealliance/lucet"
license = "Apache-2.0 WITH LLVM-exception"
categories = ["wasm"]
authors = ["Lucet team <[email protected]>"]
edition = "2018"

[lib]
proc-macro = true

[dependencies]
syn = { version = "1.0", features = ["full"] }
quote = "1.0"
122 changes: 122 additions & 0 deletions lucet-runtime/lucet-runtime-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::spanned::Spanned;

/// This attribute generates a Lucet hostcall from a standalone Rust function that takes a `&mut
/// Vmctx` as its first argument.
///
/// It is important to use this attribute for hostcalls, rather than exporting them
/// directly. Otherwise the behavior of instance termination and timeouts are
/// undefined. Additionally, the attribute makes the resulting function `unsafe extern "C"`
/// regardless of how the function is defined, as this ABI is required for all hostcalls.
///
/// In most cases, you will want to also provide the `#[no_mangle]` attribute and `pub` visibility
/// in order for the hostcall to be exported from the final executable.
///
/// ```ignore
/// #[lucet_hostcall]
/// #[no_mangle]
/// pub fn yield_5(vmctx: &mut Vmctx) {
/// vmctx.yield_val(5);
/// }
/// ```
///
/// Note that `lucet-runtime` must be a dependency of any crate where this attribute is used, and it
/// may not be renamed (this restriction may be lifted once [this
/// issue](https://github.com/rust-lang/rust/issues/54363) is resolved).
#[proc_macro_attribute]
pub fn lucet_hostcall(_attr: TokenStream, item: TokenStream) -> TokenStream {
// determine whether we need to import from `lucet_runtime_internals`; this is useful if we want
// to define a hostcall for a target (or tests, more concretely) that doesn't depend on
// `lucet-runtime`
let in_internals = std::env::var("CARGO_PKG_NAME").unwrap() == "lucet-runtime-internals";

let mut hostcall = syn::parse_macro_input!(item as syn::ItemFn);
let hostcall_ident = hostcall.sig.ident.clone();

// use the same attributes and visibility as the impl hostcall
let attrs = hostcall.attrs.clone();
let vis = hostcall.vis.clone();

// remove #[no_mangle] from the attributes of the impl hostcall if it's there
hostcall.attrs = attrs
.iter()
.filter(|attr| !attr.path.is_ident("no_mangle"))
.cloned()
.collect();
// make the impl hostcall private
hostcall.vis = syn::Visibility::Inherited;

// modify the type signature of the exported raw hostcall based on the original signature
let mut raw_sig = hostcall.sig.clone();

// hostcalls are always unsafe
raw_sig.unsafety = Some(syn::Token![unsafe](raw_sig.span()));

// hostcalls are always extern "C"
raw_sig.abi = Some(syn::parse_quote!(extern "C"));

let vmctx_mod = if in_internals {
quote! { lucet_runtime_internals::vmctx }
} else {
quote! { lucet_runtime::vmctx }
};

// replace the first argument to the raw hostcall with the vmctx pointer
if let Some(arg0) = raw_sig.inputs.iter_mut().nth(0) {
let lucet_vmctx: syn::FnArg = syn::parse_quote!(vmctx_raw: *mut #vmctx_mod::lucet_vmctx);
*arg0 = lucet_vmctx;
}

// the args after the first to provide to the hostcall impl
let impl_args = hostcall
.sig
.inputs
.iter()
.skip(1)
.map(|arg| match arg {
syn::FnArg::Receiver(_) => {
// this case is an error, but humor the token stream so it will produce a nicer
// error later
let s = syn::Token![self](arg.span());
quote!(#s)
}
syn::FnArg::Typed(syn::PatType { pat, .. }) => quote!(#pat),
})
.collect::<Vec<_>>();

let termination_details = if in_internals {
quote! { lucet_runtime_internals::instance::TerminationDetails }
} else {
quote! { lucet_runtime::TerminationDetails }
};

let raw_hostcall = quote! {
#(#attrs)*
#vis
#raw_sig {
#[inline(always)]
#hostcall

let mut vmctx = #vmctx_mod::Vmctx::from_raw(vmctx_raw);
#vmctx_mod::VmctxInternal::instance_mut(&mut vmctx).uninterruptable(|| {
let res = std::panic::catch_unwind(move || {
#hostcall_ident(&mut #vmctx_mod::Vmctx::from_raw(vmctx_raw), #(#impl_args),*)
});
match res {
Ok(res) => res,
Err(e) => {
match e.downcast::<#termination_details>() {
Ok(details) => {
#vmctx_mod::Vmctx::from_raw(vmctx_raw).terminate_no_unwind(*details)
},
Err(e) => std::panic::resume_unwind(e),
}
}
}
})
}
};
raw_hostcall.into()
}
Loading

0 comments on commit d4f3e3b

Please sign in to comment.