diff --git a/Cargo.lock b/Cargo.lock index e25b37e05aff..ac6545cd1f57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -472,6 +472,14 @@ dependencies = [ "syn", ] +[[package]] +name = "component-macro-test-helpers" +version = "0.0.0" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "component-test-util" version = "0.0.0" @@ -2235,6 +2243,17 @@ dependencies = [ "cc", ] +[[package]] +name = "pulldown-cmark" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -2969,6 +2988,15 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.8" @@ -3496,10 +3524,15 @@ dependencies = [ name = "wasmtime-component-macro" version = "5.0.0" dependencies = [ + "component-macro-test-helpers", "proc-macro2", "quote", "syn", + "tracing", + "wasmtime", "wasmtime-component-util", + "wasmtime-wit-bindgen", + "wit-parser", ] [[package]] @@ -3757,6 +3790,14 @@ dependencies = [ "winch-codegen", ] +[[package]] +name = "wasmtime-wit-bindgen" +version = "5.0.0" +dependencies = [ + "heck", + "wit-parser", +] + [[package]] name = "wast" version = "35.0.2" @@ -3986,6 +4027,19 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "wit-parser" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "893834cffb239f88413eead7cf91862a6f24c2233afae15d7808256d8c58f91e" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "pulldown-cmark", + "unicode-xid", +] + [[package]] name = "witx" version = "0.9.1" diff --git a/Cargo.toml b/Cargo.toml index 9c5071c447ab..7f9cf233a7c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -133,6 +133,7 @@ wasi-tokio = { path = "crates/wasi-common/tokio", version = "=5.0.0" } wasi-cap-std-sync = { path = "crates/wasi-common/cap-std-sync", version = "=5.0.0" } wasmtime-fuzzing = { path = "crates/fuzzing" } wasmtime-jit-icache-coherence = { path = "crates/jit-icache-coherence", version = "=5.0.0" } +wasmtime-wit-bindgen = { path = "crates/wit-bindgen", version = "=5.0.0" } cranelift-wasm = { path = "cranelift/wasm", version = "0.92.0" } cranelift-codegen = { path = "cranelift/codegen", version = "0.92.0" } @@ -162,6 +163,7 @@ wasmprinter = "0.2.44" wasm-encoder = "0.20.0" wasm-smith = "0.11.9" wasm-mutate = "0.2.12" +wit-parser = "0.3" windows-sys = "0.42.0" env_logger = "0.9" rustix = "0.36.0" @@ -179,6 +181,7 @@ tracing = "0.1.26" bitflags = "1.2" thiserror = "1.0.15" async-trait = "0.1.42" +heck = "0.4" [features] default = [ diff --git a/crates/component-macro/Cargo.toml b/crates/component-macro/Cargo.toml index 882f497092d9..ecc319821b92 100644 --- a/crates/component-macro/Cargo.toml +++ b/crates/component-macro/Cargo.toml @@ -12,12 +12,24 @@ edition.workspace = true [lib] proc-macro = true +test = false +doctest = false [dependencies] proc-macro2 = "1.0" quote = "1.0" syn = { version = "1.0", features = ["extra-traits"] } wasmtime-component-util = { workspace = true } +wasmtime-wit-bindgen = { workspace = true } +wit-parser = { workspace = true } [badges] maintenance = { status = "actively-developed" } + +[dev-dependencies] +wasmtime = { path = '../wasmtime', features = ['component-model'] } +component-macro-test-helpers = { path = 'test-helpers' } +tracing = { workspace = true } + +[features] +async = [] diff --git a/crates/component-macro/src/bindgen.rs b/crates/component-macro/src/bindgen.rs new file mode 100644 index 000000000000..b132cfdf17b2 --- /dev/null +++ b/crates/component-macro/src/bindgen.rs @@ -0,0 +1,137 @@ +use proc_macro2::{Span, TokenStream}; +use std::path::{Path, PathBuf}; +use syn::parse::{Error, Parse, ParseStream, Result}; +use syn::punctuated::Punctuated; +use syn::token; +use syn::Token; +use wasmtime_wit_bindgen::Opts; +use wit_parser::World; + +#[derive(Default)] +pub struct Config { + opts: Opts, // ... + world: World, + files: Vec, +} + +pub fn expand(input: &Config) -> Result { + if !cfg!(feature = "async") && input.opts.async_ { + return Err(Error::new( + Span::call_site(), + "cannot enable async bindings unless `async` crate feature is active", + )); + } + + let src = input.opts.generate(&input.world); + let mut contents = src.parse::().unwrap(); + + // Include a dummy `include_str!` for any files we read so rustc knows that + // we depend on the contents of those files. + let cwd = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + for file in input.files.iter() { + contents.extend( + format!( + "const _: &str = include_str!(r#\"{}\"#);\n", + Path::new(&cwd).join(file).display() + ) + .parse::() + .unwrap(), + ); + } + + Ok(contents) +} + +impl Parse for Config { + fn parse(input: ParseStream<'_>) -> Result { + let call_site = Span::call_site(); + let mut world = None; + let mut ret = Config::default(); + + if input.peek(token::Brace) { + let content; + syn::braced!(content in input); + let fields = Punctuated::::parse_terminated(&content)?; + for field in fields.into_pairs() { + match field.into_value() { + Opt::Path(path) => { + if world.is_some() { + return Err(Error::new(path.span(), "cannot specify second world")); + } + world = Some(ret.parse(path)?); + } + Opt::Inline(span, w) => { + if world.is_some() { + return Err(Error::new(span, "cannot specify second world")); + } + world = Some(w); + } + Opt::Tracing(val) => ret.opts.tracing = val, + Opt::Async(val) => ret.opts.async_ = val, + } + } + } else { + let s = input.parse::()?; + world = Some(ret.parse(s)?); + } + ret.world = world.ok_or_else(|| { + Error::new( + call_site, + "must specify a `*.wit` file to generate bindings for", + ) + })?; + Ok(ret) + } +} + +impl Config { + fn parse(&mut self, path: syn::LitStr) -> Result { + let span = path.span(); + let path = path.value(); + let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + let path = manifest_dir.join(path); + self.files.push(path.to_str().unwrap().to_string()); + World::parse_file(path).map_err(|e| Error::new(span, e)) + } +} + +mod kw { + syn::custom_keyword!(path); + syn::custom_keyword!(inline); + syn::custom_keyword!(tracing); +} + +enum Opt { + Path(syn::LitStr), + Inline(Span, World), + Tracing(bool), + Async(bool), +} + +impl Parse for Opt { + fn parse(input: ParseStream<'_>) -> Result { + let l = input.lookahead1(); + if l.peek(kw::path) { + input.parse::()?; + input.parse::()?; + Ok(Opt::Path(input.parse()?)) + } else if l.peek(kw::inline) { + let span = input.parse::()?.span; + input.parse::()?; + let s = input.parse::()?; + let world = + World::parse("", &s.value()).map_err(|e| Error::new(s.span(), e))?; + Ok(Opt::Inline(span, world)) + } else if l.peek(kw::tracing) { + input.parse::()?; + input.parse::()?; + Ok(Opt::Tracing(input.parse::()?.value)) + } else if l.peek(Token![async]) { + input.parse::()?; + input.parse::()?; + Ok(Opt::Async(input.parse::()?.value)) + } else { + Err(l.error()) + } + } +} diff --git a/crates/component-macro/src/component.rs b/crates/component-macro/src/component.rs new file mode 100644 index 000000000000..b87fb6cb3c42 --- /dev/null +++ b/crates/component-macro/src/component.rs @@ -0,0 +1,1191 @@ +use proc_macro2::{Literal, TokenStream, TokenTree}; +use quote::{format_ident, quote}; +use std::collections::HashSet; +use std::fmt; +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::{braced, parse_quote, Data, DeriveInput, Error, Result, Token}; +use wasmtime_component_util::{DiscriminantSize, FlagsSize}; + +#[derive(Debug, Copy, Clone)] +pub enum VariantStyle { + Variant, + Enum, + Union, +} + +impl fmt::Display for VariantStyle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::Variant => "variant", + Self::Enum => "enum", + Self::Union => "union", + }) + } +} + +#[derive(Debug, Copy, Clone)] +enum Style { + Record, + Variant(VariantStyle), +} + +fn find_style(input: &DeriveInput) -> Result