-
Notifications
You must be signed in to change notification settings - Fork 83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sketch: allow a variable number of imports/exports of fixed type in Wit #172
Comments
Nice write-up! The cargo component example really makes sense and makes it easy for developers to express configs. I have a few questions regarding the later part of the write-up in this proposal:
|
This looks great @lukewagner , thanks for capturing all of this! +1 to not encoding the rule I might not be making the connection here, but how is the RHS |
Ah, I think my Cargo.toml example was confusing. Because [package.metadata.component.target]
path = "my-world.wit"
[package.metadata.component.parameters]
config = [ "a", "b", "c" ] What I wrote would make sense once type parameters were possible and I think that answers @fibonacci1729 and partially @Mossaka's question 1. Answering the rest:
|
FYI, @fibonacci1729 and I have this pretty much implemented; we'll demo it at tomorrow's Component Model meeting, then clean things up and start opening PRs. I'm sure @alexcrichton will have opinions :) bytecodealliance/wasm-tools@main...fermyon:wasm-tools:wit-templates |
I unfortunately won't be able to make the meeting tomorrow, but I look forward to digging into these bits on Monday! |
Per WebAssembly/component-model#172, this implements "part 1" of WIT templates, allowing WIT files to define interfaces which contain a single wildcard function, which worlds may import or export. I've chosen to implement the bindings for host-implemented functions in such a way that the host may delay import resolution until the latest possible moment, i.e. when the guest is actually calling the function. This allows for fully dynamic resolution (e.g. using the function name as a key to be looked up in a remote key-value store) when desired. This does come at a small performance cost compared to doing resolution at e.g. link time instead. In cases where the host wants to do resolution earlier (e.g. at deploy or instantiation time), that's certainly possible, e.g.: ```rust let component = Component::new(&engine, wasm)?; let funcs = component .names("imports") .map(|name| Ok((name.to_owned(), my_resolver(name)?))) .collect::<Result<HashMap<_, _>>>()?; struct MyImports<F> { funcs: HashMap<String, F> } impl <F: Fn() -> Result<u32>> imports::Host for MyImports<F> { fn call(&mut self, name: &str) -> Result<u32> { (self.funcs.get(name).unwrap())() } } let mut store = Store::new(&engine, MyImports { funcs }); ... ``` If we feel that early resolution is the more common case, we could consider adding a configuration option to the binding generator which indicates whether early or late resolution is desired, allowing the generator to optimize (ergonmically and performance-wise) accordingly. Note that the generated `add_to_linker` functions now take a `&Component` parameter as well as a `&mut Linker`. This allows those functions to inspect the component in order to determine how many `func_wrap{_async}` calls to make, and with what names. I'm open to alternatives to this if there's a better way. Finally, I've added a temporary dependency override to Cargo.toml, pointing to our fork of `wasm-tools`, which includes the necessary `wit-parser` changes. We're still iterating on that and will PR those changes separately. We also have a fork of `wit-bindgen` with a new "wildcards" test to verify everything works end-to-end: bytecodealliance/wit-bindgen@main...dicej:wit-templates. I'll PR that last once everything else is stable. Signed-off-by: Joel Dice <[email protected]>
First draft PR for feedback: bytecodealliance/wasmtime#5925 |
Per WebAssembly/component-model#172, this implements "part 1" of WIT templates, allowing WIT files to define interfaces which contain a single wildcard function, which worlds may import or export. I've chosen to implement the bindings for host-implemented functions in such a way that the host may delay import resolution until the latest possible moment, i.e. when the guest is actually calling the function. This allows for fully dynamic resolution (e.g. using the function name as a key to be looked up in a remote key-value store) when desired. This does come at a small performance cost compared to doing resolution at e.g. link time instead. In cases where the host wants to do resolution earlier (e.g. at deploy or instantiation time), that's certainly possible, e.g.: ```rust let component = Component::new(&engine, wasm)?; let funcs = component .names("imports") .map(|name| Ok((name.to_owned(), my_resolver(name)?))) .collect::<Result<HashMap<_, _>>>()?; struct MyImports<F> { funcs: HashMap<String, F> } impl <F: Fn() -> Result<u32>> imports::Host for MyImports<F> { fn call(&mut self, name: &str) -> Result<u32> { (self.funcs.get(name).unwrap())() } } let mut store = Store::new(&engine, MyImports { funcs }); ... ``` If we feel that early resolution is the more common case, we could consider adding a configuration option to the binding generator which indicates whether early or late resolution is desired, allowing the generator to optimize (ergonmically and performance-wise) accordingly. Note that the generated `add_to_linker` functions now take a `&Component` parameter as well as a `&mut Linker`. This allows those functions to inspect the component in order to determine how many `func_wrap{_async}` calls to make, and with what names. I'm open to alternatives to this if there's a better way. Finally, I've added a temporary dependency override to Cargo.toml, pointing to our fork of `wasm-tools`, which includes the necessary `wit-parser` changes. We're still iterating on that and will PR those changes separately. We also have a fork of `wit-bindgen` with a new "wildcards" test to verify everything works end-to-end: bytecodealliance/wit-bindgen@main...dicej:wit-templates. I'll PR that last once everything else is stable. Signed-off-by: Joel Dice <[email protected]>
templates, allowing WIT files to define interfaces which contain a single wildcard function, which worlds may import or export. I've taken a "minimalist" approach to this, such that the host binding generator just skips wildcards entirely, leaving it to the host to use dynamic APIs to reflect on the component and do the necessary func_wrap (for imports) and typed_func (for exports) calls directly. This adds two new functions to the public API: - `Component::names`: provides an iterator over the names of the functions imported by the specified instance. - `ExportInstance::funcs`: provides an iterator over the names of the functions exported by this instance. In both cases, I'm open to alternative API designs for getting that information. Note that I've added a temporary dependency override to Cargo.toml, pointing to our fork of wasm-tools, which includes the necessary wit-parser changes. We're still iterating on that and will PR those changes separately. We also have a fork of wit-bindgen with a new "wildcards" test to verify everything works end-to-end: bytecodealliance/wit-bindgen@main...dicej:wit-templates. I'll PR that last once everything else is stable. Signed-off-by: Joel Dice <[email protected]>
templates, allowing WIT files to define interfaces which contain a single wildcard function, which worlds may import or export. I've taken a "minimalist" approach to this, such that the host binding generator just skips wildcards entirely, leaving it to the host to use dynamic APIs to reflect on the component and do the necessary func_wrap (for imports) and typed_func (for exports) calls directly. This adds two new functions to the public API: - `Component::names`: provides an iterator over the names of the functions imported by the specified instance. - `ExportInstance::funcs`: provides an iterator over the names of the functions exported by this instance. In both cases, I'm open to alternative API designs for getting that information. Note that I've added a temporary dependency override to Cargo.toml, pointing to our fork of wasm-tools, which includes the necessary wit-parser changes. We're still iterating on that and will PR those changes separately. We also have a fork of wit-bindgen with a new "wildcards" test to verify everything works end-to-end: bytecodealliance/wit-bindgen@main...dicej:wit-templates. I'll PR that last once everything else is stable. Signed-off-by: Joel Dice <[email protected]>
Per WebAssembly/component-model#172, this implements "part 1" of WIT templates, allowing WIT files to define interfaces which contain a single wildcard function, which worlds may import or export. I've taken a "minimalist" approach to this, such that the host binding generator just skips wildcards entirely, leaving it to the host to use dynamic APIs to reflect on the component and do the necessary func_wrap (for imports) and typed_func (for exports) calls directly. This adds two new functions to the public API: - `Component::names`: provides an iterator over the names of the functions imported by the specified instance. - `ExportInstance::funcs`: provides an iterator over the names of the functions exported by this instance. In both cases, I'm open to alternative API designs for getting that information. Note that I've added a temporary dependency override to Cargo.toml, pointing to our fork of wasm-tools, which includes the necessary wit-parser changes. We're still iterating on that and will PR those changes separately. We also have a fork of wit-bindgen with a new "wildcards" test to verify everything works end-to-end: bytecodealliance/wit-bindgen@main...dicej:wit-templates. I'll PR that last once everything else is stable. Signed-off-by: Joel Dice <[email protected]>
Per WebAssembly/component-model#172, this implements "part 1" of WIT templates, allowing WIT files to define interfaces which contain a single wildcard function, which worlds may import or export. I've taken a "minimalist" approach to this, such that the host binding generator just skips wildcards entirely, leaving it to the host to use dynamic APIs to reflect on the component and do the necessary func_wrap (for imports) and typed_func (for exports) calls directly. This adds two new functions to the public API: - `Component::names`: provides an iterator over the names of the functions imported by the specified instance. - `ExportInstance::funcs`: provides an iterator over the names of the functions exported by this instance. In both cases, I'm open to alternative API designs for getting that information. Note that I've added a temporary dependency override to Cargo.toml, pointing to our fork of wasm-tools, which includes the necessary wit-parser changes. We're still iterating on that and will PR those changes separately. We also have a fork of wit-bindgen with a new "wildcards" test to verify everything works end-to-end: bytecodealliance/wit-bindgen@main...dicej:wit-templates. I'll PR that last once everything else is stable. Signed-off-by: Joel Dice <[email protected]>
@lukewagner Brian and I ran into what seems to be a pretty fundamental issue while implementing this. Consider this world:
Now imagine we want to build a component that targets that world, using Of course, we could split |
I just realized my example above is rejected by Also, perhaps I was incorrect in claiming there's no way to represent the component type of the "concretized" world, given that input wildcards.wit: interface foo {
*: func() -> u32
}
default world wildcards {
import imports: self.foo
export exports: self.foo
} input substitutions.toml: [wildcards]
imports = ["a", "b", "c"]
exports = ["x", "y", "z"] output concrete.wat:
The above component validates just fine, but there's no way we could convert it back into WIT, since we've given an inconsistent definition of So where does that leave us? I see two ways forward:
|
Assuming we do want to modify WIT and the CM to enable multiple instantiation of wildcard interfaces, I think it might be helpful to compare it to the existing "genericity" in WIT/CM: Analogously, I would suggest that an interface containing a wildcard is not an interface at all -- it's an interface constructor, i.e. a "function" that takes a list of names and produces an interface. And just as you can't say We can bikeshed the syntax, but for the moment let's imagine we used a similar syntax for interface constructors (and world constructors!) to what we use for type constructors:
The main virtue of this syntax is that you can see at a glance which things are worlds or interfaces (monomorphic) vs. which ones are world or interface constructors (polymorphic). And crucially for our purposes, we can invoke a given interface or world constructor multiple times without ambiguity. To be clear, I'm not necessarily advocating for that specific syntax -- just that we make the distinction between interfaces/worlds and interface/world constructors clear and unambiguous in both WIT and the CM, so we can round-trip between them losslessly -- both before and after monomorphization. This will be even more important when we introduce type holes (which are really just type parameters to interface/world constructors). |
Nice job digging into this. I think you're right that Other than the commented-out bit about the |
I generally agree with the discussion here and that these interface templates are not themselves interfaces, but I don't know that that means they need a different syntax then what was initially proposed. I think trying to enumerate all of the parameters used in templates at the beginning of the declaration will become quite verbose. If the main concern is going backwards to wit, I think the simplest answer (thought this may break/test other assumptions/constraints) would be to generate a different concrete interface definition that references the original template for each unique parameterization. In our current scope, the following 2-level example (and other k-level examples) will be possible. I'm not clear on how the explicit parameters syntax would work here without a lot of syntax. interface foo {
*: bar
}
interface bar {
*: func() -> u32
}
default world wildcards {
import imports: self.foo
export exports: self.foo
} I think we should treat nested interfaces seriously in this initial version, because they are prototypical of the kind of nesting that will arise from templated parameters, fields, etc. For nested interfaces templates and other future kinds of nesting, I'm not sure how some of the presented TOML syntaxes will work and frankly TOML isn't very good at arbitrary nesting with its constraints on e.g. inline tables. I'm interested in experimenting with what a nested template instantiation configuration might look like and considering using e.g. JSON or KDL. As a random idea: if we want to distinguish between interfaces/worlds and their templates, what about labeling them with keywords |
@Kylebrown9 AFAICT k-level support of interfaces is not supported anywhere in the CM today (except maybe the binary format?); It's not currently possible to parse/resolve or bindgen the WIT you mentioned above (see WIT.md). I'm weary of using this proposal to motivate nested interfaces (if that's what you are proposing) because i don't see the connection. However, if nested interfaces were a thing actively planned we would absolutely reflect that in the substitutions DSL. If I am misunderstanding the k-level support piece above, can you link me to any resources discussing this that I may have missed? Thanks! |
That's my mistake, I didn't realize k-level interfaces were still purely hypothetical. @lukewagner can you weigh in on the direction of interface nesting? |
Right --
I would contend that such a thing shouldn't validate since we're using the same interface URI to mean three separate things, which makes no sense given the semantics we've ascribed to these URIs. I think we could resolve this issue with two additions to the CM and WIT:
With those two things in place, we could round-trip WIT templates (and their monomorphizations) to components and back in an unambiguous way, which I believe is necessary for any implementable subset of the design. |
Good points, and sorry for the slow reply. I agree that we need to allow some way to represent wildcards in the CM to enable roundtripping wit->wat->wit. I think there's two related-but different cases to tease apart though:
In the case of 1, what we want to serialize is the wildcards, with no substitutions applied. For now, just using the illegal kebab-name In the case of 2, I think it's not necessary for us to be able to reproduce the original Wit templates: in general, compiling a component targeting a world will lose parts of the original world (e.g., unused imports). Rather, the property I think we want is that, if render the component's type as Wit, I get a world that "matches" the pattern of the original Wit templates. But that still does raise the question you're getting of what to do when a single interface template's Here's an idea. So let's say I start with this template: // w.wit
interface i {
*: func()
}
default world w {
import in: self.i
export out: self.i
} and then monomorphize (component
(import "in" "pkg:/w/w" (instance
(export "a" (func))
(export "b" (func))
))
(export "out" "pkg:/w/w" (instance
(export "x" (func))
(export "y" (func))
))
) Now, if I naively tried to create a single out-of-line
which intuitively "matches" the |
@lukewagner & @dicej that looks great! I want to suggest a use case that we have but the current design doesn't cover. In our use case, the guest consumes CRUD operations from records in the WIT file, and the host dynamically learns the different records used and provides the CRUD operations based on that. It may seems like generics, but it's templates. For example, I will be able to call the CRUD operations when I implement the run func in the example interface.
For example, it will be similar to writing:
|
@omersadika I agree that's a great use case for templates. We've also talked about this sort of idea in previous WASI meetings, with @Kylebrown9 presenting a sketch of this in a DB UDF context. I think extending Wit templates to types would make sense as a second step after what's proposed above, since it's rather more involved, but I think we should definitely do it. |
Per WebAssembly/component-model#172, this implements "part 1" of WIT templates, allowing WIT files to define interfaces which contain a single wildcard function, which worlds may import or export. I've taken a "minimalist" approach to this, such that the host binding generator just skips wildcards entirely, leaving it to the host to use dynamic APIs to reflect on the component and do the necessary func_wrap (for imports) and typed_func (for exports) calls directly. This adds two new functions to the public API: - `Component::names`: provides an iterator over the names of the functions imported by the specified instance. - `ExportInstance::funcs`: provides an iterator over the names of the functions exported by this instance. In both cases, I'm open to alternative API designs for getting that information. Note that I've added a temporary dependency override to Cargo.toml, pointing to our fork of wasm-tools, which includes the necessary wit-parser changes. We're still iterating on that and will PR those changes separately. We also have a fork of wit-bindgen with a new "wildcards" test to verify everything works end-to-end: bytecodealliance/wit-bindgen@main...dicej:wit-templates. I'll PR that last once everything else is stable. Signed-off-by: Joel Dice <[email protected]> generate code for wildcards in `bindgen!` This improves upon the raw `func_wrap` and `typed_func` APIs by providing static type checking. Signed-off-by: Joel Dice <[email protected]> address review feedback - Add TODO comment to `Component::function_import_names` (renamed from `names`) - Add `Component::function_export_names` for symmetry (since an embedder may want both sets of names) - Add a couple of codegen tests for the bindgen macro - Use `Func` instead of `TypedFunc` for `__wildcard_matches` to avoid lifetime issues - Update `wit-parser` and use the new `Interface::wildcard` field where applicable - Add `name: &str` parameter to `WildcardMatch::call` trait function Signed-off-by: Joel Dice <[email protected]>
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]>
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 <[email protected]>
Per WebAssembly/component-model#172, this implements "part 1" of WIT templates, allowing WIT files to define interfaces which contain a single wildcard function, which worlds may import or export. I've taken a "minimalist" approach to this, such that the host binding generator just skips wildcards entirely, leaving it to the host to use dynamic APIs to reflect on the component and do the necessary func_wrap (for imports) and typed_func (for exports) calls directly. This adds two new functions to the public API: - `Component::names`: provides an iterator over the names of the functions imported by the specified instance. - `ExportInstance::funcs`: provides an iterator over the names of the functions exported by this instance. In both cases, I'm open to alternative API designs for getting that information. Note that I've added a temporary dependency override to Cargo.toml, pointing to our fork of wasm-tools, which includes the necessary wit-parser changes. We're still iterating on that and will PR those changes separately. We also have a fork of wit-bindgen with a new "wildcards" test to verify everything works end-to-end: bytecodealliance/wit-bindgen@main...dicej:wit-templates. I'll PR that last once everything else is stable. Signed-off-by: Joel Dice <[email protected]> generate code for wildcards in `bindgen!` This improves upon the raw `func_wrap` and `typed_func` APIs by providing static type checking. Signed-off-by: Joel Dice <[email protected]> address review feedback - Add TODO comment to `Component::function_import_names` (renamed from `names`) - Add `Component::function_export_names` for symmetry (since an embedder may want both sets of names) - Add a couple of codegen tests for the bindgen macro - Use `Func` instead of `TypedFunc` for `__wildcard_matches` to avoid lifetime issues - Update `wit-parser` and use the new `Interface::wildcard` field where applicable - Add `name: &str` parameter to `WildcardMatch::call` trait function Signed-off-by: Joel Dice <[email protected]>
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]>
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 <[email protected]>
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 <[email protected]>
Per WebAssembly/component-model#172, this implements "part 1" of WIT templates, allowing WIT files to define interfaces which contain a single wildcard function, which worlds may import or export. I've taken a "minimalist" approach to this, such that the host binding generator just skips wildcards entirely, leaving it to the host to use dynamic APIs to reflect on the component and do the necessary func_wrap (for imports) and typed_func (for exports) calls directly. This adds two new functions to the public API: - `Component::names`: provides an iterator over the names of the functions imported by the specified instance. - `ExportInstance::funcs`: provides an iterator over the names of the functions exported by this instance. In both cases, I'm open to alternative API designs for getting that information. Note that I've added a temporary dependency override to Cargo.toml, pointing to our fork of wasm-tools, which includes the necessary wit-parser changes. We're still iterating on that and will PR those changes separately. We also have a fork of wit-bindgen with a new "wildcards" test to verify everything works end-to-end: bytecodealliance/wit-bindgen@main...dicej:wit-templates. I'll PR that last once everything else is stable. Signed-off-by: Joel Dice <[email protected]> generate code for wildcards in `bindgen!` This improves upon the raw `func_wrap` and `typed_func` APIs by providing static type checking. Signed-off-by: Joel Dice <[email protected]> address review feedback - Add TODO comment to `Component::function_import_names` (renamed from `names`) - Add `Component::function_export_names` for symmetry (since an embedder may want both sets of names) - Add a couple of codegen tests for the bindgen macro - Use `Func` instead of `TypedFunc` for `__wildcard_matches` to avoid lifetime issues - Update `wit-parser` and use the new `Interface::wildcard` field where applicable - Add `name: &str` parameter to `WildcardMatch::call` trait function Signed-off-by: Joel Dice <[email protected]>
I went ahead and created draft PRs containing a minimum viable implementation: I like @lukewagner 's idea for specifying ids for otherwise-anonymous interfaces. For now, though, our implementation only clones the wildcard interface as an anonymous interface prior to expanding the wildcard, which is enough to get an end-to-end test working. |
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]>
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 <[email protected]>
Per WebAssembly/component-model#172, this implements "part 1" of WIT templates, allowing WIT files to define interfaces which contain a single wildcard function, which worlds may import or export. I've taken a "minimalist" approach to this, such that the host binding generator just skips wildcards entirely, leaving it to the host to use dynamic APIs to reflect on the component and do the necessary func_wrap (for imports) and typed_func (for exports) calls directly. This adds two new functions to the public API: - `Component::names`: provides an iterator over the names of the functions imported by the specified instance. - `ExportInstance::funcs`: provides an iterator over the names of the functions exported by this instance. In both cases, I'm open to alternative API designs for getting that information. Note that I've added a temporary dependency override to Cargo.toml, pointing to our fork of wasm-tools, which includes the necessary wit-parser changes. We're still iterating on that and will PR those changes separately. We also have a fork of wit-bindgen with a new "wildcards" test to verify everything works end-to-end: bytecodealliance/wit-bindgen@main...dicej:wit-templates. I'll PR that last once everything else is stable. Signed-off-by: Joel Dice <[email protected]> generate code for wildcards in `bindgen!` This improves upon the raw `func_wrap` and `typed_func` APIs by providing static type checking. Signed-off-by: Joel Dice <[email protected]> address review feedback - Add TODO comment to `Component::function_import_names` (renamed from `names`) - Add `Component::function_export_names` for symmetry (since an embedder may want both sets of names) - Add a couple of codegen tests for the bindgen macro - Use `Func` instead of `TypedFunc` for `__wildcard_matches` to avoid lifetime issues - Update `wit-parser` and use the new `Interface::wildcard` field where applicable - Add `name: &str` parameter to `WildcardMatch::call` trait function Signed-off-by: Joel Dice <[email protected]>
Per WebAssembly/component-model#172, this implements "part 1" of WIT templates, allowing WIT files to define interfaces which contain a single wildcard function, which worlds may import or export. I've taken a "minimalist" approach to this, such that the host binding generator just skips wildcards entirely, leaving it to the host to use dynamic APIs to reflect on the component and do the necessary func_wrap (for imports) and typed_func (for exports) calls directly. This adds two new functions to the public API: - `Component::names`: provides an iterator over the names of the functions imported by the specified instance. - `ExportInstance::funcs`: provides an iterator over the names of the functions exported by this instance. In both cases, I'm open to alternative API designs for getting that information. Note that I've added a temporary dependency override to Cargo.toml, pointing to our fork of wasm-tools, which includes the necessary wit-parser changes. We're still iterating on that and will PR those changes separately. We also have a fork of wit-bindgen with a new "wildcards" test to verify everything works end-to-end: bytecodealliance/wit-bindgen@main...dicej:wit-templates. I'll PR that last once everything else is stable. Signed-off-by: Joel Dice <[email protected]> generate code for wildcards in `bindgen!` This improves upon the raw `func_wrap` and `typed_func` APIs by providing static type checking. Signed-off-by: Joel Dice <[email protected]> address review feedback - Add TODO comment to `Component::function_import_names` (renamed from `names`) - Add `Component::function_export_names` for symmetry (since an embedder may want both sets of names) - Add a couple of codegen tests for the bindgen macro - Use `Func` instead of `TypedFunc` for `__wildcard_matches` to avoid lifetime issues - Update `wit-parser` and use the new `Interface::wildcard` field where applicable - Add `name: &str` parameter to `WildcardMatch::call` trait function Signed-off-by: Joel Dice <[email protected]>
Does this mean reading some information from an additional toml file? I find this inconvenient. Is there a solution that is compatible with inline packages(#313), where one file contains all the necessary information? |
No, the toml file was just one option for specific use cases where it lines up with what you want to say. But there are lots of other producer toolchain options or WIT features that could be used to fill in the |
Motivation
Let's say I'd like to build a component that consumes 3 configuration values
a
,b
andc
(which in a 12-factor app I'd take as 3 environment variables). I could define a component with type:However, this loses the fact that my component specifically wants
a
,b
andc
. If I'm using or deploying this component, I have to learn about these values from the docs or observe the behavior ofget-config
at runtime. If I make a mistake, the error will only show up at runtime.If instead I give my component this type:
then there's a lot more declarative information that a host can leverage to provide a better developer experience. E.g., at deployment time, the host can check that there are indeed configuration values
a
,b
andc
available and give a deployment-time error if not. There are also new optimizations made available at runtime:a
,b
andc
can be held in a dense array accessed by internally-computed static index, thereby avoiding the hash-table lookups at runtime (which can really matter when the target instantiation-time is in microseconds).This example is in the domain of configuration, but you can also find analogous examples in:
(func (param "increment" u32))
import per metric)wasi:http/outgoing-handler
per upstream.In all these cases, the workaround for supporting multiple instances of the same interface inevitably involves using a dynamic
string
parameter which loses the otherwise-declarative dependency information. (To be clear, some use cases do really need a runtime-dynamicstring
name; we're talking here about the cases where the string would otherwise be a magic constant in the code.)So given that we'd like to write components with the above type, how do we capture all these varying types in Wit? Of course we can write the Wit for any single component; for example, a Wit
world
that supports the above component is:The challenge is writing a single
world
that captures this component and all the other components, each with their own varying set of configuration values.The proposal is to allow Wit to express not just a single
interface
orworld
, but a family ofinterface
s/world
s produced by substituting various parameters. Concretely, for the above I'd like to write (roughly; the exact syntax here is open for debate, of course):Using this, it would be natural for WASI standards to use
*
in standardized interfaces such as:which would then allow
my-world
to be rewritten as:and leverage standard implementations of
wasi:config/values
.Sketch
While in general I think we'll want to allow putting parameters everywhere in Wit (types, names, parameters, results, fields, cases, imports, exports, ...), as a first step, I'd like to keep things scoped to just the case I showed above where a
*
can show up in the name of the lone field of aninterface
. While this won't be easy (there are a number of interesting producer/consumer design questions to work out), I think it'll be much easier than parameters in types, and so a good starting point. For terminology, I've been calling the general feature "Wit templates", and I think this first milestone could be called "variable imports and exports", but open to hearing alternatives.From a Wit grammar perspective, the addition is fairly tiny, defining a:
and then using
variable-id
infunc-item
andtypedef-item
. As an additional validation-time constraint,*
would only be allowed inside aninterface
block as the only item. (Or we could capture this constraint in the grammar; but I think we'll want to loosen this constraint over time.)To allow encoding Wit documents as
.wasm
binaries, we'll also need to extend Binary.md to support*
in names. One thing to be clear about here is that a concrete component won't be allowed to have any*
s in its imports/exports; only Wit documents. In a far-post-MVP future, one could imagine generalizing components with a staged-compilation model that did allow components to talk about*
names, so while we don't need to design how that all would work, it would be good to pick an encoding of*
that could be retconned into this far-post-MVP future if needed.Significant design work will be needed per-language-toolchain around how to allow the developer to fill in the
*
s in the world they are targeting. Riffing on an idea from @dicej and @fibonacci1729, this could come from buildconfig, with each line adding a field. E.g., in the context of cargo component, I could write:which would declare 3 imported configuration values
a
,b
andc
, looking ahead to a future milestone where non-string
types could be allowed. More design work is probably necessary here to think through how to express all the not-so-simple cases. But the idea is that, from this language-specific buildconfig, tooling could derive a language-agnostic substitution which would then be applied to the target world to produce a "monomorphized" world with no*
s that is fed into bindings generation, keeping the rest of the build pipeline working like normal.This is just a sketch, and more work is needed to flesh out the design, but I thought it'd be useful to put up this much now since the interface design questions above are coming up in a number of places at the moment. @dicej and @fibonacci1729 have also done a lot of thinking about this, so I'd invite them to drop in whatever they're thinking too.
The text was updated successfully, but these errors were encountered: