diff --git a/Cargo.lock b/Cargo.lock index c9ace3324..a23400da1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -713,6 +713,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -957,6 +963,10 @@ name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", + "serde", +] [[package]] name = "heck" @@ -1090,10 +1100,36 @@ dependencies = [ "flatbuffers", "hyperlight-testing", "log", + "spin", "strum", "tracing", ] +[[package]] +name = "hyperlight-component-macro" +version = "0.2.0" +dependencies = [ + "hyperlight-component-util", + "itertools 0.14.0", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wasmparser", +] + +[[package]] +name = "hyperlight-component-util" +version = "0.2.0" +dependencies = [ + "itertools 0.14.0", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wasmparser", +] + [[package]] name = "hyperlight-fuzz" version = "0.0.0" @@ -1392,6 +1428,7 @@ checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ "equivalent", "hashbrown 0.15.2", + "serde", ] [[package]] @@ -3337,6 +3374,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasmparser" +version = "0.224.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f17a5917c2ddd3819e84c661fae0d6ba29d7b9c1f0e96c708c65a9c4188e11" +dependencies = [ + "bitflags 2.9.0", + "hashbrown 0.15.2", + "indexmap 2.8.0", + "semver", + "serde", +] + [[package]] name = "web-sys" version = "0.3.77" diff --git a/Cargo.toml b/Cargo.toml index ee32c3493..fedbc5e4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,8 @@ default-members = [ members = [ "src/hyperlight_guest_capi", "fuzz", + "src/hyperlight_component_util", + "src/hyperlight_component_macro", ] # Guests have custom linker flags, so we need to exclude them from the workspace exclude = [ @@ -30,6 +32,8 @@ hyperlight-common = { path = "src/hyperlight_common", version = "0.2.0", default hyperlight-host = { path = "src/hyperlight_host", version = "0.2.0", default-features = false } hyperlight-guest = { path = "src/hyperlight_guest", version = "0.2.0", default-features = false } hyperlight-testing = { path = "src/hyperlight_testing", default-features = false } +hyperlight-component-util = { path = "src/hyperlight_component_util" } +hyperlight-component-macro = { path = "src/hyperlight_component_macro" } [workspace.lints.rust] unsafe_op_in_unsafe_fn = "deny" diff --git a/src/hyperlight_common/Cargo.toml b/src/hyperlight_common/Cargo.toml index dbc7219a6..5d7c0a014 100644 --- a/src/hyperlight_common/Cargo.toml +++ b/src/hyperlight_common/Cargo.toml @@ -21,10 +21,12 @@ log = "0.4.26" tracing = { version = "0.1.41", optional = true } strum = {version = "0.27", default-features = false, features = ["derive"]} arbitrary = {version = "1.4.1", optional = true, features = ["derive"]} +spin = "0.9.8" [features] default = ["tracing"] fuzzing = ["dep:arbitrary"] +std = [] [dev-dependencies] hyperlight-testing = { workspace = true } diff --git a/src/hyperlight_common/src/lib.rs b/src/hyperlight_common/src/lib.rs index f05baa9f9..6eb0dd79d 100644 --- a/src/hyperlight_common/src/lib.rs +++ b/src/hyperlight_common/src/lib.rs @@ -36,3 +36,5 @@ pub mod flatbuffer_wrappers; mod flatbuffers; /// cbindgen:ignore pub mod mem; + +pub mod resource; diff --git a/src/hyperlight_common/src/resource.rs b/src/hyperlight_common/src/resource.rs new file mode 100644 index 000000000..0e9265fda --- /dev/null +++ b/src/hyperlight_common/src/resource.rs @@ -0,0 +1,154 @@ +//! Shared operations around resources + +// "Needless" lifetimes are useful for clarity +#![allow(clippy::needless_lifetimes)] + +use alloc::sync::Arc; + +#[cfg(feature = "std")] +extern crate std; +use core::marker::{PhantomData, Send}; +use core::ops::Deref; +#[cfg(feature = "std")] +use std::sync::{RwLock, RwLockReadGuard}; + +#[cfg(not(feature = "std"))] +use spin::{RwLock, RwLockReadGuard}; + +/// The semantics of component model resources are, pleasingly, +/// roughly compatible with those of Rust. Less pleasingly, it's not +/// terribly easy to show that statically. +/// +/// In particular, if the host calls into the guest and gives it a +/// borrow of a resource, reentrant host function calls that use that +/// borrow need to be able to resolve the original reference and use +/// it in an appropriately scoped manner, but it is not simple to do +/// this, because the core Hyperlight machinery doesn't offer an easy +/// way to augment the host's context for the span of time of a guest +/// function call. This may be worth revisiting at some time, but in +/// the meantime, it's easier to just do it dynamically. +/// +/// # Safety +/// Informally: this only creates SharedRead references, so having a +/// bunch of them going at once is fine. Safe Rust in the host can't +/// use any earlier borrows (potentially invalidating these) until +/// borrow passed into [`ResourceEntry::lend`] has expired. Because +/// that borrow outlives the [`LentResourceGuard`], it will not expire +/// until that destructor is called. That destructor ensures that (a) +/// there are no outstanding [`BorrowedResourceGuard`]s alive (since +/// they would be holding the read side of the [`RwLock`] if they +/// were), and that (b) the shared flag has been set to false, so +/// [`ResourceEntry::borrow`] will never create another borrow +pub enum ResourceEntry { + Empty, + Owned(T), + Borrowed(Arc>, *const T), +} +unsafe impl Send for ResourceEntry {} + +pub struct LentResourceGuard<'a> { + flag: Arc>, + already_revoked: bool, + _phantom: core::marker::PhantomData<&'a mut ()>, +} +impl<'a> LentResourceGuard<'a> { + pub fn revoke_nonblocking(&mut self) -> bool { + #[cfg(feature = "std")] + let Ok(mut flag) = self.flag.try_write() else { + return false; + }; + #[cfg(not(feature = "std"))] + let Some(mut flag) = self.flag.try_write() else { + return false; + }; + *flag = false; + self.already_revoked = true; + true + } +} +impl<'a> Drop for LentResourceGuard<'a> { + fn drop(&mut self) { + if !self.already_revoked { + #[allow(unused_mut)] // it isn't actually unused + let mut guard = self.flag.write(); + #[cfg(feature = "std")] + // If a mutex that is just protecting us from our own + // mistakes is poisoned, something is so seriously + // wrong that dying is a sensible response. + #[allow(clippy::unwrap_used)] + { + *guard.unwrap() = false; + } + #[cfg(not(feature = "std"))] + { + *guard = false; + } + } + } +} +pub struct BorrowedResourceGuard<'a, T> { + _flag: Option>, + reference: &'a T, +} +impl<'a, T> Deref for BorrowedResourceGuard<'a, T> { + type Target = T; + fn deref(&self) -> &T { + self.reference + } +} +impl ResourceEntry { + pub fn give(x: T) -> ResourceEntry { + ResourceEntry::Owned(x) + } + pub fn lend<'a>(x: &'a T) -> (LentResourceGuard<'a>, ResourceEntry) { + let flag = Arc::new(RwLock::new(true)); + ( + LentResourceGuard { + flag: flag.clone(), + already_revoked: false, + _phantom: PhantomData {}, + }, + ResourceEntry::Borrowed(flag, x as *const T), + ) + } + pub fn borrow<'a>(&'a self) -> Option> { + match self { + ResourceEntry::Empty => None, + ResourceEntry::Owned(t) => Some(BorrowedResourceGuard { + _flag: None, + reference: t, + }), + ResourceEntry::Borrowed(flag, t) => { + let guard = flag.read(); + // If a mutex that is just protecting us from our own + // mistakes is poisoned, something is so seriously + // wrong that dying is a sensible response. + #[allow(clippy::unwrap_used)] + let flag = { + #[cfg(feature = "std")] + { + guard.unwrap() + } + #[cfg(not(feature = "std"))] + { + guard + } + }; + if *flag { + Some(BorrowedResourceGuard { + _flag: Some(flag), + reference: unsafe { &**t }, + }) + } else { + None + } + } + } + } + pub fn take(&mut self) -> Option { + match core::mem::replace(self, ResourceEntry::Empty) { + ResourceEntry::Owned(t) => Some(t), + _ => None, + } + } +} diff --git a/src/hyperlight_component_macro/Cargo.toml b/src/hyperlight_component_macro/Cargo.toml new file mode 100644 index 000000000..a94398ae7 --- /dev/null +++ b/src/hyperlight_component_macro/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "hyperlight-component-macro" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +readme.workspace = true +description = """ +Procedural macros to generate Hyperlight host and guest bindings from component types +""" + +[lib] +name = "hyperlight_component_macro" +proc-macro = true + +[dependencies] +wasmparser = { version = "0.224.0" } +quote = { version = "1.0.38" } +proc-macro2 = { version = "1.0.93" } +syn = { version = "2.0.96" } +itertools = { version = "0.14.0" } +prettyplease = { version = "0.2.31" } +hyperlight-component-util = { workspace = true } \ No newline at end of file diff --git a/src/hyperlight_component_macro/src/lib.rs b/src/hyperlight_component_macro/src/lib.rs new file mode 100644 index 000000000..03305dc15 --- /dev/null +++ b/src/hyperlight_component_macro/src/lib.rs @@ -0,0 +1,18 @@ +extern crate proc_macro; + +use hyperlight_component_util::*; + +#[proc_macro] +pub fn host_bindgen(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + 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("HYPERLIGHT_WASM_WORLD").unwrap()); + util::read_wit_type_from_file(path, |kebab_name, ct| { + let decls = emit::run_state(false, |s| { + rtypes::emit_toplevel(s, &kebab_name, ct); + host::emit_toplevel(s, &kebab_name, ct); + }); + util::emit_decls(decls).into() + }) +} diff --git a/src/hyperlight_component_util/Cargo.toml b/src/hyperlight_component_util/Cargo.toml new file mode 100644 index 000000000..655f94bd6 --- /dev/null +++ b/src/hyperlight_component_util/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "hyperlight-component-util" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +readme.workspace = true +description = """ +Shared implementation for the procedural macros that generate Hyperlight host and guest bindings from component types +""" + +[lib] +name = "hyperlight_component_util" + +[dependencies] +wasmparser = { version = "0.224.0" } +quote = { version = "1.0.38" } +proc-macro2 = { version = "1.0.93" } +syn = { version = "2.0.96" } +itertools = { version = "0.14.0" } +prettyplease = { version = "0.2.31" } \ No newline at end of file diff --git a/src/hyperlight_component_util/src/component.rs b/src/hyperlight_component_util/src/component.rs new file mode 100644 index 000000000..6f007534e --- /dev/null +++ b/src/hyperlight_component_util/src/component.rs @@ -0,0 +1,67 @@ +//! Just enough component parsing support to get at the actual types + +use wasmparser::Payload::{ComponentExportSection, ComponentTypeSection, Version}; +use wasmparser::{ComponentExternalKind, ComponentType, ComponentTypeRef, Payload}; + +use crate::etypes::{Component, Ctx, Defined}; + +fn raw_type_export_type<'p, 'a, 'c>( + ctx: &'c Ctx<'p, 'a>, + ce: &'c wasmparser::ComponentExport<'a>, +) -> &'c Defined<'a> { + match ce.ty { + Some(ComponentTypeRef::Component(n)) => match ctx.types.iter().nth(n as usize) { + Some(t) => return t, + t => panic!("bad component type 1 {:?}", t), + }, + None => match ctx.types.iter().nth(ce.index as usize) { + Some(t) => return &t, + t => panic!("bad component type 2 {:?}", t), + }, + _ => panic!("non-component ascribed type"), + } +} + +pub fn read_component_single_exported_type<'a>( + items: impl Iterator>>, +) -> Component<'a> { + let mut ctx = Ctx::new(None, false); + let mut last_idx = None; + for x in items { + match x { + Ok(Version { .. }) => (), + Ok(ComponentTypeSection(ts)) => { + for t in ts { + match t { + Ok(ComponentType::Component(ct)) => { + let ct_ = ctx.elab_component(&ct); + ctx.types.push(Defined::Component(ct_.unwrap())); + } + _ => panic!("non-component type"), + } + } + } + Ok(ComponentExportSection(es)) => { + for e in es { + match e { + 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()); + } + } + } + } + } + _ => {} + } + } + match last_idx { + None => panic!("no exported type"), + Some(n) => match ctx.types.into_iter().nth(n) { + Some(Defined::Component(c)) => c, + _ => panic!("final export is not component"), + }, + } +} diff --git a/src/hyperlight_component_util/src/elaborate.rs b/src/hyperlight_component_util/src/elaborate.rs new file mode 100644 index 000000000..370554efb --- /dev/null +++ b/src/hyperlight_component_util/src/elaborate.rs @@ -0,0 +1,593 @@ +use wasmparser::{ + ComponentAlias, ComponentDefinedType, ComponentExternalKind, ComponentFuncResult, + ComponentFuncType, ComponentOuterAliasKind, ComponentType, ComponentTypeDeclaration, + ComponentTypeRef, ComponentValType, CompositeInnerType, CoreType, ExternalKind, + InstanceTypeDeclaration, ModuleTypeDeclaration, OuterAliasKind, PrimitiveValType, TypeBounds, + TypeRef, +}; + +use crate::etypes::{ + self, BoundedTyvar, Component, CoreDefined, CoreExportDecl, CoreExternDesc, CoreModule, + CoreOrComponentExternDesc, Ctx, Defined, ExternDecl, ExternDesc, FloatWidth, Func, Handleable, + Instance, IntWidth, Name, Param, QualifiedInstance, RecordField, Resource, ResourceId, + TypeBound, Tyvar, Value, VariantCase, +}; +use crate::structure::{CoreSort, Sort}; +use crate::substitute::{self, Substitution, Unvoidable}; +use crate::tv::ResolvedTyvar; +use crate::wf; + +// Basic utility conversion functions +fn sort_matches_core_ed<'a>(sort: Sort, ed: &CoreExternDesc) { + match (sort, ed) { + (Sort::Core(CoreSort::Func), CoreExternDesc::Func(_)) => (), + (Sort::Core(CoreSort::Table), CoreExternDesc::Table(_)) => (), + (Sort::Core(CoreSort::Memory), CoreExternDesc::Memory(_)) => (), + (Sort::Core(CoreSort::Global), CoreExternDesc::Global(_)) => (), + _ => panic!("sort does not match core extern descriptor"), + } +} + +fn external_kind(k: ExternalKind) -> Sort { + match k { + ExternalKind::Func => Sort::Core(CoreSort::Func), + ExternalKind::Table => Sort::Core(CoreSort::Table), + ExternalKind::Memory => Sort::Core(CoreSort::Memory), + ExternalKind::Global => Sort::Core(CoreSort::Global), + ExternalKind::Tag => panic!("core type tags are not supported"), + } +} + +fn sort_matches_ed<'a>(sort: Sort, ed: &ExternDesc<'a>) { + match (sort, ed) { + (Sort::Core(CoreSort::Module), ExternDesc::CoreModule(_)) => (), + (Sort::Func, ExternDesc::Func(_)) => (), + (Sort::Type, ExternDesc::Type(_)) => (), + (Sort::Instance, ExternDesc::Instance(_)) => (), + (Sort::Component, ExternDesc::Component(_)) => (), + _ => panic!("sort does not match extern descriptor"), + } +} + +fn component_external_kind(k: ComponentExternalKind) -> Sort { + match k { + ComponentExternalKind::Module => Sort::Core(CoreSort::Module), + ComponentExternalKind::Func => Sort::Func, + ComponentExternalKind::Value => Sort::Value, + ComponentExternalKind::Type => Sort::Type, + ComponentExternalKind::Instance => Sort::Instance, + ComponentExternalKind::Component => Sort::Component, + } +} + +#[derive(Debug)] +#[allow(dead_code)] +pub enum Error<'a> { + InvalidOuterAlias(substitute::InnerizeError), + IllFormedOuterAlias(wf::Error<'a>), + ResourceInDeclarator, + HandleToNonResource, + ValTypeRefToNonVal(Defined<'a>), + ClosingError(substitute::ClosingError), +} +impl<'a> From for Error<'a> { + fn from(e: substitute::ClosingError) -> Error<'a> { + Error::ClosingError(e) + } +} + +// Elaboration + +impl<'p, 'a> Ctx<'p, 'a> { + pub fn elab_component<'c>( + &'c mut self, + decls: &[ComponentTypeDeclaration<'a>], + ) -> Result, Error<'a>> { + let mut ctx = Ctx::new(Some(self), false); + let mut imports = Vec::new(); + let mut exports = Vec::new(); + for decl in decls { + let (import, export) = ctx.elab_component_decl(decl)?; + if let Some(import) = import { + imports.push(import); + } + if let Some(export) = export { + exports.push(export); + } + } + ctx.finish_component(&imports, &exports) + } + + fn elab_core_module_decl<'c>( + &'c mut self, + decl: &ModuleTypeDeclaration<'a>, + ) -> (Option>, Option>) { + match decl { + ModuleTypeDeclaration::Import(i) => (Some(i.clone()), None), + ModuleTypeDeclaration::Type(rg) => { + let ct = self.elab_core_type_rec(rg); + self.core.types.push(ct); + (None, None) + }, + ModuleTypeDeclaration::OuterAlias { + kind: OuterAliasKind::Type, + count, index + } => { + let ct = self.parents().nth(*count as usize).unwrap() + .core.types[*index as usize].clone(); + self.core.types.push(ct); + (None, None) + } + ModuleTypeDeclaration::Export { name, ty } => (None, Some(CoreExportDecl { + name: Name { name: *name }, + desc: match ty { + TypeRef::Func(n) => match &self.core.types[*n as usize] { + CoreDefined::Func(ft) => CoreExternDesc::Func(ft.clone()), + _ => panic!("internal invariant violation: WasmParser function TypeRef refers to non-function"), + }, + TypeRef::Table(tt) => CoreExternDesc::Table(*tt), + TypeRef::Memory(mt) => CoreExternDesc::Memory(*mt), + TypeRef::Global(gt) => CoreExternDesc::Global(*gt), + TypeRef::Tag(_) => panic!("core type tags are not supported"), + }, + })), + } + } + + fn elab_core_module<'c>(&'c mut self, decls: &[ModuleTypeDeclaration<'a>]) -> CoreModule<'a> { + let mut ctx = Ctx::new(Some(self), false); + let mut imports = Vec::new(); + let mut exports = Vec::new(); + for decl in decls { + let (import, export) = ctx.elab_core_module_decl(decl); + if let Some(import) = import { + imports.push(import) + } + if let Some(export) = export { + exports.push(export) + } + } + CoreModule { + _imports: imports, + _exports: exports, + } + } + + fn elab_core_type_rec<'c>(&'c mut self, rg: &wasmparser::RecGroup) -> CoreDefined<'a> { + match &rg.types().nth(0).unwrap().composite_type.inner { + CompositeInnerType::Func(ft) => CoreDefined::Func(ft.clone()), + _ => panic!("GC core types are not presently supported"), + } + } + + fn elab_core_type<'c>(&'c mut self, ct: &wasmparser::CoreType<'a>) -> CoreDefined<'a> { + match ct { + CoreType::Rec(rg) => self.elab_core_type_rec(rg), + CoreType::Module(ds) => CoreDefined::Module(self.elab_core_module(&ds)), + } + } + + fn resolve_alias<'c>( + &'c mut self, + alias: &ComponentAlias<'a>, + ) -> Result, Error<'a>> { + match alias { + ComponentAlias::InstanceExport { + kind, + instance_index, + name, + } => { + let it = &self.instances[*instance_index as usize]; + let ed = &it + .exports + .iter() + .find(|e| e.kebab_name == *name) + .unwrap() + .desc; + let sort = component_external_kind(*kind); + sort_matches_ed(sort, ed); + Ok(CoreOrComponentExternDesc::Component(ed.clone())) + } + ComponentAlias::CoreInstanceExport { + kind, + instance_index, + name, + } => { + let it = &self.core.instances[*instance_index as usize]; + let ed = &it + .exports + .iter() + .find(|e| e.name.name == *name) + .unwrap() + .desc; + let sort = external_kind(*kind); + sort_matches_core_ed(sort, ed); + Ok(CoreOrComponentExternDesc::Core(ed.clone())) + } + ComponentAlias::Outer { kind, count, index } => { + if *kind != ComponentOuterAliasKind::Type { + panic!("In types, only outer type aliases are allowed"); + } + // Walk through each of the contexts between us and + // the targeted type, so that we can innerize each one + let mut ctxs = self.parents().take(*count as usize + 1).collect::>(); + ctxs.reverse(); + let mut target_type = ctxs[0].types[*index as usize].clone(); + let mut ob_crossed = false; + for ctxs_ in ctxs.windows(2) { + ob_crossed |= ctxs_[1].outer_boundary; + let sub = substitute::Innerize::new(ctxs_[0], ctxs_[1].outer_boundary); + target_type = sub + .defined(&target_type) + .map_err(Error::InvalidOuterAlias)?; + } + if ob_crossed { + self.wf_defined(wf::DefinedTypePosition::export(), &target_type) + .map_err(Error::IllFormedOuterAlias)?; + } + Ok(CoreOrComponentExternDesc::Component(ExternDesc::Type( + target_type, + ))) + } + } + } + + fn add_core_ed<'c>(&'c mut self, ed: CoreExternDesc) { + match ed { + CoreExternDesc::Func(ft) => self.core.funcs.push(ft), + CoreExternDesc::Table(tt) => self.core.tables.push(tt), + CoreExternDesc::Memory(mt) => self.core.mems.push(mt), + CoreExternDesc::Global(gt) => self.core.globals.push(gt), + } + } + + fn add_ed<'c>(&'c mut self, ed: &ExternDesc<'a>) { + match ed { + ExternDesc::CoreModule(cmd) => self.core.modules.push(cmd.clone()), + ExternDesc::Func(ft) => self.funcs.push(ft.clone()), + ExternDesc::Type(dt) => self.types.push(dt.clone()), + ExternDesc::Instance(it) => self.instances.push(it.clone()), + ExternDesc::Component(ct) => self.components.push(ct.clone()), + } + } + + fn add_core_or_component_ed<'c>(&'c mut self, ed: CoreOrComponentExternDesc<'a>) { + match ed { + CoreOrComponentExternDesc::Core(ced) => self.add_core_ed(ced), + CoreOrComponentExternDesc::Component(ed) => self.add_ed(&ed), + } + } + + fn elab_value<'c>(&'c mut self, ctr: &ComponentValType) -> Result, Error<'a>> { + match ctr { + ComponentValType::Type(n) => match &self.types[*n as usize] { + Defined::Value(vt) => Ok(vt.clone()), + dt @ Defined::Handleable(Handleable::Var(tv)) => match self.resolve_tyvar(tv) { + ResolvedTyvar::Definite(Defined::Value(vt)) => { + Ok(Value::Var(Some(tv.clone()), Box::new(vt))) + } + _ => Err(Error::ValTypeRefToNonVal(dt.clone())), + }, + dt => Err(Error::ValTypeRefToNonVal(dt.clone())), + }, + ComponentValType::Primitive(pt) => Ok(match pt { + PrimitiveValType::Bool => Value::Bool, + PrimitiveValType::S8 => Value::S(IntWidth::I8), + PrimitiveValType::U8 => Value::U(IntWidth::I8), + PrimitiveValType::S16 => Value::S(IntWidth::I16), + PrimitiveValType::U16 => Value::U(IntWidth::I16), + PrimitiveValType::S32 => Value::S(IntWidth::I32), + PrimitiveValType::U32 => Value::U(IntWidth::I32), + PrimitiveValType::S64 => Value::S(IntWidth::I64), + PrimitiveValType::U64 => Value::U(IntWidth::I64), + PrimitiveValType::F32 => Value::F(FloatWidth::F32), + PrimitiveValType::F64 => Value::F(FloatWidth::F64), + PrimitiveValType::Char => Value::Char, + PrimitiveValType::String => Value::String, + }), + } + } + + fn elab_defined_value<'c>( + &'c mut self, + vt: &ComponentDefinedType<'a>, + ) -> Result, Error<'a>> { + match vt { + ComponentDefinedType::Primitive(pvt) => { + self.elab_value(&ComponentValType::Primitive(*pvt)) + } + ComponentDefinedType::Record(rfs) => { + let rfs = rfs + .iter() + .map(|(name, ty)| { + Ok::<_, Error<'a>>(RecordField { + name: Name { name: *name }, + ty: self.elab_value(ty)?, + }) + }) + .collect::, Error<'a>>>()?; + Ok(Value::Record(rfs)) + } + ComponentDefinedType::Variant(vcs) => { + let vcs = vcs + .iter() + .map(|vc| { + Ok(VariantCase { + name: Name { name: vc.name }, + ty: vc.ty.as_ref().map(|ty| self.elab_value(ty)).transpose()?, + refines: vc.refines, + }) + }) + .collect::, Error<'a>>>()?; + Ok(Value::Variant(vcs)) + } + ComponentDefinedType::List(vt) => Ok(Value::List(Box::new(self.elab_value(vt)?))), + ComponentDefinedType::Tuple(vts) => Ok(Value::Tuple( + vts.iter() + .map(|vt| self.elab_value(vt)) + .collect::, Error<'a>>>()?, + )), + ComponentDefinedType::Flags(ns) => { + Ok(Value::Flags(ns.iter().map(|n| Name { name: *n }).collect())) + } + ComponentDefinedType::Enum(ns) => { + Ok(Value::Enum(ns.iter().map(|n| Name { name: *n }).collect())) + } + ComponentDefinedType::Option(vt) => Ok(Value::Option(Box::new(self.elab_value(vt)?))), + ComponentDefinedType::Result { ok, err } => Ok(Value::Result( + Box::new(ok.map(|ok| self.elab_value(&ok)).transpose()?), + Box::new(err.map(|err| self.elab_value(&err)).transpose()?), + )), + ComponentDefinedType::Own(n) => match &self.types[*n as usize] { + Defined::Handleable(h) => Ok(Value::Own(h.clone())), + _ => Err(Error::HandleToNonResource), + }, + ComponentDefinedType::Borrow(n) => match &self.types[*n as usize] { + Defined::Handleable(h) => Ok(Value::Borrow(h.clone())), + _ => Err(Error::HandleToNonResource), + }, + ComponentDefinedType::Future(_) + | ComponentDefinedType::Stream(_) + | ComponentDefinedType::ErrorContext => panic!("async not yet supported"), + } + } + + fn elab_func<'c>(&'c mut self, ft: &ComponentFuncType<'a>) -> Result, Error<'a>> { + Ok(Func { + params: ft + .params + .iter() + .map(|(n, vt)| { + Ok(Param { + name: Name { name: *n }, + ty: self.elab_value(vt)?, + }) + }) + .collect::, Error<'a>>>()?, + result: match &ft.results { + ComponentFuncResult::Unnamed(vt) => etypes::Result::Unnamed(self.elab_value(vt)?), + ComponentFuncResult::Named(rs) => etypes::Result::Named( + rs.iter() + .map(|(n, vt)| { + Ok(Param { + name: Name { name: *n }, + ty: self.elab_value(vt)?, + }) + }) + .collect::, Error<'a>>>()?, + ), + }, + }) + } + + fn elab_extern_desc<'c>( + &'c mut self, + ed: &ComponentTypeRef, + ) -> Result<(Vec>, ExternDesc<'a>), Error<'a>> { + match ed { + ComponentTypeRef::Module(i) => match &self.core.types[*i as usize] { + CoreDefined::Module(mt) => Ok((vec![], ExternDesc::CoreModule(mt.clone()))), + _ => { + panic!("internal invariant violation: bad sort for ComponentTypeRef to Module") + } + }, + ComponentTypeRef::Func(i) => match &self.types[*i as usize] { + Defined::Func(ft) => Ok((vec![], ExternDesc::Func(ft.clone()))), + _ => panic!("internal invariant violation: bad sort for ComponentTypeRef to Func"), + }, + ComponentTypeRef::Value(_) => panic!("First-class values are not yet supported"), + ComponentTypeRef::Type(tb) => { + let bound = match tb { + TypeBounds::Eq(i) => TypeBound::Eq(self.types[*i as usize].clone()), + TypeBounds::SubResource => TypeBound::SubResource, + }; + let dt = Defined::Handleable(Handleable::Var(Tyvar::Bound(0))); + Ok((vec![BoundedTyvar::new(bound)], ExternDesc::Type(dt))) + } + ComponentTypeRef::Instance(i) => match &self.types[*i as usize] { + Defined::Instance(qit) => Ok(( + qit.evars.clone(), + ExternDesc::Instance(qit.unqualified.clone()), + )), + _ => panic!( + "internal invariant violation: bad sort for ComponentTypeRef to Instance" + ), + }, + ComponentTypeRef::Component(i) => match &self.types[*i as usize] { + Defined::Component(ct) => Ok((vec![], ExternDesc::Component(ct.clone()))), + _ => panic!( + "internal invariant violation: bad sort for ComponentTypeRef to Component" + ), + }, + } + } + + fn elab_instance_decl<'c>( + &'c mut self, + decl: &InstanceTypeDeclaration<'a>, + ) -> Result>, Error<'a>> { + match decl { + InstanceTypeDeclaration::CoreType(ct) => { + let ct = self.elab_core_type(ct); + self.core.types.push(ct); + Ok(None) + } + InstanceTypeDeclaration::Type(t) => { + let t = self.elab_defined(t)?; + if let Defined::Handleable(_) = t { + return Err(Error::ResourceInDeclarator); + } + self.types.push(t); + Ok(None) + } + InstanceTypeDeclaration::Alias(a) => { + let ed = self.resolve_alias(a)?; + self.add_core_or_component_ed(ed); + Ok(None) + } + InstanceTypeDeclaration::Export { name, ty } => { + let (vs, ed) = self.elab_extern_desc(ty)?; + let sub = self.bound_to_evars(Some(name.0), &vs); + let ed = sub.extern_desc(&ed).not_void(); + self.add_ed(&ed); + Ok(Some(ExternDecl { + kebab_name: name.0, + desc: ed, + })) + } + } + } + + fn elab_instance<'c>( + &'c mut self, + decls: &[InstanceTypeDeclaration<'a>], + ) -> Result, Error<'a>> { + let mut ctx = Ctx::new(Some(self), false); + let mut exports = Vec::new(); + for decl in decls { + let export = ctx.elab_instance_decl(decl)?; + if let Some(export) = export { + exports.push(export); + } + } + ctx.finish_instance(&exports) + } + + fn finish_instance_evars( + self, + exports: &[ExternDecl<'a>], + ) -> Result, Error<'a>> { + let mut evars = Vec::new(); + let mut sub = substitute::Closing::new(false); + for (bound, _) in self.evars { + let bound = sub.bounded_tyvar(&bound)?; + evars.push(bound); + sub.next_e(); + } + let unqualified = sub.instance(&Instance { + exports: exports.to_vec(), + })?; + Ok(QualifiedInstance { evars, unqualified }) + } + + fn finish_instance( + self, + exports: &[ExternDecl<'a>], + ) -> Result, Error<'a>> { + let qi = self.finish_instance_evars(exports)?; + let raise_u_sub = substitute::Closing::new(true); + Ok(raise_u_sub.qualified_instance(&qi)?) + } + + fn elab_component_decl<'c>( + &'c mut self, + decl: &ComponentTypeDeclaration<'a>, + ) -> Result<(Option>, Option>), Error<'a>> { + match decl { + ComponentTypeDeclaration::CoreType(ct) => { + let ct = self.elab_core_type(ct); + self.core.types.push(ct); + Ok((None, None)) + } + ComponentTypeDeclaration::Type(t) => { + let t = self.elab_defined(t)?; + if let Defined::Handleable(_) = t { + return Err(Error::ResourceInDeclarator); + } + self.types.push(t); + Ok((None, None)) + } + ComponentTypeDeclaration::Alias(a) => { + let ed = self.resolve_alias(a)?; + self.add_core_or_component_ed(ed); + Ok((None, None)) + } + ComponentTypeDeclaration::Export { name, ty, .. } => { + let (vs, ed) = self.elab_extern_desc(ty)?; + let sub = self.bound_to_evars(Some(name.0), &vs); + let ed = sub.extern_desc(&ed).not_void(); + self.add_ed(&ed); + Ok(( + None, + Some(ExternDecl { + kebab_name: name.0, + desc: ed, + }), + )) + } + ComponentTypeDeclaration::Import(i) => { + let (vs, ed) = self.elab_extern_desc(&i.ty)?; + let sub = self.bound_to_uvars(Some(i.name.0), &vs, true); + let ed = sub.extern_desc(&ed).not_void(); + self.add_ed(&ed); + Ok(( + Some(ExternDecl { + kebab_name: i.name.0, + desc: ed, + }), + None, + )) + } + } + } + + fn finish_component( + self, + imports: &[ExternDecl<'a>], + exports: &[ExternDecl<'a>], + ) -> Result, Error<'a>> { + let mut uvars = Vec::new(); + let mut sub = substitute::Closing::new(true); + for (bound, imported) in &self.uvars { + let bound = sub.bounded_tyvar(&bound)?; + uvars.push(bound); + sub.next_u(*imported); + } + let imports = imports + .iter() + .map(|ed| sub.extern_decl(ed).map_err(Into::into)) + .collect::>, Error<'a>>>()?; + let instance = sub.qualified_instance(&self.finish_instance_evars(exports)?)?; + Ok(Component { + uvars, + imports, + instance, + }) + } + + fn elab_defined<'c>(&'c mut self, dt: &ComponentType<'a>) -> Result, Error<'a>> { + match dt { + ComponentType::Defined(vt) => Ok(Defined::Value(self.elab_defined_value(vt)?)), + ComponentType::Func(ft) => Ok(Defined::Func(self.elab_func(ft)?)), + ComponentType::Component(cds) => Ok(Defined::Component(self.elab_component(cds)?)), + ComponentType::Instance(ids) => Ok(Defined::Instance(self.elab_instance(ids)?)), + ComponentType::Resource { dtor, .. } => { + let rid = ResourceId { + id: self.rtypes.len() as u32, + }; + self.rtypes.push(Resource { _dtor: *dtor }); + Ok(Defined::Handleable(Handleable::Resource(rid))) + } + } + } +} diff --git a/src/hyperlight_component_util/src/emit.rs b/src/hyperlight_component_util/src/emit.rs new file mode 100644 index 000000000..5b3a5aa43 --- /dev/null +++ b/src/hyperlight_component_util/src/emit.rs @@ -0,0 +1,536 @@ +use std::collections::{BTreeMap, BTreeSet, VecDeque}; +use std::vec::Vec; + +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use syn::Ident; + +use crate::dbg_println; +use crate::etypes::{BoundedTyvar, Defined, Handleable, ImportExport, TypeBound, Tyvar}; + +#[derive(Debug)] +pub struct Trait { + pub supertraits: BTreeMap, TokenStream>, + pub tvs: BTreeMap, TokenStream)>, + pub items: TokenStream, +} +impl Trait { + pub fn new() -> Self { + Self { + supertraits: BTreeMap::new(), + tvs: BTreeMap::new(), + items: TokenStream::new(), + } + } + pub fn tv_idxs(&self) -> Vec { + self.tvs.iter().map(|(_, (n, _))| n.unwrap()).collect() + } + pub fn adjust_vars(&mut self, n: u32) { + for (_, (v, _)) in self.tvs.iter_mut() { + v.as_mut().map(|v| *v += n); + } + } + pub fn tv_toks_inner(&mut self) -> TokenStream { + let tvs = self + .tvs + .iter() + .map(|(k, (_, v))| { + let colon = if v.is_empty() { + quote! {} + } else { + quote! { : } + }; + quote! { #k #colon #v } + }) + .collect::>(); + quote! { #(#tvs),* } + } + pub fn tv_toks(&mut self) -> TokenStream { + if self.tvs.len() > 0 { + let toks = self.tv_toks_inner(); + quote! { <#toks> } + } else { + quote! {} + } + } + pub fn emit(&mut self, n: Ident) -> TokenStream { + let trait_colon = if self.supertraits.len() > 0 { + quote! { : } + } else { + quote! {} + }; + let supertraits = self + .supertraits + .iter() + .map(|(is, ts)| { + quote! { #(#is)::*#ts } + }) + .collect::>(); + let tvs = self.tv_toks(); + let items = &self.items; + quote! { + pub trait #n #tvs #trait_colon #(#supertraits)+* { #items } + } + } +} + +#[derive(Debug)] +pub struct Mod { + pub submods: BTreeMap, + pub items: TokenStream, + pub traits: BTreeMap, +} +impl Mod { + pub fn empty() -> Self { + Self { + submods: BTreeMap::new(), + items: TokenStream::new(), + traits: BTreeMap::new(), + } + } + pub fn submod<'a>(&'a mut self, i: Ident) -> &'a mut Self { + self.submods.entry(i).or_insert(Self::empty()) + } + pub fn submod_immut<'a>(&'a self, i: Ident) -> &'a Self { + &self.submods[&i] + } + pub fn r#trait<'a>(&'a mut self, i: Ident) -> &'a mut Trait { + self.traits.entry(i).or_insert(Trait::new()) + } + pub fn trait_immut<'a>(&'a self, i: Ident) -> &'a Trait { + &self.traits[&i] + } + pub fn adjust_vars(&mut self, n: u32) { + self.submods + .iter_mut() + .map(|(_, m)| m.adjust_vars(n)) + .for_each(drop); + self.traits + .iter_mut() + .map(|(_, t)| t.adjust_vars(n)) + .for_each(drop); + } + pub fn into_tokens(self) -> TokenStream { + let mut tt = TokenStream::new(); + for (k, v) in self.submods { + let vt = v.into_tokens(); + tt.extend(quote! { + pub mod #k { #vt } + }); + } + for (n, mut t) in self.traits { + tt.extend(t.emit(n)); + } + tt.extend(self.items); + tt + } +} + +#[derive(Debug)] +pub struct State<'a, 'b> { + pub root_mod: &'a mut Mod, + pub mod_cursor: Vec, + pub cur_trait: Option, + pub cur_helper_mod: Option, + pub is_helper: bool, + pub bound_vars: &'a mut VecDeque>, + pub var_offset: usize, + pub origin: Vec>, + pub cur_needs_vars: Option<&'a mut BTreeSet>, + pub vars_needs_vars: &'a mut VecDeque>, + pub helper_type_name: Option, + pub import_param_var: Option, + pub self_param_var: Option, + pub is_impl: bool, + pub root_component_name: Option<(TokenStream, &'a str)>, + pub is_guest: bool, +} + +pub fn run_state<'b, F: for<'a> FnMut(&mut State<'a, 'b>)>( + is_guest: bool, + mut f: F, +) -> TokenStream { + let mut root_mod = Mod::empty(); + let mut bound_vars = std::collections::VecDeque::new(); + let mut vars_needs_vars = std::collections::VecDeque::new(); + { + let mut state = State::new( + &mut root_mod, + &mut bound_vars, + &mut vars_needs_vars, + is_guest, + ); + f(&mut state); + } + root_mod.into_tokens() +} + +impl<'a, 'b> State<'a, 'b> { + pub fn new( + root_mod: &'a mut Mod, + bound_vars: &'a mut VecDeque>, + vars_needs_vars: &'a mut VecDeque>, + is_guest: bool, + ) -> Self { + Self { + root_mod, + mod_cursor: Vec::new(), + cur_trait: None, + cur_helper_mod: None, + is_helper: false, + bound_vars, + var_offset: 0, + origin: Vec::new(), + cur_needs_vars: None, + vars_needs_vars, + helper_type_name: None, + import_param_var: None, + self_param_var: None, + is_impl: false, + root_component_name: None, + is_guest, + } + } + pub fn clone<'c>(&'c mut self) -> State<'c, 'b> { + State { + root_mod: &mut self.root_mod, + mod_cursor: self.mod_cursor.clone(), + cur_trait: self.cur_trait.clone(), + cur_helper_mod: self.cur_helper_mod.clone(), + is_helper: self.is_helper, + bound_vars: &mut self.bound_vars, + var_offset: self.var_offset, + origin: self.origin.clone(), + cur_needs_vars: self.cur_needs_vars.as_mut().map(|v| &mut **v), + vars_needs_vars: &mut self.vars_needs_vars, + helper_type_name: self.helper_type_name.clone(), + import_param_var: self.import_param_var.clone(), + self_param_var: self.self_param_var.clone(), + is_impl: self.is_impl, + root_component_name: self.root_component_name.clone(), + is_guest: self.is_guest, + } + } + pub fn cur_mod<'c>(&'c mut self) -> &'c mut Mod { + let mut m: &'c mut Mod = &mut self.root_mod; + for i in &self.mod_cursor { + m = m.submod(i.clone()); + } + if self.is_helper { + m = m.submod(self.cur_helper_mod.clone().unwrap()); + } + m + } + pub fn cur_mod_immut<'c>(&'c self) -> &'c Mod { + let mut m: &'c Mod = &self.root_mod; + for i in &self.mod_cursor { + m = m.submod_immut(i.clone()); + } + if self.is_helper { + m = m.submod_immut(self.cur_helper_mod.clone().unwrap()); + } + m + } + pub fn with_cursor<'c>(&'c mut self, cursor: Vec) -> State<'c, 'b> { + let mut s = self.clone(); + s.mod_cursor = cursor; + s + } + pub fn with_needs_vars<'c>(&'c mut self, needs_vars: &'c mut BTreeSet) -> State<'c, 'b> { + let mut s = self.clone(); + s.cur_needs_vars = Some(needs_vars); + s + } + pub fn need_noff_var(&mut self, n: u32) { + self.cur_needs_vars.as_mut().map(|vs| vs.insert(n)); + } + pub fn record_needs_vars(&mut self, n: u32) { + let un = n as usize; + if self.vars_needs_vars.len() < un + 1 { + self.vars_needs_vars.resize(un + 1, BTreeSet::new()); + } + let Some(ref mut cnvs) = self.cur_needs_vars else { + return; + }; + dbg_println!("debug varref: recording {:?} for var {:?}", cnvs.iter(), un); + self.vars_needs_vars[un].extend(cnvs.iter()); + } + pub fn get_noff_var_refs(&mut self, n: u32) -> BTreeSet { + let un = n as usize; + if self.vars_needs_vars.len() < un + 1 { + return BTreeSet::new(); + }; + dbg_println!( + "debug varref: looking up {:?} for var {:?}", + self.vars_needs_vars[un].iter(), + un + ); + self.vars_needs_vars[un].clone() + } + pub fn noff_var_id(&self, n: u32) -> Ident { + let Some(n) = self.bound_vars[n as usize].origin.last_name() else { + panic!("missing origin on tyvar in rust emit") + }; + kebab_to_type(n) + } + pub fn helper<'c>(&'c mut self) -> State<'c, 'b> { + let mut s = self.clone(); + s.is_helper = true; + s + } + pub fn root_path(&self) -> TokenStream { + if self.is_impl { + return TokenStream::new(); + } + let mut s = self + .mod_cursor + .iter() + .map(|_| quote! { super }) + .collect::>(); + if self.is_helper { + s.push(quote! { super }); + } + quote! { #(#s::)* } + } + pub fn helper_path(&self) -> TokenStream { + if self.is_impl { + let c = &self.mod_cursor; + let helper = self.cur_helper_mod.clone().unwrap(); + let h = if !self.is_helper { + quote! { #helper:: } + } else { + TokenStream::new() + }; + quote! { #(#c::)*#h } + } else if self.is_helper { + quote! { self:: } + } else { + let helper = self.cur_helper_mod.clone().unwrap(); + quote! { #helper:: } + } + } + pub fn cur_trait_path(&self) -> TokenStream { + let tns = &self.mod_cursor; + let tid = self.cur_trait.clone().unwrap(); + quote! { #(#tns::)* #tid } + } + pub fn add_helper_supertrait(&mut self, r: Ident) { + let (Some(t), Some(hm)) = (self.cur_trait.clone(), &self.cur_helper_mod.clone()) else { + panic!("invariant violation") + }; + self.cur_mod() + .r#trait(t) + .supertraits + .insert(vec![hm.clone(), r], TokenStream::new()); + } + pub fn cur_trait<'c>(&'c mut self) -> &'c mut Trait { + let n = self.cur_trait.as_ref().unwrap().clone(); + self.cur_mod().r#trait(n) + } + pub fn cur_trait_immut<'c>(&'c self) -> &'c Trait { + let n = self.cur_trait.as_ref().unwrap().clone(); + self.cur_mod_immut().trait_immut(n) + } + pub fn r#trait<'c>(&'c mut self, namespace: &'c [Ident], name: Ident) -> &'c mut Trait { + let mut m: &'c mut Mod = &mut self.root_mod; + for i in namespace { + m = m.submod(i.clone()); + } + m.r#trait(name) + } + pub fn push_origin<'c>(&'c mut self, is_export: bool, name: &'b str) -> State<'c, 'b> { + let mut s = self.clone(); + s.origin.push(if is_export { + ImportExport::Export(name) + } else { + ImportExport::Import(name) + }); + s + } + pub fn is_var_defn(&self, t: &Defined<'b>) -> Option<(u32, TypeBound<'b>)> { + match t { + Defined::Handleable(Handleable::Var(tv)) => match tv { + Tyvar::Bound(n) => { + let bv = &self.bound_vars[self.var_offset + (*n as usize)]; + dbg_println!("checking an origin {:?} {:?}", bv.origin, self.origin); + if bv.origin.matches(self.origin.iter()) { + Some((*n, bv.bound.clone())) + } else { + None + } + } + Tyvar::Free(_) => panic!("free tyvar in finished type"), + }, + _ => None, + } + } + pub fn is_noff_var_local<'c>( + &'c self, + n: u32, + ) -> Option<(Vec>, TypeBound<'a>)> { + let bv = &self.bound_vars[n as usize]; + if let Some(path) = bv.origin.is_local(self.origin.iter()) { + Some((path, bv.bound.clone())) + } else { + None + } + } + pub fn resolve_trait_immut(&self, absolute: bool, path: &[Ident]) -> &Trait { + dbg_println!("resolving trait {:?} {:?}", absolute, path); + let mut m = if absolute { + &*self.root_mod + } else { + self.cur_mod_immut() + }; + for x in &path[0..path.len() - 1] { + m = &m.submods[x]; + } + &m.traits[&path[path.len() - 1]] + } + pub fn adjust_vars(&mut self, n: u32) { + let _ = self + .vars_needs_vars + .iter_mut() + .enumerate() + .map(|(i, vs)| { + *vs = vs.iter().map(|v| v + n).collect(); + dbg_println!("updated {:?} to {:?}", i, *vs); + }) + .collect::<()>(); + for _ in 0..n { + self.vars_needs_vars.push_front(BTreeSet::new()); + } + self.root_mod.adjust_vars(n); + } + /// either this ends up with a definition, in which case, let's get that, + /// or it ends up with a resource type + pub fn resolve_tv(&self, n: u32) -> (u32, Option>) { + match &self.bound_vars[self.var_offset + n as usize].bound { + TypeBound::Eq(Defined::Handleable(Handleable::Var(Tyvar::Bound(nn)))) => { + self.resolve_tv(n + 1 + nn) + } + TypeBound::Eq(t) => (n, Some(t.clone())), + TypeBound::SubResource => (n, None), + } + } +} + +#[derive(Debug, Clone)] +pub struct WitName<'a> { + pub namespaces: Vec<&'a str>, + pub name: &'a str, + pub _version: Vec<&'a str>, +} +impl<'a> WitName<'a> { + pub fn namespace_idents(&self) -> Vec { + self.namespaces + .iter() + .map(|x| kebab_to_namespace(x)) + .collect::>() + } + pub fn namespace_path(&self) -> TokenStream { + let ns = self.namespace_idents(); + quote! { #(#ns)::* } + } +} +pub fn split_wit_name(n: &str) -> WitName { + let mut namespaces = Vec::new(); + let mut colon_components = n.split(':').rev(); + let last = colon_components.next().unwrap(); + namespaces.extend(colon_components.rev()); + let mut slash_components = last.split('/').rev(); + let mut versioned_name = slash_components.next().unwrap().split('@'); + let name = versioned_name.next().unwrap(); + namespaces.extend(slash_components.rev()); + WitName { + namespaces, + name, + _version: versioned_name.collect(), + } +} + +fn kebab_to_snake(n: &str) -> Ident { + if n == "self" { + return format_ident!("self_"); + } + let mut ret = String::new(); + for c in n.chars() { + if c == '-' { + ret.push('_'); + continue; + } + ret.push(c); + } + format_ident!("r#{}", ret) +} + +fn kebab_to_camel(n: &str) -> Ident { + let mut word_start = true; + let mut ret = String::new(); + for c in n.chars() { + if c == '-' { + word_start = true; + continue; + } + if word_start { + ret.extend(c.to_uppercase()) + } else { + ret.push(c) + }; + word_start = false; + } + format_ident!("{}", ret) +} + +pub fn kebab_to_var(n: &str) -> Ident { + kebab_to_snake(n) +} +pub fn kebab_to_cons(n: &str) -> Ident { + kebab_to_camel(n) +} +pub fn kebab_to_getter(n: &str) -> Ident { + kebab_to_snake(n) +} + +pub enum ResourceItemName { + Constructor, + Method(Ident), + Static(Ident), +} + +pub enum FnName { + Associated(Ident, ResourceItemName), + Plain(Ident), +} +pub fn kebab_to_fn(n: &str) -> FnName { + if let Some(n) = n.strip_prefix("[constructor]") { + return FnName::Associated(kebab_to_type(n), ResourceItemName::Constructor); + } + if let Some(n) = n.strip_prefix("[method]") { + let mut i = n.split('.'); + let r = i.next().unwrap(); + let n = i.next().unwrap(); + return FnName::Associated( + kebab_to_type(r), + ResourceItemName::Method(kebab_to_snake(n)), + ); + } + if let Some(n) = n.strip_prefix("[static]") { + let mut i = n.split('.'); + let r = i.next().unwrap(); + let n = i.next().unwrap(); + return FnName::Associated( + kebab_to_type(r), + ResourceItemName::Static(kebab_to_snake(n)), + ); + } + FnName::Plain(kebab_to_snake(n)) +} + +pub fn kebab_to_type(n: &str) -> Ident { + kebab_to_camel(n) +} + +pub fn kebab_to_namespace(n: &str) -> Ident { + kebab_to_snake(n) +} diff --git a/src/hyperlight_component_util/src/etypes.rs b/src/hyperlight_component_util/src/etypes.rs new file mode 100644 index 000000000..9a435fe92 --- /dev/null +++ b/src/hyperlight_component_util/src/etypes.rs @@ -0,0 +1,477 @@ +/// Elaborated component model types +/// +/// This has the basic type definitions for the elaborated types. They +/// correspond roughly to the "Elaborated Types" section in the +/// specification. +use crate::structure::*; + +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct Name<'a> { + pub name: &'a str, +} + +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum IntWidth { + I8, + I16, + I32, + I64, +} +impl IntWidth { + pub fn width(self) -> u8 { + match self { + IntWidth::I8 => 8, + IntWidth::I16 => 16, + IntWidth::I32 => 32, + IntWidth::I64 => 64, + } + } +} + +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum FloatWidth { + F32, + F64, +} +impl FloatWidth { + pub fn width(self) -> u8 { + match self { + FloatWidth::F32 => 32, + FloatWidth::F64 => 64, + } + } +} + +/// recordfield_e in the specification +#[derive(Debug, Clone)] +pub struct RecordField<'a> { + pub name: Name<'a>, + pub ty: Value<'a>, +} + +/// variantcase_e in the specification +#[derive(Debug, Clone)] +pub struct VariantCase<'a> { + pub name: Name<'a>, + pub ty: Option>, + pub refines: Option, +} + +/// valtype_e in the specification +#[derive(Debug, Clone)] +pub enum Value<'a> { + Bool, + S(IntWidth), + U(IntWidth), + F(FloatWidth), + Char, + String, + List(Box>), + Record(Vec>), + Tuple(Vec>), + Flags(Vec>), + Variant(Vec>), + Enum(Vec>), + Option(Box>), + Result(Box>>, Box>>), + Own(Handleable), + Borrow(Handleable), + /// This records that a type variable was once here, and is used + /// to enforce export namedness checks. + Var(Option, Box>), +} + +/// global resource identifier +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ResourceId { + pub(super) id: u32, +} + +#[derive(Debug, Clone)] +pub enum FreeTyvar { + U(u32, u32), + E(u32, u32), +} + +#[derive(Debug, Clone)] +pub enum Tyvar { + Bound(u32), + Free(FreeTyvar), +} + +#[derive(Debug, Clone)] +pub struct Param<'a> { + pub name: Name<'a>, + pub ty: Value<'a>, +} + +#[derive(Debug, Clone)] +pub enum Result<'a> { + Unnamed(Value<'a>), + Named(Vec>), +} + +/// functype_e in the specification +#[derive(Debug, Clone)] +pub struct Func<'a> { + pub params: Vec>, + pub result: Result<'a>, +} + +/// In the spec, this does not exist, but a validation rule ensures an +/// invariant that certain deftype_e s are of this form. +#[derive(Debug, Clone)] +pub enum Handleable { + Var(Tyvar), + Resource(ResourceId), +} + +/// deftype_e in the specification +#[derive(Debug, Clone)] +pub enum Defined<'a> { + Handleable(Handleable), + Value(Value<'a>), + Func(Func<'a>), + Instance(QualifiedInstance<'a>), + Component(Component<'a>), +} + +/// typebound_e in the specification +#[derive(Debug, Clone)] +pub enum TypeBound<'a> { + Eq(Defined<'a>), + SubResource, +} + +/// The name of an import or export of the current +/// component/context. Not in the spec; only used for +/// [`BoundedTyvar::origin`] below. +/// +/// Any string present in one of these should also be present in an +/// [`ExternDecl::kebab_name`] in a relevant place. +#[derive(Debug, Clone, PartialEq)] +pub enum ImportExport<'a> { + Import(&'a str), + Export(&'a str), +} +impl<'a> ImportExport<'a> { + pub fn name(&self) -> &'a str { + match self { + ImportExport::Import(s) => s, + ImportExport::Export(s) => s, + } + } + pub fn imported(&self) -> bool { + match self { + ImportExport::Import(_) => true, + ImportExport::Export(_) => false, + } + } +} + +/// An (optional) path through the imports/exports of a current +/// component/context. Not in the spec; only used for +/// [`BoundedTyvar::origin`] below. +#[derive(Debug, Clone, PartialEq)] +pub struct TyvarOrigin<'a> { + /// Note that the most recent (closest) element is last + pub path: Option>>, +} + +impl<'a> TyvarOrigin<'a> { + pub fn new() -> Self { + TyvarOrigin { path: Some(vec![]) } + } + pub fn push(&self, x: Option>) -> Self { + match (&self.path, x) { + (None, _) => TyvarOrigin { path: None }, + (_, None) => self.clone(), + (Some(xs), Some(x)) => { + let mut xs = xs.clone(); + xs.push(x); + TyvarOrigin { path: Some(xs) } + } + } + } + pub fn matches>>(&self, path: I) -> bool { + self.path + .as_ref() + .map(|p| p.iter().rev().eq(path)) + .unwrap_or(false) + } + pub fn is_local< + I: DoubleEndedIterator> + + ExactSizeIterator>, + >( + &self, + path: I, + ) -> Option>> { + let mut other = path.rev().skip(1).rev(); + let Some(path) = self.path.as_ref() else { + return None; + }; + let path = path.iter(); + let mut path = path.rev(); + while let Some(elem) = other.next() { + match path.next() { + None => break, + Some(oe) if oe != elem => return None, + _ => (), + } + } + Some(path.map(|p| p.clone()).collect()) + } + pub fn last_name(&self) -> Option<&'a str> { + self.path + .as_ref() + .and_then(|x| x.get(0)) + .map(|ie| ie.name()) + } + pub fn is_imported(&self) -> bool { + let Some(p) = &self.path else { + return false; + }; + p[p.len() - 1].imported() + } +} + +/// boundedtyvar_e in the spec +/// +/// Because we use a de bruijn representation of type indices, this is +/// only the type_bound - which variable it is binding is implicit in +/// its position in the list. +#[derive(Debug, Clone)] +pub struct BoundedTyvar<'a> { + /// This is not important for typechecking, but is used to keep + /// track of where a type variable originated from in order to + /// decide on a canonical name to be used in bindings + /// generation. + pub origin: TyvarOrigin<'a>, + pub bound: TypeBound<'a>, +} + +impl<'a> BoundedTyvar<'a> { + pub fn new(bound: TypeBound<'a>) -> Self { + BoundedTyvar { + origin: TyvarOrigin::new(), + bound, + } + } + pub fn push_origin(&self, x: Option>) -> Self { + BoundedTyvar { + origin: self.origin.push(x), + ..self.clone() + } + } +} + +/// externdesc_e in the specification +#[derive(Debug, Clone)] +pub enum ExternDesc<'a> { + CoreModule(CoreModule<'a>), + Func(Func<'a>), + /* TODO: First-class values */ + Type(Defined<'a>), + /// This uses an [`Instance`] rather than a [`QualifiedInstance`] + /// because the instance's evars need to be propagated up to the + /// surrounding component/instance (so that e.g. `alias`ing them + /// and using them in another import/export is possible). + Instance(Instance<'a>), + Component(Component<'a>), +} + +/// Merely a convenience for certain helper code +#[derive(Debug, Clone)] +pub enum CoreOrComponentExternDesc<'a> { + Core(CoreExternDesc), + Component(ExternDesc<'a>), +} + +/// externdecl_e in the specification +#[derive(Debug, Clone)] +pub struct ExternDecl<'a> { + pub kebab_name: &'a str, + pub desc: ExternDesc<'a>, +} + +/// `instancetype_e` in the specification. +/// +/// An "opened" instance, whose existential variables are recorded in +/// some surrounding context. +#[derive(Debug, Clone)] +pub struct Instance<'a> { + pub exports: Vec>, +} + +/// This is an instance together with its existential variables. This +/// concept doesn't exist as a named syntax class in the specification, but +/// is the payload of the instance case of `deftype_e` and the output +/// of the instance declaration inference judgement. +#[derive(Debug, Clone)] +pub struct QualifiedInstance<'a> { + /// Existential variables produced by this instance (which may be + /// referred to by [`exports`](Instance::exports)). These are stored in + /// "outside-in" order that matches how they would be written on + /// paper: de Bruijn index Bound(0) in the imports is the last + /// element in the list, and later elements can depend on earlier + /// ones. + pub evars: Vec>, + pub unqualified: Instance<'a>, +} + +/// componenttype_e in the specification +#[derive(Debug, Clone)] +pub struct Component<'a> { + /// Universal variables over which this component is parameterized + /// (which may be referred to by `imports`). These are stored in + /// "outside-in" order that matches how they would be written on + /// paper: de Bruijn index Bound(0) in the imports is the last + /// element in the list, and later elements can depend on earlier + /// ones. + pub uvars: Vec>, + pub imports: Vec>, + /// Since we already have [`QualifiedInstance`], we use that to + /// keep track of both the evars and the actual instance, unlike + /// in the spec; this is quite natural, since during inference the + /// evars are generated by the exports. However, they conceptually + /// belong here as much as there: instantiating a component should + /// add them to the context as non-imported uvars and produce an + /// [`Instance`], rather than a [`QualifiedInstance`] directly. + pub instance: QualifiedInstance<'a>, +} + +// core:importdecl in the specification is wasmparser::Import + +// core:importdesc in the specification +#[derive(Debug, Clone)] +pub enum CoreExternDesc { + Func(wasmparser::FuncType), + Table(wasmparser::TableType), + Memory(wasmparser::MemoryType), + Global(wasmparser::GlobalType), +} + +/// core:exportdecl in the specification +#[derive(Debug, Clone)] +pub struct CoreExportDecl<'a> { + pub name: Name<'a>, + pub desc: CoreExternDesc, +} + +// core:functype is wasmparser::FuncType + +/// core:instancetype_e in the specification +#[derive(Debug, Clone)] +pub struct CoreInstance<'a> { + pub exports: Vec>, +} + +/// core:moduletype_e in the specification +#[derive(Debug, Clone)] +pub struct CoreModule<'a> { + pub _imports: Vec>, + pub _exports: Vec>, +} + +/// core:deftype_e in the specification +#[derive(Debug, Clone)] +pub enum CoreDefined<'a> { + Func(wasmparser::FuncType), + Module(CoreModule<'a>), +} + +/// gamma_c in the specification +#[derive(Debug, Clone)] +pub struct CoreCtx<'a> { + pub types: Vec>, + pub funcs: Vec, + pub modules: Vec>, + pub instances: Vec>, + pub tables: Vec, + pub mems: Vec, + pub globals: Vec, +} + +impl<'a> CoreCtx<'a> { + pub fn new() -> Self { + CoreCtx { + types: Vec::new(), + funcs: Vec::new(), + modules: Vec::new(), + instances: Vec::new(), + tables: Vec::new(), + mems: Vec::new(), + globals: Vec::new(), + } + } +} + +/// resourcetype_e in the specification +#[derive(Debug, Clone)] +pub struct Resource { + // One day, there will be a `rep` field here... + pub _dtor: Option, +} + +/// gamma in the specification +#[derive(Debug, Clone)] +pub struct Ctx<'p, 'a> { + pub parent: Option<&'p Ctx<'p, 'a>>, + pub outer_boundary: bool, + pub core: CoreCtx<'a>, + /// Universally-quantified variables, specifying for each the + /// known bound and whether or not it was imported. Uvars can come + /// from imports or component instantiations; only the imported + /// ones can be allowed to escape in the type of a components + /// exports/imports, since only those can be named outside of the + /// component itself. + pub uvars: Vec<(BoundedTyvar<'a>, bool)>, + /// Existentially-quantified variables, specifying for each the + /// known bound and, if it was locally defined, the type which + /// instantiates it. + pub evars: Vec<(BoundedTyvar<'a>, Option>)>, + pub rtypes: Vec, + pub types: Vec>, + pub components: Vec>, + pub instances: Vec>, + pub funcs: Vec>, +} + +impl<'p, 'a> Ctx<'p, 'a> { + pub fn new<'c>(parent: Option<&'p Ctx<'c, 'a>>, outer_boundary: bool) -> Self { + Ctx { + parent, + outer_boundary, + core: CoreCtx::new(), + uvars: Vec::new(), + evars: Vec::new(), + rtypes: Vec::new(), + types: Vec::new(), + components: Vec::new(), + instances: Vec::new(), + funcs: Vec::new(), + } + } +} + +pub struct CtxParentIterator<'i, 'p: 'i, 'a: 'i> { + ctx: Option<&'i Ctx<'p, 'a>>, +} +impl<'i, 'p, 'a> Iterator for CtxParentIterator<'i, 'p, 'a> { + type Item = &'i Ctx<'p, 'a>; + fn next(&mut self) -> Option { + match self.ctx { + Some(ctx) => { + self.ctx = ctx.parent; + Some(ctx) + } + None => None, + } + } +} + +impl<'p, 'a> Ctx<'p, 'a> { + pub fn parents<'i>(self: &'i Self) -> CtxParentIterator<'i, 'p, 'a> { + CtxParentIterator { ctx: Some(self) } + } +} diff --git a/src/hyperlight_component_util/src/hl.rs b/src/hyperlight_component_util/src/hl.rs new file mode 100644 index 000000000..8e8590be8 --- /dev/null +++ b/src/hyperlight_component_util/src/hl.rs @@ -0,0 +1,582 @@ +use itertools::Itertools; +use proc_macro2::{Ident, TokenStream}; +use quote::{format_ident, quote}; + +use crate::emit::{kebab_to_cons, kebab_to_var, State}; +use crate::etypes::{self, Defined, Handleable, TypeBound, Tyvar, Value}; +use crate::{dbg_println, rtypes}; + +pub fn emit_fn_hl_name(s: &State, kebab: &str) -> String { + s.mod_cursor + .iter() + .map(|x| x.to_string()) + .chain(std::iter::once(kebab.to_string())) + .join("::") +} + +pub fn emit_hl_unmarshal_toplevel_value( + s: &mut State, + id: Ident, + tv: Tyvar, + vt: &Value, +) -> TokenStream { + let tname = rtypes::emit_var_ref_value(s, &tv); + let mut s = s.clone(); + let Tyvar::Bound(n) = tv else { + panic!("impossible tyvar") + }; + s.var_offset += n as usize + 1; + let s = &mut s; + match vt { + Value::Record(rfs) => { + let cursor = format_ident!("{}_cursor", id); + let inid = format_ident!("{}_field", id); + let (decls, uses) = rfs + .iter() + .map(|rf| { + let field_name = kebab_to_var(rf.name.name); + let field_name_var = format_ident!("{}_field_{}", id, field_name); + let vtun = emit_hl_unmarshal_value(s, inid.clone(), &rf.ty); + ( + quote! { + let #inid = &#id[#cursor..]; + let (#field_name_var, b) = { #vtun }; + #cursor += b; + }, + quote! { + #field_name: #field_name_var, + }, + ) + }) + .unzip::<_, _, Vec<_>, Vec<_>>(); + quote! { + let mut #cursor = 0; + #(#decls)* + (#tname { #(#uses)* }, #cursor) + } + } + Value::Flags(ns) => { + let bytes = usize::div_ceil(ns.len(), 8); + let fields = ns.iter().enumerate().map(|(i, n)| { + let byte_offset = i / 8; + let bit_offset = i % 8; + let fieldid = kebab_to_var(n.name); + quote! { + #fieldid: (#id[#byte_offset] >> #bit_offset) & 0x1 == 1, + } + }); + quote! { + (#tname { #(#fields)*, #bytes }) + } + } + Value::Variant(vcs) => { + let inid = format_ident!("{}_body", id); + let vcs = vcs.iter().enumerate().map(|(i, vc)| { + let case_name = kebab_to_cons(vc.name.name); + let i = i as u32; + let case_name_var = format_ident!("{}_case_{}", id, case_name); + match &vc.ty { + Some(ty) => { + let vtun = emit_hl_unmarshal_value(s, inid.clone(), ty); + quote! { + #i => { + let (#case_name_var, b) = { #vtun }; + (#tname::#case_name(#case_name_var), b + 4) + } + } + } + None => quote! { + #i => (#tname::#case_name, 4) + }, + } + }); + quote! { + let n = u32::from_ne_bytes(#id[0..4].try_into().unwrap()); + let #inid = &#id[4..]; + match n { + #(#vcs,)* + _ => panic!("invalid value for variant"), + } + } + } + Value::Enum(ns) => { + let vcs = ns.iter().enumerate().map(|(i, n)| { + let case_name = kebab_to_cons(n.name); + let i = i as u32; + quote! { #i => ( #tname::#case_name, 4) } + }); + quote! { + let n = u32::from_ne_bytes(#id[0..4].try_into().unwrap()); + match n { + #(#vcs,)* + _ => panic!("invalid value for enum"), + } + } + } + _ => emit_hl_unmarshal_value(s, id, vt), + } +} + +fn resolve_tyvar_to_resource(s: &mut State, v: u32) -> u32 { + match s.bound_vars[v as usize].bound { + TypeBound::SubResource => v, + TypeBound::Eq(Defined::Handleable(Handleable::Var(Tyvar::Bound(vv)))) => { + resolve_tyvar_to_resource(s, v + vv + 1) + } + _ => panic!("impossible: resource var is not resource"), + } +} +pub fn resolve_handleable_to_resource(s: &mut State, ht: &Handleable) -> u32 { + match ht { + Handleable::Var(Tyvar::Bound(vi)) => { + resolve_tyvar_to_resource(s, s.var_offset as u32 + *vi) + } + _ => panic!("impossible handleable in type"), + } +} + +pub fn emit_hl_unmarshal_value(s: &mut State, id: Ident, vt: &Value) -> TokenStream { + match vt { + Value::Bool => quote! { if #id[0] != 0 { true } else { false } }, + Value::S(_) | Value::U(_) | Value::F(_) => { + let (tid, width) = rtypes::numeric_rtype(vt); + let blen = width as usize / 8; + quote! { + (#tid::from_ne_bytes(#id[0..#blen].try_into().unwrap()), #blen) + } + } + Value::Char => quote! { + (char::from_u32_unchecked(u32::from_ne_bytes( + #id[0..4].try_into().unwrap())), 4) + }, + Value::String => quote! { + let n = u32::from_ne_bytes(#id[0..4].try_into().unwrap()) as usize; + let s = ::core::str::from_utf8(&#id[4..4 + n]).unwrap().to_string(); // todo: better error handling + (s, n + 4) + }, + Value::List(vt) => { + let retid = format_ident!("{}_list", id); + let inid = format_ident!("{}_elem", id); + let vtun = emit_hl_unmarshal_value(s, inid.clone(), vt); + quote! { + let n = u32::from_ne_bytes(#id[0..4].try_into().unwrap()) as usize; + let mut #retid = alloc::vec::Vec::new(); + let mut cursor = 4; + for i in 0..n { + let #inid = &#id[cursor..]; + let (x, b) = { #vtun }; + cursor += b; + #retid.push(x); + } + (#retid, cursor) + } + } + Value::Record(_) => panic!("record not at top level of valtype"), + Value::Tuple(vts) => { + let inid = format_ident!("{}_elem", id); + let len = format_ident!("{}_len", id); + let (ns, vtuns) = vts + .iter() + .enumerate() + .map(|(i, vt)| { + let vtun = emit_hl_unmarshal_value(s, inid.clone(), vt); + let retid = format_ident!("{}_elem{}", id, i); + ( + retid.clone(), + quote! { + let (#retid, b) = { #vtun }; + #len += b; + let #inid = &#inid[b..]; + }, + ) + }) + .unzip::<_, _, Vec<_>, Vec<_>>(); + quote! { + let #inid = &#id[0..]; + let mut #len = 0; + #(#vtuns)* + ((#(#ns),*), #len) + } + } + Value::Flags(_) => panic!("flags not at top level of valtype"), + Value::Variant(_) => panic!("variant not at top level of valtype"), + Value::Enum(_) => panic!("enum not at top level of valtype"), + Value::Option(vt) => { + let inid = format_ident!("{}_body", id); + let vtun = emit_hl_unmarshal_value(s, inid.clone(), vt); + quote! { + let n = u8::from_ne_bytes(#id[0..1].try_into().unwrap()); + if n != 0 { + let #inid = &#id[1..]; + let (x, b) = { #vtun }; + (::core::option::Option::Some(x), b + 1) + } else { + (::core::option::Option::None, 1) + } + } + } + Value::Result(vt1, vt2) => { + let inid = format_ident!("{}_body", id); + let vtun1 = if let Some(ref vt1) = **vt1 { + emit_hl_unmarshal_value(s, inid.clone(), vt1) + } else { + quote! { ((), 0) } + }; + let vtun2 = if let Some(ref vt2) = **vt2 { + emit_hl_unmarshal_value(s, inid.clone(), vt2) + } else { + quote! { ((), 0) } + }; + quote! { + let i = u8::from_ne_bytes(#id[0..1].try_into().unwrap()); + let #inid = &#id[1..]; + if i == 0 { + let (x, b) = { #vtun1 }; + (::core::result::Result::Ok(x), b + 1) + } else { + let (x, b)= { #vtun2 }; + (::core::result::Result::Err(x), b +1) + } + } + } + Value::Own(ht) => { + let vi = resolve_handleable_to_resource(s, ht); + dbg_println!("resolved ht to r (1) {:?} {:?}", ht, vi); + if s.is_guest { + let rid = format_ident!("HostResource{}", vi); + quote! { + let i = u32::from_ne_bytes(#id[0..4].try_into().unwrap()); + (::wasmtime::component::Resource::<#rid>::new_own(i), 4) + } + } else { + let rid = format_ident!("resource{}", vi); + quote! { + let i = u32::from_ne_bytes(#id[0..4].try_into().unwrap()); + let Some(v) = rts.#rid[i as usize].take() else { + // todo: better error handling + panic!(""); + }; + (*v, 4) + } + } + } + Value::Borrow(ht) => { + let vi = resolve_handleable_to_resource(s, ht); + dbg_println!("resolved ht to r (2) {:?} {:?}", ht, vi); + if s.is_guest { + let rid = format_ident!("HostResource{}", vi); + quote! { + let i = u32::from_ne_bytes(#id[0..4].try_into().unwrap()); + (::wasmtime::component::Resource::<#rid>::new_borrow(i), 4) + } + } else { + let rid = format_ident!("resource{}", vi); + quote! { + let i = u32::from_ne_bytes(#id[0..4].try_into().unwrap()); + let Some(v) = rts.#rid[i as usize].borrow() else { + // todo: better error handling + panic!(""); + }; + (v, 4) + } + } + } + Value::Var(tv, _) => { + let Some(Tyvar::Bound(n)) = tv else { + panic!("impossible tyvar") + }; + let (n, Some(Defined::Value(vt))) = s.resolve_tv(*n) else { + panic!("unresolvable tyvar (2)"); + }; + let vt = vt.clone(); + emit_hl_unmarshal_toplevel_value(s, id, Tyvar::Bound(n), &vt) + } + } +} + +pub fn emit_hl_marshal_toplevel_value( + s: &mut State, + id: Ident, + tv: Tyvar, + vt: &Value, +) -> TokenStream { + let tname = rtypes::emit_var_ref_value(s, &tv); + let mut s = s.clone(); + let Tyvar::Bound(n) = tv else { + panic!("impossible tyvar") + }; + s.var_offset += n as usize + 1; + let s = &mut s; + match vt { + Value::Record(rfs) => { + let retid = format_ident!("{}_record", id); + let fields = rfs + .iter() + .map(|rf| { + let field_name = kebab_to_var(rf.name.name); + let fieldid = format_ident!("{}_field_{}", id, field_name); + let vtun = emit_hl_marshal_value(s, fieldid.clone(), &rf.ty); + quote! { + let #fieldid = #id.#field_name; + #retid.extend({ #vtun }); + } + }) + .collect::>(); + quote! { + let mut #retid = alloc::vec::Vec::new(); + #(#fields)* + #retid + } + } + Value::Flags(ns) => { + let bytes = usize::div_ceil(ns.len(), 8); + let fields = ns + .iter() + .enumerate() + .map(|(i, n)| { + let byte_offset = i / 8; + let bit_offset = i % 8; + let fieldid = kebab_to_var(n.name); + quote! { + bytes[#byte_offset] |= (if #id.#fieldid { 1 } else { 0 }) << #bit_offset; + } + }) + .collect::>(); + quote! { + let mut bytes = [0; #bytes]; + #(#fields)* + alloc::vec::Vec::from(bytes) + } + } + Value::Variant(vcs) => { + let retid = format_ident!("{}_ret", id); + let bodyid = format_ident!("{}_body", id); + let vcs = vcs + .iter() + .enumerate() + .map(|(i, vc)| { + let i = i as u32; + let case_name = kebab_to_cons(vc.name.name); + match &vc.ty { + Some(ty) => { + let vtun = emit_hl_marshal_value(s, bodyid.clone(), ty); + quote! { + #tname::#case_name(#bodyid) => { + #retid.extend(u32::to_ne_bytes(#i)); + #retid.extend({ #vtun }) + } + } + } + None => { + quote! { + #tname::#case_name => { + #retid.extend(u32::to_ne_bytes(#i)); + } + } + } + } + }) + .collect::>(); + quote! { + let mut #retid = alloc::vec::Vec::new(); + match #id { + #(#vcs)* + } + #retid + } + } + Value::Enum(ns) => { + let vcs = ns.iter().enumerate().map(|(i, n)| { + let case_name = kebab_to_cons(n.name); + let i = i as u32; + quote! { #tname::#case_name => #i } + }); + quote! { + alloc::vec::Vec::from(u32::to_ne_bytes(match #id { + #(#vcs,)* + })) + } + } + _ => emit_hl_marshal_value(s, id, vt), + } +} + +pub fn emit_hl_marshal_value(s: &mut State, id: Ident, vt: &Value) -> TokenStream { + match vt { + Value::Bool => quote! { + alloc::vec::vec![if #id { 1u8 } else { 0u8 }] + }, + Value::S(_) | Value::U(_) | Value::F(_) => { + let (tid, _) = rtypes::numeric_rtype(vt); + quote! { alloc::vec::Vec::from(#tid::to_ne_bytes(#id)) } + } + Value::Char => quote! { + alloc::vec::Vec::from((#id as u32).to_ne_bytes()) + }, + Value::String => { + let retid = format_ident!("{}_string", id); + let bytesid = format_ident!("{}_bytes", id); + quote! { + let mut #retid = alloc::vec::Vec::new(); + let #bytesid = #id.into_bytes(); + #retid.extend(alloc::vec::Vec::from(u32::to_ne_bytes(#bytesid.len() as u32))); + #retid.extend(#bytesid); + #retid + } + } + Value::List(vt) => { + let retid = format_ident!("{}_list", id); + let inid = format_ident!("{}_elem", id); + let vtun = emit_hl_marshal_value(s, inid.clone(), vt); + quote! { + let mut #retid = alloc::vec::Vec::new(); + let n = #id.len(); + #retid.extend(alloc::vec::Vec::from(u32::to_ne_bytes(n as u32))); + for #inid in #id { + #retid.extend({ #vtun }) + } + #retid + } + } + Value::Record(_) => panic!("record not at top level of valtype"), + Value::Tuple(vts) => { + let retid = format_ident!("{}_tuple", id); + let inid = format_ident!("{}_elem", id); + let vtuns = vts.iter().enumerate().map(|(i, vt)| { + let i = syn::Index::from(i); + let vtun = emit_hl_marshal_value(s, inid.clone(), vt); + quote! { + let #inid = #id.#i; + #retid.extend({ #vtun }); + } + }); + quote! { + let mut #retid = alloc::vec::Vec::new(); + #(#vtuns)* + #retid + } + } + Value::Flags(_) => panic!("flags not at top level of valtype"), + Value::Variant(_) => panic!("flags not at top level of valtype"), + Value::Enum(_) => panic!("flags not at top level of valtype"), + Value::Option(vt) => { + let bodyid = format_ident!("{}_body", id); + let retid = format_ident!("{}_ret", id); + let vtun = emit_hl_marshal_value(s, bodyid.clone(), vt); + quote! { + match #id { + ::core::option::Option::Some(#bodyid) => { + let mut #retid = alloc::vec::Vec::from(u8::to_ne_bytes(1)); + #retid.extend({ #vtun }); + #retid + }, + ::core::option::Option::None => alloc::vec::Vec::from(u8::to_ne_bytes(0)) + } + } + } + Value::Result(vt1, vt2) => { + let bodyid = format_ident!("{}_body", id); + let retid = format_ident!("{}_ret", id); + let vtun1 = if let Some(ref vt1) = **vt1 { + let vtun = emit_hl_marshal_value(s, bodyid.clone(), &vt1); + quote! { #retid.extend({ #vtun }); } + } else { + quote! {} + }; + let vtun2 = if let Some(ref vt2) = **vt2 { + let vtun = emit_hl_marshal_value(s, bodyid.clone(), &vt2); + quote! { #retid.extend({ #vtun }); } + } else { + quote! {} + }; + quote! { + match #id { + ::core::result::Result::Ok(#bodyid) => { + let mut #retid = alloc::vec::Vec::from(u8::to_ne_bytes(0)); + #vtun1 + #retid + }, + ::core::result::Result::Err(#bodyid) => { + let mut #retid = alloc::vec::Vec::from(u8::to_ne_bytes(1)); + #vtun2 + #retid + }, + } + } + } + Value::Own(ht) => { + let vi = resolve_handleable_to_resource(s, ht); + dbg_println!("resolved ht to r (3) {:?} {:?}", ht, vi); + if s.is_guest { + quote! { + alloc::vec::Vec::from(u32::to_ne_bytes(#id.rep())) + } + } else { + let rid = format_ident!("resource{}", vi); + quote! { + let i = rts.#rid.len(); + rts.#rid.push_back(::hyperlight_common::resource::ResourceEntry::give(#id)); + alloc::vec::Vec::from(u32::to_ne_bytes(i as u32)) + } + } + } + Value::Borrow(ht) => { + let vi = resolve_handleable_to_resource(s, ht); + dbg_println!("resolved ht to r (6) {:?} {:?}", ht, vi); + if s.is_guest { + quote! { + alloc::vec::Vec::from(u32::to_ne_bytes(#id.rep())) + } + } else { + let rid = format_ident!("resource{}", vi); + quote! { + let i = rts.#rid.len(); + rts.#rid.push_back(::hyperlight_common::resource::ResourceEntry::lend(#id)); + alloc::vec::Vec::from(u32::to_ne_bytes(i as u32)) + } + } + } + Value::Var(tv, _) => { + let Some(Tyvar::Bound(n)) = tv else { + panic!("impossible tyvar") + }; + let (n, Some(Defined::Value(vt))) = s.resolve_tv(*n) else { + panic!("unresolvable tyvar (2)"); + }; + let vt = vt.clone(); + emit_hl_marshal_toplevel_value(s, id, Tyvar::Bound(n), &vt) + } + } +} + +pub fn emit_hl_unmarshal_param(s: &mut State, id: Ident, pt: &Value) -> TokenStream { + let toks = emit_hl_unmarshal_value(s, id, pt); + quote! { { #toks }.0 } +} + +pub fn emit_hl_unmarshal_result(s: &mut State, id: Ident, rt: &etypes::Result) -> TokenStream { + match rt { + etypes::Result::Named(rs) if rs.len() == 0 => quote! { () }, + etypes::Result::Unnamed(vt) => { + let toks = emit_hl_unmarshal_value(s, id, vt); + quote! { { #toks }.0 } + } + _ => panic!("named results not supported"), + } +} + +pub fn emit_hl_marshal_param(s: &mut State, id: Ident, pt: &Value) -> TokenStream { + let toks = emit_hl_marshal_value(s, id, pt); + quote! { { #toks } } +} + +pub fn emit_hl_marshal_result(s: &mut State, id: Ident, rt: &etypes::Result) -> TokenStream { + match rt { + etypes::Result::Named(rs) if rs.len() == 0 => quote! { ::alloc::vec::Vec::new() }, + etypes::Result::Unnamed(vt) => { + let toks = emit_hl_marshal_value(s, id, vt); + quote! { { #toks } } + } + _ => panic!("named results not supported"), + } +} diff --git a/src/hyperlight_component_util/src/host.rs b/src/hyperlight_component_util/src/host.rs new file mode 100644 index 000000000..fdb310977 --- /dev/null +++ b/src/hyperlight_component_util/src/host.rs @@ -0,0 +1,338 @@ +use proc_macro2::{Ident, TokenStream}; +use quote::{format_ident, quote}; + +use crate::emit::{ + kebab_to_fn, kebab_to_getter, kebab_to_namespace, kebab_to_type, kebab_to_var, split_wit_name, + FnName, ResourceItemName, State, WitName, +}; +use crate::etypes::{Component, ExternDecl, ExternDesc, Instance, Tyvar}; +use crate::hl::{ + emit_fn_hl_name, emit_hl_marshal_param, emit_hl_marshal_result, emit_hl_unmarshal_param, + emit_hl_unmarshal_result, +}; +use crate::{dbg_println, resource, rtypes}; + +fn emit_export_extern_decl<'a, 'b, 'c>( + s: &'c mut State<'a, 'b>, + ed: &'c ExternDecl<'b>, +) -> TokenStream { + match &ed.desc { + ExternDesc::CoreModule(_) => panic!("core module (im/ex)ports are not supported"), + ExternDesc::Func(ft) => { + match kebab_to_fn(ed.kebab_name) { + FnName::Plain(n) => { + let param_decls = ft + .params + .iter() + .map(|p| rtypes::emit_func_param(s, p)) + .collect::>(); + let result_decl = rtypes::emit_func_result(s, &ft.result); + let hln = emit_fn_hl_name(&s, ed.kebab_name); + let ret = format_ident!("ret"); + let marshal = ft + .params + .iter() + .map(|p| { + let me = emit_hl_marshal_param(s, kebab_to_var(p.name.name), &p.ty); + quote! { args.push(::hyperlight_common::flatbuffer_wrappers::function_types::ParameterValue::VecBytes(#me)); } + }) + .collect::>(); + let unmarshal = emit_hl_unmarshal_result(s, ret.clone(), &ft.result); + quote! { + fn #n(&mut self, #(#param_decls),*) -> #result_decl { + let mut args = Vec::new(); + #(#marshal)* + let #ret = ::hyperlight_host::sandbox::Callable::call(&mut self.sb, + #hln, + ::hyperlight_host::func::ReturnType::VecBytes, + Some(args), + ); + let ::std::result::Result::Ok(::hyperlight_host::func::ReturnValue::VecBytes(#ret)) = #ret else { panic!("bad return from guest {:?}", #ret) }; + #unmarshal + } + } + } + FnName::Associated(_, _) => + // this can be fixed when the guest wasm and + // general macros are split + { + panic!("guest resources are not currently supported") + } + } + } + ExternDesc::Type(_) => { + // no runtime representation is needed for types + quote! {} + } + ExternDesc::Instance(it) => { + let wn = split_wit_name(ed.kebab_name); + emit_export_instance(s, wn.clone(), it); + + let getter = kebab_to_getter(wn.name); + let tn = kebab_to_type(wn.name); + quote! { + type #tn = Self; + #[allow(refining_impl_trait)] + fn #getter<'a>(&'a mut self) -> &'a mut Self { + self + } + } + } + ExternDesc::Component(_) => { + panic!("nested components not yet supported in rust bindings"); + } + } +} + +#[derive(Clone)] +struct SelfInfo { + orig_id: Ident, + type_id: Vec, + outer_id: Ident, + inner_preamble: TokenStream, + inner_id: Ident, +} +impl SelfInfo { + fn new(orig_id: Ident) -> Self { + let outer_id = format_ident!("captured_{}", orig_id); + let inner_id = format_ident!("slf"); + SelfInfo { + orig_id, + type_id: vec![format_ident!("I")], + inner_preamble: quote! { + let mut #inner_id = #outer_id.lock().unwrap(); + let mut #inner_id = ::std::ops::DerefMut::deref_mut(&mut #inner_id); + }, + outer_id, + inner_id, + } + } + fn with_getter(&self, tp: TokenStream, type_name: Ident, getter: Ident) -> Self { + let mut toks = self.inner_preamble.clone(); + let id = self.inner_id.clone(); + let mut type_id = self.type_id.clone(); + toks.extend(quote! { + let mut #id = #tp::#getter(::std::borrow::BorrowMut::<#(#type_id)::*>::borrow_mut(&mut #id)); + }); + type_id.push(type_name); + SelfInfo { + orig_id: self.orig_id.clone(), + type_id, + outer_id: self.outer_id.clone(), + inner_preamble: toks, + inner_id: id, + } + } +} + +fn emit_import_extern_decl<'a, 'b, 'c>( + s: &'c mut State<'a, 'b>, + get_self: SelfInfo, + ed: &'c ExternDecl<'b>, +) -> TokenStream { + match &ed.desc { + ExternDesc::CoreModule(_) => panic!("core module (im/ex)ports are not supported"), + ExternDesc::Func(ft) => { + let hln = emit_fn_hl_name(&s, ed.kebab_name); + dbg_println!("providing host function {}", hln); + let (pds, pus) = ft + .params + .iter() + .map(|p| { + let id = kebab_to_var(p.name.name); + ( + quote! { #id: ::std::vec::Vec }, + emit_hl_unmarshal_param(s, id, &p.ty), + ) + }) + .unzip::<_, _, Vec<_>, Vec<_>>(); + let tp = s.cur_trait_path(); + let callname = match kebab_to_fn(ed.kebab_name) { + FnName::Plain(n) => quote! { #tp::#n }, + FnName::Associated(r, m) => { + let hp = s.helper_path(); + match m { + ResourceItemName::Constructor => quote! { #hp #r::new }, + ResourceItemName::Method(mn) => quote! { #hp #r::#mn }, + ResourceItemName::Static(mn) => quote! { #hp #r::#mn }, + } + } + }; + let SelfInfo { + orig_id, + type_id, + outer_id, + inner_preamble, + inner_id, + } = get_self; + let ret = format_ident!("ret"); + let marshal_result = emit_hl_marshal_result(s, ret.clone(), &ft.result); + quote! { + let #outer_id = #orig_id.clone(); + let captured_rts = rts.clone(); + ::std::sync::Arc::new( + ::std::sync::Mutex::new(move |#(#pds),*| { + let mut rts = captured_rts.lock().unwrap(); + #inner_preamble + let #ret = #callname(::std::borrow::BorrowMut::<#(#type_id)::*>::borrow_mut(&mut #inner_id), #(#pus),*); + Ok(#marshal_result) + }) + ).register(sb, #hln) + .unwrap(); + } + } + ExternDesc::Type(_) => { + // no runtime representation is needed for types + quote! {} + } + ExternDesc::Instance(it) => { + let mut s = s.clone(); + let wn = split_wit_name(ed.kebab_name); + let type_name = kebab_to_type(wn.name); + let getter = kebab_to_getter(wn.name); + let tp = s.cur_trait_path(); + let get_self = get_self.with_getter(tp, type_name, getter); //quote! { #get_self let mut slf = &mut #tp::#getter(&mut *slf); }; + emit_import_instance(&mut s, get_self, wn.clone(), it) + } + ExternDesc::Component(_) => { + panic!("nested components not yet supported in rust bindings"); + } + } +} + +fn emit_import_instance<'a, 'b, 'c>( + s: &'c mut State<'a, 'b>, + get_self: SelfInfo, + wn: WitName, + it: &'c Instance<'b>, +) -> TokenStream { + let mut s = s.with_cursor(wn.namespace_idents()); + s.cur_helper_mod = Some(kebab_to_namespace(wn.name)); + s.cur_trait = Some(kebab_to_type(wn.name)); + + let imports = it + .exports + .iter() + .map(|ed| emit_import_extern_decl(&mut s, get_self.clone(), ed)) + .collect::>(); + + quote! { #(#imports)* } +} + +fn emit_export_instance<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, it: &'c Instance<'b>) { + let mut s = s.with_cursor(wn.namespace_idents()); + s.cur_helper_mod = Some(kebab_to_namespace(wn.name)); + + let exports = it + .exports + .iter() + .map(|ed| emit_export_extern_decl(&mut s, ed)) + .collect::>(); + + let ns = wn.namespace_path(); + let nsi = wn.namespace_idents(); + let trait_name = kebab_to_type(wn.name); + let r#trait = s.r#trait(&nsi, trait_name.clone()); + let tvs = r#trait + .tvs + .iter() + .map(|(_, (tv, _))| tv.unwrap()) + .collect::>(); + let tvs = tvs + .iter() + .map(|tv| rtypes::emit_var_ref(&mut s, &Tyvar::Bound(*tv))) + .collect::>(); + let (root_ns, root_base_name) = s.root_component_name.unwrap(); + let wrapper_name = kebab_to_wrapper_name(root_base_name); + let imports_name = kebab_to_imports_name(root_base_name); + s.root_mod.items.extend(quote! { + impl #ns::#trait_name <#(#tvs),*> for #wrapper_name { + #(#exports)* + } + }); +} + +fn kebab_to_wrapper_name(trait_name: &str) -> Ident { + format_ident!("{}Sandbox", kebab_to_type(trait_name)) +} +fn kebab_to_imports_name(trait_name: &str) -> Ident { + format_ident!("{}Imports", kebab_to_type(trait_name)) +} + +fn emit_component<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, ct: &'c Component<'b>) { + let mut s = s.with_cursor(wn.namespace_idents()); + let ns = wn.namespace_path(); + let r#trait = kebab_to_type(wn.name); + let import_trait = kebab_to_imports_name(wn.name); + let export_trait = format_ident!("{}Exports", r#trait); + let wrapper_name = kebab_to_wrapper_name(wn.name); + let import_id = format_ident!("imports"); + + let rtsid = format_ident!("{}Resources", r#trait); + s.import_param_var = Some(format_ident!("I")); + resource::emit_tables( + &mut s, + rtsid.clone(), + quote! { #ns::#import_trait }, + None, + false, + ); + + s.var_offset = ct.instance.evars.len(); + s.cur_trait = Some(import_trait.clone()); + let imports = ct + .imports + .iter() + .map(|ed| emit_import_extern_decl(&mut s, SelfInfo::new(import_id.clone()), ed)) + .collect::>(); + s.var_offset = 0; + + s.root_component_name = Some((ns.clone(), wn.name)); + s.cur_trait = Some(export_trait.clone()); + s.import_param_var = Some(format_ident!("I")); + let exports = ct + .instance + .unqualified + .exports + .iter() + .map(|ed| emit_export_extern_decl(&mut s, ed)) + .collect::>(); + + s.root_mod.items.extend(quote! { + pub struct #wrapper_name { + pub(crate) sb: S, + pub(crate) rt: ::std::sync::Arc<::std::sync::Mutex<#rtsid>>, + } + pub(crate) fn register_host_functions(sb: &mut S, i: I) -> ::std::sync::Arc<::std::sync::Mutex<#rtsid>> { + use ::hyperlight_host::sandbox_state::sandbox::EvolvableSandbox; + use ::hyperlight_host::func::host_functions::*; + let rts = ::std::sync::Arc::new(::std::sync::Mutex::new(#rtsid::new())); + let #import_id = ::std::sync::Arc::new(::std::sync::Mutex::new(i)); + #(#imports)* + rts + } + impl #ns::#export_trait for #wrapper_name { + #(#exports)* + } + impl #ns::#r#trait for ::hyperlight_host::sandbox::UninitializedSandbox { + type Exports = #wrapper_name; + fn instantiate(mut self, i: I) -> Self::Exports { + let rts = register_host_functions(&mut self, i); + let noop = ::core::default::Default::default(); + let sb = ::hyperlight_host::sandbox_state::sandbox::EvolvableSandbox::evolve(self, noop).unwrap(); + let cc = ::hyperlight_host::func::call_ctx::MultiUseGuestCallContext::start(sb); + #wrapper_name { + sb: cc, + rt: rts, + } + } + } + }); +} + +pub fn emit_toplevel<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, n: &str, ct: &'c Component<'b>) { + s.is_impl = true; + dbg_println!("\n\n=== starting host emit ===\n"); + let wn = split_wit_name(n); + emit_component(s, wn, ct) +} diff --git a/src/hyperlight_component_util/src/lib.rs b/src/hyperlight_component_util/src/lib.rs new file mode 100644 index 000000000..193a39280 --- /dev/null +++ b/src/hyperlight_component_util/src/lib.rs @@ -0,0 +1,35 @@ +// This, unlike the rest of hyperlight, isn't really a library (since +// it's only used by our own build-time tools), so the reasons not to +// panic don't really apply. +#![allow(clippy::unwrap_used)] +// "Needless" lifetimes are useful for clarity +#![allow(clippy::needless_lifetimes)] + +// Typechecking and elaboration +pub mod component; +pub mod elaborate; +pub mod etypes; +pub mod structure; +pub mod substitute; +pub mod subtype; +pub mod tv; +pub mod wf; + +// Generally useful for code emit +pub mod emit; +pub mod hl; +pub mod resource; +pub mod rtypes; +pub mod util; + +// Specific code emit +pub mod host; + +macro_rules! dbg_println { + ($($params:tt)*) => { + if std::env::var("HYPERLIGHT_COMPONENT_MACRO_DEBUG").is_ok() { + println!($($params)*); + } + } +} +pub(crate) use dbg_println; diff --git a/src/hyperlight_component_util/src/resource.rs b/src/hyperlight_component_util/src/resource.rs new file mode 100644 index 000000000..37e15a4f4 --- /dev/null +++ b/src/hyperlight_component_util/src/resource.rs @@ -0,0 +1,94 @@ +use proc_macro2::{Ident, TokenStream}; +use quote::{format_ident, quote}; + +use crate::emit::State; +use crate::etypes::{TypeBound, Tyvar}; +use crate::rtypes::emit_var_ref; + +pub fn emit_tables<'a, 'b, 'c>( + s: &'c mut State<'a, 'b>, + rtsid: Ident, + bound: TokenStream, + sv: Option, + is_guest: bool, +) { + let vs = s.bound_vars.clone(); + let (fields, inits) = vs + .iter() + .enumerate() + .map(|(i, v)| { + let field_name = format_ident!("resource{}", i); + let alloc_ns = if s.is_guest { + quote! { ::alloc } + } else { + quote! { ::std } + }; + match v.bound { + TypeBound::Eq(_) => (quote! { #field_name: () }, quote! { #field_name: () }), + TypeBound::SubResource => { + if v.origin.is_imported() && !is_guest { + let t = emit_var_ref(s, &Tyvar::Bound(i as u32)); + ( + quote! { + #field_name: #alloc_ns::collections::VecDeque< + ::hyperlight_common::resource::ResourceEntry<#t> + > + }, + quote! { #field_name: #alloc_ns::collections::VecDeque::new() }, + ) + } else if !v.origin.is_imported() && is_guest { + let t = emit_var_ref(s, &Tyvar::Bound(i as u32)); + ( + quote! { + #field_name: #alloc_ns::collections::VecDeque< + ::hyperlight_common::resource::ResourceEntry<#t> + > + }, + quote! { #field_name: #alloc_ns::collections::VecDeque::new() }, + ) + } else { + // we don't need to keep track of anything for + // resources owned by the other side + ( + quote! { + #field_name: () + }, + quote! { #field_name: () }, + ) + } + } + } + }) + .unzip::<_, _, Vec<_>, Vec<_>>(); + let (sv, svs, sphantom, sphantominit) = if let Some(sv) = sv { + ( + quote! { , S: #sv }, + quote! { , S }, + quote! { _phantomS: ::core::marker::PhantomData, }, + quote! { _phantomS: ::core::marker::PhantomData, }, + ) + } else { + ( + TokenStream::new(), + TokenStream::new(), + TokenStream::new(), + TokenStream::new(), + ) + }; + s.root_mod.items.extend(quote! { + pub(crate) struct #rtsid { + #(#fields,)* + _phantomI: ::core::marker::PhantomData, + #sphantom + } + impl #rtsid { + fn new() -> Self { + #rtsid { + #(#inits,)* + _phantomI: ::core::marker::PhantomData, + #sphantominit + } + } + } + }); +} diff --git a/src/hyperlight_component_util/src/rtypes.rs b/src/hyperlight_component_util/src/rtypes.rs new file mode 100644 index 000000000..74f85b657 --- /dev/null +++ b/src/hyperlight_component_util/src/rtypes.rs @@ -0,0 +1,759 @@ +//! The Rust representation of a component type (etype) + +use std::collections::{BTreeMap, BTreeSet, VecDeque}; +use std::vec::Vec; + +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use syn::Ident; + +use crate::dbg_println; +use crate::emit::{ + kebab_to_cons, kebab_to_fn, kebab_to_getter, kebab_to_namespace, kebab_to_type, kebab_to_var, + split_wit_name, FnName, ResourceItemName, State, WitName, +}; +use crate::etypes::{ + Component, Defined, ExternDecl, ExternDesc, Func, Handleable, ImportExport, Instance, Param, + Result, TypeBound, Tyvar, Value, +}; + +/// type variable instantiations +fn emit_tvis(s: &mut State, tvs: Vec) -> TokenStream { + let tvs = tvs + .iter() + .map(|tv| emit_var_ref(s, &Tyvar::Bound(*tv))) + .collect::>(); + if tvs.len() > 0 { + quote! { <#(#tvs),*> } + } else { + TokenStream::new() + } +} + +fn emit_resource_ref( + s: &mut State, + n: u32, + path: Vec, + _bound: TypeBound, +) -> TokenStream { + // todo: when the guest codegen is split into generic and wasm, + // this can go away, since an appropriate impl for the imports + // trait will be there + if s.is_guest && s.is_impl { + // Morally, this should check that the var is imported, but + // that information is gone by now (in the common prefix of + // the path that was chopped off), and we won't support + // resources exported from the guest until this whole special + // case is gone, so ignore it. + let id = format_ident!("HostResource{}", n); + return quote! { #id }; + } + // There is always at least one element in the path, which names + // the thing we are referring to + let rtrait = kebab_to_type(path[path.len() - 1].name()); + + // Deal specially with being in the local instance, where there is + // no instance type & so it is not easy to resolve the + // path-from-the-root to the resource type trait in question + if path.len() == 1 { + let helper = s.cur_helper_mod.clone().unwrap(); + let rtrait = kebab_to_type(path[0].name()); + let t = s.resolve_trait_immut(false, &vec![helper.clone(), rtrait.clone()]); + let tvis = emit_tvis(s, t.tv_idxs()); + let mut sv = quote! { Self }; + if let Some(s) = &s.self_param_var { + sv = quote! { #s }; + }; + return quote! { <#sv as #helper::#rtrait #tvis>::T }; + }; + + // Generally speaking, the structure that we expect to see in + // `path` ends in an instance that exports the resource type, + // followed by the resource type itself. We locate the resource + // trait by using that final instance name directly; any other + // names are just used to get to the type that implements it + let instance = path[path.len() - 2].name(); + let iwn = split_wit_name(instance); + let extras = path[0..path.len() - 2] + .iter() + .map(|p| { + let wn = split_wit_name(p.name()); + kebab_to_type(wn.name) + }) + .collect::>(); + let extras = quote! { #(#extras::)* }; + let rp = s.root_path(); + let tns = iwn.namespace_path(); + let instance_mod = kebab_to_namespace(iwn.name); + let instance_type = kebab_to_type(iwn.name); + let mut sv = quote! { Self }; + if path[path.len() - 2].imported() { + if let Some(iv) = &s.import_param_var { + sv = quote! { #iv } + }; + } else { + if let Some(s) = &s.self_param_var { + sv = quote! { #s } + }; + }; + let mut trait_path = Vec::new(); + trait_path.extend(iwn.namespace_idents()); + trait_path.push(instance_mod.clone()); + trait_path.push(rtrait.clone()); + let t = s.resolve_trait_immut(true, &trait_path); + let tvis = emit_tvis(s, t.tv_idxs()); + quote! { <#sv::#extras #instance_type as #rp #tns::#instance_mod::#rtrait #tvis>::T } +} + +fn try_find_local_var_id( + s: &mut State, + // this should be an absolute var number (no noff) + n: u32, +) -> Option { + if let Some((path, bound)) = s.is_noff_var_local(n) { + let var_is_helper = match bound { + TypeBound::Eq(_) => true, + TypeBound::SubResource => false, + }; + if !var_is_helper { + // it is a resource type + if s.is_helper { + // but we're in that resource type, so that's ok + if path.len() == 1 && s.cur_trait == Some(kebab_to_type(path[0].name())) { + return Some(quote! { Self::T }); + } + // otherwise, there is no way to reference that from here + return None; + } else { + let mut path_strs = vec!["".to_string(); path.len()]; + for (i, p) in path.iter().enumerate() { + path_strs[i] = p.name().to_string(); + } + let path = path + .into_iter() + .enumerate() + .map(|(i, p)| match p { + ImportExport::Import(_) => ImportExport::Import(&path_strs[i]), + ImportExport::Export(_) => ImportExport::Export(&path_strs[i]), + }) + .collect::>(); + return Some(emit_resource_ref(s, n, path, bound)); + } + } + dbg_println!("path is {:?}\n", path); + let mut path = path.iter().rev(); + let name = kebab_to_type(path.next().unwrap().name()); + let owner = path.next(); + if let Some(owner) = owner { + // if we have an instance type, use it + let wn = split_wit_name(owner.name()); + let rp = s.root_path(); + let tns = wn.namespace_path(); + let helper = kebab_to_namespace(wn.name); + Some(quote! { #rp #tns::#helper::#name }) + } else { + let hp = s.helper_path(); + Some(quote! { #hp #name }) + } + } else { + None + } +} + +pub fn emit_var_ref(s: &mut State, tv: &Tyvar) -> TokenStream { + let Tyvar::Bound(n) = tv else { + panic!("free tyvar in rust emit") + }; + emit_var_ref_noff(s, n + s.var_offset as u32, false) +} +pub fn emit_var_ref_value(s: &mut State, tv: &Tyvar) -> TokenStream { + let Tyvar::Bound(n) = tv else { + panic!("free tyvar in rust emit") + }; + emit_var_ref_noff(s, n + s.var_offset as u32, true) +} +pub fn emit_var_ref_noff(s: &mut State, n: u32, is_value: bool) -> TokenStream { + dbg_println!("var_ref {:?} {:?}", &s.bound_vars[n as usize], s.origin); + // if the variable was defined locally, try to reference it directly + let id = try_find_local_var_id(s, n); + let id = match id { + Some(id) => { + // if we are referencing the local one, we need to give it + // the variables it wants + let vs = s.get_noff_var_refs(n); + let vs = vs + .iter() + .map(|n| emit_var_ref_noff(s, *n, false)) + .collect::>(); + let vs_toks = if !vs.is_empty() { + if is_value { + quote! { ::<#(#vs),*> } + } else { + quote! { <#(#vs),*> } + } + } else { + TokenStream::new() + }; + + quote! { #id #vs_toks } + } + None => { + // otherwise, record that whatever type is referencing it needs to + // have it in scope + s.need_noff_var(n); + let id = s.noff_var_id(n); + quote! { #id } + } + }; + quote! { #id } +} + +/// Invariant: `vt` is a numeric type (`S`, `U`, `F`) +pub fn numeric_rtype(vt: &Value) -> (Ident, u8) { + match vt { + Value::S(w) => (format_ident!("s{}", w.width()), w.width()), + Value::U(w) => (format_ident!("u{}", w.width()), w.width()), + Value::F(w) => (format_ident!("f{}", w.width()), w.width()), + _ => panic!("numeric_rtype"), + } +} + +pub fn emit_value(s: &mut State, vt: &Value) -> TokenStream { + match vt { + Value::Bool => quote! { bool }, + Value::S(_) | Value::U(_) | Value::F(_) => { + let (id, _) = numeric_rtype(vt); + quote! { #id } + } + Value::Char => quote! { char }, + Value::String => quote! { alloc::string::String }, + Value::List(vt) => { + let vt = emit_value(s, vt); + quote! { alloc::vec::Vec<#vt> } + } + Value::Record(_) => panic!("record not at top level of valtype"), + Value::Tuple(vts) => { + let vts = vts.iter().map(|vt| emit_value(s, vt)).collect::>(); + quote! { (#(#vts),*) } + } + Value::Flags(_) => panic!("flags not at top level of valtype"), + Value::Variant(_) => panic!("flags not at top level of valtype"), + Value::Enum(_) => panic!("enum not at top level of valtype"), + Value::Option(vt) => { + let vt = emit_value(s, vt); + quote! { ::core::option::Option<#vt> } + } + Value::Result(vt1, vt2) => { + let unit = Value::Tuple(Vec::new()); + let vt1 = emit_value(s, vt1.as_ref().as_ref().unwrap_or(&unit)); + let vt2 = emit_value(s, vt2.as_ref().as_ref().unwrap_or(&unit)); + quote! { ::core::result::Result<#vt1, #vt2> } + } + Value::Own(ht) => match ht { + Handleable::Resource(_) => panic!("bare resource in type"), + Handleable::Var(tv) => { + if s.is_guest { + if !s.is_impl { + let vr = emit_var_ref(s, tv); + quote! { ::wasmtime::component::Resource<#vr> } + } else { + let n = crate::hl::resolve_handleable_to_resource(s, ht); + dbg_println!("resolved ht to r (4) {:?} {:?}", ht, n); + let id = format_ident!("HostResource{}", n); + quote! { ::wasmtime::component::Resource<#id> } + } + } else { + emit_var_ref(s, tv) + } + } + }, + Value::Borrow(ht) => match ht { + Handleable::Resource(_) => panic!("bare resource in type"), + Handleable::Var(tv) => { + if s.is_guest { + if !s.is_impl { + let vr = emit_var_ref(s, tv); + quote! { ::wasmtime::component::Resource<#vr> } + } else { + let n = crate::hl::resolve_handleable_to_resource(s, ht); + dbg_println!("resolved ht to r (5) {:?} {:?}", ht, n); + let id = format_ident!("HostResource{}", n); + quote! { ::wasmtime::component::Resource<#id> } + } + } else { + let vr = emit_var_ref(s, tv); + quote! { ::hyperlight_common::resource::BorrowedResourceGuard<#vr> } + } + } + }, + Value::Var(Some(tv), _) => emit_var_ref(s, tv), + Value::Var(None, _) => panic!("value type with recorded but unknown var"), + } +} +fn emit_value_toplevel(s: &mut State, v: Option, id: Ident, vt: &Value) -> TokenStream { + let is_guest = s.is_guest; + match vt { + Value::Record(rfs) => { + let (vs, toks) = gather_needed_vars(s, v, |mut s| { + let rfs = rfs + .iter() + .map(|rf| { + let orig_name = rf.name.name; + let id = kebab_to_var(orig_name); + let derives = if is_guest { + quote! { #[component(name = #orig_name)] } + } else { + TokenStream::new() + }; + let ty = emit_value(&mut s, &rf.ty); + quote! { #derives pub #id: #ty } + }) + .collect::>(); + quote! { #(#rfs),* } + }); + let vs = emit_type_defn_var_list(s, vs); + let derives = if is_guest { + quote! { + #[derive(::wasmtime::component::ComponentType)] + #[derive(::wasmtime::component::Lift)] + #[derive(::wasmtime::component::Lower)] + #[component(record)] + } + } else { + TokenStream::new() + }; + quote! { + #derives + pub struct #id #vs { #toks } + } + } + Value::Flags(ns) => { + let (vs, toks) = gather_needed_vars(s, v, |_| { + let ns = ns + .iter() + .map(|n| { + let orig_name = n.name; + let id = kebab_to_var(orig_name); + quote! { pub #id: bool } + }) + .collect::>(); + quote! { #(#ns),* } + }); + let vs = emit_type_defn_var_list(s, vs); + quote! { + pub struct #id #vs { #toks } + } + } + Value::Variant(vcs) => { + let (vs, toks) = gather_needed_vars(s, v, |mut s| { + let vcs = vcs + .iter() + .map(|vc| { + let orig_name = vc.name.name; + let id = kebab_to_cons(orig_name); + let derives = if is_guest { + quote! { #[component(name = #orig_name)] } + } else { + TokenStream::new() + }; + match &vc.ty { + Some(ty) => { + let ty = emit_value(&mut s, ty); + quote! { #derives #id(#ty) } + } + None => quote! { #derives #id }, + } + }) + .collect::>(); + quote! { #(#vcs),* } + }); + let vs = emit_type_defn_var_list(s, vs); + let derives = if is_guest { + quote! { + #[derive(::wasmtime::component::ComponentType)] + #[derive(::wasmtime::component::Lift)] + #[derive(::wasmtime::component::Lower)] + #[component(variant)] + } + } else { + TokenStream::new() + }; + quote! { + #derives + pub enum #id #vs { #toks } + } + } + Value::Enum(ns) => { + let (vs, toks) = gather_needed_vars(s, v, |_| { + let ns = ns + .iter() + .map(|n| { + let orig_name = n.name; + let id = kebab_to_cons(orig_name); + let derives = if is_guest { + quote! { #[component(name = #orig_name)] } + } else { + TokenStream::new() + }; + quote! { #derives #id } + }) + .collect::>(); + quote! { #(#ns),* } + }); + let vs = emit_type_defn_var_list(s, vs); + let derives = if is_guest { + quote! { + #[derive(::wasmtime::component::ComponentType)] + #[derive(::wasmtime::component::Lift)] + #[derive(::wasmtime::component::Lower)] + #[derive(::core::clone::Clone)] + #[derive(::core::marker::Copy)] + #[component(enum)] + #[repr(u8)] // todo: should this always be u8? + } + } else { + TokenStream::new() + }; + quote! { + #derives + pub enum #id #vs { #toks } + } + } + _ => emit_type_alias(s, v, id, |s| emit_value(s, vt)), + } +} + +fn emit_defined(s: &mut State, v: Option, id: Ident, dt: &Defined) -> TokenStream { + match dt { + // the lack of trait aliases makes emitting a name for an + // instance/component difficult in rust + Defined::Instance(_) | Defined::Component(_) => TokenStream::new(), + // toplevel vars should have been handled elsewhere + Defined::Handleable(Handleable::Resource(_)) => panic!("bare resource in type"), + Defined::Handleable(Handleable::Var(tv)) => { + emit_type_alias(s, v, id, |s| emit_var_ref(s, tv)) + } + Defined::Value(vt) => emit_value_toplevel(s, v, id, vt), + Defined::Func(ft) => emit_type_alias(s, v, id, |s| emit_func(s, ft)), + } +} + +pub fn emit_func_param(s: &mut State, p: &Param) -> TokenStream { + let name = kebab_to_var(p.name.name); + let ty = emit_value(s, &p.ty); + quote! { #name: #ty } +} + +pub fn emit_func_result(s: &mut State, r: &Result) -> TokenStream { + match r { + Result::Unnamed(vt) => emit_value(s, vt), + Result::Named(rs) if rs.len() == 0 => quote! { () }, + _ => panic!("multiple named function results are not currently supported"), + } +} + +fn emit_func(s: &mut State, ft: &Func) -> TokenStream { + let params = ft + .params + .iter() + .map(|p| emit_func_param(s, p)) + .collect::>(); + let result = emit_func_result(s, &ft.result); + quote! { fn(#(#params),*) -> #result } +} + +fn gather_needed_vars TokenStream>( + s: &mut State, + v: Option, + f: F, +) -> (BTreeSet, TokenStream) { + let mut needs_vars = BTreeSet::new(); + let mut sv = s.with_needs_vars(&mut needs_vars); + let toks = f(&mut sv); + if let Some(vn) = v { + sv.record_needs_vars(vn); + } + drop(sv); + (needs_vars, toks) +} +fn emit_type_defn_var_list(s: &mut State, vs: BTreeSet) -> TokenStream { + if vs.is_empty() { + TokenStream::new() + } else { + let vs = vs + .iter() + .map(|n| { + if s.is_guest { + let t = s.noff_var_id(*n); + quote! { #t: 'static } + } else { + let t = s.noff_var_id(*n); + quote! { #t } + } + }) + .collect::>(); + quote! { <#(#vs),*> } + } +} +fn emit_type_alias TokenStream>( + s: &mut State, + v: Option, + id: Ident, + f: F, +) -> TokenStream { + let (vs, toks) = gather_needed_vars(s, v, f); + let vs = emit_type_defn_var_list(s, vs); + quote! { pub type #id #vs = #toks; } +} + +fn emit_extern_decl<'a, 'b, 'c>( + is_export: bool, + s: &'c mut State<'a, 'b>, + ed: &'c ExternDecl<'b>, +) -> TokenStream { + dbg_println!(" emitting decl {:?}", ed.kebab_name); + match &ed.desc { + ExternDesc::CoreModule(_) => panic!("core module (im/ex)ports are not supported"), + ExternDesc::Func(ft) => { + let mut s = s.push_origin(is_export, ed.kebab_name); + match kebab_to_fn(ed.kebab_name) { + FnName::Plain(n) => { + let params = ft + .params + .iter() + .map(|p| emit_func_param(&mut s, p)) + .collect::>(); + let result = emit_func_result(&mut s, &ft.result); + quote! { + fn #n(&mut self, #(#params),*) -> #result; + } + } + FnName::Associated(r, n) => { + let mut s = s.helper(); + s.cur_trait = Some(r.clone()); + let mut needs_vars = BTreeSet::new(); + let mut sv = s.with_needs_vars(&mut needs_vars); + match n { + ResourceItemName::Constructor => { + sv.cur_trait().items.extend(quote! { + fn new(&mut self) -> Self::T; + }); + } + ResourceItemName::Method(n) => { + let params = ft + .params + .iter() + .map(|p| emit_func_param(&mut sv, p)) + .collect::>(); + let result = emit_func_result(&mut sv, &ft.result); + sv.cur_trait().items.extend(quote! { + fn #n(&mut self, #(#params),*) -> #result; + }); + } + ResourceItemName::Static(n) => { + let params = ft + .params + .iter() + .map(|p| emit_func_param(&mut sv, p)) + .collect::>(); + let result = emit_func_result(&mut sv, &ft.result); + sv.cur_trait().items.extend(quote! { + fn #n(&mut self, #(#params),*) -> #result; + }); + } + } + for v in needs_vars { + let id = s.noff_var_id(v); + s.cur_trait().tvs.insert(id, (Some(v), TokenStream::new())); + } + quote! {} + } + } + } + ExternDesc::Type(t) => { + fn go_defined<'a, 'b, 'c>( + s: &'c mut State<'a, 'b>, + ed: &'c ExternDecl<'b>, + t: &'c Defined<'b>, + v: Option, + ) -> TokenStream { + let id = kebab_to_type(ed.kebab_name); + let mut s = s.helper(); + + s.helper_type_name = Some(id.clone()); + let t = emit_defined(&mut s, v, id, t); + s.cur_mod().items.extend(t); + TokenStream::new() + } + let edn: &'b str = ed.kebab_name; + let mut s: State<'_, 'b> = s.push_origin(is_export, edn); + if let Some((n, bound)) = s.is_var_defn(t) { + match bound { + TypeBound::Eq(t) => { + // ensure that when go_defined() looks up vars + // that might occur in the type, they resolve + // properly + let noff = s.var_offset as u32 + n; + s.var_offset += n as usize + 1; + go_defined(&mut s, ed, &t, Some(noff)) + } + TypeBound::SubResource => { + let rn = kebab_to_type(ed.kebab_name); + s.add_helper_supertrait(rn.clone()); + let mut s = s.helper(); + s.cur_trait = Some(rn.clone()); + s.cur_trait().items.extend(quote! { + type T: ::core::marker::Send; + }); + quote! {} + } + } + } else { + go_defined(&mut s, ed, t, None) + } + } + ExternDesc::Instance(it) => { + let mut s = s.push_origin(is_export, ed.kebab_name); + let wn = split_wit_name(ed.kebab_name); + emit_instance(&mut s, wn.clone(), it); + + let nsids = wn.namespace_idents(); + let repr = s.r#trait(&nsids, kebab_to_type(wn.name)); + let vs = if !repr.tvs.is_empty() { + let vs = repr.tvs.clone(); + let tvs = vs + .iter() + .map(|(_, (tv, _))| emit_var_ref(&mut s, &Tyvar::Bound(tv.unwrap()))); + quote! { <#(#tvs),*> } + } else { + TokenStream::new() + }; + + let getter = kebab_to_getter(wn.name); + let rp = s.root_path(); + let tns = wn.namespace_path(); + let tn = kebab_to_type(wn.name); + quote! { + type #tn: #rp #tns::#tn #vs; + fn #getter(&mut self) -> impl ::core::borrow::BorrowMut; + } + } + ExternDesc::Component(_) => { + panic!("nested components not yet supported in rust bindings"); + } + } +} + +fn emit_instance<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, it: &'c Instance<'b>) { + dbg_println!("emitting instance {:?}", wn); + let mut s = s.with_cursor(wn.namespace_idents()); + + let name = kebab_to_type(wn.name); + + s.cur_helper_mod = Some(kebab_to_namespace(wn.name)); + s.cur_trait = Some(name.clone()); + let mut needs_vars = BTreeSet::new(); + let mut sv = s.with_needs_vars(&mut needs_vars); + + let exports = it + .exports + .iter() + .map(|ed| emit_extern_decl(true, &mut sv, ed)) + .collect::>(); + + // instantiations for the supertraits + + let mut stvs = BTreeMap::new(); + let _ = sv.cur_trait(); // make sure it exists + let t = sv.cur_trait_immut(); + for (ti, _) in t.supertraits.iter() { + let t = sv.resolve_trait_immut(false, ti); + stvs.insert(ti.clone(), t.tv_idxs()); + } + // hack to make the local-definedness check work properly, since + // it usually should ignore the last origin component + sv.origin.push(ImportExport::Export("self")); + let mut stis = BTreeMap::new(); + for (id, tvs) in stvs.into_iter() { + stis.insert(id, emit_tvis(&mut sv, tvs)); + } + for (id, ts) in stis.into_iter() { + sv.cur_trait().supertraits.get_mut(&id).unwrap().extend(ts); + } + + drop(sv); + dbg_println!("after exports, ncur_needs_vars is {:?}", needs_vars); + for v in needs_vars { + let id = s.noff_var_id(v); + s.cur_trait().tvs.insert(id, (Some(v), TokenStream::new())); + } + + s.cur_trait().items.extend(quote! { #(#exports)* }); +} + +fn emit_component<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, ct: &'c Component<'b>) { + let mut s = s.with_cursor(wn.namespace_idents()); + + let base_name = kebab_to_type(wn.name); + + s.cur_helper_mod = Some(kebab_to_namespace(wn.name)); + + let import_name = format_ident!("{}Imports", base_name); + *s.bound_vars = ct + .uvars + .iter() + .rev() + .map(Clone::clone) + .collect::>(); + s.cur_trait = Some(import_name.clone()); + let imports = ct + .imports + .iter() + .map(|ed| emit_extern_decl(false, &mut s, ed)) + .collect::>(); + s.cur_trait().items.extend(quote! { #(#imports)* }); + + s.adjust_vars(ct.instance.evars.len() as u32); + + s.import_param_var = Some(format_ident!("I")); + + let export_name = format_ident!("{}Exports", base_name); + *s.bound_vars = ct + .instance + .evars + .iter() + .rev() + .chain(ct.uvars.iter().rev()) + .map(Clone::clone) + .collect::>(); + s.cur_trait = Some(export_name.clone()); + let exports = ct + .instance + .unqualified + .exports + .iter() + .map(|ed| emit_extern_decl(true, &mut s, ed)) + .collect::>(); + s.cur_trait().tvs.insert( + format_ident!("I"), + (None, quote! { #import_name + ::core::marker::Send }), + ); + s.cur_trait().items.extend(quote! { #(#exports)* }); + + s.cur_helper_mod = None; + s.cur_trait = None; + + s.cur_mod().items.extend(quote! { + pub trait #base_name { + type Exports: #export_name; + // todo: can/should this 'static bound be avoided? + // it is important right now because this is closed over in host functions + fn instantiate(self, imports: I) -> Self::Exports; + } + }); +} + +pub fn emit_toplevel<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, n: &str, ct: &'c Component<'b>) { + let wn = split_wit_name(n); + emit_component(s, wn, ct); +} diff --git a/src/hyperlight_component_util/src/structure.rs b/src/hyperlight_component_util/src/structure.rs new file mode 100644 index 000000000..d97705417 --- /dev/null +++ b/src/hyperlight_component_util/src/structure.rs @@ -0,0 +1,32 @@ +/// core:sort in the specification +#[derive(Debug, Clone, Copy)] +pub enum CoreSort { + Func, + Table, + Memory, + Global, + Type, + Module, + Instance, +} + +/// sort in the specification +#[derive(Debug, Clone, Copy)] +pub enum Sort { + Core(CoreSort), + Func, + Value, + Type, + Component, + Instance, +} + +/// sortidx in the specification +#[derive(Debug, Clone, Copy)] +pub struct SortIdx { + pub sort: Sort, + pub idx: u32, +} + +/// funcidx in the specification +pub type FuncIdx = u32; diff --git a/src/hyperlight_component_util/src/substitute.rs b/src/hyperlight_component_util/src/substitute.rs new file mode 100644 index 000000000..dc663f6a5 --- /dev/null +++ b/src/hyperlight_component_util/src/substitute.rs @@ -0,0 +1,622 @@ +use std::primitive::u32; + +use crate::etypes::{ + BoundedTyvar, Component, Ctx, Defined, ExternDecl, ExternDesc, FreeTyvar, Func, Handleable, + Instance, Param, QualifiedInstance, RecordField, TypeBound, Tyvar, Value, VariantCase, +}; +use crate::tv::ResolvedTyvar; + +/// A substitution +/// +/// The [`Shiftable`] bound is required because the implementation of +/// substitution for components and instances needs to be able to +/// shift. +pub trait Substitution<'a> +where + Self: Shiftable<'a>, +{ + type Error: From<<>::Inner as Substitution<'a>>::Error>; + fn subst_bvar(&self, i: u32) -> Result>, Self::Error>; + fn subst_evar(&self, o: u32, i: u32) -> Result>, Self::Error>; + fn subst_uvar(&self, o: u32, i: u32) -> Result>, Self::Error>; + + fn record_fields(&self, rfs: &[RecordField<'a>]) -> Result>, Self::Error> { + rfs.iter() + .map(|rf| { + Ok(RecordField { + name: rf.name.clone(), + ty: self.value(&rf.ty)?, + }) + }) + .collect() + } + + fn variant_cases(&self, vcs: &[VariantCase<'a>]) -> Result>, Self::Error> { + vcs.iter() + .map(|vc| { + Ok(VariantCase { + name: vc.name.clone(), + ty: self.value_option(&vc.ty)?, + refines: vc.refines.clone(), + }) + }) + .collect() + } + + fn value_option(&self, vt: &Option>) -> Result>, Self::Error> { + vt.as_ref().map(|ty| self.value(ty)).transpose() + } + + fn value(&self, vt: &Value<'a>) -> Result, Self::Error> { + Ok(match vt { + Value::Bool => Value::Bool, + Value::S(w) => Value::S(*w), + Value::U(w) => Value::U(*w), + Value::F(w) => Value::F(*w), + Value::Char => Value::Char, + Value::String => Value::String, + Value::List(vt) => Value::List(Box::new(self.value(vt)?)), + Value::Record(rfs) => Value::Record(self.record_fields(rfs)?), + Value::Variant(vcs) => Value::Variant(self.variant_cases(vcs)?), + Value::Flags(ns) => Value::Flags(ns.clone()), + Value::Enum(ns) => Value::Enum(ns.clone()), + Value::Option(vt) => Value::Option(Box::new(self.value(vt)?)), + Value::Tuple(vts) => Value::Tuple( + vts.iter() + .map(|vt| self.value(vt)) + .collect::>, Self::Error>>()?, + ), + Value::Result(vt1, vt2) => Value::Result( + Box::new(self.value_option(vt1)?), + Box::new(self.value_option(vt2)?), + ), + Value::Own(h) => Value::Own(self.handleable_(h)?), + Value::Borrow(h) => Value::Borrow(self.handleable_(h)?), + Value::Var(tv, vt) => Value::Var( + tv.as_ref().and_then(|tv| match self.var(tv) { + Ok(Some(Defined::Handleable(Handleable::Var(tv)))) => Some(tv), + Ok(None) => Some(tv.clone()), + _ => None, + }), + Box::new(self.value(vt)?), + ), + }) + } + + fn param(&self, pt: &Param<'a>) -> Result, Self::Error> { + Ok(Param { + name: pt.name, + ty: self.value(&pt.ty)?, + }) + } + + fn params(&self, pts: &Vec>) -> Result>, Self::Error> { + pts.iter().map(|pt| self.param(pt)).collect() + } + + fn result( + &self, + rt: &crate::etypes::Result<'a>, + ) -> Result, Self::Error> { + Ok(match rt { + crate::etypes::Result::Unnamed(vt) => crate::etypes::Result::Unnamed(self.value(vt)?), + crate::etypes::Result::Named(pts) => crate::etypes::Result::Named(self.params(pts)?), + }) + } + + fn func(&self, ft: &Func<'a>) -> Result, Self::Error> { + Ok(Func { + params: self.params(&ft.params)?, + result: self.result(&ft.result)?, + }) + } + + fn var(&self, tv: &Tyvar) -> Result>, Self::Error> { + match tv { + Tyvar::Bound(i) => self.subst_bvar(*i), + Tyvar::Free(FreeTyvar::U(o, i)) => self.subst_uvar(*o, *i), + Tyvar::Free(FreeTyvar::E(o, i)) => self.subst_evar(*o, *i), + } + } + + fn handleable(&self, h: &Handleable) -> Result, Self::Error> { + let hh = Defined::Handleable(h.clone()); + match h { + Handleable::Resource(_) => Ok(hh), + Handleable::Var(tv) => Ok(self.var(tv)?.unwrap_or(hh)), + } + } + + fn handleable_(&self, h: &Handleable) -> Result { + match self.handleable(h)? { + Defined::Handleable(h_) => Ok(h_), + _ => panic!("internal invariant a violation: owned/borrowed var is not resource"), + } + } + + fn defined(&self, dt: &Defined<'a>) -> Result, Self::Error> { + Ok(match dt { + Defined::Handleable(h) => self.handleable(h)?, + Defined::Value(vt) => Defined::Value(self.value(vt)?), + Defined::Func(ft) => Defined::Func(self.func(ft)?), + Defined::Instance(it) => Defined::Instance(self.qualified_instance(it)?), + Defined::Component(ct) => Defined::Component(self.component(ct)?), + }) + } + + fn type_bound(&self, tb: &TypeBound<'a>) -> Result, Self::Error> { + Ok(match tb { + TypeBound::Eq(dt) => TypeBound::Eq(self.defined(dt)?), + TypeBound::SubResource => TypeBound::SubResource, + }) + } + + fn bounded_tyvar(&self, btv: &BoundedTyvar<'a>) -> Result, Self::Error> { + Ok(BoundedTyvar { + origin: btv.origin.clone(), + bound: self.type_bound(&btv.bound)?, + }) + } + + fn extern_desc(&self, ed: &ExternDesc<'a>) -> Result, Self::Error> { + Ok(match ed { + ExternDesc::CoreModule(cmt) => ExternDesc::CoreModule(cmt.clone()), + ExternDesc::Func(ft) => ExternDesc::Func(self.func(ft)?), + ExternDesc::Type(dt) => ExternDesc::Type(self.defined(dt)?), + ExternDesc::Instance(it) => ExternDesc::Instance(self.instance(it)?), + ExternDesc::Component(ct) => ExternDesc::Component(self.component(ct)?), + }) + } + + fn extern_decl(&self, ed: &ExternDecl<'a>) -> Result, Self::Error> { + Ok(ExternDecl { + kebab_name: ed.kebab_name, + desc: self.extern_desc(&ed.desc)?, + }) + } + + fn instance(&self, it: &Instance<'a>) -> Result, Self::Error> { + let exports = it + .exports + .iter() + .map(|ed| self.extern_decl(ed).map_err(Into::into)) + .collect::, Self::Error>>()?; + Ok(Instance { exports }) + } + + fn qualified_instance( + &self, + qit: &QualifiedInstance<'a>, + ) -> Result, Self::Error> { + let mut evars = Vec::new(); + let mut sub = self.shifted(); + for evar in &qit.evars { + evars.push(sub.bounded_tyvar(&evar)?); + sub.bshift(1); + sub.rbshift(1); + } + let it = sub.instance(&qit.unqualified)?; + Ok(QualifiedInstance { + evars, + unqualified: it, + }) + } + + fn component(&self, ct: &Component<'a>) -> Result, Self::Error> { + let mut uvars = Vec::new(); + let mut sub = self.shifted(); + for uvar in &ct.uvars { + uvars.push(sub.bounded_tyvar(&uvar)?); + sub.bshift(1); + sub.rbshift(1); + } + let imports = ct + .imports + .iter() + .map(|ed| sub.extern_decl(ed).map_err(Into::into)) + .collect::>, Self::Error>>()?; + let instance = sub.qualified_instance(&ct.instance)?; + Ok(Component { + uvars, + imports, + instance, + }) + } +} + +/// A substitution that shifts bound variables up by a defined offset. +/// This will generally be accessed through [`Shifted`] below. It is +/// important to ensure that a bound variable produced by a +/// substitution is not captured. +struct RBShift { + rbshift: i32, +} +impl<'a> Shiftable<'a> for RBShift { + type Inner = Self; + fn shifted<'b>(&'b self) -> Shifted<'b, Self::Inner> { + Shifted::new(self) + } +} +impl<'a> Substitution<'a> for RBShift { + type Error = Void; + fn subst_bvar(&self, i: u32) -> Result>, Self::Error> { + Ok(Some(Defined::Handleable(Handleable::Var(Tyvar::Bound( + i.checked_add_signed(self.rbshift).unwrap(), + ))))) + } + fn subst_evar(&self, _o: u32, _i: u32) -> Result>, Self::Error> { + Ok(None) + } + fn subst_uvar(&self, _o: u32, _i: u32) -> Result>, Self::Error> { + Ok(None) + } +} + +/// A substitution that can be converted into a [`Shifted`] +/// substitution. All types other than [`Shifted`] itself should +/// implement this with the obvious option of +/// ``` +/// impl<'a> Shiftable<'a> for A { +/// type Inner = Self; +/// fn shifted<'b>(&'b self) -> Shifted<'b, Self::Inner> { Shifted::new(self) } +/// } +/// ``` +/// Unfortunately, it is not reasonably possible to provide this +/// automatically without specialization. +pub trait Shiftable<'a> { + type Inner: ?Sized + Substitution<'a>; + fn shifted<'c>(&'c self) -> Shifted<'c, Self::Inner>; +} + +/// A "shifted" version of a substitution, used internally to assure +/// that substitution is capture-avoiding. +pub struct Shifted<'b, A: ?Sized> { + /// The substitution which is being shifted + underlying: &'b A, + /// The offset to apply to bound variables before querying the + /// original substitution + bshift: i32, + /// The offset to apply to outer instance indices before + /// querying the original substitution + oshift: i32, + /// The offset to apply to free evar indices before + /// querying the original substitution + eshift: i32, + /// The offset to apply to free uvar indices before + /// querying the original substitution + ushift: i32, + /// The offset to apply to bound variables in the result of the + /// original substitution + rbshift: i32, +} +impl<'b, A: ?Sized> Clone for Shifted<'b, A> { + fn clone(&self) -> Self { + Self { + underlying: self.underlying, + bshift: self.bshift, + oshift: self.oshift, + eshift: self.eshift, + ushift: self.ushift, + rbshift: self.rbshift, + } + } +} +impl<'a, 'b, A: ?Sized + Substitution<'a>> Shiftable<'a> for Shifted<'b, A> { + type Inner = A; + fn shifted<'c>(&'c self) -> Shifted<'c, Self::Inner> { + self.clone() + } +} + +impl<'a, 'b, A: ?Sized + Substitution<'a>> Shifted<'b, A> { + fn new(s: &'b A) -> Self { + Self { + underlying: s, + bshift: 0, + oshift: 0, + eshift: 0, + ushift: 0, + rbshift: 0, + } + } + fn bshift(&mut self, bshift: i32) { + self.bshift += bshift; + } + #[allow(unused)] + fn oshift(&mut self, oshift: i32) { + self.oshift += oshift; + } + #[allow(unused)] + fn ushift(&mut self, ushift: i32) { + self.ushift += ushift; + } + #[allow(unused)] + fn eshift(&mut self, eshift: i32) { + self.eshift += eshift; + } + fn rbshift(&mut self, rbshift: i32) { + self.rbshift += rbshift; + } + + fn sub_rbshift( + &self, + dt: Result>, >::Error>, + ) -> Result>, >::Error> { + match dt { + Ok(Some(dt)) => { + let rbsub = RBShift { + rbshift: self.rbshift, + }; + Ok(Some(rbsub.defined(&dt).not_void())) + } + _ => dt, + } + } +} + +impl<'a, 'b, A: ?Sized + Substitution<'a>> Substitution<'a> for Shifted<'b, A> { + type Error = A::Error; + fn subst_bvar(&self, i: u32) -> Result>, Self::Error> { + match i.checked_add_signed(-self.bshift) { + Some(i) => self.sub_rbshift(self.underlying.subst_bvar(i)), + _ => Ok(None), + } + } + fn subst_evar(&self, o: u32, i: u32) -> Result>, Self::Error> { + match ( + o.checked_add_signed(-self.oshift), + i.checked_add_signed(-self.eshift), + ) { + (Some(o), Some(i)) => self.sub_rbshift(self.underlying.subst_evar(o, i)), + _ => Ok(None), + } + } + fn subst_uvar(&self, o: u32, i: u32) -> Result>, Self::Error> { + match ( + o.checked_add_signed(-self.oshift), + i.checked_add_signed(-self.ushift), + ) { + (Some(o), Some(i)) => self.sub_rbshift(self.underlying.subst_uvar(o, i)), + _ => Ok(None), + } + } +} + +#[derive(Debug)] +pub enum InnerizeError { + IndefiniteTyvar, +} +/// An innerize substitution is used to bring an outer type alias +/// inwards through one context. +pub struct Innerize<'c, 'p, 'a> { + /// What ctx was this type originally in? + ctx: &'c Ctx<'p, 'a>, + /// Are we crossing an outer_boundary? + outer_boundary: bool, +} +impl<'c, 'p, 'a> Shiftable<'a> for Innerize<'c, 'p, 'a> { + type Inner = Self; + fn shifted<'d>(&'d self) -> Shifted<'d, Self::Inner> { + Shifted::new(self) + } +} +impl<'c, 'p, 'a> Substitution<'a> for Innerize<'c, 'p, 'a> { + type Error = InnerizeError; + fn subst_bvar(&self, _i: u32) -> Result>, Self::Error> { + Ok(None) + } + // Note that even if the variables resolve, what they resolve to + // needs to itself be innerized, since it was also designed for + // this context. + fn subst_evar(&self, o: u32, i: u32) -> Result>, Self::Error> { + if !self.outer_boundary { + Ok(Some(Defined::Handleable(Handleable::Var(Tyvar::Free( + FreeTyvar::E(o + 1, i), + ))))) + } else { + match self.ctx.resolve_tyvar(&Tyvar::Free(FreeTyvar::E(o, i))) { + ResolvedTyvar::Definite(dt) => Ok(Some(self.defined(&dt)?)), + _ => Err(InnerizeError::IndefiniteTyvar), + } + } + } + fn subst_uvar(&self, o: u32, i: u32) -> Result>, Self::Error> { + if !self.outer_boundary { + Ok(Some(Defined::Handleable(Handleable::Var(Tyvar::Free( + FreeTyvar::U(o + 1, i), + ))))) + } else { + match self.ctx.resolve_tyvar(&Tyvar::Free(FreeTyvar::U(o, i))) { + ResolvedTyvar::Definite(dt) => Ok(Some(self.defined(&dt)?)), + _ => Err(InnerizeError::IndefiniteTyvar), + } + } + } +} +impl<'c, 'p, 'a> Innerize<'c, 'p, 'a> { + pub fn new(ctx: &'c Ctx<'p, 'a>, outer_boundary: bool) -> Innerize<'c, 'p, 'a> { + Innerize { + ctx, + outer_boundary, + } + } +} + +/// The empty (void) type +pub enum Void {} + +/// Things that you can call [`not_void`](Unvoidable::not_void) on +pub trait Unvoidable { + type Result; + fn not_void(self) -> Self::Result; +} + +/// Eliminate a Result<_, Void> +impl Unvoidable for Result { + type Result = A; + fn not_void(self) -> A { + match self { + Ok(x) => x, + Err(v) => match v {}, + } + } +} + +/// An opening substitution is used to map bound variables into +/// free variables. Note that because of the differences in ordering +/// for bound variable indices (inside out) and context variables +/// (left to right, but variables are inserted in outside-in order), +/// `Bound(0)` gets mapped to `Free(0, base + n)`. +pub struct Opening { + /// Whether to produce E or U free variables + is_universal: bool, + /// At what index in the context are the free variables being + /// inserted? + free_base: u32, + /// How many bound variables are being shifted to the context + how_many: u32, +} +impl<'a> Shiftable<'a> for Opening { + type Inner = Self; + fn shifted<'d>(&'d self) -> Shifted<'d, Self::Inner> { + Shifted::new(self) + } +} +impl<'a> Substitution<'a> for Opening { + type Error = Void; + fn subst_bvar(&self, i: u32) -> Result>, Void> { + let mk = |i| { + let fi = self.free_base + self.how_many - i - 1; + if self.is_universal { + FreeTyvar::U(0, fi) + } else { + FreeTyvar::E(0, fi) + } + }; + Ok(if i < self.how_many { + Some(Defined::Handleable(Handleable::Var(Tyvar::Free(mk(i))))) + } else { + None + }) + } + fn subst_evar(&self, _o: u32, _i: u32) -> Result>, Void> { + Ok(None) + } + fn subst_uvar(&self, _o: u32, _i: u32) -> Result>, Void> { + Ok(None) + } +} +impl Opening { + pub fn new(is_universal: bool, free_base: u32) -> Self { + Opening { + is_universal, + free_base, + how_many: 0, + } + } + pub fn next(&mut self) { + self.how_many += 1; + } +} + +/// A closing substitution is used to map free variables into bound +/// variables when converting a type being built in a context to a +/// closed(ish) type that is above that context. +/// +/// Like [`Opening`], a given [`Closing`] substitution either affects +/// only existential variables or affects only universal variables, as +/// these are closed at different times. +pub struct Closing { + /// If this substitution applies to universal variables, this + /// keeps track of which ones are imported and which are + /// not. Non-imported universal variables may not be referred to + /// in types. + /// + /// Invariant: If this is provided, its length must be equal to + /// self.how_many + universal_imported: Option>, + /// How many of the relevant (u/e) free vars are valid at this point. + how_many: u32, +} +impl Closing { + pub fn new(is_universal: bool) -> Self { + let universal_imported = if is_universal { Some(Vec::new()) } else { None }; + Closing { + universal_imported, + how_many: 0, + } + } + fn is_universal(&self) -> bool { + self.universal_imported.is_some() + } + pub fn next_u(&mut self, imported: bool) { + let Some(ref mut importeds) = self.universal_imported else { + panic!("next_u called on existential Closing"); + }; + importeds.push(imported); + self.how_many += 1; + } + pub fn next_e(&mut self) { + if self.is_universal() { + panic!("next_e called on universal Closing"); + }; + self.how_many += 1; + } + fn subst_uevar<'a>( + &self, + ue_is_u: bool, + o: u32, + i: u32, + ) -> Result>, ClosingError> { + if self.is_universal() ^ ue_is_u { + return Ok(None); + } + let mk_ue = |o, i| { + if self.is_universal() { + Tyvar::Free(FreeTyvar::U(o, i)) + } else { + Tyvar::Free(FreeTyvar::E(o, i)) + } + }; + let mk = |v| Ok(Some(Defined::Handleable(Handleable::Var(v)))); + if o > 0 { + return mk(mk_ue(o - 1, i)); + } + if i >= self.how_many { + return Err(ClosingError::UnknownVar(false, i)); + } + let bidx = if let Some(imported) = &self.universal_imported { + if !imported[i as usize] { + return Err(ClosingError::UnimportedVar(i)); + } + imported[i as usize..].iter().filter(|x| **x).count() as u32 - 1 + } else { + self.how_many - i - 1 + }; + mk(Tyvar::Bound(bidx)) + } +} +impl<'a> Shiftable<'a> for Closing { + type Inner = Self; + fn shifted<'d>(&'d self) -> Shifted<'d, Self::Inner> { + Shifted::new(self) + } +} +#[derive(Debug)] +#[allow(unused)] +pub enum ClosingError { + UnknownVar(bool, u32), + UnimportedVar(u32), +} +impl<'a> Substitution<'a> for Closing { + type Error = ClosingError; + fn subst_bvar(&self, _: u32) -> Result>, ClosingError> { + Ok(None) + } + fn subst_evar(&self, o: u32, i: u32) -> Result>, ClosingError> { + self.subst_uevar(false, o, i) + } + fn subst_uvar(&self, o: u32, i: u32) -> Result>, ClosingError> { + self.subst_uevar(true, o, i) + } +} diff --git a/src/hyperlight_component_util/src/subtype.rs b/src/hyperlight_component_util/src/subtype.rs new file mode 100644 index 000000000..ea90a1734 --- /dev/null +++ b/src/hyperlight_component_util/src/subtype.rs @@ -0,0 +1,217 @@ +use itertools::Itertools; + +use crate::etypes::{ + Component, Ctx, Defined, Func, Handleable, Name, QualifiedInstance, ResourceId, TypeBound, + Tyvar, Value, +}; +use crate::tv::ResolvedTyvar; + +#[derive(Debug)] +#[allow(dead_code)] +pub enum Error<'r> { + MissingValue(Value<'r>), + MissingRecordField(Name<'r>), + MissingVariantCase(Name<'r>), + MismatchedValue(Value<'r>, Value<'r>), + MismatchedDefined(Defined<'r>, Defined<'r>), + MismatchedResources(ResourceId, ResourceId), + MismatchedVars(Tyvar, Tyvar), + MismatchedResourceVar(Tyvar, ResourceId), + NotResource(Handleable), +} + +impl<'p, 'a> Ctx<'p, 'a> { + pub fn subtype_value<'r>( + &self, + vt1: &'r Value<'a>, + vt2: &'r Value<'a>, + ) -> Result<(), Error<'a>> { + use itertools::EitherOrBoth::*; + use Value::*; + match (vt1, vt2) { + (Bool, Bool) => Ok(()), + (S(w1), S(w2)) if w1 == w2 => Ok(()), + (U(w1), U(w2)) if w1 == w2 => Ok(()), + (F(w1), F(w2)) if w1 == w2 => Ok(()), + (Char, Char) => Ok(()), + (String, String) => Ok(()), + (List(vt1), List(vt2)) => self.subtype_value(vt1, vt2), + (Record(rfs1), Record(rfs2)) => { + for rf2 in rfs2.iter() { + match rfs1.iter().find(|rf| rf2.name.name == rf.name.name) { + None => return Err(Error::MissingRecordField(rf2.name)), + Some(rf1) => self.subtype_value(&rf1.ty, &rf2.ty)?, + } + } + Ok(()) + } + (Tuple(vts1), Tuple(vts2)) => vts1 + .iter() + .zip_longest(vts2.iter()) + .map(|vs| match vs { + Both(vt1, vt2) => self.subtype_value(vt1, vt2), + Left(_) => Ok(()), + Right(vt2) => Err(Error::MissingValue(vt2.clone())), + }) + .collect::>(), + (Flags(ns1), Flags(ns2)) => ns2 + .iter() + .find(|n2| ns1.iter().find(|n| n.name == n2.name).is_none()) + .map_or(Ok(()), |n| Err(Error::MissingRecordField(*n))), + (Variant(vcs1), Variant(vcs2)) => { + for vc1 in vcs1.iter() { + match vcs2.iter().find(|vc| vc1.name.name == vc.name.name) { + None => return Err(Error::MissingVariantCase(vc1.name)), + Some(vc2) => self.subtype_value_option(&vc1.ty, &vc2.ty)?, + } + } + Ok(()) + } + (Enum(ns1), Enum(ns2)) => ns1 + .iter() + .find(|n1| ns2.iter().find(|n| n.name == n1.name).is_none()) + .map_or(Ok(()), |n| Err(Error::MissingVariantCase(*n))), + (Option(vt1), Option(vt2)) => self.subtype_value(vt1, vt2), + (Result(vt11, vt12), Result(vt21, vt22)) => self + .subtype_value_option(vt11, vt21) + .and(self.subtype_value_option(vt12, vt22)), + (Own(ht1), Own(ht2)) | (Borrow(ht1), Borrow(ht2)) => { + self.subtype_handleable_is_resource(ht1)?; + self.subtype_handleable_is_resource(ht2)?; + self.subtype_handleable(ht1, ht2) + } + (Var(_, vt1), vt2) => self.subtype_value(vt1, vt2), + (vt1, Var(_, vt2)) => self.subtype_value(vt1, vt2), + _ => Err(Error::MismatchedValue(vt1.clone(), vt2.clone())), + } + } + pub fn subtype_value_option<'r>( + &self, + vt1: &'r Option>, + vt2: &'r Option>, + ) -> Result<(), Error<'a>> { + match (vt1, vt2) { + (None, None) => Ok(()), + (None, Some(vt2)) => Err(Error::MissingValue(vt2.clone())), + (Some(_), None) => Ok(()), + (Some(vt1), Some(vt2)) => self.subtype_value(vt1, vt2), + } + } + pub fn subtype_var_var<'r>(&self, v1: &'r Tyvar, v2: &'r Tyvar) -> Result<(), Error<'a>> { + match (self.resolve_tyvar(v1), self.resolve_tyvar(v2)) { + (ResolvedTyvar::Definite(dt1), ResolvedTyvar::Definite(dt2)) => { + self.subtype_defined(&dt1, &dt2) + } + (ResolvedTyvar::E(o1, i1, _), ResolvedTyvar::E(o2, i2, _)) if o1 == o2 && i1 == i2 => { + Ok(()) + } + (ResolvedTyvar::U(o1, i1, _), ResolvedTyvar::U(o2, i2, _)) if o1 == o2 && i1 == i2 => { + Ok(()) + } + (ResolvedTyvar::Bound(_), _) | (_, ResolvedTyvar::Bound(_)) => { + panic!("internal invariant violation: stray bvar in subtype_var_var") + } + _ => Err(Error::MismatchedVars(v1.clone(), v2.clone())), + } + } + pub fn subtype_var_resource<'r>( + &self, + v1: &'r Tyvar, + rid2: &'r ResourceId, + ) -> Result<(), Error<'a>> { + match self.resolve_tyvar(v1) { + ResolvedTyvar::Definite(Defined::Handleable(Handleable::Resource(rid1))) + if rid1 == *rid2 => + { + Ok(()) + } + _ => Err(Error::MismatchedResourceVar(v1.clone(), *rid2)), + } + } + pub fn subtype_resource_var<'r>( + &self, + rid1: &'r ResourceId, + v2: &'r Tyvar, + ) -> Result<(), Error<'a>> { + match self.resolve_tyvar(v2) { + ResolvedTyvar::Definite(Defined::Handleable(Handleable::Resource(rid2))) + if *rid1 == rid2 => + { + Ok(()) + } + _ => Err(Error::MismatchedResourceVar(v2.clone(), *rid1)), + } + } + pub fn subtype_handleable<'r>( + &self, + ht1: &'r Handleable, + ht2: &'r Handleable, + ) -> Result<(), Error<'a>> { + match (ht1, ht2) { + (Handleable::Var(v1), Handleable::Var(v2)) => self.subtype_var_var(v1, v2), + (Handleable::Var(v1), Handleable::Resource(rid2)) => { + self.subtype_var_resource(v1, rid2) + } + (Handleable::Resource(rid1), Handleable::Var(v2)) => { + self.subtype_resource_var(rid1, v2) + } + (Handleable::Resource(rid1), Handleable::Resource(rid2)) => { + if rid1 == rid2 { + Ok(()) + } else { + Err(Error::MismatchedResources(*rid1, *rid2)) + } + } + } + } + pub fn subtype_func<'r>( + &self, + _ft1: &'r Func<'a>, + _ft2: &'r Func<'a>, + ) -> Result<(), Error<'a>> { + panic!("func <: func should be impossible to encounter during type elaboration") + } + pub fn subtype_qualified_instance<'r>( + &self, + _qi1: &'r QualifiedInstance<'a>, + _qi2: &'r QualifiedInstance<'a>, + ) -> Result<(), Error<'a>> { + panic!("qinstance <: qinstance should be impossible to encounter during type elaboration") + } + pub fn subtype_component<'r>( + &self, + _ct1: &'r Component<'a>, + _ct2: &'r Component<'a>, + ) -> Result<(), Error<'a>> { + panic!("component <: component should be impossible to encounter during type elaboration") + } + pub fn subtype_defined<'r>( + &self, + dt1: &'r Defined<'a>, + dt2: &'r Defined<'a>, + ) -> Result<(), Error<'a>> { + match (dt1, dt2) { + (Defined::Handleable(ht1), Defined::Handleable(ht2)) => { + self.subtype_handleable(ht1, ht2) + } + (Defined::Value(vt1), Defined::Value(vt2)) => self.subtype_value(vt1, vt2), + (Defined::Func(ft1), Defined::Func(ft2)) => self.subtype_func(ft1, ft2), + (Defined::Instance(it1), Defined::Instance(it2)) => { + self.subtype_qualified_instance(it1, it2) + } + (Defined::Component(ct1), Defined::Component(ct2)) => self.subtype_component(ct1, ct2), + _ => Err(Error::MismatchedDefined(dt1.clone(), dt2.clone())), + } + } + pub fn subtype_handleable_is_resource<'r>(&self, ht: &'r Handleable) -> Result<(), Error<'a>> { + match ht { + Handleable::Resource(_) => Ok(()), + Handleable::Var(tv) => match self.resolve_tyvar(tv) { + ResolvedTyvar::Definite(Defined::Handleable(Handleable::Resource(_))) => Ok(()), + ResolvedTyvar::E(_, _, TypeBound::SubResource) => Ok(()), + ResolvedTyvar::U(_, _, TypeBound::SubResource) => Ok(()), + _ => Err(Error::NotResource(ht.clone())), + }, + } + } +} diff --git a/src/hyperlight_component_util/src/tv.rs b/src/hyperlight_component_util/src/tv.rs new file mode 100644 index 000000000..732e3ac55 --- /dev/null +++ b/src/hyperlight_component_util/src/tv.rs @@ -0,0 +1,107 @@ +use crate::etypes::{ + BoundedTyvar, Ctx, Defined, FreeTyvar, Handleable, ImportExport, TypeBound, Tyvar, +}; +use crate::substitute::{self, Substitution, Unvoidable}; + +/// The most information we possibly have about a type variable +pub enum ResolvedTyvar<'a> { + /// Invariant: the head of this [`Defined`] is not `[Defined::Handleable]([HHandleable::Var](...))` + Definite(Defined<'a>), + /// It's just some bound var... so there is no way to look it up. + #[allow(unused)] + Bound(u32), + /// Invariant: the `TypeBound` is not `TypeBound::Eq` + E(u32, u32, TypeBound<'a>), + /// Invariant: the `TypeBound` is not `TypeBound::Eq` + U(u32, u32, TypeBound<'a>), +} + +impl<'p, 'a> Ctx<'p, 'a> { + /// Look up a universal variable in the context, panicking if it doesn't exist + fn lookup_uvar<'c>(self: &'c Self, o: u32, i: u32) -> &'c (BoundedTyvar<'a>, bool) { + // unwrap because failure is an internal invariant violation + &self.parents().nth(o as usize).unwrap().uvars[i as usize] + } + /// Look up an existential variable in the context, panicking if it doesn't exist + fn lookup_evar<'c>( + self: &'c Self, + o: u32, + i: u32, + ) -> &'c (BoundedTyvar<'a>, Option>) { + // unwrap because failure is an internal invariant violation + &self.parents().nth(o as usize).unwrap().evars[i as usize] + } + /// Find a bound for the given free tyvar. Panics if given a + /// TV_bound; by the time you call this, you should have used + /// bound_to_[e/u]var. + pub fn var_bound<'c>(self: &'c Self, tv: &Tyvar) -> &'c TypeBound<'a> { + match tv { + Tyvar::Bound(_) => panic!("Requested bound for Bound tyvar"), + Tyvar::Free(FreeTyvar::U(o, i)) => &self.lookup_uvar(*o, *i).0.bound, + Tyvar::Free(FreeTyvar::E(o, i)) => &self.lookup_evar(*o, *i).0.bound, + } + } + /// Try really hard to resolve a tyvar to a definite type or a + /// descriptive bound. + pub fn resolve_tyvar<'c>(self: &'c Self, v: &Tyvar) -> ResolvedTyvar<'a> { + let check_deftype = |dt: &Defined<'a>| match dt { + Defined::Handleable(Handleable::Var(v_)) => self.resolve_tyvar(&v_), + _ => ResolvedTyvar::Definite(dt.clone()), + }; + match *v { + Tyvar::Bound(i) => ResolvedTyvar::Bound(i), + Tyvar::Free(FreeTyvar::E(o, i)) => { + let (tv, def) = self.lookup_evar(o, i); + match (&tv.bound, def) { + (TypeBound::Eq(dt), _) => check_deftype(dt), + (_, Some(dt)) => check_deftype(dt), + (tb, _) => ResolvedTyvar::E(o, i, tb.clone()), + } + } + Tyvar::Free(FreeTyvar::U(o, i)) => { + let (tv, _) = self.lookup_uvar(o, i); + match &tv.bound { + TypeBound::Eq(dt) => check_deftype(&dt), + tb => ResolvedTyvar::U(o, i, tb.clone()), + } + } + } + } + /// Modify the context to move the given variables into it as + /// existential variables and compute a substitution + /// that replaces bound variable references to them with free + /// variable references + pub fn bound_to_evars( + self: &mut Self, + origin: Option<&'a str>, + vs: &[BoundedTyvar<'a>], + ) -> substitute::Opening { + let mut sub = substitute::Opening::new(false, self.evars.len() as u32); + for var in vs { + let var = var.push_origin(origin.map(ImportExport::Export)); + let bound = sub.bounded_tyvar(&var).not_void(); + self.evars.push((bound, None)); + sub.next(); + } + sub + } + /// Modify the context to move the given variables into it as + /// universal variables and compute a substitution that replaces + /// bound variable references to them with free variable + /// references + pub fn bound_to_uvars( + self: &mut Self, + origin: Option<&'a str>, + vs: &[BoundedTyvar<'a>], + imported: bool, + ) -> substitute::Opening { + let mut sub = substitute::Opening::new(true, self.uvars.len() as u32); + for var in vs { + let var = var.push_origin(origin.map(ImportExport::Import)); + let bound = sub.bounded_tyvar(&var).not_void(); + self.uvars.push((bound, imported)); + sub.next(); + } + sub + } +} diff --git a/src/hyperlight_component_util/src/util.rs b/src/hyperlight_component_util/src/util.rs new file mode 100644 index 000000000..4bfa076d3 --- /dev/null +++ b/src/hyperlight_component_util/src/util.rs @@ -0,0 +1,47 @@ +use crate::{dbg_println, etypes}; + +pub fn read_wit_type_from_file R>( + filename: impl AsRef, + mut cb: F, +) -> R { + let path = std::path::Path::new(&filename); + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let manifest_dir = std::path::Path::new(&manifest_dir); + let path = manifest_dir.join(path); + + 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); + + // because of the two-level encapsulation scheme, we need to look + // for the single export of the component type that we just read + #[allow(unused_parens)] + if ct.uvars.len() != 0 + || ct.imports.len() != 0 + || ct.instance.evars.len() != 0 + || ct.instance.unqualified.exports.len() != 1 + { + panic!("malformed component type container for wit type"); + }; + let export = &ct.instance.unqualified.exports[0]; + use etypes::ExternDesc; + let ExternDesc::Component(ct) = &export.desc else { + panic!("component type container does not contain component type"); + }; + dbg_println!("hcm: considering component type {:?}", ct); + cb(export.kebab_name.to_string(), ct) +} + +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()) { + std::fs::write(&dbg_out, prettyplease::unparse(&file)).unwrap(); + } else { + let decls = format!("{}", &decls); + std::fs::write(&dbg_out, &decls).unwrap(); + } + (quote::quote! { include!(#dbg_out); }).into() + } else { + decls + } +} diff --git a/src/hyperlight_component_util/src/wf.rs b/src/hyperlight_component_util/src/wf.rs new file mode 100644 index 000000000..54d99c8c7 --- /dev/null +++ b/src/hyperlight_component_util/src/wf.rs @@ -0,0 +1,347 @@ +use itertools::Itertools; + +use crate::etypes::{ + BoundedTyvar, Component, Ctx, Defined, ExternDecl, ExternDesc, Func, Handleable, Instance, + Name, Param, QualifiedInstance, RecordField, TypeBound, Value, VariantCase, +}; +use crate::substitute::{Substitution, Unvoidable}; +use crate::subtype; + +/// The various position metadata that affect what value types are +/// well-formed +#[derive(Clone, Copy)] +struct ValueTypePosition { + /// Is this well-formedness check for a type that is part of the + /// parameter type of a function? (Borrows should be allowed) + is_param: bool, + dtp: DefinedTypePosition, +} + +impl From for ValueTypePosition { + fn from(p: DefinedTypePosition) -> ValueTypePosition { + ValueTypePosition { + is_param: false, + dtp: p, + } + } +} +impl ValueTypePosition { + fn not_anon_export(self) -> Self { + ValueTypePosition { + dtp: self.dtp.not_anon_export(), + ..self + } + } + fn anon_export(self) -> Self { + ValueTypePosition { + dtp: self.dtp.anon_export(), + ..self + } + } +} + +/// The various position metadata that affect what defined types are +/// well-formed +#[derive(Clone, Copy)] +pub struct DefinedTypePosition { + /// Is this well-formedness check for a type one that should be + /// exportable (e.g. one that is being + /// exported/imported/outer-aliased-through-an-outer-boundary)? + /// (Bare resource types should be disallowed) + is_export: bool, + /// Is this well-formedness check for a type that should be + /// allowed in an "unnamed" export (i.e. nested under some other + /// type constructor in an export)? (Record, variant, enum, and + /// flags types, which must always be named in exports due to WIT + /// constraints, should not be allowed). + is_anon_export: bool, +} +impl DefinedTypePosition { + pub fn export() -> Self { + DefinedTypePosition { + is_export: true, + is_anon_export: false, + } + } + fn not_anon_export(self) -> Self { + DefinedTypePosition { + is_anon_export: false, + ..self + } + } + fn anon_export(self) -> Self { + DefinedTypePosition { + is_anon_export: true, + ..self + } + } +} + +#[derive(Debug)] +#[allow(dead_code)] +pub enum Error<'a> { + BareResourceExport, + BareComplexValTypeExport(Value<'a>), + DuplicateRecordField(Name<'a>), + DuplicateVariantField(Name<'a>), + NonexistentVariantRefinement(u32), + IncompatibleVariantRefinement(subtype::Error<'a>), + DuplicateEnumName(Name<'a>), + NotAResource(subtype::Error<'a>), + BorrowOutsideParam, +} + +fn error_if_duplicates_by( + i: impl Iterator, + f: impl FnMut(&T) -> U, + e: impl Fn(T) -> E, +) -> Result<(), E> { + let mut duplicates = i.duplicates_by(f); + if let Some(x) = duplicates.next() { + Err(e(x)) + } else { + Ok(()) + } +} + +impl<'p, 'a> Ctx<'p, 'a> { + fn wf_record_fields<'r>( + &'r self, + p: ValueTypePosition, + rfs: &'r [RecordField<'a>], + ) -> Result<(), Error<'a>> { + rfs.iter() + .map(|rf: &'r RecordField<'a>| self.wf_value(p, &rf.ty)) + .collect::>>()?; + error_if_duplicates_by( + rfs.iter(), + |&rf| rf.name.name, + |rf| Error::DuplicateRecordField(rf.name), + )?; + Ok(()) + } + fn wf_variant_cases<'r>( + &'r self, + p: ValueTypePosition, + vcs: &'r [VariantCase<'a>], + ) -> Result<(), Error<'a>> { + vcs.iter() + .map(|vc: &'r VariantCase<'a>| self.wf_value_option(p, &vc.ty)) + .collect::>>()?; + error_if_duplicates_by( + vcs.iter(), + |&vc| vc.name.name, + |vc| Error::DuplicateVariantField(vc.name), + )?; + for vc in vcs { + if let Some(ri) = vc.refines { + let rvc = vcs + .get(ri as usize) + .ok_or(Error::NonexistentVariantRefinement(ri))?; + self.subtype_value_option(&vc.ty, &rvc.ty) + .map_err(Error::IncompatibleVariantRefinement)?; + } + } + Ok(()) + } + fn wf_value<'r>(&'r self, p: ValueTypePosition, vt: &'r Value<'a>) -> Result<(), Error<'a>> { + let anon_err: Result<(), Error<'a>> = if p.dtp.is_export && p.dtp.is_anon_export { + Err(Error::BareComplexValTypeExport(vt.clone())) + } else { + Ok(()) + }; + let p_ = p.anon_export(); + let resource_err = |h| { + self.wf_handleable(p.dtp, h).and( + self.subtype_handleable_is_resource(h) + .map_err(Error::NotAResource), + ) + }; + match vt { + Value::Bool => Ok(()), + Value::S(_) => Ok(()), + Value::U(_) => Ok(()), + Value::F(_) => Ok(()), + Value::Char => Ok(()), + Value::String => Ok(()), + Value::List(vt) => self.wf_value(p_, vt), + Value::Record(rfs) => anon_err.and(self.wf_record_fields(p_, rfs)), + Value::Variant(vcs) => anon_err.and(self.wf_variant_cases(p_, vcs)), + Value::Flags(ns) => anon_err.and(error_if_duplicates_by( + ns.iter(), + |&n| n.name, + |n| Error::DuplicateEnumName(*n), + )), + Value::Enum(ns) => anon_err.and(error_if_duplicates_by( + ns.iter(), + |&n| n.name, + |n| Error::DuplicateEnumName(*n), + )), + Value::Option(vt) => self.wf_value(p_, vt), + Value::Tuple(vs) => vs + .iter() + .map(|vt: &'r Value<'a>| self.wf_value(p_, &vt)) + .collect::>>(), + Value::Result(vt1, vt2) => self + .wf_value_option(p_, &vt1) + .and(self.wf_value_option(p_, &vt2)), + Value::Own(h) => resource_err(h), + Value::Borrow(h) => { + if p.is_param { + resource_err(h) + } else { + Err(Error::BorrowOutsideParam) + } + } + Value::Var(tv, vt) => tv + .as_ref() + .map(|tv| self.wf_type_bound(p.dtp, self.var_bound(&tv))) + .unwrap_or(Ok(())) + .and(self.wf_value(p.not_anon_export(), vt)), + } + } + fn wf_value_option<'r>( + &'r self, + p: ValueTypePosition, + vt: &'r Option>, + ) -> Result<(), Error<'a>> { + vt.as_ref().map_or(Ok(()), |ty| self.wf_value(p, ty)) + } + fn wf_func<'r>(&'r self, p: DefinedTypePosition, ft: &'r Func<'a>) -> Result<(), Error<'a>> { + let p_ = p.anon_export(); + let param_pos = ValueTypePosition { + is_param: true, + dtp: p_, + }; + let result_pos = ValueTypePosition { + is_param: false, + dtp: p_, + }; + ft.params + .iter() + .map(|fp: &'r Param<'a>| self.wf_value(param_pos, &fp.ty)) + .collect::>>()?; + match &ft.result { + crate::etypes::Result::Unnamed(vt) => self.wf_value(result_pos, &vt), + crate::etypes::Result::Named(ps) => ps + .iter() + .map(|fp: &'r Param<'a>| self.wf_value(result_pos, &fp.ty)) + .collect::>>(), + } + } + fn wf_type_bound<'r>( + &'r self, + p: DefinedTypePosition, + tb: &'r TypeBound<'a>, + ) -> Result<(), Error<'a>> { + match tb { + TypeBound::SubResource => Ok(()), + TypeBound::Eq(dt) => self.wf_defined(p.not_anon_export(), dt), + } + } + fn wf_bounded_tyvar<'r>( + &'r self, + p: DefinedTypePosition, + btv: &'r BoundedTyvar<'a>, + ) -> Result<(), Error<'a>> { + match &btv.bound { + TypeBound::SubResource => Ok(()), + TypeBound::Eq(dt) => self.wf_defined(p, dt), + } + } + + fn wf_handleable<'r>( + &'r self, + p: DefinedTypePosition, + ht: &'r Handleable, + ) -> Result<(), Error<'a>> { + match ht { + Handleable::Var(tv) => self.wf_type_bound(p, self.var_bound(&tv)), + Handleable::Resource(rid) => { + if p.is_export { + Err(Error::BareResourceExport) + } else { + // Internal invariant: rtidx should always exist + assert!((rid.id as usize) < self.rtypes.len()); + Ok(()) + } + } + } + } + pub fn wf_defined<'r>( + &'r self, + p: DefinedTypePosition, + dt: &'r Defined<'a>, + ) -> Result<(), Error<'a>> { + match dt { + Defined::Handleable(ht) => self.wf_handleable(p, ht), + Defined::Value(vt) => self.wf_value(p.into(), vt), + Defined::Func(ft) => self.wf_func(p, ft), + Defined::Instance(it) => self.wf_qualified_instance(p, it), + Defined::Component(ct) => self.wf_component(p, ct), + } + } + fn wf_extern_desc<'r>( + &self, + p: DefinedTypePosition, + ed: &'r ExternDesc<'a>, + ) -> Result<(), Error<'a>> { + match ed { + ExternDesc::CoreModule(_) => Ok(()), + ExternDesc::Func(ft) => self.wf_func(p, ft), + ExternDesc::Type(dt) => self.wf_defined(p, dt), + ExternDesc::Instance(it) => self.wf_instance(p, it), + ExternDesc::Component(ct) => self.wf_component(p, ct), + } + } + fn wf_extern_decl<'r>( + &self, + p: DefinedTypePosition, + ed: &'r ExternDecl<'a>, + ) -> Result<(), Error<'a>> { + self.wf_extern_desc(p, &ed.desc) + } + fn wf_instance<'r>( + &self, + p: DefinedTypePosition, + it: &'r Instance<'a>, + ) -> Result<(), Error<'a>> { + it.exports + .iter() + .map(|ed| self.wf_extern_decl(p, &ed)) + .collect::>>() + } + fn wf_qualified_instance<'r>( + &self, + p: DefinedTypePosition, + qit: &'r QualifiedInstance<'a>, + ) -> Result<(), Error<'a>> { + let mut ctx_ = self.clone(); + let subst = ctx_.bound_to_evars(None, &qit.evars); + ctx_.evars + .iter() + .map(|(btv, _)| ctx_.wf_bounded_tyvar(p, btv)) + .collect::>>()?; + let it = subst.instance(&qit.unqualified).not_void(); + ctx_.wf_instance(p, &it) + } + fn wf_component<'r>( + &self, + p: DefinedTypePosition, + ct: &'r Component<'a>, + ) -> Result<(), Error<'a>> { + let mut ctx_ = self.clone(); + let subst = ctx_.bound_to_uvars(None, &ct.uvars, false); + ctx_.uvars + .iter() + .map(|(btv, _)| ctx_.wf_bounded_tyvar(p, btv)) + .collect::>>()?; + ct.imports + .iter() + .map(|ed| subst.extern_decl(ed).not_void()) + .map(|ed| ctx_.wf_extern_decl(p, &ed)) + .collect::>>()?; + let it = subst.qualified_instance(&ct.instance).not_void(); + ctx_.wf_qualified_instance(p, &it) + } +} diff --git a/src/hyperlight_host/Cargo.toml b/src/hyperlight_host/Cargo.toml index ccae9066d..af3bce032 100644 --- a/src/hyperlight_host/Cargo.toml +++ b/src/hyperlight_host/Cargo.toml @@ -38,7 +38,7 @@ once_cell = { version = "1.21.1" } tracing = { version = "0.1.41", features = ["log"] } tracing-log = "0.2.0" tracing-core = "0.1.33" -hyperlight-common = { workspace = true, default-features = true } +hyperlight-common = { workspace = true, default-features = true, features = [ "std" ] } vmm-sys-util = "0.12.1" crossbeam = "0.8.0" crossbeam-channel = "0.5.14" diff --git a/src/hyperlight_host/benches/benchmarks.rs b/src/hyperlight_host/benches/benchmarks.rs index db71d8a95..1fa860d0a 100644 --- a/src/hyperlight_host/benches/benchmarks.rs +++ b/src/hyperlight_host/benches/benchmarks.rs @@ -19,7 +19,7 @@ use std::sync::{Arc, Mutex}; use criterion::{criterion_group, criterion_main, Criterion}; use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnType}; use hyperlight_host::func::HostFunction2; -use hyperlight_host::sandbox::{MultiUseSandbox, UninitializedSandbox}; +use hyperlight_host::sandbox::{Callable, MultiUseSandbox, UninitializedSandbox}; use hyperlight_host::sandbox_state::sandbox::EvolvableSandbox; use hyperlight_host::sandbox_state::transition::Noop; use hyperlight_host::GuestBinary; diff --git a/src/hyperlight_host/examples/func_ctx/main.rs b/src/hyperlight_host/examples/func_ctx/main.rs index 2967616e4..59412df04 100644 --- a/src/hyperlight_host/examples/func_ctx/main.rs +++ b/src/hyperlight_host/examples/func_ctx/main.rs @@ -16,7 +16,7 @@ limitations under the License. use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnType}; use hyperlight_host::func::call_ctx::MultiUseGuestCallContext; -use hyperlight_host::sandbox::{MultiUseSandbox, UninitializedSandbox}; +use hyperlight_host::sandbox::{Callable, MultiUseSandbox, UninitializedSandbox}; use hyperlight_host::sandbox_state::sandbox::EvolvableSandbox; use hyperlight_host::sandbox_state::transition::Noop; use hyperlight_host::{new_error, GuestBinary, Result}; diff --git a/src/hyperlight_host/examples/logging/main.rs b/src/hyperlight_host/examples/logging/main.rs index 3a4a6edf4..3a55dd74c 100644 --- a/src/hyperlight_host/examples/logging/main.rs +++ b/src/hyperlight_host/examples/logging/main.rs @@ -19,6 +19,7 @@ use std::sync::{Arc, Mutex}; use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnType}; use hyperlight_host::sandbox::uninitialized::UninitializedSandbox; +use hyperlight_host::sandbox::Callable; use hyperlight_host::sandbox_state::sandbox::EvolvableSandbox; use hyperlight_host::sandbox_state::transition::Noop; use hyperlight_host::{GuestBinary, MultiUseSandbox, Result}; diff --git a/src/hyperlight_host/examples/metrics/main.rs b/src/hyperlight_host/examples/metrics/main.rs index 2028c21f7..42d72168a 100644 --- a/src/hyperlight_host/examples/metrics/main.rs +++ b/src/hyperlight_host/examples/metrics/main.rs @@ -20,6 +20,7 @@ use std::thread::{spawn, JoinHandle}; use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnType}; use hyperlight_host::sandbox::uninitialized::UninitializedSandbox; +use hyperlight_host::sandbox::Callable; use hyperlight_host::sandbox_state::sandbox::EvolvableSandbox; use hyperlight_host::sandbox_state::transition::Noop; use hyperlight_host::{set_metrics_registry, GuestBinary, MultiUseSandbox, Result}; diff --git a/src/hyperlight_host/examples/otlp_tracing/main.rs b/src/hyperlight_host/examples/otlp_tracing/main.rs index ccddc3626..fb3122f85 100644 --- a/src/hyperlight_host/examples/otlp_tracing/main.rs +++ b/src/hyperlight_host/examples/otlp_tracing/main.rs @@ -29,6 +29,7 @@ use std::sync::{Arc, Mutex}; use std::thread::{self, spawn, JoinHandle}; use hyperlight_host::sandbox::uninitialized::UninitializedSandbox; +use hyperlight_host::sandbox::Callable; use hyperlight_host::sandbox_state::sandbox::EvolvableSandbox; use hyperlight_host::sandbox_state::transition::Noop; use hyperlight_host::{GuestBinary, MultiUseSandbox, Result as HyperlightResult}; diff --git a/src/hyperlight_host/examples/tracing/main.rs b/src/hyperlight_host/examples/tracing/main.rs index 1778a3d47..8fb020fc7 100644 --- a/src/hyperlight_host/examples/tracing/main.rs +++ b/src/hyperlight_host/examples/tracing/main.rs @@ -21,6 +21,7 @@ use std::sync::{Arc, Mutex}; use std::thread::{spawn, JoinHandle}; use hyperlight_host::sandbox::uninitialized::UninitializedSandbox; +use hyperlight_host::sandbox::Callable; use hyperlight_host::sandbox_state::sandbox::EvolvableSandbox; use hyperlight_host::sandbox_state::transition::Noop; use hyperlight_host::{GuestBinary, MultiUseSandbox, Result}; diff --git a/src/hyperlight_host/src/func/call_ctx.rs b/src/hyperlight_host/src/func/call_ctx.rs index bc48e249c..73e340354 100644 --- a/src/hyperlight_host/src/func/call_ctx.rs +++ b/src/hyperlight_host/src/func/call_ctx.rs @@ -20,6 +20,7 @@ use hyperlight_common::flatbuffer_wrappers::function_types::{ use tracing::{instrument, Span}; use super::guest_dispatch::call_function_on_guest; +use crate::sandbox::Callable; use crate::{MultiUseSandbox, Result}; /// A context for calling guest functions. /// @@ -50,6 +51,31 @@ impl MultiUseGuestCallContext { Self { sbox } } + /// Close out the context and get back the internally-stored + /// `MultiUseSandbox`. Future contexts opened by the returned sandbox + /// will have guest state restored. + #[instrument(err(Debug), skip(self), parent = Span::current())] + pub fn finish(mut self) -> Result { + self.sbox.restore_state()?; + Ok(self.sbox) + } + /// Close out the context and get back the internally-stored + /// `MultiUseSandbox`. + /// + /// Note that this method is pub(crate) and does not reset the state of the + /// sandbox. + /// + /// It is intended to be used when evolving a MultiUseSandbox to a new state + /// and is not intended to be called publicly. It allows the state of the guest to be altered + /// during the evolution of one sandbox state to another, enabling the new state created + /// to be captured and stored in the Sandboxes state stack. + /// + pub(crate) fn finish_no_reset(self) -> MultiUseSandbox { + self.sbox + } +} + +impl Callable for MultiUseGuestCallContext { /// Call the guest function called `func_name` with the given arguments /// `args`, and expect the return value have the same type as /// `func_ret_type`. @@ -61,7 +87,7 @@ impl MultiUseGuestCallContext { /// If you want to reset state, call `finish()` on this `MultiUseGuestCallContext` /// and get a new one from the resulting `MultiUseSandbox` #[instrument(err(Debug),skip(self, args),parent = Span::current())] - pub fn call( + fn call( &mut self, func_name: &str, func_ret_type: ReturnType, @@ -74,29 +100,6 @@ impl MultiUseGuestCallContext { call_function_on_guest(&mut self.sbox, func_name, func_ret_type, args) } - - /// Close out the context and get back the internally-stored - /// `MultiUseSandbox`. Future contexts opened by the returned sandbox - /// will have guest state restored. - #[instrument(err(Debug), skip(self), parent = Span::current())] - pub fn finish(mut self) -> Result { - self.sbox.restore_state()?; - Ok(self.sbox) - } - /// Close out the context and get back the internally-stored - /// `MultiUseSandbox`. - /// - /// Note that this method is pub(crate) and does not reset the state of the - /// sandbox. - /// - /// It is intended to be used when evolving a MultiUseSandbox to a new state - /// and is not intended to be called publicly. It allows the state of the guest to be altered - /// during the evolution of one sandbox state to another, enabling the new state created - /// to be captured and stored in the Sandboxes state stack. - /// - pub(crate) fn finish_no_reset(self) -> MultiUseSandbox { - self.sbox - } } #[cfg(test)] @@ -109,6 +112,7 @@ mod tests { }; use hyperlight_testing::simple_guest_as_string; + use crate::sandbox::Callable; use crate::sandbox_state::sandbox::EvolvableSandbox; use crate::sandbox_state::transition::Noop; use crate::{GuestBinary, HyperlightError, MultiUseSandbox, Result, UninitializedSandbox}; diff --git a/src/hyperlight_host/src/func/guest_dispatch.rs b/src/hyperlight_host/src/func/guest_dispatch.rs index 4462a8a66..eb49320dd 100644 --- a/src/hyperlight_host/src/func/guest_dispatch.rs +++ b/src/hyperlight_host/src/func/guest_dispatch.rs @@ -113,8 +113,8 @@ mod tests { use super::*; use crate::func::call_ctx::MultiUseGuestCallContext; use crate::func::host_functions::HostFunction0; - use crate::sandbox::is_hypervisor_present; use crate::sandbox::uninitialized::GuestBinary; + use crate::sandbox::{is_hypervisor_present, Callable}; use crate::sandbox_state::sandbox::EvolvableSandbox; use crate::sandbox_state::transition::Noop; use crate::{new_error, HyperlightError, MultiUseSandbox, Result, UninitializedSandbox}; diff --git a/src/hyperlight_host/src/func/host_functions.rs b/src/hyperlight_host/src/func/host_functions.rs index ce30d9c45..841f47264 100644 --- a/src/hyperlight_host/src/func/host_functions.rs +++ b/src/hyperlight_host/src/func/host_functions.rs @@ -18,7 +18,7 @@ limitations under the License. use std::sync::{Arc, Mutex}; use hyperlight_common::flatbuffer_wrappers::function_types::ParameterValue; -use hyperlight_common::flatbuffer_wrappers::host_function_definition::HostFunctionDefinition; +pub use hyperlight_common::flatbuffer_wrappers::host_function_definition::HostFunctionDefinition; use paste::paste; use tracing::{instrument, Span}; @@ -27,6 +27,55 @@ use crate::sandbox::{ExtraAllowedSyscall, UninitializedSandbox}; use crate::HyperlightError::UnexpectedNoOfArguments; use crate::{log_then_return, new_error, Result}; +/// A sandbox on which (primitive) host functions can be registered +/// +/// The details of this trait and the structures it uses are unstable +/// and subject to change, but it must be `pub` because it is used by +/// hyperlight-wasm and the component macros. +pub trait Registerable { + /// Register a primitive host function + fn register_host_function( + &mut self, + hfd: &HostFunctionDefinition, + hf: HyperlightFunction, + ) -> Result<()>; + /// Register a primitive host function whose worker thread has + /// extra permissive seccomp filters installed + #[cfg(all(feature = "seccomp", target_os = "linux"))] + fn register_host_function_with_syscalls( + &mut self, + hfd: &HostFunctionDefinition, + hf: HyperlightFunction, + eas: Vec, + ) -> Result<()>; +} +impl Registerable for UninitializedSandbox { + fn register_host_function( + &mut self, + hfd: &HostFunctionDefinition, + hf: HyperlightFunction, + ) -> Result<()> { + let mut hfs = self + .host_funcs + .try_lock() + .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?; + (*hfs).register_host_function(self.mgr.as_mut(), hfd, hf) + } + #[cfg(all(feature = "seccomp", target_os = "linux"))] + fn register_host_function_with_syscalls( + &mut self, + hfd: &HostFunctionDefinition, + hf: HyperlightFunction, + eas: Vec, + ) -> Result<()> { + let mut hfs = self + .host_funcs + .try_lock() + .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?; + (*hfs).register_host_function_with_syscalls(self.mgr.as_mut(), hfd, hf, eas) + } +} + macro_rules! host_function { // Special case for zero parameters (0) => { @@ -36,7 +85,7 @@ macro_rules! host_function { /// Register the host function with the given name in the sandbox. fn register( &self, - sandbox: &mut UninitializedSandbox, + sandbox: &mut dyn Registerable, name: &str, ) -> Result<()>; @@ -44,7 +93,7 @@ macro_rules! host_function { #[cfg(all(feature = "seccomp", target_os = "linux"))] fn register_with_extra_allowed_syscalls( &self, - sandbox: &mut UninitializedSandbox, + sandbox: &mut dyn Registerable, name: &str, extra_allowed_syscalls: Vec, ) -> Result<()>; @@ -60,7 +109,7 @@ macro_rules! host_function { )] fn register( &self, - sandbox: &mut UninitializedSandbox, + sandbox: &mut dyn Registerable, name: &str, ) -> Result<()> { register_host_function_0(self.clone(), sandbox, name, None) @@ -73,7 +122,7 @@ macro_rules! host_function { )] fn register_with_extra_allowed_syscalls( &self, - sandbox: &mut UninitializedSandbox, + sandbox: &mut dyn Registerable, name: &str, extra_allowed_syscalls: Vec, ) -> Result<()> { @@ -83,7 +132,7 @@ macro_rules! host_function { fn register_host_function_0( self_: Arc>, - sandbox: &mut UninitializedSandbox, + sandbox: &mut dyn Registerable, name: &str, extra_allowed_syscalls: Option>, ) -> Result<()> @@ -104,16 +153,11 @@ macro_rules! host_function { // Register with extra allowed syscalls #[cfg(all(feature = "seccomp", target_os = "linux"))] { - sandbox - .host_funcs - .try_lock() - .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? - .register_host_function_with_syscalls( - sandbox.mgr.as_mut(), - &HostFunctionDefinition::new(name.to_string(), None, R::get_hyperlight_type()), - HyperlightFunction::new(func), - _eas, - )?; + sandbox.register_host_function_with_syscalls( + &HostFunctionDefinition::new(name.to_string(), None, R::get_hyperlight_type()), + HyperlightFunction::new(func), + _eas, + )?; } } else { // Log and return an error @@ -121,15 +165,10 @@ macro_rules! host_function { } } else { // Register without extra allowed syscalls - sandbox - .host_funcs - .try_lock() - .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? - .register_host_function( - sandbox.mgr.as_mut(), - &HostFunctionDefinition::new(name.to_string(), None, R::get_hyperlight_type()), - HyperlightFunction::new(func), - )?; + sandbox.register_host_function( + &HostFunctionDefinition::new(name.to_string(), None, R::get_hyperlight_type()), + HyperlightFunction::new(func), + )?; } Ok(()) @@ -148,7 +187,7 @@ macro_rules! host_function { /// Register the host function with the given name in the sandbox. fn register( &self, - sandbox: &mut UninitializedSandbox, + sandbox: &mut dyn Registerable, name: &str, ) -> Result<()>; @@ -156,7 +195,7 @@ macro_rules! host_function { #[cfg(all(feature = "seccomp", target_os = "linux"))] fn register_with_extra_allowed_syscalls( &self, - sandbox: &mut UninitializedSandbox, + sandbox: &mut dyn Registerable, name: &str, extra_allowed_syscalls: Vec, ) -> Result<()>; @@ -173,7 +212,7 @@ macro_rules! host_function { )] fn register( &self, - sandbox: &mut UninitializedSandbox, + sandbox: &mut dyn Registerable, name: &str, ) -> Result<()> { [](self.clone(), sandbox, name, None) @@ -186,7 +225,7 @@ macro_rules! host_function { )] fn register_with_extra_allowed_syscalls( &self, - sandbox: &mut UninitializedSandbox, + sandbox: &mut dyn Registerable, name: &str, extra_allowed_syscalls: Vec, ) -> Result<()> { @@ -196,7 +235,7 @@ macro_rules! host_function { fn []<'a, T, $($P,)* R>( self_: Arc>, - sandbox: &mut UninitializedSandbox, + sandbox: &mut dyn Registerable, name: &str, extra_allowed_syscalls: Option>, ) -> Result<()> @@ -231,20 +270,15 @@ macro_rules! host_function { // Register with extra allowed syscalls #[cfg(all(feature = "seccomp", target_os = "linux"))] { - sandbox - .host_funcs - .try_lock() - .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? - .register_host_function_with_syscalls( - sandbox.mgr.as_mut(), - &HostFunctionDefinition::new( - name.to_string(), - parameter_types, - R::get_hyperlight_type(), - ), + sandbox.register_host_function_with_syscalls( + &HostFunctionDefinition::new( + name.to_string(), + parameter_types, + R::get_hyperlight_type(), + ), HyperlightFunction::new(func), - _eas, - )?; + _eas, + )?; } } else { // Log and return an error @@ -252,19 +286,14 @@ macro_rules! host_function { } } else { // Register without extra allowed syscalls - sandbox - .host_funcs - .try_lock() - .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))? - .register_host_function( - sandbox.mgr.as_mut(), - &HostFunctionDefinition::new( - name.to_string(), - parameter_types, - R::get_hyperlight_type(), - ), - HyperlightFunction::new(func), - )?; + sandbox.register_host_function( + &HostFunctionDefinition::new( + name.to_string(), + parameter_types, + R::get_hyperlight_type(), + ), + HyperlightFunction::new(func), + )?; } Ok(()) diff --git a/src/hyperlight_host/src/hypervisor/hypervisor_handler.rs b/src/hyperlight_host/src/hypervisor/hypervisor_handler.rs index f602b840e..ba37db19c 100644 --- a/src/hyperlight_host/src/hypervisor/hypervisor_handler.rs +++ b/src/hyperlight_host/src/hypervisor/hypervisor_handler.rs @@ -622,13 +622,20 @@ impl HypervisorHandler { // from the handler thread, as the thread may be paused by gdb. // In this case, we will wait indefinitely for a message from the handler thread. // Note: This applies to all the running sandboxes, not just the one being debugged. - #[cfg(gdb)] - let response = self.communication_channels.from_handler_rx.recv(); #[cfg(not(gdb))] - let response = self - .communication_channels - .from_handler_rx - .recv_timeout(self.execution_variables.get_timeout()?); + let timeout = self.execution_variables.get_timeout()?; + #[cfg(gdb)] + let timeout = Duration::ZERO; + let response = if timeout.is_zero() { + self.communication_channels + .from_handler_rx + .recv() + .map_err(Into::into) + } else { + self.communication_channels + .from_handler_rx + .recv_timeout(timeout) + }; match response { Ok(msg) => match msg { diff --git a/src/hyperlight_host/src/sandbox/callable.rs b/src/hyperlight_host/src/sandbox/callable.rs new file mode 100644 index 000000000..fa631ff6a --- /dev/null +++ b/src/hyperlight_host/src/sandbox/callable.rs @@ -0,0 +1,16 @@ +use hyperlight_common::flatbuffer_wrappers::function_types::{ + ParameterValue, ReturnType, ReturnValue, +}; + +use crate::Result; + +/// Trait used by the macros to paper over the differences between hyperlight and hyperlight-wasm +pub trait Callable { + /// Call a guest function dynamically + fn call( + &mut self, + func_name: &str, + func_ret_type: ReturnType, + args: Option>, + ) -> Result; +} diff --git a/src/hyperlight_host/src/sandbox/config.rs b/src/hyperlight_host/src/sandbox/config.rs index e0bbaddbe..4a8ec60e4 100644 --- a/src/hyperlight_host/src/sandbox/config.rs +++ b/src/hyperlight_host/src/sandbox/config.rs @@ -295,13 +295,15 @@ impl SandboxConfiguration { self.kernel_stack_size = max(kernel_stack_size, Self::MIN_KERNEL_STACK_SIZE); } - /// Set the maximum execution time of a guest function execution. If set to 0, the max_execution_time - /// will be set to the default value of DEFAULT_MAX_EXECUTION_TIME if the guest execution does not complete within the time specified - /// then the execution will be cancelled, the minimum value is MIN_MAX_EXECUTION_TIME + /// Set the maximum execution time of a guest function + /// execution. If set to 0, the maximum execution time will be + /// unlimited. If the guest execution does not complete within the + /// time specified, then the execution will be cancelled. The + /// minimum value is MIN_MAX_EXECUTION_TIME #[instrument(skip_all, parent = Span::current(), level= "Trace")] pub fn set_max_execution_time(&mut self, max_execution_time: Duration) { match max_execution_time.as_millis() { - 0 => self.max_execution_time = Self::DEFAULT_MAX_EXECUTION_TIME, + 0 => self.max_execution_time = 0, 1.. => { self.max_execution_time = min( Self::MAX_MAX_EXECUTION_TIME.into(), diff --git a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs index d92252a8a..fbbaf1e47 100644 --- a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs +++ b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs @@ -263,7 +263,7 @@ mod tests { use hyperlight_testing::simple_guest_as_string; use crate::func::call_ctx::MultiUseGuestCallContext; - use crate::sandbox::SandboxConfiguration; + use crate::sandbox::{Callable, SandboxConfiguration}; use crate::sandbox_state::sandbox::{DevolvableSandbox, EvolvableSandbox}; use crate::sandbox_state::transition::{MultiUseContextCallback, Noop}; use crate::{GuestBinary, MultiUseSandbox, UninitializedSandbox}; diff --git a/src/hyperlight_host/src/sandbox/mod.rs b/src/hyperlight_host/src/sandbox/mod.rs index a88556878..4edd11820 100644 --- a/src/hyperlight_host/src/sandbox/mod.rs +++ b/src/hyperlight_host/src/sandbox/mod.rs @@ -48,8 +48,12 @@ pub(crate) mod uninitialized_evolve; /// Metric definitions for Sandbox module. pub(crate) mod metrics; +/// Trait used by the macros to paper over the differences between hyperlight and hyperlight-wasm +mod callable; use std::collections::HashMap; +/// Trait used by the macros to paper over the differences between hyperlight and hyperlight-wasm +pub use callable::Callable; /// Re-export for `SandboxConfiguration` type pub use config::SandboxConfiguration; /// Re-export for the `MultiUseSandbox` type diff --git a/src/hyperlight_host/tests/sandbox_host_tests.rs b/src/hyperlight_host/tests/sandbox_host_tests.rs index cb8452cac..53a1e1a21 100644 --- a/src/hyperlight_host/tests/sandbox_host_tests.rs +++ b/src/hyperlight_host/tests/sandbox_host_tests.rs @@ -19,7 +19,7 @@ use std::sync::{Arc, Mutex}; use common::new_uninit; use hyperlight_host::func::{HostFunction1, ParameterValue, ReturnType, ReturnValue}; -use hyperlight_host::sandbox::SandboxConfiguration; +use hyperlight_host::sandbox::{Callable, SandboxConfiguration}; use hyperlight_host::sandbox_state::sandbox::EvolvableSandbox; use hyperlight_host::sandbox_state::transition::Noop; use hyperlight_host::{ diff --git a/src/tests/rust_guests/callbackguest/Cargo.lock b/src/tests/rust_guests/callbackguest/Cargo.lock index 2346856e6..554237598 100644 --- a/src/tests/rust_guests/callbackguest/Cargo.lock +++ b/src/tests/rust_guests/callbackguest/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "anyhow" @@ -81,6 +81,7 @@ dependencies = [ "anyhow", "flatbuffers", "log", + "spin", "strum", ] diff --git a/src/tests/rust_guests/simpleguest/Cargo.lock b/src/tests/rust_guests/simpleguest/Cargo.lock index 32a75e39c..184ca24ca 100644 --- a/src/tests/rust_guests/simpleguest/Cargo.lock +++ b/src/tests/rust_guests/simpleguest/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "anyhow" @@ -73,6 +73,7 @@ dependencies = [ "anyhow", "flatbuffers", "log", + "spin", "strum", ] diff --git a/typos.toml b/typos.toml index 32d9e9fde..a7c109a48 100644 --- a/typos.toml +++ b/typos.toml @@ -1,5 +1,5 @@ [default] -extend-ignore-identifiers-re = ["Fo"] +extend-ignore-identifiers-re = ["Fo", "edn", "ue"] [files] extend-exclude = ["**/*.patch", "src/hyperlight_guest/third_party/**/*", "NOTICE.txt"]