Skip to content

Commit a36c0a5

Browse files
authored
Add WASI p2 support (#488)
* Add WASI p2 support * Use store optional to determine WASI context version * Centralize error messages about WASI * Use static functions for adding WASI to linker * Fix examples and error text * Fix docs * Fix inaccurate comment * Remove unused interned values
1 parent 6b1b135 commit a36c0a5

File tree

18 files changed

+379
-80
lines changed

18 files changed

+379
-80
lines changed

examples/linking.rb

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
engine = Wasmtime::Engine.new
44

5-
# Create a linker to link modules together. We want to use WASI with
6-
# the linker, so we pass in `wasi: true`.
7-
linker = Wasmtime::Linker.new(engine, wasi: true)
5+
# Create a linker to link modules together.
6+
linker = Wasmtime::Linker.new(engine)
7+
# We want to use WASI with # the linker, so we call add_to_linker_sync.
8+
Wasmtime::WASI::P1.add_to_linker_sync(linker)
89

910
mod1 = Wasmtime::Module.from_file(engine, "examples/linking1.wat")
1011
mod2 = Wasmtime::Module.from_file(engine, "examples/linking2.wat")
@@ -13,7 +14,7 @@
1314
.inherit_stdin
1415
.inherit_stdout
1516

16-
store = Wasmtime::Store.new(engine, wasi_config: wasi_config)
17+
store = Wasmtime::Store.new(engine, wasi_p1_config: wasi_config)
1718

1819
# Instantiate `mod2` which only uses WASI, then register
1920
# that instance with the linker so `mod1` can use it.

examples/wasi-p2.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
require "wasmtime"
2+
3+
engine = Wasmtime::Engine.new
4+
component = Wasmtime::Component::Component.from_file(engine, "spec/fixtures/wasi-debug-p2.wasm")
5+
6+
linker = Wasmtime::Component::Linker.new(engine)
7+
Wasmtime::WASI::P2.add_to_linker_sync(linker)
8+
9+
wasi_config = Wasmtime::WasiConfig.new
10+
.set_stdin_string("hi!")
11+
.inherit_stdout
12+
.inherit_stderr
13+
.set_argv(ARGV)
14+
.set_env(ENV)
15+
store = Wasmtime::Store.new(engine, wasi_config: wasi_config)
16+
17+
Wasmtime::Component::WasiCommand.new(store, component, linker).call_run(store)

examples/wasi.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33
engine = Wasmtime::Engine.new
44
mod = Wasmtime::Module.from_file(engine, "spec/fixtures/wasi-debug.wasm")
55

6-
linker = Wasmtime::Linker.new(engine, wasi: true)
6+
linker = Wasmtime::Linker.new(engine)
7+
Wasmtime::WASI::P1.add_to_linker_sync(linker)
78

89
wasi_config = Wasmtime::WasiConfig.new
910
.set_stdin_string("hi!")
1011
.inherit_stdout
1112
.inherit_stderr
1213
.set_argv(ARGV)
1314
.set_env(ENV)
14-
store = Wasmtime::Store.new(engine, wasi_config: wasi_config)
15+
store = Wasmtime::Store.new(engine, wasi_p1_config: wasi_config)
1516

1617
instance = linker.instantiate(store, mod)
1718
instance.invoke("_start")

ext/src/ruby_api/component.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod convert;
22
mod func;
33
mod instance;
44
mod linker;
5+
mod wasi_command;
56

67
use super::root;
78
use magnus::{
@@ -13,6 +14,8 @@ use wasmtime::component::Component as ComponentImpl;
1314

1415
pub use func::Func;
1516
pub use instance::Instance;
17+
pub use linker::Linker;
18+
pub use wasi_command::WasiCommand;
1619

1720
pub fn component_namespace(ruby: &Ruby) -> RModule {
1821
static COMPONENT_NAMESPACE: Lazy<RModule> =
@@ -162,6 +165,7 @@ pub fn init(ruby: &Ruby) -> Result<(), Error> {
162165
instance::init(ruby, &namespace)?;
163166
func::init(ruby, &namespace)?;
164167
convert::init(ruby)?;
168+
wasi_command::init(ruby, &namespace)?;
165169

166170
Ok(())
167171
}

ext/src/ruby_api/component/linker.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,26 @@ use super::{Component, Instance};
22
use crate::{
33
err,
44
ruby_api::{
5+
errors,
56
store::{StoreContextValue, StoreData},
67
Engine, Module, Store,
78
},
89
};
9-
use std::{borrow::BorrowMut, cell::RefCell};
10+
use std::{
11+
borrow::BorrowMut,
12+
cell::{RefCell, RefMut},
13+
};
1014

1115
use crate::error;
1216
use magnus::{
1317
class, function, gc::Marker, method, r_string::RString, scan_args, typed_data::Obj,
1418
DataTypeFunctions, Error, Module as _, Object, RModule, Ruby, TryConvert, TypedData, Value,
1519
};
1620
use wasmtime::component::{Linker as LinkerImpl, LinkerInstance as LinkerInstanceImpl};
21+
use wasmtime_wasi::{
22+
p2::{IoView, WasiCtx, WasiView},
23+
ResourceTable,
24+
};
1725

1826
/// @yard
1927
/// @rename Wasmtime::Component::Linker
@@ -23,6 +31,7 @@ use wasmtime::component::{Linker as LinkerImpl, LinkerInstance as LinkerInstance
2331
pub struct Linker {
2432
inner: RefCell<LinkerImpl<StoreData>>,
2533
refs: RefCell<Vec<Value>>,
34+
has_wasi: RefCell<bool>,
2635
}
2736
unsafe impl Send for Linker {}
2837

@@ -38,13 +47,23 @@ impl Linker {
3847
/// @param engine [Engine]
3948
/// @return [Linker]
4049
pub fn new(engine: &Engine) -> Result<Self, Error> {
41-
let linker = LinkerImpl::new(engine.get());
50+
let linker: LinkerImpl<StoreData> = LinkerImpl::new(engine.get());
51+
4252
Ok(Linker {
4353
inner: RefCell::new(linker),
4454
refs: RefCell::new(Vec::new()),
55+
has_wasi: RefCell::new(false),
4556
})
4657
}
4758

59+
pub(crate) fn inner_mut(&self) -> RefMut<'_, LinkerImpl<StoreData>> {
60+
self.inner.borrow_mut()
61+
}
62+
63+
pub(crate) fn has_wasi(&self) -> bool {
64+
*self.has_wasi.borrow()
65+
}
66+
4867
/// @yard
4968
/// @def root
5069
/// Define items in the root of this {Linker}.
@@ -105,6 +124,10 @@ impl Linker {
105124
store: Obj<Store>,
106125
component: &Component,
107126
) -> Result<Instance, Error> {
127+
if *rb_self.has_wasi.borrow() && !store.context().data().has_wasi_ctx() {
128+
return err!("{}", errors::missing_wasi_ctx_error("linker.instantiate"));
129+
}
130+
108131
let inner = rb_self.inner.borrow();
109132
inner
110133
.instantiate(store.context_mut(), component.get())
@@ -119,6 +142,12 @@ impl Linker {
119142
})
120143
.map_err(|e| error!("{}", e))
121144
}
145+
146+
pub(crate) fn add_wasi_p2(&self) -> Result<(), Error> {
147+
*self.has_wasi.borrow_mut() = true;
148+
let mut inner = self.inner.borrow_mut();
149+
wasmtime_wasi::p2::add_to_linker_sync(&mut inner).map_err(|e| error!("{e}"))
150+
}
122151
}
123152

124153
/// @yard
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use magnus::{
2+
class, function, method, module::Module, typed_data::Obj, DataTypeFunctions, Error, Object,
3+
RModule, Ruby,
4+
};
5+
use wasmtime_wasi::p2::bindings::sync::Command;
6+
7+
use crate::{
8+
err, error,
9+
ruby_api::{
10+
component::{linker::Linker, Component},
11+
errors,
12+
},
13+
Store,
14+
};
15+
16+
#[magnus::wrap(class = "Wasmtime::Component::WasiCommand", size, free_immediately)]
17+
pub struct WasiCommand {
18+
command: Command,
19+
}
20+
21+
impl WasiCommand {
22+
/// @yard
23+
/// @def new(store, component, linker)
24+
/// @param store [Store]
25+
/// @param component [Component]
26+
/// @param linker [Linker]
27+
/// @return [WasiCommand]
28+
pub fn new(store: &Store, component: &Component, linker: &Linker) -> Result<Self, Error> {
29+
if linker.has_wasi() && !store.context().data().has_wasi_ctx() {
30+
return err!("{}", errors::missing_wasi_ctx_error("WasiCommand.new"));
31+
}
32+
let command =
33+
Command::instantiate(store.context_mut(), component.get(), &linker.inner_mut())
34+
.map_err(|e| error!("{e}"))?;
35+
Ok(Self { command })
36+
}
37+
38+
/// @yard
39+
/// @def call_run(store)
40+
/// @param store [Store]
41+
/// @return [nil]
42+
pub fn call_run(_ruby: &Ruby, rb_self: Obj<Self>, store: &Store) -> Result<(), Error> {
43+
rb_self
44+
.command
45+
.wasi_cli_run()
46+
.call_run(store.context_mut())
47+
.map_err(|err| error!("{err}"))?
48+
.map_err(|_| error!("Error running `run`"))
49+
}
50+
}
51+
52+
pub fn init(_ruby: &Ruby, namespace: &RModule) -> Result<(), Error> {
53+
let linker = namespace.define_class("WasiCommand", class::object())?;
54+
linker.define_singleton_method("new", function!(WasiCommand::new, 3))?;
55+
linker.define_method("call_run", method!(WasiCommand::call_run, 1))?;
56+
57+
Ok(())
58+
}

ext/src/ruby_api/errors.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,29 @@ impl ExceptionMessage for magnus::Error {
8383
}
8484
}
8585

86+
pub(crate) fn missing_wasi_ctx_error(callee: &str) -> String {
87+
missing_wasi_error(callee, "WASI", "P2", "wasi_config")
88+
}
89+
90+
pub(crate) fn missing_wasi_p1_ctx_error() -> String {
91+
missing_wasi_error("linker.instantiate", "WASI p1", "P1", "wasi_p1_config")
92+
}
93+
94+
fn missing_wasi_error(
95+
callee: &str,
96+
wasi_text: &str,
97+
add_wasi_call: &str,
98+
option_name: &str,
99+
) -> String {
100+
format!(
101+
"Store is missing {wasi_text} configuration.\n\n\
102+
When using `WASI::{add_wasi_call}::add_to_linker_sync(linker)`, the Store given to\n\
103+
`{callee}` must have a {wasi_text} configuration.\n\
104+
To fix this, provide the `{option_name}` when creating the Store:\n\
105+
Wasmtime::Store.new(engine, {option_name}: WasiConfig.new)"
106+
)
107+
}
108+
86109
mod bundled {
87110
include!(concat!(env!("OUT_DIR"), "/bundled/error.rs"));
88111
}

ext/src/ruby_api/linker.rs

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use super::{
99
root,
1010
store::{Store, StoreContextValue, StoreData},
1111
};
12-
use crate::{define_rb_intern, err, error};
12+
use crate::{err, error, ruby_api::errors};
1313
use magnus::{
1414
block::Proc, class, function, gc::Marker, method, prelude::*, scan_args, scan_args::scan_args,
1515
typed_data::Obj, DataTypeFunctions, Error, Object, RArray, RHash, RString, Ruby, TypedData,
@@ -18,18 +18,14 @@ use magnus::{
1818
use std::cell::RefCell;
1919
use wasmtime::Linker as LinkerImpl;
2020

21-
define_rb_intern!(
22-
WASI=> "wasi",
23-
);
24-
2521
/// @yard
2622
/// @see https://docs.rs/wasmtime/latest/wasmtime/struct.Linker.html Wasmtime's Rust doc
2723
#[derive(TypedData)]
2824
#[magnus(class = "Wasmtime::Linker", size, mark, free_immediately)]
2925
pub struct Linker {
3026
inner: RefCell<LinkerImpl<StoreData>>,
3127
refs: RefCell<Vec<Value>>,
32-
has_wasi: bool,
28+
has_wasi: RefCell<bool>,
3329
}
3430

3531
unsafe impl Send for Linker {}
@@ -42,25 +38,15 @@ impl DataTypeFunctions for Linker {
4238

4339
impl Linker {
4440
/// @yard
45-
/// @def new(engine, wasi: false)
41+
/// @def new(engine)
4642
/// @param engine [Engine]
47-
/// @param wasi [Boolean] Whether WASI should be defined in this Linker. Defaults to false.
4843
/// @return [Linker]
49-
pub fn new(args: &[Value]) -> Result<Self, Error> {
50-
let args = scan_args::scan_args::<(&Engine,), (), (), (), _, ()>(args)?;
51-
let kw = scan_args::get_kwargs::<_, (), (Option<bool>,), ()>(args.keywords, &[], &[*WASI])?;
52-
let (engine,) = args.required;
53-
let wasi = kw.optional.0.unwrap_or(false);
54-
55-
let mut inner: LinkerImpl<StoreData> = LinkerImpl::new(engine.get());
56-
if wasi {
57-
wasmtime_wasi::preview1::add_to_linker_sync(&mut inner, |s| s.wasi_ctx_mut())
58-
.map_err(|e| error!("{}", e))?
59-
}
44+
pub fn new(engine: &Engine) -> Result<Self, Error> {
45+
let inner: LinkerImpl<StoreData> = LinkerImpl::new(engine.get());
6046
Ok(Self {
6147
inner: RefCell::new(inner),
6248
refs: Default::default(),
63-
has_wasi: wasi,
49+
has_wasi: RefCell::new(false),
6450
})
6551
}
6652

@@ -280,14 +266,8 @@ impl Linker {
280266
/// @param mod [Module]
281267
/// @return [Instance]
282268
pub fn instantiate(&self, store: Obj<Store>, module: &Module) -> Result<Instance, Error> {
283-
if self.has_wasi && !store.context().data().has_wasi_ctx() {
284-
return err!(
285-
"Store is missing WASI configuration.\n\n\
286-
When using `wasi: true`, the Store given to\n\
287-
`Linker#instantiate` must have a WASI configuration.\n\
288-
To fix this, provide the `wasi_config` when creating the Store:\n\
289-
Wasmtime::Store.new(engine, wasi_config: WasiConfig.new)"
290-
);
269+
if *self.has_wasi.borrow() && !store.context().data().has_wasi_p1_ctx() {
270+
return err!("{}", errors::missing_wasi_p1_ctx_error());
291271
}
292272

293273
self.inner
@@ -322,11 +302,18 @@ impl Linker {
322302
let mut inner = self.inner.borrow_mut();
323303
deterministic_wasi_ctx::replace_scheduling_functions(&mut inner).map_err(|e| error!("{e}"))
324304
}
305+
306+
pub(crate) fn add_wasi_p1(&self) -> Result<(), Error> {
307+
*self.has_wasi.borrow_mut() = true;
308+
let mut inner = self.inner.borrow_mut();
309+
wasmtime_wasi::preview1::add_to_linker_sync(&mut inner, |s| s.wasi_p1_ctx_mut())
310+
.map_err(|e| error!("{e}"))
311+
}
325312
}
326313

327314
pub fn init() -> Result<(), Error> {
328315
let class = root().define_class("Linker", class::object())?;
329-
class.define_singleton_method("new", function!(Linker::new, -1))?;
316+
class.define_singleton_method("new", function!(Linker::new, 1))?;
330317
class.define_method("allow_shadowing=", method!(Linker::set_allow_shadowing, 1))?;
331318
class.define_method(
332319
"allow_unknown_exports=",

ext/src/ruby_api/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ mod pooling_allocation_config;
2727
mod store;
2828
mod table;
2929
mod trap;
30+
mod wasi;
3031
mod wasi_config;
3132

3233
pub use caller::Caller;
@@ -83,6 +84,7 @@ pub fn init(ruby: &Ruby) -> Result<(), Error> {
8384
memory::init(ruby)?;
8485
linker::init()?;
8586
externals::init()?;
87+
wasi::init()?;
8688
wasi_config::init()?;
8789
table::init()?;
8890
global::init()?;

0 commit comments

Comments
 (0)