Skip to content

Commit

Permalink
add --component-type and --string-encoding options (#32)
Browse files Browse the repository at this point in the history
* add `--component-type` and `--string-encoding` options

Per bytecodealliance/wit-bindgen#994, this allows all
or part of the component type to be specified as one or more WIT files rather
than binary-encoded custom sections.  This is akin to inserting a `wasm-tools
component embed` command between the linking and componentization stages.

Signed-off-by: Joel Dice <[email protected]>

* use Resolve::select_world to select world to merge

Signed-off-by: Joel Dice <[email protected]>

* add `component_type_wit_file` test

Signed-off-by: Joel Dice <[email protected]>

* use result of Resolve::push_path when calling select_world

Signed-off-by: Joel Dice <[email protected]>

---------

Signed-off-by: Joel Dice <[email protected]>
  • Loading branch information
dicej authored Jul 15, 2024
1 parent fba6fbd commit 4a5ffa9
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 3 deletions.
1 change: 1 addition & 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 @@ -30,3 +30,4 @@ tempfile = "3.10.0"
wasmparser = "0.213.0"
wat = "1.213.0"
wit-component = "0.213.0"
wit-parser = "0.213.0"
65 changes: 62 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use std::path::{Path, PathBuf};
use std::process::Command;
use std::str::FromStr;
use wasmparser::Payload;
use wit_component::StringEncoding;
use wit_parser::{Resolve, WorldId};

/// Representation of a flag passed to `wasm-ld`
///
Expand Down Expand Up @@ -263,6 +265,21 @@ struct ComponentLdArgs {
/// Adapters to use when creating the final component.
#[clap(long = "adapt", value_name = "[NAME=]MODULE", value_parser = parse_adapter)]
adapters: Vec<(String, Vec<u8>)>,

/// WIT file representing additional component type information to use.
///
/// May be specified more than once.
///
/// See also the `--string-encoding` option.
#[clap(long = "component-type", value_name = "WIT_FILE")]
component_types: Vec<PathBuf>,

/// String encoding to use when creating the final component.
///
/// This may be either "utf8", "utf16", or "compact-utf16". This value is
/// only used when one or more `--component-type` options are specified.
#[clap(long, value_parser = parse_encoding, default_value = "utf8")]
string_encoding: StringEncoding,
}

fn parse_adapter(s: &str) -> Result<(String, Vec<u8>)> {
Expand All @@ -271,6 +288,15 @@ fn parse_adapter(s: &str) -> Result<(String, Vec<u8>)> {
Ok((name.to_string(), wasm))
}

fn parse_encoding(s: &str) -> Result<StringEncoding> {
Ok(match s {
"utf8" => StringEncoding::UTF8,
"utf16" => StringEncoding::UTF16,
"compact-utf16" => StringEncoding::CompactUTF16,
_ => bail!("unknown string encoding: {s:?}"),
})
}

fn parse_optionally_name_file(s: &str) -> (&str, &str) {
let mut parts = s.splitn(2, '=');
let name_or_path = parts.next().unwrap();
Expand Down Expand Up @@ -462,8 +488,11 @@ impl App {
component_ld_args.push(format!("--{c}").into());
if let Some(arg) = command.get_arguments().find(|a| a.get_long() == Some(c))
{
if let ArgAction::Set = arg.get_action() {
component_ld_args.push(parser.value()?);
match arg.get_action() {
ArgAction::Set | ArgAction::Append => {
component_ld_args.push(parser.value()?)
}
_ => (),
}
}
}
Expand Down Expand Up @@ -521,7 +550,7 @@ impl App {
let reactor_adapter = include_bytes!("wasi_snapshot_preview1.reactor.wasm");
let command_adapter = include_bytes!("wasi_snapshot_preview1.command.wasm");
let proxy_adapter = include_bytes!("wasi_snapshot_preview1.proxy.wasm");
let core_module = std::fs::read(lld_output.path())
let mut core_module = std::fs::read(lld_output.path())
.with_context(|| format!("failed to read {linker:?} output"))?;

// Inspect the output module to see if it's a command or reactor.
Expand All @@ -542,6 +571,36 @@ impl App {
}
}

if !self.component.component_types.is_empty() {
let mut merged = None::<(Resolve, WorldId)>;
for wit_file in &self.component.component_types {
let mut resolve = Resolve::default();
let (packages, _) = resolve
.push_path(wit_file)
.with_context(|| format!("unable to add component type {wit_file:?}"))?;

let world = resolve.select_world(&packages, None)?;

if let Some((merged_resolve, merged_world)) = &mut merged {
let world = merged_resolve.merge(resolve)?.map_world(world, None)?;
merged_resolve.merge_worlds(world, *merged_world)?;
} else {
merged = Some((resolve, world));
}
}

let Some((resolve, world)) = merged else {
unreachable!()
};

wit_component::embed_component_metadata(
&mut core_module,
&resolve,
world,
self.component.string_encoding,
)?;
}

let mut encoder = wit_component::ComponentEncoder::default()
.module(&core_module)
.context("failed to parse core wasm for componentization")?
Expand Down
60 changes: 60 additions & 0 deletions tests/all.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
use std::env;
use std::fs;
use std::io::{Read, Write};
use std::process::{Command, Stdio};

fn compile(args: &[&str], src: &str) -> Vec<u8> {
compile_with_files(args, src, &[])
}

fn compile_with_files(args: &[&str], src: &str, files: &[(&str, &str)]) -> Vec<u8> {
let tempdir = tempfile::TempDir::new().unwrap();

for (name, content) in files {
fs::write(tempdir.path().join(name), content.as_bytes()).unwrap();
}

let mut myself = env::current_exe().unwrap();
myself.pop(); // exe name
myself.pop(); // 'deps'
Expand Down Expand Up @@ -117,3 +127,53 @@ fn main() {
);
assert_component(&output);
}

#[test]
fn component_type_wit_file() {
let output = compile_with_files(
&[
"-Clink-arg=--component-type",
"-Clink-arg=foo.wit",
"-Clink-arg=--string-encoding",
"-Clink-arg=utf16",
"--crate-type",
"cdylib",
],
r#"
#[no_mangle]
pub extern "C" fn cabi_realloc(ptr: *mut u8, old_size: i32, align: i32, new_size: i32) -> *mut u8 {
_ = (ptr, old_size, align, new_size);
unreachable!()
}
#[link(wasm_import_module = "foo:bar/foo")]
extern "C" {
#[link_name = "bar"]
fn import(ptr: *mut u8, len: i32, return_ptr: *mut *mut u8);
}
#[export_name = "foo:bar/foo#bar"]
pub unsafe extern "C" fn export(ptr: *mut u8, len: i32) -> *mut u8 {
let mut result = std::ptr::null_mut();
import(ptr, len, &mut result);
result
}
"#,
&[(
"foo.wit",
r#"
package foo:bar;
interface foo {
bar: func(s: string) -> string;
}
world root {
import foo;
export foo;
}
"#,
)],
);
assert_component(&output);
}

0 comments on commit 4a5ffa9

Please sign in to comment.