Skip to content

Integrate worlds into the Rust & JS generators #381

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

Merged
merged 6 commits into from
Oct 18, 2022
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
31 changes: 13 additions & 18 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ members = [
"crates/wit-bindgen-demo",
"crates/wit-component",
"crates/wasi_snapshot_preview1",
"crates/wasi_snapshot_preview1/host-wasmtime-rust",
]
resolver = "2"

Expand Down Expand Up @@ -46,7 +45,8 @@ wit-bindgen-gen-rust-lib = { path = 'crates/gen-rust-lib', version = '0.3.0' }
wit-bindgen-guest-rust = { path = 'crates/guest-rust', version = '0.3.0' }
wit-bindgen-host-wasmtime-rust = { path = 'crates/host-wasmtime-rust', version = '0.3.0' }
wit-parser = { path = 'crates/wit-parser', version = '0.3.0' }
wit-component = { path = 'crates/wit-component', version = '0.3.0' }
wit-component = { path = 'crates/wit-component', version = '0.3.0', default-features = false }
wit-bindgen-rust-macro-shared = { path = 'crates/rust-macro-shared', version = '0.3.0' }

[[bin]]
name = "wit-bindgen"
Expand All @@ -64,3 +64,4 @@ wit-bindgen-gen-guest-c = { path = 'crates/gen-guest-c', features = ['clap'] }
wit-bindgen-gen-markdown = { path = 'crates/gen-markdown', features = ['clap'] }
wit-bindgen-gen-guest-teavm-java = { path = 'crates/gen-guest-teavm-java', features = ['clap'] }
wat = { workspace = true }
wit-component = { workspace = true }
6 changes: 6 additions & 0 deletions crates/bindgen-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@ doctest = false
[dependencies]
wit-parser = { workspace = true }
anyhow = { workspace = true }
wit-component = { workspace = true }
wasmtime-environ = { workspace = true, features = ['component-model'], optional = true }
wasmparser = { workspace = true, optional = true }

[features]
component-generator = ['dep:wasmtime-environ', 'dep:wasmparser']
104 changes: 104 additions & 0 deletions crates/bindgen-core/src/component.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//! Support to generate bindings for a host for a single component.
//!
//! This is currently used by the JS host generator and is planned to be used
//! for the Python host generator as well. This module is conditionally defined
//! since it depends on a few somewhat-heavyweight dependencies.
//!
//! The main definition here is the `ComponentGenerator` trait as well as the
//! `generate` function.

use crate::{Files, WorldGenerator};
use anyhow::{Context, Result};
use wasmparser::{Validator, WasmFeatures};
use wasmtime_environ::component::{
Component, ComponentTypesBuilder, StaticModuleIndex, Translator,
};
use wasmtime_environ::{ModuleTranslation, PrimaryMap, ScopeVec, Tunables};
use wit_component::ComponentInterfaces;

/// Generate bindings to load and instantiate the specific binary component
/// provided.
pub fn generate(
gen: &mut dyn ComponentGenerator,
name: &str,
binary: &[u8],
files: &mut Files,
) -> Result<()> {
// Use the `wit-component` crate here to parse `binary` and discover
// the type-level descriptions and `Interface`s corresponding to the
// component binary. This is effectively a step that infers a "world" of
// a component. Right now `interfaces` is a world-like thing and this
// will likely change as worlds are iterated on in the component model
// standard. Regardless though this is the step where types are learned
// and `Interface`s are constructed for further code generation below.
let interfaces = wit_component::decode_interface_component(binary)
.context("failed to extract interface information from component")?;

// Components are complicated, there's no real way around that. To
// handle all the work of parsing a component and figuring out how to
// instantiate core wasm modules and such all the work is offloaded to
// Wasmtime itself. This crate generator is based on Wasmtime's
// low-level `wasmtime-environ` crate which is technically not a public
// dependency but the same author who worked on that in Wasmtime wrote
// this as well so... "seems fine".
//
// Note that we're not pulling in the entire Wasmtime engine here,
// moreso just the "spine" of validating a component. This enables using
// Wasmtime's internal `Component` representation as a much easier to
// process version of a component that has decompiled everything
// internal to a component to a straight linear list of initializers
// that need to be executed to instantiate a component.
let scope = ScopeVec::new();
let tunables = Tunables::default();
let mut types = ComponentTypesBuilder::default();
let mut validator = Validator::new_with_features(WasmFeatures {
component_model: true,
..WasmFeatures::default()
});
let (component, modules) = Translator::new(&tunables, &mut validator, &mut types, &scope)
.translate(binary)
.context("failed to parse the input component")?;

// Insert all core wasm modules into the generated `Files` which will
// end up getting used in the `generate_instantiate` method.
for (i, module) in modules.iter() {
let i = i.as_u32();
let name = format!("module{i}.wasm");
files.push(&name, module.wasm);
}

// With all that prep work delegate to `WorldGenerator::generate` here
// to generate all the type-level descriptions for this component now
// that the interfaces in/out are understood.
gen.generate(name, &interfaces, files);

// And finally generate the code necessary to instantiate the given
// component to this method using the `Component` that
// `wasmtime-environ` parsed.
gen.instantiate(name, &component, &modules, &interfaces);

gen.finish_component(name, files);

Ok(())
}

/// Trait for hosts that can execute a component by generating bindings for a
/// single component.
///
/// This trait inherits from `WorldGenerator` to describe type-level bindings
/// for the host in question. This then additionally defines an `instantiate`
/// method which will generate code to perform the precise instantiation for
/// the component specified.
///
/// This trait is used in conjunction with the [`generate`] method.
pub trait ComponentGenerator: WorldGenerator {
fn instantiate(
&mut self,
name: &str,
component: &Component,
modules: &PrimaryMap<StaticModuleIndex, ModuleTranslation<'_>>,
interfaces: &ComponentInterfaces,
);

fn finish_component(&mut self, name: &str, files: &mut Files);
}
24 changes: 24 additions & 0 deletions crates/bindgen-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ use std::collections::{btree_map::Entry, BTreeMap, HashMap};
use std::fmt::{self, Write};
use std::ops::Deref;
use std::path::Path;
use wit_component::ComponentInterfaces;
use wit_parser::*;

pub use wit_parser;
mod ns;

pub use ns::Ns;

#[cfg(feature = "component-generator")]
pub mod component;

/// This is the direction from the user's perspective. Are we importing
/// functions to call, or defining functions and exporting them to be called?
///
Expand Down Expand Up @@ -531,6 +535,26 @@ mod tests {
}
}

pub trait WorldGenerator {
fn generate(&mut self, name: &str, interfaces: &ComponentInterfaces, files: &mut Files) {
for (name, import) in interfaces.imports.iter() {
self.import(name, import, files);
}
for (name, export) in interfaces.exports.iter() {
self.export(name, export, files);
}
if let Some(iface) = &interfaces.default {
self.export_default(name, iface, files);
}
self.finish(name, interfaces, files);
}

fn import(&mut self, name: &str, iface: &Interface, files: &mut Files);
fn export(&mut self, name: &str, iface: &Interface, files: &mut Files);
fn export_default(&mut self, name: &str, iface: &Interface, files: &mut Files);
fn finish(&mut self, name: &str, interfaces: &ComponentInterfaces, files: &mut Files);
}

/// This is a possible replacement for the `Generator` trait above, currently
/// only used by the JS bindings for generating bindings for a component.
///
Expand Down
3 changes: 1 addition & 2 deletions crates/gen-guest-rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,4 @@ clap = { workspace = true, optional = true }

[dev-dependencies]
wit-bindgen-guest-rust = { path = '../guest-rust' }
guest-rust-test-macro = { path = 'test-macro' }
test-helpers = { path = '../test-helpers', default-features = false }
test-helpers = { path = '../test-helpers', default-features = false, features = ['macros'] }
Loading