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
8 changes: 8 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ members = [
"crates/test-rust-wasm",
"crates/wit-bindgen-demo",
"crates/wit-component",
"crates/wasi_snapshot_preview1",
]
resolver = "2"

Expand Down
27 changes: 19 additions & 8 deletions crates/test-helpers/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,33 @@ fn main() {

let mut wasms = Vec::new();

// Build the `wasi_snapshot_preview1.wasm` adapter which is used to convert
// core wasm modules below into components via `wit-component`.
let mut cmd = Command::new("cargo");
cmd.arg("build")
.arg("--release")
.current_dir("../wasi_snapshot_preview1")
.arg("--target=wasm32-unknown-unknown")
.env("CARGO_TARGET_DIR", &out_dir)
.env("RUSTFLAGS", "-Clink-args=--import-memory")
.env_remove("CARGO_ENCODED_RUSTFLAGS");
let status = cmd.status().unwrap();
assert!(status.success());
println!("cargo:rerun-if-changed=../wasi_snapshot_preview1");
let wasi_adapter = out_dir.join("wasm32-unknown-unknown/release/wasi_snapshot_preview1.wasm");

if cfg!(feature = "guest-rust") {
let mut cmd = Command::new("cargo");
cmd.arg("build")
.current_dir("../test-rust-wasm")
// TODO: this should go back to wasm32-wasi once we have an adapter
// for snapshot 1 to a component
.arg("--target=wasm32-unknown-unknown")
.arg("--target=wasm32-wasi")
.env("CARGO_TARGET_DIR", &out_dir)
.env("CARGO_PROFILE_DEV_DEBUG", "1")
.env("RUSTFLAGS", "-Clink-args=--export-table")
.env_remove("CARGO_ENCODED_RUSTFLAGS");
let status = cmd.status().unwrap();
assert!(status.success());
for file in out_dir
.join("wasm32-unknown-unknown/debug")
.read_dir()
.unwrap()
{
for file in out_dir.join("wasm32-wasi/debug").read_dir().unwrap() {
let file = file.unwrap().path();
if file.extension().and_then(|s| s.to_str()) != Some("wasm") {
continue;
Expand All @@ -46,6 +55,8 @@ fn main() {
.module(module.as_slice())
.expect("pull custom sections from module")
.validate(true)
.adapter_file(&wasi_adapter)
.expect("adapter failed to get loaded")
.encode()
.expect("module can be translated to a component");

Expand Down
12 changes: 12 additions & 0 deletions crates/wasi_snapshot_preview1/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "wasi_snapshot_preview1"
version = "0.0.0"
edition.workspace = true

[dependencies]
wasi = "0.11.0"
wit-bindgen-guest-rust = { workspace = true }

[lib]
crate-type = ["cdylib"]
test = false
44 changes: 44 additions & 0 deletions crates/wasi_snapshot_preview1/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//! This module is intended to be an "adapter module" fed into `wit-component`
//! to translate the `wasi_snapshot_preview1` ABI into an ABI that uses the
//! component model. This library is compiled as a standalone wasm file and is
//! used to implement `wasi_snapshot_preview1` interfaces required by the tests
//! throughout the `wit-bindgen` repository.
//!
//! This is not intended to be a comprehensive polyfill. Instead this is just
//! the bare bones necessary to get `wit-bindgen` itself and its tests working.
//!
//! Currently all functions are trapping stubs since nothing actually runs the
//! output component just yet. These stubs should get filled in as necessary
//! once hosts start running components. The current assumption is that the
//! imports will be adapted to a custom `wit-bindgen`-specific host `*.wit` file
//! which is only suitable for `wit-bindgen` tests.

#![allow(unused_variables)]

use std::arch::wasm32::unreachable;
use wasi::*;

#[no_mangle]
pub extern "C" fn environ_get(environ: *mut *mut u8, environ_buf: *mut u8) -> Errno {
unreachable()
}

#[no_mangle]
pub extern "C" fn environ_sizes_get(environc: *mut Size, environ_buf_size: *mut Size) -> Errno {
unreachable()
}

#[no_mangle]
pub extern "C" fn fd_write(
fd: Fd,
iovs_ptr: *const Ciovec,
iovs_len: usize,
nwritten: *mut Size,
) -> Errno {
unreachable()
}

#[no_mangle]
pub extern "C" fn proc_exit(rval: Exitcode) -> ! {
unreachable()
}
20 changes: 16 additions & 4 deletions crates/wit-component/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#![deny(missing_docs)]

use crate::extract::{extract_module_interfaces, ModuleInterfaces};
use crate::{
decode_interface_component, ComponentEncoder, InterfaceEncoder, InterfacePrinter,
StringEncoding,
Expand Down Expand Up @@ -61,10 +62,21 @@ fn parse_adapter(s: &str) -> Result<(String, Vec<u8>, Interface)> {
Ok((name.to_string(), wasm, interface))
}
None => {
// TODO: implement inferring the `interface` from the `wasm`
// specified
drop((name, wasm));
bail!("inferring from the core wasm module is not supported at this time")
let ModuleInterfaces {
mut imports,
exports,
wasm,
interface,
} = extract_module_interfaces(&wasm)?;
if exports.len() > 0 || interface.is_some() {
bail!("adapter modules cannot have an exported interface");
}
let import = match imports.len() {
0 => Interface::default(),
1 => imports.remove(0),
_ => bail!("adapter modules can only import one interface at this time"),
};
Ok((name.to_string(), wasm, import))
}
}
}
Expand Down
108 changes: 61 additions & 47 deletions crates/wit-component/src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@
//! otherwise there's no way to run a `wasi_snapshot_preview1` module within the
//! component model.

use crate::extract::{extract_module_interfaces, ModuleInterfaces};
use crate::{
decode_interface_component,
validation::{
expected_export_name, validate_adapter_module, validate_module, ValidatedAdapter,
ValidatedModule,
Expand All @@ -64,6 +64,7 @@ use anyhow::{anyhow, bail, Context, Result};
use indexmap::{map::Entry, IndexMap, IndexSet};
use std::hash::{Hash, Hasher};
use std::mem;
use std::path::Path;
use wasm_encoder::*;
use wasmparser::{Validator, WasmFeatures};
use wit_parser::{
Expand Down Expand Up @@ -2095,55 +2096,32 @@ impl<'a> ImportEncoder<'a> {

/// An encoder of components based on `wit` interface definitions.
#[derive(Default)]
pub struct ComponentEncoder<'a> {
module: &'a [u8],
pub struct ComponentEncoder {
module: Vec<u8>,
encoding: StringEncoding,
interface: Option<Interface>,
imports: Vec<Interface>,
exports: Vec<Interface>,
validate: bool,
types_only: bool,
adapters: IndexMap<&'a str, (&'a [u8], &'a Interface)>,
adapters: IndexMap<String, (Vec<u8>, Interface)>,
}

impl<'a> ComponentEncoder<'a> {
impl ComponentEncoder {
/// Set the core module to encode as a component.
/// This method will also parse any component type information stored in custom sections
/// inside the module, and add them as the interface, imports, and exports.
pub fn module(mut self, module: &'a [u8]) -> Result<Self> {
for payload in wasmparser::Parser::new(0).parse_all(&module) {
match payload.context("decoding item in module")? {
wasmparser::Payload::CustomSection(cs) => {
if let Some(export) = cs.name().strip_prefix("component-type:export:") {
let mut i = decode_interface_component(cs.data()).with_context(|| {
format!("decoding component-type in export section {}", export)
})?;
i.name = export.to_owned();
self.interface = Some(i);
} else if let Some(import) = cs.name().strip_prefix("component-type:import:") {
let mut i = decode_interface_component(cs.data()).with_context(|| {
format!("decoding component-type in import section {}", import)
})?;
i.name = import.to_owned();
self.imports.push(i);
} else if let Some(export_instance) =
cs.name().strip_prefix("component-type:export-instance:")
{
let mut i = decode_interface_component(cs.data()).with_context(|| {
format!(
"decoding component-type in export-instance section {}",
export_instance
)
})?;
i.name = export_instance.to_owned();
self.exports.push(i);
}
}
_ => {}
}
}

self.module = module;
pub fn module(mut self, module: &[u8]) -> Result<Self> {
let ModuleInterfaces {
imports,
exports,
wasm,
interface,
} = extract_module_interfaces(module)?;
self.interface = interface;
self.imports.extend(imports);
self.exports.extend(exports);
self.module = wasm;
Ok(self)
}

Expand All @@ -2160,21 +2138,21 @@ impl<'a> ComponentEncoder<'a> {
}

/// Set the default interface exported by the component.
pub fn interface(mut self, interface: &'a Interface) -> Self {
pub fn interface(mut self, interface: &Interface) -> Self {
self.interface = Some(interface.clone());
self
}

/// Set the interfaces the component imports.
pub fn imports(mut self, imports: &'a [Interface]) -> Self {
pub fn imports(mut self, imports: &[Interface]) -> Self {
for i in imports {
self.imports.push(i.clone())
}
self
}

/// Set the interfaces the component exports.
pub fn exports(mut self, exports: &'a [Interface]) -> Self {
pub fn exports(mut self, exports: &[Interface]) -> Self {
for e in exports {
self.exports.push(e.clone())
}
Expand All @@ -2198,17 +2176,53 @@ impl<'a> ComponentEncoder<'a> {
/// wasm module specified by `bytes` imports. The `bytes` will then import
/// `interface` and export functions to get imported from the module `name`
/// in the core wasm that's being wrapped.
pub fn adapter(mut self, name: &'a str, bytes: &'a [u8], interface: &'a Interface) -> Self {
self.adapters.insert(name, (bytes, interface));
pub fn adapter(mut self, name: &str, bytes: &[u8], interface: &Interface) -> Self {
self.adapters
.insert(name.to_string(), (bytes.to_vec(), interface.clone()));
self
}

/// This is a convenience method for [`ComponentEncoder::adapter`] for
/// inferring everything from just one `path` specified.
///
/// The wasm binary at `path` is read and parsed and is assumed to have
/// embedded information about its imported interfaces. Additionally the
/// name of the adapter is inferred from the file name itself as the file
/// stem.
pub fn adapter_file(mut self, path: &Path) -> Result<Self> {
let name = path
.file_stem()
.and_then(|s| s.to_str())
.ok_or_else(|| anyhow!("input filename was not valid utf-8"))?;
let wasm = wat::parse_file(path)?;
let ModuleInterfaces {
mut imports,
exports,
wasm,
interface,
} = extract_module_interfaces(&wasm)?;
if exports.len() > 0 || interface.is_some() {
bail!("adapter modules cannot have an exported interface");
}
let import = match imports.len() {
0 => Interface::default(),
1 => imports.remove(0),
_ => bail!("adapter modules can only import one interface at this time"),
};
self.adapters.insert(name.to_string(), (wasm, import));
Ok(self)
}

/// Encode the component and return the bytes.
pub fn encode(&self) -> Result<Vec<u8>> {
let info = if !self.module.is_empty() {
let adapters = self.adapters.keys().copied().collect::<IndexSet<_>>();
let adapters = self
.adapters
.keys()
.map(|s| s.as_str())
.collect::<IndexSet<_>>();
validate_module(
self.module,
&self.module,
&self.interface,
&self.imports,
&self.exports,
Expand Down Expand Up @@ -2257,7 +2271,7 @@ impl<'a> ComponentEncoder<'a> {
types.finish(&mut state.component);

state.encode_imports(&imports);
state.encode_core_module(self.module);
state.encode_core_module(&self.module);
state.encode_core_instantiation(self.encoding, &imports, &info)?;
state.encode_exports(self.encoding, exports, &types.func_type_map)?;
}
Expand Down
Loading