Skip to content

Commit

Permalink
feat: preliminary support for WIT templates
Browse files Browse the repository at this point in the history
This is a minimum viable implementation of WIT templates per
WebAssembly/component-model#172.  It
supports interfaces with at most one wildcard function, which may be
expanded (i.e. monomorphized) with a set of substitutions provided by
the application developer when generating guest bindings.

Signed-off-by: Joel Dice <joel.dice@fermyon.com>
  • Loading branch information
fibonacci1729 authored and dicej committed Mar 20, 2023
1 parent 80d9ffa commit 8272e20
Show file tree
Hide file tree
Showing 21 changed files with 360 additions and 182 deletions.
346 changes: 183 additions & 163 deletions Cargo.lock

Large diffs are not rendered by default.

14 changes: 11 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ indexmap = "1.9.1"
wasm-encoder = "0.25.0"
wasm-metadata = "0.3.1"
wat = "1.0.61"
wit-parser = "0.6.3"
wit-component = "0.7.2"
wit-parser = "0.6.4"
wit-component = "0.7.3"

wit-bindgen-core = { path = 'crates/core', version = '0.4.0' }
wit-bindgen-c = { path = 'crates/c', version = '0.4.0' }
Expand All @@ -60,6 +60,7 @@ wit-bindgen-go = { workspace = true, features = ['clap'], optional = true }
wat = { workspace = true }
wit-component = { workspace = true }
wasm-encoder = { workspace = true }
toml = "0.7.2"

[features]
default = ['c', 'rust', 'markdown', 'teavm-java', 'go']
Expand All @@ -71,6 +72,13 @@ go = ['dep:wit-bindgen-go']

[dev-dependencies]
heck = { workspace = true }
wasmtime = { version = "6", features = ['component-model'] }
wasmtime = { version = "8", features = ['component-model'] }
test-artifacts = { path = 'crates/test-rust-wasm/artifacts' }
wit-parser = { workspace = true }

[patch.crates-io]
wit-component = { git = "https://github.com/dicej/wasm-tools", branch = "wit-templates" }
wit-parser = { git = "https://github.com/dicej/wasm-tools", branch = "wit-templates" }
wasm-metadata = { git = "https://github.com/dicej/wasm-tools", branch = "wit-templates" }
wasmtime = { git = "https://github.com/dicej/wasmtime", branch = "wit-templates-minimal" }
wasmtime-wit-bindgen = { git = "https://github.com/dicej/wasmtime", branch = "wit-templates-minimal" }
1 change: 1 addition & 0 deletions crates/rust-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ wit-bindgen-core = { workspace = true }
wit-bindgen-rust = { workspace = true }
wit-component = { workspace = true }
anyhow = { workspace = true }
toml = "0.7.2"
52 changes: 47 additions & 5 deletions crates/rust-macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use proc_macro2::{Span, TokenStream};
use std::fs;
use std::path::{Path, PathBuf};
use syn::parse::{Error, Parse, ParseStream, Result};
use syn::punctuated::Punctuated;
use syn::{token, Token};
use wit_bindgen_core::wit_parser::{PackageId, Resolve, UnresolvedPackage, WorldId};
use wit_bindgen_core::wit_parser::{self, PackageId, Resolve, UnresolvedPackage, WorldId};
use wit_bindgen_rust::Opts;

#[proc_macro]
Expand Down Expand Up @@ -32,6 +33,7 @@ impl Parse for Config {
let mut opts = Opts::default();
let mut world = None;
let mut source = None;
let mut substitutions = None;

if input.peek(token::Brace) {
let content;
Expand All @@ -57,6 +59,24 @@ impl Parse for Config {
}
source = Some(Source::Inline(s.value()));
}
Opt::SubstitutionsPath(s) => {
if substitutions.is_some() {
return Err(Error::new(
s.span(),
"cannot specify second substitutions",
));
}
substitutions = Some(Source::Path(s.value()));
}
Opt::SubstitutionsInline(s) => {
if substitutions.is_some() {
return Err(Error::new(
s.span(),
"cannot specify second substitutions",
));
}
substitutions = Some(Source::Inline(s.value()));
}
Opt::UseStdFeature => opts.std_feature = true,
Opt::RawStrings => opts.raw_strings = true,
Opt::MacroExport => opts.macro_export = true,
Expand All @@ -71,8 +91,8 @@ impl Parse for Config {
source = Some(Source::Path(input.parse::<syn::LitStr>()?.value()));
}
}
let (resolve, pkg, files) =
parse_source(&source).map_err(|err| Error::new(call_site, format!("{err:?}")))?;
let (resolve, pkg, files) = parse_source(&source, &substitutions)
.map_err(|err| Error::new(call_site, format!("{err:?}")))?;
let world = resolve
.select_world(pkg, world.as_deref())
.map_err(|e| Error::new(call_site, format!("{e:?}")))?;
Expand All @@ -85,7 +105,10 @@ impl Parse for Config {
}
}

fn parse_source(source: &Option<Source>) -> anyhow::Result<(Resolve, PackageId, Vec<PathBuf>)> {
fn parse_source(
source: &Option<Source>,
substitutions: &Option<Source>,
) -> anyhow::Result<(Resolve, PackageId, Vec<PathBuf>)> {
let mut resolve = Resolve::default();
let mut files = Vec::new();
let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
Expand All @@ -108,7 +131,14 @@ fn parse_source(source: &Option<Source>) -> anyhow::Result<(Resolve, PackageId,
Some(Source::Path(s)) => parse(&root.join(&s))?,
None => parse(&root.join("wit"))?,
};

match substitutions {
Some(Source::Inline(s)) => wit_parser::expand(&mut resolve, toml::from_str(s)?)?,
Some(Source::Path(s)) => wit_parser::expand(
&mut resolve,
toml::from_str(&fs::read_to_string(&root.join(&s))?)?,
)?,
None => (),
}
Ok((resolve, pkg, files))
}

Expand Down Expand Up @@ -146,12 +176,16 @@ mod kw {
syn::custom_keyword!(world);
syn::custom_keyword!(path);
syn::custom_keyword!(inline);
syn::custom_keyword!(substitutions_path);
syn::custom_keyword!(substitutions_inline);
}

enum Opt {
World(syn::LitStr),
Path(syn::LitStr),
Inline(syn::LitStr),
SubstitutionsPath(syn::LitStr),
SubstitutionsInline(syn::LitStr),
UseStdFeature,
RawStrings,
MacroExport,
Expand All @@ -171,6 +205,14 @@ impl Parse for Opt {
input.parse::<kw::inline>()?;
input.parse::<Token![:]>()?;
Ok(Opt::Inline(input.parse()?))
} else if l.peek(kw::substitutions_path) {
input.parse::<kw::substitutions_path>()?;
input.parse::<Token![:]>()?;
Ok(Opt::SubstitutionsPath(input.parse()?))
} else if l.peek(kw::substitutions_inline) {
input.parse::<kw::substitutions_inline>()?;
input.parse::<Token![:]>()?;
Ok(Opt::SubstitutionsInline(input.parse()?))
} else if l.peek(kw::world) {
input.parse::<kw::world>()?;
input.parse::<Token![:]>()?;
Expand Down
4 changes: 4 additions & 0 deletions crates/test-rust-wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,7 @@ test = false
[[bin]]
name = "results"
test = false

[[bin]]
name = "wildcards"
test = false
3 changes: 3 additions & 0 deletions crates/test-rust-wasm/src/bin/wildcards.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
include!("../../../../tests/runtime/wildcards/wasm.rs");

fn main() {}
17 changes: 16 additions & 1 deletion src/bin/wit-bindgen.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use anyhow::{bail, Context, Result};
use clap::Parser;
use std::path::PathBuf;
use std::str;
use std::{fs, str};
use wit_bindgen_core::{wit_parser, Files, WorldGenerator};
use wit_parser::{Resolve, UnresolvedPackage};

Expand Down Expand Up @@ -78,6 +78,10 @@ struct Common {
/// they're up-to-date with the source files.
#[clap(long)]
check: bool,

/// Path to template substitutions for expansion.
#[clap(long)]
expand: Option<PathBuf>,
}

fn main() -> Result<()> {
Expand Down Expand Up @@ -143,6 +147,15 @@ fn gen_world(
opts: &Common,
files: &mut Files,
) -> Result<()> {
let substitutions = match &opts.expand {
Some(path) => {
let input =
fs::read_to_string(path).context("failed to read substitutions from file")?;
toml::from_str(&input).context("failed to parse substitutions from TOML")?
}
None => Default::default(),
};

let mut resolve = Resolve::default();
let pkg = if opts.wit.is_dir() {
resolve.push_dir(&opts.wit)?.0
Expand All @@ -152,6 +165,8 @@ fn gen_world(
&Default::default(),
)?
};

wit_parser::expand(&mut resolve, substitutions)?;
let world = resolve.select_world(pkg, opts.world.as_deref())?;
generator.generate(&resolve, world, files);
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion tests/runtime/flavorful.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub struct MyImports {
errored: bool,
}

impl imports::Imports for MyImports {
impl imports::Host for MyImports {
fn f_list_in_record1(&mut self, ty: imports::ListInRecord1Result) -> Result<()> {
assert_eq!(ty.a, "list_in_record1");
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion tests/runtime/lists.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use imports::*;
#[derive(Default)]
pub struct MyImports;

impl Imports for MyImports {
impl Host for MyImports {
fn empty_list_param(&mut self, a: Vec<u8>) -> Result<()> {
assert_eq!(a, []);
Ok(())
Expand Down
3 changes: 2 additions & 1 deletion tests/runtime/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ mod smoke;
mod strings;
mod unions;
mod variants;
mod wildcards;

wasmtime::component::bindgen!("testwasi" in "crates/wasi_snapshot_preview1/wit");

#[derive(Default)]
struct Wasi<T>(T);

impl<T> testwasi::Testwasi for Wasi<T> {
impl<T> testwasi::Host for Wasi<T> {
fn log(&mut self, bytes: Vec<u8>) -> Result<()> {
std::io::stdout().write_all(&bytes)?;
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion tests/runtime/many_arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ wasmtime::component::bindgen!("world" in "tests/runtime/many_arguments");
#[derive(Default)]
pub struct MyImports {}

impl imports::Imports for MyImports {
impl imports::Host for MyImports {
fn many_arguments(
&mut self,
a1: u64,
Expand Down
2 changes: 1 addition & 1 deletion tests/runtime/numbers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub struct MyImports {
scalar: u32,
}

impl imports::Imports for MyImports {
impl imports::Host for MyImports {
fn roundtrip_u8(&mut self, val: u8) -> Result<u8> {
Ok(val)
}
Expand Down
2 changes: 1 addition & 1 deletion tests/runtime/records.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ wasmtime::component::bindgen!("world" in "tests/runtime/records");
#[derive(Default)]
pub struct MyImports;

impl imports::Imports for MyImports {
impl imports::Host for MyImports {
fn multiple_results(&mut self) -> Result<(u8, u16)> {
Ok((4, 5))
}
Expand Down
2 changes: 1 addition & 1 deletion tests/runtime/smoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub struct MyImports {
hit: bool,
}

impl imports::Imports for MyImports {
impl imports::Host for MyImports {
fn thunk(&mut self) -> Result<()> {
self.hit = true;
println!("in the host");
Expand Down
2 changes: 1 addition & 1 deletion tests/runtime/strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ wasmtime::component::bindgen!("world" in "tests/runtime/strings");
#[derive(Default)]
pub struct MyImports;

impl imports::Imports for MyImports {
impl imports::Host for MyImports {
fn take_basic(&mut self, s: String) -> Result<()> {
assert_eq!(s, "latin utf16");
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion tests/runtime/unions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ wasmtime::component::bindgen!("world" in "tests/runtime/unions");
#[derive(Default)]
pub struct MyImports;

impl imports::Imports for MyImports {
impl imports::Host for MyImports {
fn add_one_integer(&mut self, num: imports::AllIntegers) -> Result<imports::AllIntegers> {
use imports::AllIntegers;
Ok(match num {
Expand Down
2 changes: 1 addition & 1 deletion tests/runtime/variants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ wasmtime::component::bindgen!("world" in "tests/runtime/variants");
#[derive(Default)]
pub struct MyImports;

impl imports::Imports for MyImports {
impl imports::Host for MyImports {
fn roundtrip_option(&mut self, a: Option<f32>) -> anyhow::Result<Option<u8>> {
Ok(a.map(|x| x as u8))
}
Expand Down
52 changes: 52 additions & 0 deletions tests/runtime/wildcards.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use anyhow::Result;
use wasmtime::Store;

wasmtime::component::bindgen!(in "tests/runtime/wildcards");

#[derive(Default)]
struct Host;

impl imports::Host for Host {}

struct Match(u32);

impl imports::WildcardMatch<Host> for Match {
fn call(&self, _host: &mut Host, _name: &str) -> Result<u32> {
Ok(self.0)
}
}

#[test]
fn run() -> Result<()> {
eprintln!("yossa hello");
crate::run_test(
"wildcards",
|linker| {
eprintln!("yossa add to linker");
Wildcards::add_to_linker(
linker,
WildcardMatches {
imports: vec![("a", Match(42)), ("b", Match(43)), ("c", Match(44))],
},
|x| &mut x.0,
)
},
|store, component, linker| Wildcards::instantiate(store, component, linker),
run_test,
)
}

fn run_test(wildcards: Wildcards, store: &mut Store<crate::Wasi<Host>>) -> Result<()> {
for (name, value) in [("x", 42), ("y", 43), ("z", 44)] {
assert_eq!(
value,
wildcards
.exports
.get_wildcard_match(name)
.unwrap()
.call(&mut *store)?
);
}

Ok(())
}
3 changes: 3 additions & 0 deletions tests/runtime/wildcards/substitutions.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[wildcards]
imports = ["a", "b", "c"]
exports = ["x", "y", "z"]
21 changes: 21 additions & 0 deletions tests/runtime/wildcards/wasm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
wit_bindgen::generate!({
world: "world",
path: "../../tests/runtime/wildcards",
substitutions_path: "../../tests/runtime/wildcards/substitutions.toml",
});

struct Exports;

export_wildcards!(Exports);

impl exports::Exports for Exports {
fn x() -> u32 {
imports::a()
}
fn y() -> u32 {
imports::b()
}
fn z() -> u32 {
imports::c()
}
}
8 changes: 8 additions & 0 deletions tests/runtime/wildcards/world.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
interface foo {
*: func() -> u32
}

default world wildcards {
import imports: self.foo
export exports: self.foo
}

0 comments on commit 8272e20

Please sign in to comment.