Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 41 additions & 9 deletions crates/component-macro/src/bindgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
use syn::parse::{Error, Parse, ParseStream, Result};
use syn::punctuated::Punctuated;
use syn::{braced, token, Ident, Token};
use wasmtime_wit_bindgen::{Opts, TrappableError};
use wasmtime_wit_bindgen::{Opts, Ownership, TrappableError};
use wit_parser::{PackageId, Resolve, UnresolvedPackage, WorldId};

pub struct Config {
Expand Down Expand Up @@ -73,7 +73,7 @@ impl Parse for Config {
Opt::Tracing(val) => opts.tracing = val,
Opt::Async(val) => opts.async_ = val,
Opt::TrappableErrorType(val) => opts.trappable_error_type = val,
Opt::DuplicateIfNecessary(val) => opts.duplicate_if_necessary = val,
Opt::Ownership(val) => opts.ownership = val,
Opt::Interfaces(s) => {
if inline.is_some() {
return Err(Error::new(s.span(), "cannot specify a second source"));
Expand Down Expand Up @@ -168,7 +168,7 @@ mod kw {
syn::custom_keyword!(tracing);
syn::custom_keyword!(trappable_error_type);
syn::custom_keyword!(world);
syn::custom_keyword!(duplicate_if_necessary);
syn::custom_keyword!(ownership);
syn::custom_keyword!(interfaces);
syn::custom_keyword!(with);
}
Expand All @@ -180,7 +180,7 @@ enum Opt {
Tracing(bool),
Async(bool),
TrappableErrorType(Vec<TrappableError>),
DuplicateIfNecessary(bool),
Ownership(Ownership),
Interfaces(syn::LitStr),
With(HashMap<String, String>),
}
Expand Down Expand Up @@ -208,12 +208,44 @@ impl Parse for Opt {
input.parse::<Token![async]>()?;
input.parse::<Token![:]>()?;
Ok(Opt::Async(input.parse::<syn::LitBool>()?.value))
} else if l.peek(kw::duplicate_if_necessary) {
input.parse::<kw::duplicate_if_necessary>()?;
} else if l.peek(kw::ownership) {
input.parse::<kw::ownership>()?;
input.parse::<Token![:]>()?;
Ok(Opt::DuplicateIfNecessary(
input.parse::<syn::LitBool>()?.value,
))
let ownership = input.parse::<syn::Ident>()?;
Ok(Opt::Ownership(match ownership.to_string().as_str() {
"Owning" => Ownership::Owning,
"Borrowing" => Ownership::Borrowing {
duplicate_if_necessary: {
let contents;
braced!(contents in input);
let field = contents.parse::<syn::Ident>()?;
match field.to_string().as_str() {
"duplicate_if_necessary" => {
contents.parse::<Token![:]>()?;
contents.parse::<syn::LitBool>()?.value
}
name => {
return Err(Error::new(
field.span(),
format!(
"unrecognized `Ownership::Borrowing` field: `{name}`; \
expected `duplicate_if_necessary`"
),
));
}
}
},
},
name => {
return Err(Error::new(
ownership.span(),
format!(
"unrecognized ownership: `{name}`; \
expected `Owning` or `Borrowing`"
),
));
}
}))
} else if l.peek(kw::trappable_error_type) {
input.parse::<kw::trappable_error_type>()?;
input.parse::<Token![:]>()?;
Expand Down
4 changes: 3 additions & 1 deletion crates/component-macro/tests/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ macro_rules! gentest {
wasmtime::component::bindgen!({
path: $path,
tracing: true,
duplicate_if_necessary: true,
ownership: Borrowing {
duplicate_if_necessary: true
}
});
}
}
Expand Down
3 changes: 3 additions & 0 deletions crates/test-programs/tests/reactor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ wasmtime::component::bindgen!({
"wasi:cli-base/stdout": preview2::wasi::cli_base::stdout,
"wasi:cli-base/stderr": preview2::wasi::cli_base::stderr,
},
ownership: Borrowing {
duplicate_if_necessary: false
}
});

struct ReactorCtx {
Expand Down
29 changes: 23 additions & 6 deletions crates/wit-bindgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,25 @@ struct Exports {
funcs: Vec<String>,
}

#[derive(Default, Debug, Clone, Copy)]
pub enum Ownership {
/// Generated types will be composed entirely of owning fields, regardless
/// of whether they are used as parameters to guest exports or not.
#[default]
Owning,

/// Generated types used as parameters to guest exports will be "deeply
/// borrowing", i.e. contain references rather than owned values when
/// applicable.
Borrowing {
/// Whether or not to generate "duplicate" type definitions for a single
/// WIT type if necessary, for example if it's used as both an import
/// and an export, or if it's used both as a parameter to an export and
/// a return value from an export.
duplicate_if_necessary: bool,
},
}

#[derive(Default, Debug, Clone)]
pub struct Opts {
/// Whether or not `rustfmt` is executed to format generated code.
Expand All @@ -78,10 +97,8 @@ pub struct Opts {
/// `result<T, E>` found in WIT.
pub trappable_error_type: Vec<TrappableError>,

/// Whether or not to generate "duplicate" type definitions for a single
/// WIT type if necessary, for example if it's used as both an import and an
/// export.
pub duplicate_if_necessary: bool,
/// Whether to generate owning or borrowing type definitions.
pub ownership: Ownership,

/// Whether or not to generate code for only the interfaces of this wit file or not.
pub only_interfaces: bool,
Expand Down Expand Up @@ -1582,8 +1599,8 @@ impl<'a> RustGenerator<'a> for InterfaceGenerator<'a> {
self.resolve
}

fn duplicate_if_necessary(&self) -> bool {
self.gen.opts.duplicate_if_necessary
fn ownership(&self) -> Ownership {
self.gen.opts.ownership
}

fn path_to_interface(&self, interface: InterfaceId) -> Option<String> {
Expand Down
54 changes: 36 additions & 18 deletions crates/wit-bindgen/src/rust.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::types::TypeInfo;
use crate::{types::TypeInfo, Ownership};
use heck::*;
use std::collections::HashMap;
use std::fmt::Write;
Expand All @@ -17,15 +17,16 @@ pub trait RustGenerator<'a> {
fn info(&self, ty: TypeId) -> TypeInfo;
fn path_to_interface(&self, interface: InterfaceId) -> Option<String>;

/// This, if enabled, will possibly cause types to get duplicate copies to
/// get generated of each other. For example a record containing a string
/// used both in the import and export context would get one variant
/// generated for both.
/// This determines whether we generate owning types or (where appopriate)
/// borrowing types.
///
/// If this is disabled then the import context would require the same type
/// used for the export context, which has an owned string that might not
/// otherwise be necessary.
fn duplicate_if_necessary(&self) -> bool;
/// For example, when generating a type which is only used as a parameter to
/// a guest-exported function, there is no need for it to own its fields.
/// However, constructing deeply-nested borrows (e.g. `&[&[&[&str]]]]` for
/// `list<list<list<string>>>`) can be very awkward, so by default we
/// generate owning types and use only shallow borrowing at the top level
/// inside function signatures.
fn ownership(&self) -> Ownership;

fn print_ty(&mut self, ty: &Type, mode: TypeMode) {
match ty {
Expand Down Expand Up @@ -188,6 +189,11 @@ pub trait RustGenerator<'a> {
}

fn print_list(&mut self, ty: &Type, mode: TypeMode) {
let next_mode = if matches!(self.ownership(), Ownership::Owning) {
TypeMode::Owned
} else {
mode
};
match mode {
TypeMode::AllBorrowed(lt) => {
self.push_str("&");
Expand All @@ -196,12 +202,12 @@ pub trait RustGenerator<'a> {
self.push_str(" ");
}
self.push_str("[");
self.print_ty(ty, mode);
self.print_ty(ty, next_mode);
self.push_str("]");
}
TypeMode::Owned => {
self.push_str("Vec<");
self.print_ty(ty, mode);
self.print_ty(ty, next_mode);
self.push_str(">");
}
}
Expand All @@ -225,12 +231,13 @@ pub trait RustGenerator<'a> {
return Vec::new();
}
let mut result = Vec::new();
let first_mode = if info.owned || !info.borrowed {
TypeMode::Owned
} else {
assert!(!self.uses_two_names(&info));
TypeMode::AllBorrowed("'a")
};
let first_mode =
if info.owned || !info.borrowed || matches!(self.ownership(), Ownership::Owning) {
TypeMode::Owned
} else {
assert!(!self.uses_two_names(&info));
TypeMode::AllBorrowed("'a")
};
result.push((self.result_name(ty), first_mode));
if self.uses_two_names(&info) {
result.push((self.param_name(ty), TypeMode::AllBorrowed("'a")));
Expand Down Expand Up @@ -378,10 +385,21 @@ pub trait RustGenerator<'a> {
}

fn uses_two_names(&self, info: &TypeInfo) -> bool {
info.has_list && info.borrowed && info.owned && self.duplicate_if_necessary()
info.has_list
&& info.borrowed
&& info.owned
&& matches!(
self.ownership(),
Ownership::Borrowing {
duplicate_if_necessary: true
}
)
}

fn lifetime_for(&self, info: &TypeInfo, mode: TypeMode) -> Option<&'static str> {
if matches!(self.ownership(), Ownership::Owning) {
return None;
}
let lt = match mode {
TypeMode::AllBorrowed(s) => s,
_ => return None,
Expand Down
16 changes: 10 additions & 6 deletions crates/wit-bindgen/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,13 @@ impl Types {
live.add_type(resolve, ty);
}
for id in live.iter() {
let info = self.type_info.get_mut(&id).unwrap();
if import {
info.owned = true;
} else {
info.borrowed = true;
if resolve.types[id].name.is_some() {
let info = self.type_info.get_mut(&id).unwrap();
if import {
info.owned = true;
} else {
info.borrowed = true;
}
}
}
let mut live = LiveTypes::default();
Expand All @@ -82,7 +84,9 @@ impl Types {
live.add_type(resolve, ty);
}
for id in live.iter() {
self.type_info.get_mut(&id).unwrap().owned = true;
if resolve.types[id].name.is_some() {
self.type_info.get_mut(&id).unwrap().owned = true;
}
}

for ty in func.results.iter_types() {
Expand Down
1 change: 1 addition & 0 deletions tests/all/component_model/bindgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use wasmtime::{
Store,
};

mod ownership;
mod results;

mod no_imports {
Expand Down
Loading