diff --git a/Cargo.lock b/Cargo.lock index 2ff529ac9..d99efec09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2038,10 +2038,13 @@ dependencies = [ name = "wit-bindgen-gen-guest-c" version = "0.2.0" dependencies = [ + "anyhow", "clap", "heck 0.3.3", "test-helpers", + "wasm-encoder 0.18.0", "wit-bindgen-core", + "wit-component", ] [[package]] diff --git a/crates/bindgen-core/src/lib.rs b/crates/bindgen-core/src/lib.rs index a6869d45c..4ee2a00c2 100644 --- a/crates/bindgen-core/src/lib.rs +++ b/crates/bindgen-core/src/lib.rs @@ -33,8 +33,9 @@ pub use ns::Ns; /// `export` means I'm exporting functions to be called, and `import` means I'm /// importing functions that I'm going to call, in both wasm modules and host /// code. The enum here represents this user perspective. -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq, Default)] pub enum Direction { + #[default] Import, Export, } diff --git a/crates/gen-guest-c/Cargo.toml b/crates/gen-guest-c/Cargo.toml index 53da2923d..33de6c26f 100644 --- a/crates/gen-guest-c/Cargo.toml +++ b/crates/gen-guest-c/Cargo.toml @@ -10,6 +10,9 @@ test = false [dependencies] wit-bindgen-core = { workspace = true } +wit-component = { workspace = true } +wasm-encoder = { workspace = true } +anyhow = { workspace = true } heck = { workspace = true } clap = { workspace = true, optional = true } diff --git a/crates/gen-guest-c/src/component_type_object.rs b/crates/gen-guest-c/src/component_type_object.rs new file mode 100644 index 000000000..d2ea7d9e6 --- /dev/null +++ b/crates/gen-guest-c/src/component_type_object.rs @@ -0,0 +1,71 @@ +use anyhow::{Context, Result}; +use heck::SnakeCase; +use wasm_encoder::{ + CodeSection, CustomSection, Encode, Function, FunctionSection, Module, TypeSection, +}; +use wit_bindgen_core::{wit_parser::Interface, Direction}; +use wit_component::InterfaceEncoder; + +pub fn linking_symbol(iface: &Interface, direction: Direction) -> String { + format!( + "__component_type_object_force_link_{}_{}", + iface.name.to_snake_case(), + match direction { + Direction::Import => "import", + Direction::Export => "export", + } + ) +} + +pub fn object(iface: &Interface, direction: Direction) -> Result> { + let mut module = Module::new(); + + // Build a module with one function that's a "dummy function" + let mut types = TypeSection::new(); + types.function([], []); + module.section(&types); + let mut funcs = FunctionSection::new(); + funcs.function(0); + module.section(&funcs); + let mut code = CodeSection::new(); + code.function(&Function::new([])); + module.section(&code); + + let name = format!( + "component-type:{}:{}", + match direction { + Direction::Import => "import", + Direction::Export => "export", + }, + iface.name + ); + let data = InterfaceEncoder::new(iface) + .encode() + .with_context(|| format!("translating interface {} to component type", iface.name))?; + // Add our custom section + module.section(&CustomSection { + name: &name, + data: data.as_slice(), + }); + + // Append the `.linking` section + let mut data = Vec::new(); + data.push(0x02); // version 2 + { + let mut subsection = Vec::::new(); + subsection.push(0x01); // syminfo count + subsection.push(0x00); // SYMTAB_FUNCTION + 0u32.encode(&mut subsection); // flags + 0u32.encode(&mut subsection); // index + linking_symbol(iface, direction).encode(&mut subsection); // name + + data.push(0x08); // `WASM_SYMBOL_TABLE` + subsection.encode(&mut data); + } + module.section(&CustomSection { + name: "linking", + data: &data, + }); + + Ok(module.finish()) +} diff --git a/crates/gen-guest-c/src/lib.rs b/crates/gen-guest-c/src/lib.rs index f0be76805..a810b3091 100644 --- a/crates/gen-guest-c/src/lib.rs +++ b/crates/gen-guest-c/src/lib.rs @@ -1,3 +1,5 @@ +mod component_type_object; + use heck::*; use std::collections::{BTreeSet, HashMap, HashSet}; use std::fmt::Write; @@ -34,6 +36,8 @@ pub struct C { types: HashMap, needs_string: bool, + + direction: Direction, } struct Func { @@ -700,6 +704,7 @@ impl Return { impl Generator for C { fn preprocess_one(&mut self, iface: &Interface, dir: Direction) { + self.direction = dir; let variant = Self::abi_variant(dir); self.sizes.fill(iface); self.in_import = variant == AbiVariant::GuestImport; @@ -1156,11 +1161,20 @@ impl Generator for C { ", iface.name.to_shouty_snake_case(), ); + let linking_symbol = component_type_object::linking_symbol(iface, self.direction); uwrite!( self.src.c, "\ #include #include <{}.h> + + // The following symbols are never called, but they are sufficient + // to get the custom sections in the component type object linked + // into the wasm when this compilation unit is linked. + extern void {linking_symbol}(void); + void {linking_symbol}_public_use_in_this_compilation_unit(void) {{ + {linking_symbol}(); + }} ", iface.name.to_kebab_case(), ); @@ -1289,6 +1303,12 @@ impl Generator for C { &format!("{}.h", iface.name.to_kebab_case()), self.src.h.as_bytes(), ); + files.push( + &format!("{}_component_type.o", iface.name.to_kebab_case()), + component_type_object::object(iface, self.direction) + .unwrap() + .as_slice(), + ); } } diff --git a/crates/test-helpers/build.rs b/crates/test-helpers/build.rs index da10ae57f..1e6149296 100644 --- a/crates/test-helpers/build.rs +++ b/crates/test-helpers/build.rs @@ -58,7 +58,10 @@ fn main() { .adapter_file(&wasi_adapter) .expect("adapter failed to get loaded") .encode() - .expect("module can be translated to a component"); + .expect(&format!( + "module {:?} can be translated to a component", + file + )); let dep_file = file.with_extension("d"); let deps = fs::read_to_string(&dep_file).expect("failed to read dep file"); @@ -118,7 +121,9 @@ fn main() { cmd.arg("--sysroot").arg(path.join("share/wasi-sysroot")); cmd.arg(c_impl) .arg(out_dir.join("imports.c")) + .arg(out_dir.join("imports_component_type.o")) .arg(out_dir.join("exports.c")) + .arg(out_dir.join("exports_component_type.o")) .arg("-I") .arg(&out_dir) .arg("-Wall") @@ -149,6 +154,22 @@ fn main() { test_dir.file_stem().unwrap().to_str().unwrap().to_string(), out_wasm.to_str().unwrap().to_string(), )); + + // Validate that the module can be translated to a component, using + // the component-type custom sections. We don't yet consume this component + // anywhere. + let module = fs::read(&out_wasm).expect("failed to read wasm file"); + ComponentEncoder::default() + .module(module.as_slice()) + .expect("pull custom sections from module") + .validate(true) + .adapter_file(&wasi_adapter) + .expect("adapter failed to get loaded") + .encode() + .expect(&format!( + "module {:?} can be translated to a component", + out_wasm + )); } } @@ -230,15 +251,35 @@ fn main() { panic!("failed to build"); } + let out_wasm = out_dir.join("target/generated/wasm/teavm-wasm/classes.wasm"); + wasms.push(( "java", test_dir.file_stem().unwrap().to_str().unwrap().to_string(), - out_dir - .join("target/generated/wasm/teavm-wasm/classes.wasm") - .to_str() - .unwrap() - .to_string(), + out_wasm.to_str().unwrap().to_string(), )); + + let imports = [Interface::parse_file(test_dir.join("imports.wit")).unwrap()]; + let interface = Interface::parse_file(test_dir.join("exports.wit")).unwrap(); + + // Validate that the module can be translated to a component, using + // wit interfaces explicitly passed to ComponentEncoder, because the + // TeaVM guest doesnt yet support putting component types into custom + // sections. + let module = fs::read(&out_wasm).expect("failed to read wasm file"); + ComponentEncoder::default() + .imports(&imports) + .interface(&interface) + .module(module.as_slice()) + .expect("pull custom sections from module") + .validate(true) + .adapter_file(&wasi_adapter) + .expect("adapter failed to get loaded") + .encode() + .expect(&format!( + "module {:?} can be translated to a component", + out_wasm + )); } } diff --git a/crates/wasi_snapshot_preview1/src/lib.rs b/crates/wasi_snapshot_preview1/src/lib.rs index 15b77c0dc..738c4a14a 100644 --- a/crates/wasi_snapshot_preview1/src/lib.rs +++ b/crates/wasi_snapshot_preview1/src/lib.rs @@ -28,6 +28,25 @@ pub extern "C" fn environ_sizes_get(environc: *mut Size, environ_buf_size: *mut unreachable() } +#[no_mangle] +pub extern "C" fn args_get(args: *mut *mut u8, args_buf: *mut u8) -> Errno { + unreachable() +} + +#[no_mangle] +pub extern "C" fn args_sizes_get(argc: *mut Size, arg_buf_size: *mut Size) -> Errno { + unreachable() +} + +#[no_mangle] +pub extern "C" fn clock_time_get( + clockid: Clockid, + precision: Timestamp, + out: *mut Timestamp, +) -> Errno { + unreachable() +} + #[no_mangle] pub extern "C" fn fd_write( fd: Fd, @@ -38,6 +57,16 @@ pub extern "C" fn fd_write( unreachable() } +#[no_mangle] +pub extern "C" fn fd_seek(fd: Fd, offset: Filedelta, whence: Whence, filesize: *mut Size) -> Errno { + unreachable() +} + +#[no_mangle] +pub extern "C" fn fd_close(fd: Fd) -> Errno { + unreachable() +} + #[no_mangle] pub extern "C" fn proc_exit(rval: Exitcode) -> ! { unreachable()