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.  I will be
posting separate PRs to the `wit-bindgen` and `wasmtime` repos to
implement and test guest and host binding generation, respectively.

Signed-off-by: Joel Dice <[email protected]>
  • Loading branch information
fibonacci1729 authored and dicej committed Mar 20, 2023
1 parent 7108f55 commit 7add182
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 17 deletions.
2 changes: 2 additions & 0 deletions crates/wit-component/src/decoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,7 @@ impl WitPackageDecoder<'_> {
types: IndexMap::default(),
functions: IndexMap::new(),
document: doc,
wildcard: None,
})
});
Ok(interface)
Expand All @@ -624,6 +625,7 @@ impl WitPackageDecoder<'_> {
types: IndexMap::default(),
functions: IndexMap::new(),
document: doc,
wildcard: None,
};

for (name, export_url, ty) in ty.exports(self.info.types.as_ref()) {
Expand Down
41 changes: 38 additions & 3 deletions crates/wit-parser/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ use std::path::{Path, PathBuf};

pub mod lex;

pub use expand::expand;
mod expand;

pub use resolve::Resolver;
mod resolve;
pub mod toposort;
Expand Down Expand Up @@ -464,10 +467,31 @@ struct Stream<'a> {

pub struct Value<'a> {
docs: Docs<'a>,
name: Id<'a>,
name: VariableId<'a>,
kind: ValueKind<'a>,
}

impl<'a> Value<'a> {
pub(crate) fn name(&self) -> &'a str {
match &self.name {
VariableId::Id(id) => id.name,
VariableId::Wildcard(_) => "*",
}
}

pub(crate) fn span(&self) -> Span {
match self.name {
VariableId::Id(Id { span, .. }) => span,
VariableId::Wildcard(span) => span,
}
}
}

pub enum VariableId<'a> {
Wildcard(Span), // `*`
Id(Id<'a>),
}

struct Union<'a> {
span: Span,
cases: Vec<UnionCase<'a>>,
Expand Down Expand Up @@ -552,7 +576,7 @@ impl<'a> InterfaceItem<'a> {
Some((_span, Token::Union)) => {
TypeDef::parse_union(tokens, docs).map(InterfaceItem::TypeDef)
}
Some((_span, Token::Id)) | Some((_span, Token::ExplicitId)) => {
Some((_span, Token::Star | Token::Id | Token::ExplicitId)) => {
Value::parse(tokens, docs).map(InterfaceItem::Value)
}
Some((_span, Token::Use)) => Use::parse(tokens).map(InterfaceItem::Use),
Expand Down Expand Up @@ -670,13 +694,24 @@ impl<'a> TypeDef<'a> {

impl<'a> Value<'a> {
fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result<Self> {
let name = parse_id(tokens)?;
let name = parse_variable_id(tokens)?;
tokens.expect(Token::Colon)?;
let kind = ValueKind::parse(tokens)?;
Ok(Value { docs, name, kind })
}
}

fn parse_variable_id<'a>(tokens: &mut Tokenizer<'a>) -> Result<VariableId<'a>> {
match tokens.clone().next()? {
Some((_span, Token::Id | Token::ExplicitId)) => parse_id(tokens).map(VariableId::Id),
Some((span, Token::Star)) => {
tokens.expect(Token::Star)?;
Ok(VariableId::Wildcard(span))
}
other => Err(err_expected(tokens, "an identifier or `*`", other).into()),
}
}

fn parse_id<'a>(tokens: &mut Tokenizer<'a>) -> Result<Id<'a>> {
match tokens.next()? {
Some((span, Token::Id)) => Ok(Id {
Expand Down
90 changes: 90 additions & 0 deletions crates/wit-parser/src/ast/expand.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use crate::{Interface, InterfaceId, Resolve, WorldItem};
use anyhow::{anyhow, Result};
use id_arena::Arena;
use indexmap::IndexMap;
use std::collections::{HashMap, HashSet};

pub type Substitutions = HashMap<String, HashMap<String, HashSet<String>>>;

pub fn expand(resolve: &mut Resolve, mut substitutions: Substitutions) -> Result<()> {
let mut new_interfaces = resolve.interfaces.clone();
for (_, world) in &mut resolve.worlds {
let mut subs = substitutions.remove(&world.name).unwrap_or_default();
expand_interfaces(
&world.name,
"imports",
&mut world.imports,
&mut new_interfaces,
&mut subs,
)?;
expand_interfaces(
&world.name,
"exports",
&mut world.exports,
&mut new_interfaces,
&mut subs,
)?;
}

if !substitutions.is_empty() {
log::warn!("unused substitutions were provided: {substitutions:?}",);
}

resolve.interfaces = new_interfaces;

Ok(())
}

fn expand_interfaces(
world_name: &str,
desc: &str,
items: &mut IndexMap<String, WorldItem>,
new_interfaces: &mut Arena<Interface>,
substitutions: &mut HashMap<String, HashSet<String>>,
) -> Result<()> {
for (name, item) in items {
if let WorldItem::Interface(interface) = item {
if new_interfaces[*interface].wildcard.is_some() {
let new_interface = expand_interface(
*interface,
new_interfaces,
substitutions.remove(name).ok_or_else(|| {
anyhow!(
"world {world_name} {desc} item {name} contains wildcards \
but no substitutions were provided",
)
})?,
);
*interface = new_interfaces.alloc(new_interface);
}
}
}

if !substitutions.is_empty() {
log::warn!("unused substitutions were provided for world {world_name}: {substitutions:?}",);
}

Ok(())
}

fn expand_interface(
interface: InterfaceId,
new_interfaces: &Arena<Interface>,
substitutions: HashSet<String>,
) -> Interface {
let mut new_interface = new_interfaces[interface].clone();
// Make the expanded interface anonymous; otherwise the generated component type will fail to validate due to
// the existence of multiple interfaces with the same name.
//
// TODO: implement something like
// https://github.com/WebAssembly/component-model/issues/172#issuecomment-1466939890, which may entail changes
// to how interfaces are modeled in WIT, `Resolve`, and the component model.
new_interface.name = None;
let function = new_interface.wildcard.take().unwrap();
for var_name in substitutions {
let mut new_func = function.clone();
new_func.name = var_name.clone();
assert!(new_interface.functions.insert(var_name, new_func).is_none());
}
new_interface
}
39 changes: 27 additions & 12 deletions crates/wit-parser/src/ast/resolve.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{Error, ParamList, ResultList, ValueKind};
use super::{Error, ParamList, ResultList, ValueKind, VariableId};
use crate::ast::toposort::toposort;
use crate::*;
use anyhow::{anyhow, bail, Result};
Expand Down Expand Up @@ -186,6 +186,7 @@ impl<'a> Resolver<'a> {
docs: Docs::default(),
document: doc,
functions: IndexMap::new(),
wildcard: None,
});
DocumentItem::Interface(id)
});
Expand All @@ -205,6 +206,7 @@ impl<'a> Resolver<'a> {
docs: Docs::default(),
document: doc,
functions: IndexMap::new(),
wildcard: None,
})
}),
};
Expand Down Expand Up @@ -531,6 +533,7 @@ impl<'a> Resolver<'a> {
name: name.map(|s| s.to_string()),
functions: IndexMap::new(),
types: IndexMap::new(),
wildcard: None,
});
if let Some(name) = name {
self.document_interfaces[document.index()]
Expand Down Expand Up @@ -563,11 +566,21 @@ impl<'a> Resolver<'a> {
match field {
ast::InterfaceItem::Value(value) => match &value.kind {
ValueKind::Func(func) => {
self.define_interface_name(&value.name, TypeOrItem::Item("function"))?;
let func = self.resolve_function(&value.docs, value.name.name, func)?;
let prev = self.interfaces[interface_id]
.functions
.insert(value.name.name.to_string(), func);
if !matches!(value.name, VariableId::Wildcard(_)) {
self.define_interface_name(
value.name(),
value.span(),
TypeOrItem::Item("function"),
)?;
}
let func = self.resolve_function(&value.docs, value.name(), func)?;
let prev = if matches!(value.name, VariableId::Wildcard(_)) {
self.interfaces[interface_id].wildcard.replace(func)
} else {
self.interfaces[interface_id]
.functions
.insert(value.name().to_string(), func)
};
assert!(prev.is_none());
}
},
Expand Down Expand Up @@ -646,7 +659,9 @@ impl<'a> Resolver<'a> {
name: Some(def.name.name.to_string()),
owner,
});
self.define_interface_name(&def.name, TypeOrItem::Type(id))?;
let name = def.name.name;
let span = def.name.span;
self.define_interface_name(name, span, TypeOrItem::Type(id))?;
}
Ok(())
}
Expand Down Expand Up @@ -675,7 +690,7 @@ impl<'a> Resolver<'a> {
name: Some(name.name.to_string()),
owner,
});
self.define_interface_name(name, TypeOrItem::Type(id))?;
self.define_interface_name(name.name, name.span, TypeOrItem::Type(id))?;
}
Ok(())
}
Expand Down Expand Up @@ -758,12 +773,12 @@ impl<'a> Resolver<'a> {
}
}

fn define_interface_name(&mut self, name: &ast::Id<'a>, item: TypeOrItem) -> Result<()> {
let prev = self.type_lookup.insert(name.name, (item, name.span));
fn define_interface_name(&mut self, name: &'a str, span: Span, item: TypeOrItem) -> Result<()> {
let prev = self.type_lookup.insert(name, (item, span));
if prev.is_some() {
Err(Error {
span: name.span,
msg: format!("name `{}` is defined more than once", name.name),
span,
msg: format!("name `{}` is defined more than once", name),
}
.into())
} else {
Expand Down
5 changes: 4 additions & 1 deletion crates/wit-parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::path::Path;
pub mod abi;
mod ast;
use ast::lex::Span;
pub use ast::SourceMap;
pub use ast::{expand, SourceMap};
mod sizealign;
pub use sizealign::*;
mod resolve;
Expand Down Expand Up @@ -289,6 +289,9 @@ pub struct Interface {

/// The document that this interface belongs to.
pub document: DocumentId,

/// TODO: A templated function.
pub wildcard: Option<Function>,
}

#[derive(Debug, Clone, PartialEq)]
Expand Down
3 changes: 3 additions & 0 deletions crates/wit-parser/src/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,9 @@ impl Remap {
for (_, func) in iface.functions.iter_mut() {
self.update_function(func);
}
if let Some(func) = &mut iface.wildcard {
self.update_function(func);
}
}

fn update_function(&self, func: &mut Function) {
Expand Down
2 changes: 1 addition & 1 deletion tests/testsuite
Submodule testsuite updated 65 files
+5 −5 binary.wast
+0 −20 call_indirect.wast
+0 −19 elem.wast
+5 −5 proposals/exception-handling/binary.wast
+0 −19 proposals/extended-const/elem.wast
+5 −5 proposals/function-references/binary.wast
+40 −0 proposals/function-references/local_get.wast
+0 −74 proposals/function-references/local_init.wast
+0 −202 proposals/function-references/return_call.wast
+0 −536 proposals/function-references/return_call_indirect.wast
+0 −17 proposals/function-references/select.wast
+0 −212 proposals/multi-memory/address0.wast
+0 −295 proposals/multi-memory/address1.wast
+0 −43 proposals/multi-memory/align0.wast
+5 −5 proposals/multi-memory/binary.wast
+0 −67 proposals/multi-memory/binary0.wast
+0 −73 proposals/multi-memory/data0.wast
+0 −146 proposals/multi-memory/data1.wast
+0 −28 proposals/multi-memory/data_drop0.wast
+0 −51 proposals/multi-memory/exports0.wast
+0 −38 proposals/multi-memory/float_exprs0.wast
+0 −105 proposals/multi-memory/float_exprs1.wast
+0 −60 proposals/multi-memory/float_memory0.wast
+0 −45 proposals/multi-memory/imports0.wast
+0 −16 proposals/multi-memory/imports1.wast
+0 −73 proposals/multi-memory/imports2.wast
+0 −75 proposals/multi-memory/imports3.wast
+0 −47 proposals/multi-memory/imports4.wast
+0 −42 proposals/multi-memory/linking0.wast
+0 −65 proposals/multi-memory/linking1.wast
+0 −30 proposals/multi-memory/linking2.wast
+0 −83 proposals/multi-memory/linking3.wast
+0 −19 proposals/multi-memory/load0.wast
+0 −41 proposals/multi-memory/load1.wast
+0 −213 proposals/multi-memory/load2.wast
+42 −0 proposals/multi-memory/memory-multi.wast
+0 −60 proposals/multi-memory/memory_copy0.wast
+0 −1 proposals/multi-memory/memory_copy1.wast
+0 −46 proposals/multi-memory/memory_fill0.wast
+0 −42 proposals/multi-memory/memory_init0.wast
+0 −19 proposals/multi-memory/memory_size0.wast
+0 −29 proposals/multi-memory/memory_size1.wast
+0 −34 proposals/multi-memory/memory_size2.wast
+0 −25 proposals/multi-memory/memory_size3.wast
+0 −36 proposals/multi-memory/memory_trap0.wast
+0 −250 proposals/multi-memory/memory_trap1.wast
+0 −42 proposals/multi-memory/simd_memory-multi.wast
+0 −42 proposals/multi-memory/start0.wast
+0 −25 proposals/multi-memory/store0.wast
+0 −52 proposals/multi-memory/store1.wast
+0 −35 proposals/multi-memory/traps0.wast
+0 −13 proposals/relaxed-simd/i16x8_relaxed_q15mulr_s.wast
+6 −73 proposals/relaxed-simd/i32x4_relaxed_trunc.wast
+0 −19 proposals/relaxed-simd/i8x16_relaxed_swizzle.wast
+16 −79 proposals/relaxed-simd/relaxed_dot_product.wast
+3 −52 proposals/relaxed-simd/relaxed_laneselect.wast
+4 −116 proposals/relaxed-simd/relaxed_madd_nmadd.wast
+1 −80 proposals/relaxed-simd/relaxed_min_max.wast
+0 −17 select.wast
+0 −12 simd_linking.wast
+1 −5 table_fill.wast
+1 −1 table_get.wast
+1 −4 table_grow.wast
+1 −1 table_set.wast
+1 −1 table_size.wast

0 comments on commit 7add182

Please sign in to comment.