Skip to content

Commit 9d50539

Browse files
Integrate wit-component into rbwasm command
We no longer need `wasm-tools` cli
1 parent 377ee26 commit 9d50539

File tree

6 files changed

+236
-24
lines changed

6 files changed

+236
-24
lines changed

Cargo.lock

Lines changed: 85 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/ruby_wasm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ magnus = "0.6.2"
1414
wizer = "4.0.0"
1515
wasi-vfs-cli = { git = "https://github.com/kateinoigakukun/wasi-vfs/", tag = "0.5.2" }
1616
structopt = "0.3.26"
17+
wit-component = "0.202.0"

ext/ruby_wasm/src/lib.rs

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use magnus::{
66
value::{self, InnerValue},
77
wrap, Error, ExceptionClass, RModule, Ruby,
88
};
9-
use wizer::Wizer;
109
use structopt::StructOpt;
10+
use wizer::Wizer;
1111

1212
static RUBY_WASM: value::Lazy<RModule> =
1313
value::Lazy::new(|ruby| ruby.define_module("RubyWasmExt").unwrap());
@@ -64,6 +64,83 @@ impl WasiVfs {
6464
}
6565
}
6666

67+
#[wrap(class = "RubyWasmExt::ComponentLink")]
68+
struct ComponentLink(std::cell::RefCell<Option<wit_component::Linker>>);
69+
70+
impl ComponentLink {
71+
fn new() -> Self {
72+
Self(std::cell::RefCell::new(Some(wit_component::Linker::default())))
73+
}
74+
fn linker(&self, body: impl FnOnce(wit_component::Linker) -> Result<wit_component::Linker, Error>) -> Result<(), Error> {
75+
let mut linker = self.0.take().ok_or_else(|| {
76+
Error::new(
77+
exception::standard_error(),
78+
"linker is already consumed".to_string(),
79+
)
80+
})?;
81+
linker = body(linker)?;
82+
self.0.replace(Some(linker));
83+
Ok(())
84+
}
85+
86+
fn library(&self, name: String, module: Vec<u8>, dl_openable: bool) -> Result<(), Error> {
87+
self.linker(|linker| {
88+
linker.library(&name, &module, dl_openable).map_err(|e| {
89+
Error::new(
90+
exception::standard_error(),
91+
format!("failed to link library: {}", e),
92+
)
93+
})
94+
})
95+
}
96+
fn adapter(&self, name: String, module: Vec<u8>) -> Result<(), Error> {
97+
self.linker(|linker| {
98+
linker.adapter(&name, &module).map_err(|e| {
99+
Error::new(
100+
exception::standard_error(),
101+
format!("failed to link adapter: {}", e),
102+
)
103+
})
104+
})
105+
}
106+
fn validate(&self, validate: bool) -> Result<(), Error> {
107+
self.linker(|linker| {
108+
Ok(linker.validate(validate))
109+
})
110+
}
111+
fn stack_size(&self, size: u32) -> Result<(), Error> {
112+
self.linker(|linker| {
113+
Ok(linker.stack_size(size))
114+
})
115+
}
116+
fn stub_missing_functions(&self, stub: bool) -> Result<(), Error> {
117+
self.linker(|linker| {
118+
Ok(linker.stub_missing_functions(stub))
119+
})
120+
}
121+
fn use_built_in_libdl(&self, use_libdl: bool) -> Result<(), Error> {
122+
self.linker(|linker| {
123+
Ok(linker.use_built_in_libdl(use_libdl))
124+
})
125+
}
126+
fn encode(&self) -> Result<Vec<u8>, Error> {
127+
// Take the linker out of the cell and consume it
128+
let linker = self.0.borrow_mut().take().ok_or_else(|| {
129+
Error::new(
130+
exception::standard_error(),
131+
"linker is already consumed".to_string(),
132+
)
133+
})?;
134+
let encoded = linker.encode().map_err(|e| {
135+
Error::new(
136+
exception::standard_error(),
137+
format!("failed to encode linker: {}", e),
138+
)
139+
})?;
140+
Ok(encoded)
141+
}
142+
}
143+
67144
#[magnus::init]
68145
fn init(ruby: &Ruby) -> Result<(), Error> {
69146
let module = RUBY_WASM.get_inner_with(ruby);
@@ -76,5 +153,16 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
76153
wasi_vfs.define_singleton_method("run_cli", function!(WasiVfs::run_cli, 1))?;
77154
wasi_vfs.define_method("map_dir", method!(WasiVfs::map_dir, 2))?;
78155
wasi_vfs.define_method("pack", method!(WasiVfs::pack, 1))?;
156+
157+
let component_link = module.define_class("ComponentLink", ruby.class_object())?;
158+
component_link.define_singleton_method("new", function!(ComponentLink::new, 0))?;
159+
component_link.define_method("library", method!(ComponentLink::library, 3))?;
160+
component_link.define_method("adapter", method!(ComponentLink::adapter, 2))?;
161+
component_link.define_method("validate", method!(ComponentLink::validate, 1))?;
162+
component_link.define_method("stack_size", method!(ComponentLink::stack_size, 1))?;
163+
component_link.define_method("stub_missing_functions", method!(ComponentLink::stub_missing_functions, 1))?;
164+
component_link.define_method("use_built_in_libdl", method!(ComponentLink::use_built_in_libdl, 1))?;
165+
component_link.define_method("encode", method!(ComponentLink::encode, 0))?;
166+
79167
Ok(())
80168
}

lib/ruby_wasm/packager.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,7 @@ def package(executor, dest_dir, options)
2727

2828
fs = RubyWasm::Packager::FileSystem.new(dest_dir, self)
2929
fs.package_ruby_root(tarball, executor)
30-
31-
ruby_wasm_bin = File.expand_path("bin/ruby", fs.ruby_root)
32-
wasm_bytes = File.binread(ruby_wasm_bin).bytes
30+
wasm_bytes = ruby_core.build_and_link_exts(executor)
3331

3432
fs.package_gems
3533
fs.remove_non_runtime_files(executor)

lib/ruby_wasm/packager/core.rb

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def build(executor, options)
1212

1313
extend Forwardable
1414

15-
def_delegators :build_strategy, :cache_key, :artifact
15+
def_delegators :build_strategy, :cache_key, :artifact, :build_and_link_exts
1616

1717
private
1818

@@ -37,7 +37,7 @@ def build(executor, options)
3737
raise NotImplementedError
3838
end
3939

40-
def build_and_link_exts(executor, output)
40+
def build_and_link_exts(executor)
4141
end
4242

4343
# Array of paths to extconf.rb files.
@@ -89,27 +89,23 @@ def build(executor, options)
8989
else
9090
do_build.call
9191
end
92-
self.build_exts(executor, build)
9392
build.crossruby.artifact
9493
end
9594

96-
def build_and_link_exts(executor, output)
95+
def build_and_link_exts(executor)
9796
build = derive_build
9897
self.build_exts(executor, build)
99-
self.link_exts(executor, build, output)
98+
self.link_exts(executor, build)
10099
end
101100

102-
def link_exts(executor, build, output)
103-
wasm_tools = ENV["WASM_TOOLS"] || "wasm-tools"
101+
def link_exts(executor, build)
104102
ruby_root = build.crossruby.dest_dir
105103

106-
link_args = %W[component link]
107-
link_args << File.join(ruby_root, "usr", "local", "bin", "ruby")
104+
libraries = [File.join(ruby_root, "usr", "local", "bin", "ruby")]
108105

109106
# TODO: Should be computed from dyinfo of ruby binary
110107
wasi_libc_shared_libs = [
111108
"libc.so",
112-
"libdl.so",
113109
"libwasi-emulated-getpid.so",
114110
"libwasi-emulated-mman.so",
115111
"libwasi-emulated-process-clocks.so",
@@ -118,18 +114,47 @@ def link_exts(executor, build, output)
118114

119115
wasi_libc_shared_libs.each do |lib|
120116
wasi_sdk_path = build.toolchain.wasi_sdk_path
121-
link_args << File.join(wasi_sdk_path, "share/wasi-sysroot/lib/wasm32-wasi", lib)
117+
libraries << File.join(wasi_sdk_path, "share/wasi-sysroot/lib/wasm32-wasi", lib)
122118
end
123119
wasi_adapter = ENV["WASI_COMPONENT_ADAPTER"] or raise "WASI_COMPONENT_ADAPTER is not set"
124-
link_args.concat ["--adapt", wasi_adapter]
125-
Dir.glob(File.join(ruby_root, "usr", "local", "lib", "ruby", "**", "*.so")).each do |so|
126-
link_args << "--dl-openable"
127-
link_args << "#{so.delete_prefix(ruby_root)}=#{so}"
120+
adapters = [wasi_adapter]
121+
dl_openable_libs = Dir.glob(File.join(ruby_root, "usr", "local", "lib", "ruby", "**", "*.so"))
122+
linker = RubyWasmExt::ComponentLink.new
123+
linker.use_built_in_libdl(true)
124+
linker.stub_missing_functions(false)
125+
linker.validate(true)
126+
127+
libraries.each do |lib|
128+
# Non-DL openable libraries should be referenced as base name
129+
lib_name = File.basename(lib)
130+
module_bytes = File.binread(lib).unpack("C*")
131+
RubyWasm.logger.info "Linking #{lib_name} (#{module_bytes.size} bytes)"
132+
linker.library(lib_name, module_bytes, false)
128133
end
129-
link_args << "-o"
130-
link_args << File.join(ruby_root, "usr", "local", "bin", "ruby.component.wasm")
131134

132-
executor.system(wasm_tools, *link_args)
135+
filter_libs = [
136+
"enc/encdb.so",
137+
"stringio.so",
138+
"monitor.so",
139+
"pathname.so",
140+
"strscan.so",
141+
]
142+
dl_openable_libs.each do |lib|
143+
# DL openable lib_name should be a relative path from ruby_root
144+
lib_name = "/" + Pathname.new(lib).relative_path_from(Pathname.new(ruby_root)).to_s
145+
# next unless filter_libs.any? { |filter| lib_name.include?(filter) }
146+
module_bytes = File.binread(lib).unpack("C*")
147+
RubyWasm.logger.info "Linking #{lib_name} (#{module_bytes.size} bytes)"
148+
linker.library(lib_name, module_bytes, true)
149+
end
150+
151+
adapters.each do |adapter|
152+
adapter_name = File.basename(adapter)
153+
# e.g. wasi_snapshot_preview1.command.wasm -> wasi_snapshot_preview1
154+
adapter_name = adapter_name.split(".")[0]
155+
linker.adapter(adapter_name, File.binread(adapter).unpack("C*"))
156+
end
157+
return linker.encode()
133158
end
134159

135160
def build_exts(executor, build)
@@ -275,6 +300,12 @@ def derive_build
275300
build
276301
end
277302

303+
def build_and_link_exts(executor)
304+
build = derive_build
305+
ruby_root = build.crossruby.dest_dir
306+
File.binread(File.join(ruby_root, "usr", "local", "bin", "ruby"))
307+
end
308+
278309
def user_exts(build)
279310
@user_exts ||=
280311
specs_with_extensions.flat_map do |spec, exts|

0 commit comments

Comments
 (0)