Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ guests: build-and-move-rust-guests build-and-move-c-guests
witguest-wit:
cargo install --locked wasm-tools
cd src/tests/rust_guests/witguest && wasm-tools component wit guest.wit -w -o interface.wasm
cd src/tests/rust_guests/witguest && wasm-tools component wit two_worlds.wit -w -o twoworlds.wasm

build-rust-guests target=default-target features="": (witguest-wit)
cargo install --locked cargo-hyperlight
Expand Down
82 changes: 72 additions & 10 deletions src/hyperlight_component_macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ limitations under the License.
extern crate proc_macro;

use hyperlight_component_util::*;
use syn::parse::{Parse, ParseStream};
use syn::{Ident, LitStr, Result, Token};

/// Create host bindings for the wasm component type in the file
/// passed in (or `$WIT_WORLD`, if nothing is passed in). This will
Expand All @@ -66,11 +68,14 @@ use hyperlight_component_util::*;
#[proc_macro]
pub fn host_bindgen(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let _ = env_logger::try_init();
let path: Option<syn::LitStr> = syn::parse_macro_input!(input as Option<syn::LitStr>);
let path = path
.map(|x| x.value().into())
.unwrap_or_else(|| std::env::var_os("WIT_WORLD").unwrap());
util::read_wit_type_from_file(path, |kebab_name, ct| {
let parsed_bindgen_input = syn::parse_macro_input!(input as BindgenInputParams);
let path = match parsed_bindgen_input.path {
Some(path_env) => path_env.into_os_string(),
None => std::env::var_os("WIT_WORLD").unwrap(),
};
let world_name = parsed_bindgen_input.world_name;

util::read_wit_type_from_file(path, world_name, |kebab_name, ct| {
let decls = emit::run_state(false, false, |s| {
rtypes::emit_toplevel(s, &kebab_name, ct);
host::emit_toplevel(s, &kebab_name, ct);
Expand All @@ -89,11 +94,14 @@ pub fn host_bindgen(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
#[proc_macro]
pub fn guest_bindgen(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let _ = env_logger::try_init();
let path: Option<syn::LitStr> = syn::parse_macro_input!(input as Option<syn::LitStr>);
let path = path
.map(|x| x.value().into())
.unwrap_or_else(|| std::env::var_os("WIT_WORLD").unwrap());
util::read_wit_type_from_file(path, |kebab_name, ct| {
let parsed_bindgen_input = syn::parse_macro_input!(input as BindgenInputParams);
let path = match parsed_bindgen_input.path {
Some(path_env) => path_env.into_os_string(),
None => std::env::var_os("WIT_WORLD").unwrap(),
};
let world_name = parsed_bindgen_input.world_name;

util::read_wit_type_from_file(path, world_name, |kebab_name, ct| {
let decls = emit::run_state(true, false, |s| {
// Emit type/trait definitions for all instances in the world
rtypes::emit_toplevel(s, &kebab_name, ct);
Expand All @@ -107,3 +115,57 @@ pub fn guest_bindgen(input: proc_macro::TokenStream) -> proc_macro::TokenStream
util::emit_decls(decls).into()
})
}

#[derive(Debug)]
struct BindgenInputParams {
world_name: Option<String>,
path: Option<std::path::PathBuf>,
}

impl Parse for BindgenInputParams {
fn parse(input: ParseStream) -> Result<Self> {
let mut path = None;
let mut world_name = None;

if input.peek(syn::token::Brace) {
let content;
syn::braced!(content in input);

// Parse key-value pairs inside the braces
while !content.is_empty() {
let key: Ident = content.parse()?;
content.parse::<Token![:]>()?;

match key.to_string().as_str() {
"world_name" => {
let value: LitStr = content.parse()?;
world_name = Some(value.value());
}
"path" => {
let value: LitStr = content.parse()?;
path = Some(std::path::PathBuf::from(value.value()));
}
_ => {
return Err(syn::Error::new(
key.span(),
format!(
"unknown parameter '{}'; expected 'path' or 'world_name'",
key
),
));
}
}
// Parse optional comma
if content.peek(Token![,]) {
content.parse::<Token![,]>()?;
}
}
} else {
let option_path_litstr = input.parse::<Option<syn::LitStr>>()?;
if let Some(concrete_path) = option_path_litstr {
path = Some(std::path::PathBuf::from(concrete_path.value()));
}
}
Ok(Self { world_name, path })
}
}
23 changes: 19 additions & 4 deletions src/hyperlight_component_util/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,10 @@ fn raw_type_export_type<'p, 'a, 'c>(
/// export.
pub fn read_component_single_exported_type<'a>(
items: impl Iterator<Item = wasmparser::Result<Payload<'a>>>,
world_name: Option<String>,
) -> Component<'a> {
let mut ctx = Ctx::new(None, false);
let mut last_idx = None;
let mut selected_type_idx = None;
for x in items {
match x {
Ok(Version { num, encoding, .. }) => {
Expand Down Expand Up @@ -112,8 +113,18 @@ pub fn read_component_single_exported_type<'a>(
Err(_) => panic!("invalid export section"),
Ok(ce) => {
if ce.kind == ComponentExternalKind::Type {
last_idx = Some(ctx.types.len());
ctx.types.push(raw_type_export_type(&ctx, &ce).clone());

// picks the world index if world_name is passed in the proc_macro
// else picks the index of last type, exported by core module
if let Some(world) = world_name.as_ref() {
let name = ce.name.0;
if name.eq_ignore_ascii_case(world) {
selected_type_idx = Some(ctx.types.len() - 1);
}
} else {
selected_type_idx = Some(ctx.types.len() - 1);
}
}
}
}
Expand Down Expand Up @@ -149,11 +160,15 @@ pub fn read_component_single_exported_type<'a>(
_ => {}
}
}
match last_idx {
None => panic!("no exported type"),

match selected_type_idx {
Some(n) => match ctx.types.into_iter().nth(n) {
Some(Defined::Component(c)) => c,
_ => panic!("final export is not component"),
},
None => match &world_name {
Some(name) => panic!("world '{}' not found in component", name),
None => panic!("no exported type"),
},
}
}
6 changes: 3 additions & 3 deletions src/hyperlight_component_util/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::etypes;
/// given filename, relative to the cargo manifest directory.
pub fn read_wit_type_from_file<R, F: FnMut(String, &etypes::Component) -> R>(
filename: impl AsRef<std::ffi::OsStr>,
world_name: Option<String>,
mut cb: F,
) -> R {
let path = std::path::Path::new(&filename);
Expand All @@ -30,7 +31,7 @@ pub fn read_wit_type_from_file<R, F: FnMut(String, &etypes::Component) -> R>(

let bytes = std::fs::read(path).unwrap();
let i = wasmparser::Parser::new(0).parse_all(&bytes);
let ct = crate::component::read_component_single_exported_type(i);
let ct = crate::component::read_component_single_exported_type(i, world_name);

// because of the two-level encapsulation scheme, we need to look
// for the single export of the component type that we just read
Expand All @@ -52,8 +53,7 @@ pub fn read_wit_type_from_file<R, F: FnMut(String, &etypes::Component) -> R>(

/// Deal with `$HYPERLIGHT_COMPONENT_MACRO_DEBUG`: if it is present,
/// save the given token stream (representing the result of
/// macroexpansion) to the debug file and include that file instead of
/// directly returning the given token stream.
/// macroexpansion) to the debug file and then return the token stream
pub fn emit_decls(decls: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
if let Ok(dbg_out) = std::env::var("HYPERLIGHT_COMPONENT_MACRO_DEBUG") {
if let Ok(file) = syn::parse2(decls.clone()) {
Expand Down
46 changes: 46 additions & 0 deletions src/hyperlight_host/tests/wit_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,3 +426,49 @@ mod wit_test {
drop(guard);
}
}

mod pick_world_bindings {
hyperlight_component_macro::host_bindgen!({path: "../tests/rust_guests/witguest/twoworlds.wasm", world_name: "firstworld"});
}
mod pick_world_binding_test {
use crate::pick_world_bindings::r#twoworlds::r#wit::r#first_import::RecFirstImport;

impl crate::pick_world_bindings::r#twoworlds::r#wit::r#first_import::RecFirstImport {
fn new() -> Self {
Self {
r#key: String::from("dummyKey"),
r#value: String::from("dummyValue"),
}
}
}

#[test]
fn test_first_import_instance() {
let first_import = RecFirstImport::new();
assert_eq!(first_import.r#key, "dummyKey");
assert_eq!(first_import.r#value, "dummyValue");
}
}

mod pick_world_bindings2 {
hyperlight_component_macro::host_bindgen!({path: "../tests/rust_guests/witguest/twoworlds.wasm", world_name: "secondworld"});
}
mod pick_world_binding_test2 {
use crate::pick_world_bindings2::r#twoworlds::r#wit::r#second_export::RecSecondExport;

impl crate::pick_world_bindings2::r#twoworlds::r#wit::r#second_export::RecSecondExport {
fn new() -> Self {
Self {
r#key: String::from("dummyKey"),
r#value: String::from("dummyValue"),
}
}
}

#[test]
fn test_second_export_instance() {
let first_import = RecSecondExport::new();
assert_eq!(first_import.r#key, "dummyKey");
assert_eq!(first_import.r#value, "dummyValue");
}
}
23 changes: 23 additions & 0 deletions src/tests/rust_guests/witguest/two_worlds.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package twoworlds:wit;

world secondworld {
export second-export;
}

world firstworld {
import first-import;
}

interface second-export {
record rec-second-export {
key: string,
value: string
}
}

interface first-import {
record rec-first-import {
key: string,
value : string
}
}
Loading