Skip to content

Commit 4a5ffa9

Browse files
authored
add --component-type and --string-encoding options (#32)
* 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 <joel.dice@fermyon.com> * use Resolve::select_world to select world to merge Signed-off-by: Joel Dice <joel.dice@fermyon.com> * add `component_type_wit_file` test Signed-off-by: Joel Dice <joel.dice@fermyon.com> * use result of Resolve::push_path when calling select_world Signed-off-by: Joel Dice <joel.dice@fermyon.com> --------- Signed-off-by: Joel Dice <joel.dice@fermyon.com>
1 parent fba6fbd commit 4a5ffa9

4 files changed

Lines changed: 124 additions & 3 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@ tempfile = "3.10.0"
3030
wasmparser = "0.213.0"
3131
wat = "1.213.0"
3232
wit-component = "0.213.0"
33+
wit-parser = "0.213.0"

src/lib.rs

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use std::path::{Path, PathBuf};
77
use std::process::Command;
88
use std::str::FromStr;
99
use wasmparser::Payload;
10+
use wit_component::StringEncoding;
11+
use wit_parser::{Resolve, WorldId};
1012

1113
/// Representation of a flag passed to `wasm-ld`
1214
///
@@ -263,6 +265,21 @@ struct ComponentLdArgs {
263265
/// Adapters to use when creating the final component.
264266
#[clap(long = "adapt", value_name = "[NAME=]MODULE", value_parser = parse_adapter)]
265267
adapters: Vec<(String, Vec<u8>)>,
268+
269+
/// WIT file representing additional component type information to use.
270+
///
271+
/// May be specified more than once.
272+
///
273+
/// See also the `--string-encoding` option.
274+
#[clap(long = "component-type", value_name = "WIT_FILE")]
275+
component_types: Vec<PathBuf>,
276+
277+
/// String encoding to use when creating the final component.
278+
///
279+
/// This may be either "utf8", "utf16", or "compact-utf16". This value is
280+
/// only used when one or more `--component-type` options are specified.
281+
#[clap(long, value_parser = parse_encoding, default_value = "utf8")]
282+
string_encoding: StringEncoding,
266283
}
267284

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

291+
fn parse_encoding(s: &str) -> Result<StringEncoding> {
292+
Ok(match s {
293+
"utf8" => StringEncoding::UTF8,
294+
"utf16" => StringEncoding::UTF16,
295+
"compact-utf16" => StringEncoding::CompactUTF16,
296+
_ => bail!("unknown string encoding: {s:?}"),
297+
})
298+
}
299+
274300
fn parse_optionally_name_file(s: &str) -> (&str, &str) {
275301
let mut parts = s.splitn(2, '=');
276302
let name_or_path = parts.next().unwrap();
@@ -462,8 +488,11 @@ impl App {
462488
component_ld_args.push(format!("--{c}").into());
463489
if let Some(arg) = command.get_arguments().find(|a| a.get_long() == Some(c))
464490
{
465-
if let ArgAction::Set = arg.get_action() {
466-
component_ld_args.push(parser.value()?);
491+
match arg.get_action() {
492+
ArgAction::Set | ArgAction::Append => {
493+
component_ld_args.push(parser.value()?)
494+
}
495+
_ => (),
467496
}
468497
}
469498
}
@@ -521,7 +550,7 @@ impl App {
521550
let reactor_adapter = include_bytes!("wasi_snapshot_preview1.reactor.wasm");
522551
let command_adapter = include_bytes!("wasi_snapshot_preview1.command.wasm");
523552
let proxy_adapter = include_bytes!("wasi_snapshot_preview1.proxy.wasm");
524-
let core_module = std::fs::read(lld_output.path())
553+
let mut core_module = std::fs::read(lld_output.path())
525554
.with_context(|| format!("failed to read {linker:?} output"))?;
526555

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

574+
if !self.component.component_types.is_empty() {
575+
let mut merged = None::<(Resolve, WorldId)>;
576+
for wit_file in &self.component.component_types {
577+
let mut resolve = Resolve::default();
578+
let (packages, _) = resolve
579+
.push_path(wit_file)
580+
.with_context(|| format!("unable to add component type {wit_file:?}"))?;
581+
582+
let world = resolve.select_world(&packages, None)?;
583+
584+
if let Some((merged_resolve, merged_world)) = &mut merged {
585+
let world = merged_resolve.merge(resolve)?.map_world(world, None)?;
586+
merged_resolve.merge_worlds(world, *merged_world)?;
587+
} else {
588+
merged = Some((resolve, world));
589+
}
590+
}
591+
592+
let Some((resolve, world)) = merged else {
593+
unreachable!()
594+
};
595+
596+
wit_component::embed_component_metadata(
597+
&mut core_module,
598+
&resolve,
599+
world,
600+
self.component.string_encoding,
601+
)?;
602+
}
603+
545604
let mut encoder = wit_component::ComponentEncoder::default()
546605
.module(&core_module)
547606
.context("failed to parse core wasm for componentization")?

tests/all.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
use std::env;
2+
use std::fs;
23
use std::io::{Read, Write};
34
use std::process::{Command, Stdio};
45

56
fn compile(args: &[&str], src: &str) -> Vec<u8> {
7+
compile_with_files(args, src, &[])
8+
}
9+
10+
fn compile_with_files(args: &[&str], src: &str, files: &[(&str, &str)]) -> Vec<u8> {
611
let tempdir = tempfile::TempDir::new().unwrap();
12+
13+
for (name, content) in files {
14+
fs::write(tempdir.path().join(name), content.as_bytes()).unwrap();
15+
}
16+
717
let mut myself = env::current_exe().unwrap();
818
myself.pop(); // exe name
919
myself.pop(); // 'deps'
@@ -117,3 +127,53 @@ fn main() {
117127
);
118128
assert_component(&output);
119129
}
130+
131+
#[test]
132+
fn component_type_wit_file() {
133+
let output = compile_with_files(
134+
&[
135+
"-Clink-arg=--component-type",
136+
"-Clink-arg=foo.wit",
137+
"-Clink-arg=--string-encoding",
138+
"-Clink-arg=utf16",
139+
"--crate-type",
140+
"cdylib",
141+
],
142+
r#"
143+
#[no_mangle]
144+
pub extern "C" fn cabi_realloc(ptr: *mut u8, old_size: i32, align: i32, new_size: i32) -> *mut u8 {
145+
_ = (ptr, old_size, align, new_size);
146+
unreachable!()
147+
}
148+
149+
#[link(wasm_import_module = "foo:bar/foo")]
150+
extern "C" {
151+
#[link_name = "bar"]
152+
fn import(ptr: *mut u8, len: i32, return_ptr: *mut *mut u8);
153+
}
154+
155+
#[export_name = "foo:bar/foo#bar"]
156+
pub unsafe extern "C" fn export(ptr: *mut u8, len: i32) -> *mut u8 {
157+
let mut result = std::ptr::null_mut();
158+
import(ptr, len, &mut result);
159+
result
160+
}
161+
"#,
162+
&[(
163+
"foo.wit",
164+
r#"
165+
package foo:bar;
166+
167+
interface foo {
168+
bar: func(s: string) -> string;
169+
}
170+
171+
world root {
172+
import foo;
173+
export foo;
174+
}
175+
"#,
176+
)],
177+
);
178+
assert_component(&output);
179+
}

0 commit comments

Comments
 (0)