Skip to content

Commit b11fafd

Browse files
Add RUBY_WASM_EXPERIMENTAL_COMPONENT_MODEL feature
This commit adds a new feature flag to enable *only* the component model without enabling dynamic linking.
1 parent df8172e commit b11fafd

File tree

11 files changed

+183
-22
lines changed

11 files changed

+183
-22
lines changed

ext/ruby_wasm/src/lib.rs

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
use std::path::PathBuf;
1+
use std::{collections::HashMap, path::PathBuf};
22

33
use magnus::{
4-
eval, exception, function, method,
5-
prelude::*,
6-
value::{self, InnerValue},
7-
wrap, Error, ExceptionClass, RModule, Ruby,
4+
eval, exception, function, method, prelude::*, value::{self, InnerValue}, wrap, Error, ExceptionClass, RModule, Ruby
85
};
96
use structopt::StructOpt;
107
use wizer::Wizer;
@@ -142,6 +139,84 @@ impl ComponentLink {
142139
}
143140
}
144141

142+
#[wrap(class = "RubyWasmExt::ComponentEncode")]
143+
struct ComponentEncode(std::cell::RefCell<Option<wit_component::ComponentEncoder>>);
144+
145+
impl ComponentEncode {
146+
fn new() -> Self {
147+
Self(std::cell::RefCell::new(Some(wit_component::ComponentEncoder::default())))
148+
}
149+
150+
fn encoder(&self, body: impl FnOnce(wit_component::ComponentEncoder) -> Result<wit_component::ComponentEncoder, Error>) -> Result<(), Error> {
151+
let mut encoder = self.0.take().ok_or_else(|| {
152+
Error::new(
153+
exception::standard_error(),
154+
"encoder is already consumed".to_string(),
155+
)
156+
})?;
157+
encoder = body(encoder)?;
158+
self.0.replace(Some(encoder));
159+
Ok(())
160+
}
161+
162+
fn validate(&self, validate: bool) -> Result<(), Error> {
163+
self.encoder(|encoder| {
164+
Ok(encoder.validate(validate))
165+
})
166+
}
167+
168+
fn adapter(&self, name: String, module: bytes::Bytes) -> Result<(), Error> {
169+
self.encoder(|encoder| {
170+
encoder.adapter(&name, &module).map_err(|e| {
171+
Error::new(
172+
exception::standard_error(),
173+
format!("failed to encode adapter: {}", e),
174+
)
175+
})
176+
})
177+
}
178+
179+
fn module(&self, module: bytes::Bytes) -> Result<(), Error> {
180+
self.encoder(|encoder| {
181+
encoder.module(&module).map_err(|e| {
182+
Error::new(
183+
exception::standard_error(),
184+
format!("failed to encode module: {}", e),
185+
)
186+
})
187+
})
188+
}
189+
190+
fn realloc_via_memory_grow(&self, realloc: bool) -> Result<(), Error> {
191+
self.encoder(|encoder| {
192+
Ok(encoder.realloc_via_memory_grow(realloc))
193+
})
194+
}
195+
196+
fn import_name_map(&self, map: HashMap<String, String>) -> Result<(), Error> {
197+
self.encoder(|encoder| {
198+
Ok(encoder.import_name_map(map))
199+
})
200+
}
201+
202+
fn encode(&self) -> Result<bytes::Bytes, Error> {
203+
// Take the encoder out of the cell and consume it
204+
let encoder = self.0.borrow_mut().take().ok_or_else(|| {
205+
Error::new(
206+
exception::standard_error(),
207+
"encoder is already consumed".to_string(),
208+
)
209+
})?;
210+
let encoded = encoder.encode().map_err(|e| {
211+
Error::new(
212+
exception::standard_error(),
213+
format!("failed to encode component: {}", e),
214+
)
215+
})?;
216+
Ok(encoded.into())
217+
}
218+
}
219+
145220
#[magnus::init]
146221
fn init(ruby: &Ruby) -> Result<(), Error> {
147222
let module = RUBY_WASM.get_inner_with(ruby);
@@ -165,5 +240,14 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
165240
component_link.define_method("use_built_in_libdl", method!(ComponentLink::use_built_in_libdl, 1))?;
166241
component_link.define_method("encode", method!(ComponentLink::encode, 0))?;
167242

243+
let component_encode = module.define_class("ComponentEncode", ruby.class_object())?;
244+
component_encode.define_singleton_method("new", function!(ComponentEncode::new, 0))?;
245+
component_encode.define_method("validate", method!(ComponentEncode::validate, 1))?;
246+
component_encode.define_method("adapter", method!(ComponentEncode::adapter, 2))?;
247+
component_encode.define_method("module", method!(ComponentEncode::module, 1))?;
248+
component_encode.define_method("realloc_via_memory_grow", method!(ComponentEncode::realloc_via_memory_grow, 1))?;
249+
component_encode.define_method("import_name_map", method!(ComponentEncode::import_name_map, 1))?;
250+
component_encode.define_method("encode", method!(ComponentEncode::encode, 0))?;
251+
168252
Ok(())
169253
}

lib/ruby_wasm.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require_relative "ruby_wasm/version"
44
require_relative "ruby_wasm/util"
55
require_relative "ruby_wasm/build"
6+
require_relative "ruby_wasm/feature_set"
67
require_relative "ruby_wasm/packager"
78
require_relative "ruby_wasm/packager/component_adapter"
89
require_relative "ruby_wasm/packager/file_system"

lib/ruby_wasm/cli.rb

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -169,11 +169,12 @@ def pack(args)
169169
private
170170

171171
def build_config(options)
172+
build_source, all_default_exts = compute_build_source(options)
172173
# @type var config: Packager::build_config
173-
config = { target: options[:target_triplet], src: compute_build_source(options) }
174+
config = { target: options[:target_triplet], src: build_source }
174175
case options[:profile]
175176
when "full"
176-
config[:default_exts] = config[:src][:all_default_exts]
177+
config[:default_exts] = all_default_exts || ""
177178
env_additional_exts = ENV["RUBY_WASM_ADDITIONAL_EXTS"] || ""
178179
unless env_additional_exts.empty?
179180
config[:default_exts] += "," + env_additional_exts
@@ -203,7 +204,7 @@ def compute_build_source(options)
203204
local_source = { type: "local", path: src_name }
204205
# @type var local_source: RubyWasm::Packager::build_source
205206
local_source = local_source.merge(name: "local", patches: [])
206-
return local_source
207+
return [local_source, nil]
207208
end
208209
# Otherwise, it's an unknown source.
209210
raise(
@@ -212,7 +213,9 @@ def compute_build_source(options)
212213
end
213214
# Apply user-specified patches in addition to bundled patches.
214215
source[:patches].concat(options[:patches])
215-
source
216+
# @type var all_default_exts: String
217+
__skip__ = all_default_exts = source[:all_default_exts]
218+
[source, all_default_exts]
216219
end
217220

218221
# Retrieves the alias definitions for the Ruby sources.
@@ -305,7 +308,10 @@ def derive_packager(options)
305308
end
306309
end
307310
RubyWasm.logger.info "Using Gemfile: #{definition.gemfiles}" if definition
308-
RubyWasm::Packager.new(root, build_config(options), definition)
311+
RubyWasm::Packager.new(
312+
root, build_config(options), definition,
313+
features: RubyWasm::FeatureSet.derive_from_env
314+
)
309315
end
310316

311317
def do_print_ruby_cache_key(packager)

lib/ruby_wasm/feature_set.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
##
2+
# A set of feature flags that can be used to enable or disable experimental features.
3+
class RubyWasm::FeatureSet
4+
def initialize(features)
5+
@features = features
6+
end
7+
8+
# Maps the feature to the environment variable.
9+
FEATURES = {
10+
dynamic_linking: "RUBY_WASM_EXPERIMENTAL_DYNAMIC_LINKING",
11+
component_model: "RUBY_WASM_EXPERIMENTAL_COMPONENT_MODEL",
12+
}.freeze
13+
private_constant :FEATURES
14+
15+
# Derives the feature set from the environment variables. A feature
16+
# is enabled if the corresponding environment variable is set to "1",
17+
# otherwise it is disabled.
18+
def self.derive_from_env
19+
values = FEATURES.transform_values { |key| ENV[key] == "1" }
20+
new(values)
21+
end
22+
23+
def support_dynamic_linking?
24+
@features[:dynamic_linking]
25+
end
26+
27+
def support_component_model?
28+
@features[:component_model] || @features[:dynamic_linking]
29+
end
30+
end

lib/ruby_wasm/packager.rb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ class RubyWasm::Packager
99
# * build
1010
# @param config [Hash] The build config used for building Ruby.
1111
# @param definition [Bundler::Definition] The Bundler definition.
12-
def initialize(root, config = nil, definition = nil)
12+
# @param features [RubyWasm::FeatureSet] The features used for packaging.
13+
def initialize(root, config = nil, definition = nil, features: RubyWasm::FeatureSet.derive_from_env)
1314
@root = root
1415
@definition = definition
1516
@config = config
17+
@features = features
1618
end
1719

1820
# Packages the Ruby code into a Wasm binary. (including extensions)
@@ -33,7 +35,7 @@ def package(executor, dest_dir, options)
3335
fs.remove_non_runtime_files(executor)
3436
fs.remove_stdlib(executor) unless options[:stdlib]
3537

36-
if full_build_options[:target] == "wasm32-unknown-wasip1" && !support_dynamic_linking?
38+
if full_build_options[:target] == "wasm32-unknown-wasip1" && !features.support_component_model?
3739
# wasi-vfs supports only WASI target
3840
wasi_vfs = RubyWasmExt::WasiVfs.new
3941
wasi_vfs.map_dir("/bundle", fs.bundle_dir)
@@ -61,9 +63,8 @@ def specs
6163
@specs
6264
end
6365

64-
# Checks if dynamic linking is supported.
65-
def support_dynamic_linking?
66-
ENV["RUBY_WASM_EXPERIMENTAL_DYNAMIC_LINKING"] == "1"
66+
def features
67+
@features
6768
end
6869

6970
ALL_DEFAULT_EXTS =

lib/ruby_wasm/packager/core.rb

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def build_strategy
2020
@build_strategy ||=
2121
begin
2222
has_exts = @packager.specs.any? { |spec| spec.extensions.any? }
23-
if @packager.support_dynamic_linking?
23+
if @packager.features.support_dynamic_linking?
2424
DynamicLinking.new(@packager)
2525
else
2626
StaticLinking.new(@packager)
@@ -299,7 +299,18 @@ def derive_build
299299
def build_and_link_exts(executor)
300300
build = derive_build
301301
ruby_root = build.crossruby.dest_dir
302-
File.binread(File.join(ruby_root, "usr", "local", "bin", "ruby"))
302+
module_bytes = File.binread(File.join(ruby_root, "usr", "local", "bin", "ruby"))
303+
return module_bytes unless @packager.features.support_component_model?
304+
305+
linker = RubyWasmExt::ComponentEncode.new
306+
linker.validate(true)
307+
linker.module(module_bytes)
308+
linker.adapter(
309+
"wasi_snapshot_preview1",
310+
File.binread(RubyWasm::Packager::ComponentAdapter.wasi_snapshot_preview1("reactor"))
311+
)
312+
313+
linker.encode()
303314
end
304315

305316
def user_exts(build)

lib/ruby_wasm/packager/file_system.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def remove_non_runtime_files(executor)
7171
usr/local/include
7272
]
7373

74-
patterns << "**/*.so" unless @packager.support_dynamic_linking?
74+
patterns << "**/*.so" unless @packager.features.support_dynamic_linking?
7575
patterns.each do |pattern|
7676
Dir
7777
.glob(File.join(@dest_dir, pattern))

sig/ruby_wasm/cli.rbs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ module RubyWasm
3333
private
3434

3535
def build_config: (cli_options options) -> Packager::build_config
36-
def compute_build_source: (cli_options options) -> Packager::build_source
36+
def compute_build_source: (cli_options options) -> [Packager::build_source, String?]
3737
def self.build_source_aliases: (string root) -> Hash[string, Packager::build_source]
3838
def self.bundled_patches_path: () -> string
3939
def root: () -> string

sig/ruby_wasm/ext.rbs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,14 @@ module RubyWasmExt
2323
def use_built_in_libdl: (bool) -> void
2424
def encode: () -> bytes
2525
end
26+
27+
class ComponentEncode
28+
def initialize: () -> void
29+
def validate: (bool) -> void
30+
def adapter: (String name, bytes module) -> void
31+
def module: (bytes module) -> void
32+
def realloc_via_memory_grow: (bool) -> void
33+
def import_name_map: (Hash[String, String] map) -> void
34+
def encode: () -> bytes
35+
end
2636
end

sig/ruby_wasm/feature_set.rbs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class RubyWasm::FeatureSet
2+
@features: Hash[Symbol, bool]
3+
4+
def initialize: (Hash[Symbol, bool]) -> void
5+
6+
FEATURES: Hash[Symbol, String]
7+
8+
def self.derive_from_env: () -> RubyWasm::FeatureSet
9+
10+
def support_dynamic_linking?: () -> bool
11+
def support_component_model?: () -> bool
12+
end

0 commit comments

Comments
 (0)