diff --git a/Justfile b/Justfile index a79e9ab65..e0ab070be 100644 --- a/Justfile +++ b/Justfile @@ -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 diff --git a/src/hyperlight_component_macro/src/lib.rs b/src/hyperlight_component_macro/src/lib.rs index 2d7fd9992..94a312448 100644 --- a/src/hyperlight_component_macro/src/lib.rs +++ b/src/hyperlight_component_macro/src/lib.rs @@ -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 @@ -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::parse_macro_input!(input as Option); - 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); @@ -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::parse_macro_input!(input as Option); - 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); @@ -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, + path: Option, +} + +impl Parse for BindgenInputParams { + fn parse(input: ParseStream) -> Result { + 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::()?; + + 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::()?; + } + } + } else { + let option_path_litstr = input.parse::>()?; + if let Some(concrete_path) = option_path_litstr { + path = Some(std::path::PathBuf::from(concrete_path.value())); + } + } + Ok(Self { world_name, path }) + } +} diff --git a/src/hyperlight_component_util/src/component.rs b/src/hyperlight_component_util/src/component.rs index 866419abc..92910e223 100644 --- a/src/hyperlight_component_util/src/component.rs +++ b/src/hyperlight_component_util/src/component.rs @@ -82,9 +82,10 @@ fn raw_type_export_type<'p, 'a, 'c>( /// export. pub fn read_component_single_exported_type<'a>( items: impl Iterator>>, + world_name: Option, ) -> 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, .. }) => { @@ -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); + } } } } @@ -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"), + }, } } diff --git a/src/hyperlight_component_util/src/util.rs b/src/hyperlight_component_util/src/util.rs index 8f9b853ae..c068721d8 100644 --- a/src/hyperlight_component_util/src/util.rs +++ b/src/hyperlight_component_util/src/util.rs @@ -21,6 +21,7 @@ use crate::etypes; /// given filename, relative to the cargo manifest directory. pub fn read_wit_type_from_file R>( filename: impl AsRef, + world_name: Option, mut cb: F, ) -> R { let path = std::path::Path::new(&filename); @@ -30,7 +31,7 @@ pub fn read_wit_type_from_file 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 @@ -52,8 +53,7 @@ pub fn read_wit_type_from_file 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()) { diff --git a/src/hyperlight_host/tests/wit_test.rs b/src/hyperlight_host/tests/wit_test.rs index e47faf6da..7d8f646f9 100644 --- a/src/hyperlight_host/tests/wit_test.rs +++ b/src/hyperlight_host/tests/wit_test.rs @@ -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"); + } +} diff --git a/src/tests/rust_guests/witguest/two_worlds.wit b/src/tests/rust_guests/witguest/two_worlds.wit new file mode 100644 index 000000000..5635e6b50 --- /dev/null +++ b/src/tests/rust_guests/witguest/two_worlds.wit @@ -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 + } +} \ No newline at end of file