Skip to content

Commit 4b2b0ab

Browse files
authored
Merge pull request #282 from cameronbarker/cb/wasi-context
Expose Wasi Context to Ruby
2 parents 0bfc4ae + 3b77451 commit 4b2b0ab

12 files changed

Lines changed: 301 additions & 20 deletions

File tree

Cargo.lock

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

examples/linking.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
wasi_ctx_builder = Wasmtime::WasiCtxBuilder.new
1313
.inherit_stdin
1414
.inherit_stdout
15+
.build
1516

1617
store = Wasmtime::Store.new(engine, wasi_ctx: wasi_ctx_builder)
1718

examples/wasi.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
.inherit_stderr
1212
.set_argv(ARGV)
1313
.set_env(ENV)
14+
.build
1415
store = Wasmtime::Store.new(engine, wasi_ctx: wasi_ctx)
1516

1617
instance = linker.instantiate(store, mod)

ext/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ async-timer = { version = "1.0.0-beta.11", features = [
4040
static_assertions = "1.1.0"
4141
wasmtime-runtime = "16.0.0"
4242
wasmtime-environ = "= 16.0.0"
43+
deterministic-wasi-ctx = "0.1.17"
4344

4445
[build-dependencies]
4546
rb-sys-env = "0.1.2"

ext/src/ruby_api/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ mod params;
2525
mod store;
2626
mod table;
2727
mod trap;
28+
mod wasi_ctx;
2829
mod wasi_ctx_builder;
2930

3031
pub use caller::Caller;
@@ -37,6 +38,7 @@ pub use module::Module;
3738
pub use params::Params;
3839
pub use store::Store;
3940
pub use trap::Trap;
41+
pub use wasi_ctx::WasiCtx;
4042
pub use wasi_ctx_builder::WasiCtxBuilder;
4143

4244
/// The "Wasmtime" Ruby module.
@@ -83,6 +85,7 @@ pub fn init(ruby: &Ruby) -> Result<(), Error> {
8385
wasi_ctx_builder::init()?;
8486
table::init()?;
8587
global::init()?;
88+
wasi_ctx::init()?;
8689

8790
Ok(())
8891
}

ext/src/ruby_api/store.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::errors::wasi_exit_error;
2-
use super::{caller::Caller, engine::Engine, root, trap::Trap, wasi_ctx_builder::WasiCtxBuilder};
2+
use super::{caller::Caller, engine::Engine, root, trap::Trap, wasi_ctx::WasiCtx};
33
use crate::{define_rb_intern, error};
44
use magnus::value::StaticSymbol;
55
use magnus::{
@@ -11,13 +11,14 @@ use magnus::{
1111
DataTypeFunctions, Error, IntoValue, Module, Object, Ruby, TypedData, Value,
1212
};
1313
use magnus::{Class, RHash};
14+
use std::borrow::Borrow;
1415
use std::cell::UnsafeCell;
1516
use std::convert::TryFrom;
1617
use wasmtime::{
1718
AsContext, AsContextMut, Store as StoreImpl, StoreContext, StoreContextMut, StoreLimits,
1819
StoreLimitsBuilder,
1920
};
20-
use wasmtime_wasi::{I32Exit, WasiCtx};
21+
use wasmtime_wasi::{I32Exit, WasiCtx as WasiCtxImpl};
2122

2223
define_rb_intern!(
2324
WASI_CTX => "wasi_ctx",
@@ -26,7 +27,7 @@ define_rb_intern!(
2627

2728
pub struct StoreData {
2829
user_data: Value,
29-
wasi: Option<WasiCtx>,
30+
wasi: Option<WasiCtxImpl>,
3031
refs: Vec<Value>,
3132
last_error: Option<Error>,
3233
store_limits: StoreLimits,
@@ -41,7 +42,7 @@ impl StoreData {
4142
self.wasi.is_some()
4243
}
4344

44-
pub fn wasi_ctx_mut(&mut self) -> &mut WasiCtx {
45+
pub fn wasi_ctx_mut(&mut self) -> &mut WasiCtxImpl {
4546
self.wasi.as_mut().expect("Store must have a WASI context")
4647
}
4748

@@ -132,9 +133,9 @@ impl Store {
132133
///
133134
/// @example
134135
/// store = Wasmtime::Store.new(Wasmtime::Engine.new, {})
135-
pub fn new(ruby: &Ruby, args: &[Value]) -> Result<Self, Error> {
136+
pub fn new(args: &[Value]) -> Result<Self, Error> {
136137
let args = scan_args::scan_args::<(&Engine,), (Option<Value>,), (), (), _, ()>(args)?;
137-
let kw = scan_args::get_kwargs::<_, (), (Option<&WasiCtxBuilder>, Option<RHash>), ()>(
138+
let kw = scan_args::get_kwargs::<_, (), (Option<&WasiCtx>, Option<RHash>), ()>(
138139
args.keywords,
139140
&[],
140141
&[*WASI_CTX, *LIMITS],
@@ -143,10 +144,7 @@ impl Store {
143144
let (engine,) = args.required;
144145
let (user_data,) = args.optional;
145146
let user_data = user_data.unwrap_or_else(|| ().into_value());
146-
let wasi = match kw.optional.0 {
147-
None => None,
148-
Some(wasi_ctx_builder) => Some(wasi_ctx_builder.build_context(ruby)?),
149-
};
147+
let wasi = kw.optional.0.map(|wasi_ctx| wasi_ctx.get_inner());
150148

151149
let limiter = match kw.optional.1 {
152150
None => StoreLimitsBuilder::new(),

ext/src/ruby_api/wasi_ctx.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
use super::{
2+
root,
3+
wasi_ctx_builder::{file_r, file_w, wasi_file},
4+
WasiCtxBuilder,
5+
};
6+
use crate::error;
7+
use deterministic_wasi_ctx::build_wasi_ctx as wasi_deterministic_ctx;
8+
use magnus::{
9+
class, function, gc::Marker, method, prelude::*, typed_data::Obj, Error, Object, RString,
10+
RTypedData, Ruby, TypedData, Value,
11+
};
12+
use std::{borrow::Borrow, cell::RefCell, fs::File, path::PathBuf};
13+
use wasi_common::pipe::ReadPipe;
14+
use wasmtime_wasi::WasiCtx as WasiCtxImpl;
15+
16+
/// @yard
17+
/// WASI context to be sent as {Store#new}’s +wasi_ctx+ keyword argument.
18+
///
19+
/// Instance methods mutate the current object and return +self+.
20+
///
21+
/// @see https://docs.rs/wasmtime-wasi/latest/wasmtime_wasi/struct.WasiCtx.html
22+
/// Wasmtime's Rust doc
23+
#[magnus::wrap(class = "Wasmtime::WasiCtx", size, free_immediately)]
24+
pub struct WasiCtx {
25+
inner: RefCell<WasiCtxImpl>,
26+
}
27+
28+
type RbSelf = Obj<WasiCtx>;
29+
30+
impl WasiCtx {
31+
/// @yard
32+
/// Create a new deterministic {WasiCtx}. See https://github.com/Shopify/deterministic-wasi-ctx for more details
33+
/// @return [WasiCtx]
34+
pub fn deterministic() -> Self {
35+
Self {
36+
inner: RefCell::new(wasi_deterministic_ctx()),
37+
}
38+
}
39+
40+
/// @yard
41+
/// Set stdin to read from the specified file.
42+
/// @param path [String] The path of the file to read from.
43+
/// @def set_stdin_file(path)
44+
/// @return [WasiCtxBuilder] +self+
45+
fn set_stdin_file(rb_self: RbSelf, path: RString) -> RbSelf {
46+
let inner = rb_self.inner.borrow_mut();
47+
let cs = file_r(path).map(wasi_file).unwrap();
48+
inner.set_stdin(cs);
49+
rb_self
50+
}
51+
52+
/// @yard
53+
/// Set stdin to the specified String.
54+
/// @param content [String]
55+
/// @def set_stdin_string(content)
56+
/// @return [WasiCtx] +self+
57+
fn set_stdin_string(rb_self: RbSelf, content: RString) -> RbSelf {
58+
let inner = rb_self.inner.borrow_mut();
59+
let str = unsafe { content.as_slice() };
60+
let pipe = ReadPipe::from(str);
61+
inner.set_stdin(Box::new(pipe));
62+
rb_self
63+
}
64+
65+
/// @yard
66+
/// Set stdout to write to a file. Will truncate the file if it exists,
67+
/// otherwise try to create it.
68+
/// @param path [String] The path of the file to write to.
69+
/// @def set_stdout_file(path)
70+
/// @return [WasiCtx] +self+
71+
fn set_stdout_file(rb_self: RbSelf, path: RString) -> RbSelf {
72+
let inner = rb_self.inner.borrow_mut();
73+
let cs = file_w(path).map(wasi_file).unwrap();
74+
inner.set_stdout(cs);
75+
rb_self
76+
}
77+
78+
/// @yard
79+
/// Set stderr to write to a file. Will truncate the file if it exists,
80+
/// otherwise try to create it.
81+
/// @param path [String] The path of the file to write to.
82+
/// @def set_stderr_file(path)
83+
/// @return [WasiCtx] +self+
84+
fn set_stderr_file(rb_self: RbSelf, path: RString) -> RbSelf {
85+
let inner = rb_self.inner.borrow_mut();
86+
let cs = file_w(path).map(wasi_file).unwrap();
87+
inner.set_stderr(cs);
88+
rb_self
89+
}
90+
91+
pub fn from_inner(inner: WasiCtxImpl) -> Self {
92+
Self {
93+
inner: RefCell::new(inner),
94+
}
95+
}
96+
97+
pub fn get_inner(&self) -> WasiCtxImpl {
98+
return self.inner.borrow().clone();
99+
}
100+
}
101+
102+
pub fn init() -> Result<(), Error> {
103+
let class = root().define_class("WasiCtx", class::object())?;
104+
class.define_singleton_method("deterministic", function!(WasiCtx::deterministic, 0))?;
105+
class.define_method("set_stdin_file", method!(WasiCtx::set_stdin_file, 1))?;
106+
class.define_method("set_stdin_string", method!(WasiCtx::set_stdin_string, 1))?;
107+
class.define_method("set_stdout_file", method!(WasiCtx::set_stdout_file, 1))?;
108+
class.define_method("set_stderr_file", method!(WasiCtx::set_stderr_file, 1))?;
109+
Ok(())
110+
}

ext/src/ruby_api/wasi_ctx_builder.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::root;
1+
use super::{root, WasiCtx};
22
use crate::error;
33
use magnus::{
44
class, function, gc::Marker, method, typed_data::Obj, value::Opaque, DataTypeFunctions, Error,
@@ -192,9 +192,9 @@ impl WasiCtxBuilder {
192192
rb_self
193193
}
194194

195-
pub fn build_context(&self, ruby: &Ruby) -> Result<wasmtime_wasi::WasiCtx, Error> {
195+
pub fn build(ruby: &Ruby, rb_self: RbSelf) -> Result<WasiCtx, Error> {
196196
let mut builder = wasmtime_wasi::WasiCtxBuilder::new();
197-
let inner = self.inner.borrow();
197+
let inner = rb_self.inner.borrow();
198198

199199
if let Some(stdin) = inner.stdin.as_ref() {
200200
match stdin {
@@ -243,23 +243,24 @@ impl WasiCtxBuilder {
243243
builder.envs(&env_vec).map_err(|e| error!("{}", e))?;
244244
}
245245

246-
Ok(builder.build())
246+
let ctx = WasiCtx::from_inner(builder.build());
247+
Ok(ctx)
247248
}
248249
}
249250

250-
fn file_r(path: RString) -> Result<File, Error> {
251+
pub fn file_r(path: RString) -> Result<File, Error> {
251252
// SAFETY: &str copied before calling in to Ruby, no GC can happen before.
252253
File::open(PathBuf::from(unsafe { path.as_str()? }))
253254
.map_err(|e| error!("Failed to open file {}\n{}", path, e))
254255
}
255256

256-
fn file_w(path: RString) -> Result<File, Error> {
257+
pub fn file_w(path: RString) -> Result<File, Error> {
257258
// SAFETY: &str copied before calling in to Ruby, no GC can happen before.
258259
File::create(unsafe { path.as_str()? })
259260
.map_err(|e| error!("Failed to write to file {}\n{}", path, e))
260261
}
261262

262-
fn wasi_file(file: File) -> Box<wasi_cap_std_sync::file::File> {
263+
pub fn wasi_file(file: File) -> Box<wasi_cap_std_sync::file::File> {
263264
let file = cap_std::fs::File::from_std(file);
264265
let file = wasi_cap_std_sync::file::File::from_cap_std(file);
265266
Box::new(file)
@@ -292,5 +293,7 @@ pub fn init() -> Result<(), Error> {
292293

293294
class.define_method("set_argv", method!(WasiCtxBuilder::set_argv, 1))?;
294295

296+
class.define_method("build", method!(WasiCtxBuilder::build, 0))?;
297+
295298
Ok(())
296299
}

0 commit comments

Comments
 (0)