Skip to content

Commit

Permalink
Implement support for exported resources in bindgen! (#7050)
Browse files Browse the repository at this point in the history
* Implement support for exported resources in `bindgen!`

This commit updates the `wasmtime::component::bindgen!` to support
exported resources. Exported resources are themselves always modeled as
`ResourceAny` at runtime and are required to perform dynamic type checks
to ensure that the right type of resource is passed in. Exported
resources have their own `GuestXXX` structure exported to namespace all
of their methods. Otherwise, like imports, there's not a whole lot of
special treatment of exported resources.

* Work around `heck` issue

Looks like `to_snake_case` behaves differently if the `unicode` feature
is enabled or not so bypass that entirely.
  • Loading branch information
alexcrichton authored Sep 15, 2023
1 parent 848764a commit 2ad057d
Show file tree
Hide file tree
Showing 3 changed files with 422 additions and 70 deletions.
43 changes: 43 additions & 0 deletions crates/component-macro/tests/codegen/resources-export.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package foo:foo

world w {
export simple-export
export export-using-import

export export-using-export1
export export-using-export2
}

interface simple-export {
resource a {
constructor()
static-a: static func() -> u32
method-a: func() -> u32
}
}

interface export-using-import {
use transitive-import.{y}
resource a {
constructor(y: y)
static-a: static func() -> y
method-a: func(y: y) -> y
}
}

interface transitive-import {
resource y
}

interface export-using-export1 {
resource a {
constructor()
}
}

interface export-using-export2 {
use export-using-export1.{a}
resource b {
constructor(a: a)
}
}
221 changes: 151 additions & 70 deletions crates/wit-bindgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,13 @@ impl Opts {
}

impl Wasmtime {
fn name_interface(&mut self, resolve: &Resolve, id: InterfaceId, name: &WorldKey) -> bool {
fn name_interface(
&mut self,
resolve: &Resolve,
id: InterfaceId,
name: &WorldKey,
is_export: bool,
) -> bool {
let with_name = resolve.name_world_key(name);
let entry = if let Some(remapped_path) = self.opts.with.get(&with_name) {
let name = format!("__with_name{}", self.with_name_counter);
Expand All @@ -193,6 +199,11 @@ impl Wasmtime {
)
}
};
let path = if is_export {
format!("exports::{path}")
} else {
path
};
InterfaceName {
remapped: false,
path,
Expand Down Expand Up @@ -239,7 +250,7 @@ impl Wasmtime {
}
WorldItem::Interface(id) => {
gen.gen.interface_last_seen_as_import.insert(*id, true);
if gen.gen.name_interface(resolve, *id, name) {
if gen.gen.name_interface(resolve, *id, name, false) {
return;
}
gen.current_interface = Some((*id, name, false));
Expand Down Expand Up @@ -301,15 +312,15 @@ impl Wasmtime {
assert!(gen.src.is_empty());
self.exports.funcs.push(body);
(
func.name.to_snake_case(),
func_field_name(resolve, func),
"wasmtime::component::Func".to_string(),
getter,
)
}
WorldItem::Type(_) => unreachable!(),
WorldItem::Interface(id) => {
gen.gen.interface_last_seen_as_import.insert(*id, false);
gen.gen.name_interface(resolve, *id, name);
gen.gen.name_interface(resolve, *id, name, true);
gen.current_interface = Some((*id, name, true));
gen.types(*id);
let iface = &resolve.interfaces[*id];
Expand All @@ -320,18 +331,11 @@ impl Wasmtime {
let camel = to_rust_upper_camel_case(iface_name);
uwriteln!(gen.src, "pub struct {camel} {{");
for (_, func) in iface.functions.iter() {
match func.kind {
FunctionKind::Freestanding => {
uwriteln!(
gen.src,
"{}: wasmtime::component::Func,",
func.name.to_snake_case()
);
}
// Resource methods are handled separately in
// `type_resource`.
_ => {}
}
uwriteln!(
gen.src,
"{}: wasmtime::component::Func,",
func_field_name(resolve, func)
);
}
uwriteln!(gen.src, "}}");

Expand All @@ -346,35 +350,56 @@ impl Wasmtime {
);
let mut fields = Vec::new();
for (_, func) in iface.functions.iter() {
match func.kind {
FunctionKind::Freestanding => {
let (name, getter) = gen.extract_typed_function(func);
uwriteln!(gen.src, "let {name} = {getter};");
fields.push(name);
}
// Resource methods are handled separately in
// `type_resource`.
_ => {}
}
let (name, getter) = gen.extract_typed_function(func);
uwriteln!(gen.src, "let {name} = {getter};");
fields.push(name);
}
uwriteln!(gen.src, "Ok({camel} {{");
for name in fields {
uwriteln!(gen.src, "{name},");
}
uwriteln!(gen.src, "}})");
uwriteln!(gen.src, "}}");

let mut resource_methods = IndexMap::new();

for (_, func) in iface.functions.iter() {
match func.kind {
FunctionKind::Freestanding => {
gen.define_rust_guest_export(resolve, Some(name), func);
}
// Resource methods are handled separately in
// `type_resource`.
_ => {}
FunctionKind::Method(id)
| FunctionKind::Constructor(id)
| FunctionKind::Static(id) => {
resource_methods.entry(id).or_insert(Vec::new()).push(func);
}
}
}

for (id, _) in resource_methods.iter() {
let name = resolve.types[*id].name.as_ref().unwrap();
let snake = name.to_snake_case();
let camel = name.to_upper_camel_case();
uwriteln!(
gen.src,
"pub fn {snake}(&self) -> Guest{camel}<'_> {{
Guest{camel} {{ funcs: self }}
}}"
);
}

uwriteln!(gen.src, "}}");

for (id, methods) in resource_methods {
let resource_name = resolve.types[id].name.as_ref().unwrap();
let camel = resource_name.to_upper_camel_case();
uwriteln!(gen.src, "impl Guest{camel}<'_> {{");
for method in methods {
gen.define_rust_guest_export(resolve, Some(name), method);
}
uwriteln!(gen.src, "}}");
}

let module = &gen.src[..];
let snake = iface_name.to_snake_case();

Expand Down Expand Up @@ -841,6 +866,13 @@ impl<'a> InterfaceGenerator<'a> {
}
}

fn types_imported(&self) -> bool {
match self.current_interface {
Some((_, _, is_export)) => !is_export,
None => true,
}
}

fn types(&mut self, id: InterfaceId) {
for (name, id) in self.resolve.interfaces[id].types.iter() {
self.define_type(name, *id);
Expand Down Expand Up @@ -887,51 +919,72 @@ impl<'a> InterfaceGenerator<'a> {
.expect("resources are required to be named")
.to_upper_camel_case();

self.rustdoc(docs);
uwriteln!(self.src, "pub enum {camel} {{}}");
if self.types_imported() {
self.rustdoc(docs);
uwriteln!(self.src, "pub enum {camel} {{}}");

if self.gen.opts.async_.maybe_async() {
uwriteln!(self.src, "#[wasmtime::component::__internal::async_trait]")
}
if self.gen.opts.async_.maybe_async() {
uwriteln!(self.src, "#[wasmtime::component::__internal::async_trait]")
}

uwriteln!(self.src, "pub trait Host{camel} {{");
uwriteln!(self.src, "pub trait Host{camel} {{");

let functions = match resource.owner {
TypeOwner::World(id) => self.resolve.worlds[id]
.imports
.values()
.filter_map(|item| match item {
WorldItem::Function(f) => Some(f),
_ => None,
})
.collect(),
TypeOwner::Interface(id) => self.resolve.interfaces[id]
.functions
.values()
.collect::<Vec<_>>(),
TypeOwner::None => {
panic!("A resource must be owned by a world or interface");
}
};

let functions = match resource.owner {
TypeOwner::World(id) => self.resolve.worlds[id]
.imports
.values()
.filter_map(|item| match item {
WorldItem::Function(f) => Some(f),
_ => None,
})
.collect(),
TypeOwner::Interface(id) => self.resolve.interfaces[id]
.functions
.values()
.collect::<Vec<_>>(),
TypeOwner::None => {
panic!("A resource must be owned by a world or interface");
}
};
for func in functions {
match func.kind {
FunctionKind::Method(resource)
| FunctionKind::Static(resource)
| FunctionKind::Constructor(resource)
if id == resource => {}
_ => continue,
}

for func in functions {
match func.kind {
FunctionKind::Method(resource)
| FunctionKind::Static(resource)
| FunctionKind::Constructor(resource)
if id == resource => {}
_ => continue,
self.generate_function_trait_sig(func);
}

self.generate_function_trait_sig(func);
}
uwrite!(
self.src,
"fn drop(&mut self, rep: wasmtime::component::Resource<{camel}>) -> wasmtime::Result<()>;");

uwrite!(
self.src,
"fn drop(&mut self, rep: wasmtime::component::Resource<{camel}>) -> wasmtime::Result<()>;"
);
uwriteln!(self.src, "}}");
} else {
let iface_name = match self.current_interface.unwrap().1 {
WorldKey::Name(name) => name.to_upper_camel_case(),
WorldKey::Interface(i) => self.resolve.interfaces[*i]
.name
.as_ref()
.unwrap()
.to_upper_camel_case(),
};
self.rustdoc(docs);
uwriteln!(
self.src,
"
pub type {camel} = wasmtime::component::ResourceAny;
uwriteln!(self.src, "}}");
pub struct Guest{camel}<'a> {{
funcs: &'a {iface_name},
}}
"
);
}
}

fn type_record(&mut self, id: TypeId, _name: &str, record: &Record, docs: &Docs) {
Expand Down Expand Up @@ -1668,7 +1721,7 @@ impl<'a> InterfaceGenerator<'a> {

fn extract_typed_function(&mut self, func: &Function) -> (String, String) {
let prev = mem::take(&mut self.src);
let snake = func.name.to_snake_case();
let snake = func_field_name(self.resolve, func);
uwrite!(self.src, "*__exports.typed_func::<(");
for (_, ty) in func.params.iter() {
self.print_ty(ty, TypeMode::AllBorrowed("'_"));
Expand Down Expand Up @@ -1709,7 +1762,7 @@ impl<'a> InterfaceGenerator<'a> {
uwrite!(
self.src,
"pub {async_} fn call_{}<S: wasmtime::AsContextMut>(&self, mut store: S, ",
func.name.to_snake_case(),
func.item_name().to_snake_case(),
);

for (i, param) in func.params.iter().enumerate() {
Expand Down Expand Up @@ -1758,10 +1811,14 @@ impl<'a> InterfaceGenerator<'a> {
self.print_ty(ty, TypeMode::Owned);
self.push_str(", ");
}
let projection_to_func = match &func.kind {
FunctionKind::Freestanding => "",
_ => ".funcs",
};
uwriteln!(
self.src,
")>::new_unchecked(self.{})",
func.name.to_snake_case()
")>::new_unchecked(self{projection_to_func}.{})",
func_field_name(self.resolve, func),
);
self.src.push_str("};\n");
self.src.push_str("let (");
Expand Down Expand Up @@ -1923,6 +1980,30 @@ fn rust_function_name(func: &Function) -> String {
}
}

fn func_field_name(resolve: &Resolve, func: &Function) -> String {
let mut name = String::new();
match func.kind {
FunctionKind::Method(id) => {
name.push_str("method-");
name.push_str(resolve.types[id].name.as_ref().unwrap());
name.push_str("-");
}
FunctionKind::Static(id) => {
name.push_str("static-");
name.push_str(resolve.types[id].name.as_ref().unwrap());
name.push_str("-");
}
FunctionKind::Constructor(id) => {
name.push_str("constructor-");
name.push_str(resolve.types[id].name.as_ref().unwrap());
name.push_str("-");
}
FunctionKind::Freestanding => {}
}
name.push_str(func.item_name());
name.to_snake_case()
}

fn get_resources<'a>(resolve: &'a Resolve, id: InterfaceId) -> impl Iterator<Item = &'a str> + 'a {
resolve.interfaces[id]
.types
Expand Down
Loading

0 comments on commit 2ad057d

Please sign in to comment.