wasmer 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/config.yml +6 -4
- data/Cargo.lock +371 -312
- data/Cargo.toml +4 -4
- data/README.md +17 -16
- data/bors.toml +4 -0
- data/lib/wasmer/version.rb +1 -1
- data/src/error.rs +16 -0
- data/src/instance.rs +126 -97
- data/src/lib.rs +76 -50
- data/src/memory/mod.rs +72 -33
- data/src/memory/view.rs +36 -18
- metadata +4 -2
data/Cargo.toml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
[package]
|
2
2
|
publish = false
|
3
3
|
name = "ruby-ext-wasm"
|
4
|
-
version = "0.
|
4
|
+
version = "0.3.0"
|
5
5
|
authors = ["Ivan Enderlin <ivan.enderlin@hoa-project.net>"]
|
6
6
|
edition = "2018"
|
7
7
|
description = "Ruby extension to run WebAssembly binaries"
|
@@ -15,7 +15,7 @@ name = "wasmer"
|
|
15
15
|
crate-type = ["dylib"]
|
16
16
|
|
17
17
|
[dependencies]
|
18
|
-
wasmer-runtime = "0.
|
19
|
-
wasmer-runtime-core = "0.
|
20
|
-
rutie = "0.
|
18
|
+
wasmer-runtime = "0.5.5"
|
19
|
+
wasmer-runtime-core = "0.5.5"
|
20
|
+
rutie = "0.6.0"
|
21
21
|
lazy_static = "1.3.0"
|
data/README.md
CHANGED
@@ -10,7 +10,7 @@
|
|
10
10
|
<a href="https://rubygems.org/gems/wasmer">
|
11
11
|
<img src="https://img.shields.io/gem/v/wasmer.svg" alt="Gem"></a>
|
12
12
|
<a href="https://rubygems.org/gems/wasmer">
|
13
|
-
<img src="https://img.shields.io/gem/dt/wasmer.svg" alt="
|
13
|
+
<img src="https://img.shields.io/gem/dt/wasmer.svg" alt="Number of gem downloads"></a>
|
14
14
|
<a href="https://github.com/wasmerio/wasmer/blob/master/LICENSE">
|
15
15
|
<img src="https://img.shields.io/github/license/wasmerio/wasmer.svg" alt="License"></a>
|
16
16
|
</p>
|
@@ -61,7 +61,7 @@ Then, we can execute it in Ruby (!) with the `examples/simple.rb` file:
|
|
61
61
|
require "wasmer"
|
62
62
|
|
63
63
|
bytes = IO.read "simple.wasm", mode: "rb"
|
64
|
-
instance = Instance.new bytes
|
64
|
+
instance = Wasmer::Instance.new bytes
|
65
65
|
puts instance.exports.sum 1, 2
|
66
66
|
```
|
67
67
|
|
@@ -85,7 +85,7 @@ require "wasmer"
|
|
85
85
|
wasm_bytes = IO.read "my_program.wasm", mode: "rb"
|
86
86
|
|
87
87
|
# Instantiates the Wasm module.
|
88
|
-
instance = Instance.new wasm_bytes
|
88
|
+
instance = Wasmer::Instance.new wasm_bytes
|
89
89
|
|
90
90
|
# Call a function on it.
|
91
91
|
result = instance.exports.sum 1, 2
|
@@ -109,7 +109,14 @@ See below for more information.
|
|
109
109
|
## The `Memory` class
|
110
110
|
|
111
111
|
A WebAssembly instance has its own memory, represented by the `Memory`
|
112
|
-
class. It is accessible by the `Instance.memory` getter.
|
112
|
+
class. It is accessible by the `Wasmer::Instance.memory` getter.
|
113
|
+
|
114
|
+
The `Memory.grow` methods allows to grow the memory by a number of
|
115
|
+
pages (of 65kb each).
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
instance.memory.grow 1
|
119
|
+
```
|
113
120
|
|
114
121
|
The `Memory` class offers methods to create views of the memory
|
115
122
|
internal buffer, e.g. `uint8_view`, `int8_view`, `uint16_view`
|
@@ -159,7 +166,7 @@ require "wasmer"
|
|
159
166
|
wasm_bytes = IO.read "my_program.wasm", mode: "rb"
|
160
167
|
|
161
168
|
# Instantiates the Wasm module.
|
162
|
-
instance = Instance.new wasm_bytes
|
169
|
+
instance = Wasmer::Instance.new wasm_bytes
|
163
170
|
|
164
171
|
# Call a function that returns a pointer to a string for instance.
|
165
172
|
pointer = instance.exports.return_string
|
@@ -168,18 +175,12 @@ pointer = instance.exports.return_string
|
|
168
175
|
memory = instance.memory.uint8_view pointer
|
169
176
|
|
170
177
|
# Read the string pointed by the pointer.
|
171
|
-
nth = 0
|
172
|
-
string = ""
|
173
|
-
|
174
|
-
while true
|
175
|
-
char = memory[nth]
|
176
178
|
|
177
|
-
|
178
|
-
break
|
179
|
-
end
|
179
|
+
string = ""
|
180
180
|
|
181
|
+
memory.each do |char|
|
182
|
+
break if char == 0
|
181
183
|
string += char.chr
|
182
|
-
nth += 1
|
183
184
|
end
|
184
185
|
|
185
186
|
puts string # Hello, World!
|
@@ -252,7 +253,7 @@ require "wasmer"
|
|
252
253
|
|
253
254
|
wasm_bytes = IO.read "my_program.wasm", mode: "rb"
|
254
255
|
|
255
|
-
if not Module.validate wasm_bytes
|
256
|
+
if not Wasmer::Module.validate wasm_bytes
|
256
257
|
puts "The program seems corrupted."
|
257
258
|
end
|
258
259
|
```
|
@@ -298,7 +299,7 @@ About safety:
|
|
298
299
|
|
299
300
|
# License
|
300
301
|
|
301
|
-
The entire project is under the
|
302
|
+
The entire project is under the MIT License. Please read [the
|
302
303
|
`LICENSE` file][license].
|
303
304
|
|
304
305
|
[license]: https://github.com/wasmerio/wasmer/blob/master/LICENSE
|
data/bors.toml
ADDED
data/lib/wasmer/version.rb
CHANGED
data/src/error.rs
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
//! Functions to handle error or exception correctly.
|
2
|
+
|
3
|
+
use rutie::{AnyException, VM};
|
4
|
+
|
5
|
+
pub fn unwrap_or_raise<Output, Function>(f: Function) -> Output
|
6
|
+
where
|
7
|
+
Function: FnOnce() -> Result<Output, AnyException>,
|
8
|
+
{
|
9
|
+
match f() {
|
10
|
+
Ok(x) => x,
|
11
|
+
Err(e) => {
|
12
|
+
VM::raise_ex(e);
|
13
|
+
unreachable!()
|
14
|
+
}
|
15
|
+
}
|
16
|
+
}
|
data/src/instance.rs
CHANGED
@@ -1,14 +1,17 @@
|
|
1
1
|
//! The `Instance` WebAssembly class.
|
2
2
|
|
3
|
-
use crate::
|
3
|
+
use crate::{
|
4
|
+
error::unwrap_or_raise,
|
5
|
+
memory::{Memory, RubyMemory, MEMORY_WRAPPER},
|
6
|
+
};
|
4
7
|
use lazy_static::lazy_static;
|
5
8
|
use rutie::{
|
6
9
|
class, methods,
|
7
10
|
rubysys::{class, value::ValueType},
|
8
11
|
types::{Argc, Value},
|
9
12
|
util::str_to_cstring,
|
10
|
-
wrappable_struct, AnyException, AnyObject, Array,
|
11
|
-
RString, Symbol,
|
13
|
+
wrappable_struct, AnyException, AnyObject, Array, Boolean, Exception, Fixnum, Float, Module,
|
14
|
+
NilClass, Object, RString, Symbol,
|
12
15
|
};
|
13
16
|
use std::{mem, rc::Rc};
|
14
17
|
use wasmer_runtime::{self as runtime, imports, Export};
|
@@ -26,18 +29,23 @@ impl ExportedFunctions {
|
|
26
29
|
Self { instance }
|
27
30
|
}
|
28
31
|
|
32
|
+
/// Check that an exported function exists.
|
33
|
+
pub fn respond_to_missing(&self, method_name: &str) -> bool {
|
34
|
+
self.instance.dyn_func(method_name).is_ok()
|
35
|
+
}
|
36
|
+
|
29
37
|
/// Call an exported function on the given WebAssembly instance.
|
30
|
-
pub fn method_missing(
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
))
|
39
|
-
|
40
|
-
|
38
|
+
pub fn method_missing(
|
39
|
+
&self,
|
40
|
+
method_name: &str,
|
41
|
+
arguments: Array,
|
42
|
+
) -> Result<AnyObject, AnyException> {
|
43
|
+
let function = self.instance.dyn_func(method_name).map_err(|_| {
|
44
|
+
AnyException::new(
|
45
|
+
"RuntimeError",
|
46
|
+
Some(&format!("Function `{}` does not exist.", method_name)),
|
47
|
+
)
|
48
|
+
})?;
|
41
49
|
let signature = function.signature();
|
42
50
|
let parameters = signature.params();
|
43
51
|
let number_of_parameters = parameters.len() as isize;
|
@@ -45,23 +53,21 @@ impl ExportedFunctions {
|
|
45
53
|
let diff: isize = number_of_parameters - number_of_arguments;
|
46
54
|
|
47
55
|
if diff > 0 {
|
48
|
-
|
56
|
+
return Err(AnyException::new(
|
49
57
|
"ArgumentError",
|
50
58
|
Some(&format!(
|
51
59
|
"Missing {} argument(s) when calling `{}`: Expect {} argument(s), given {}.",
|
52
60
|
diff, method_name, number_of_parameters, number_of_arguments
|
53
61
|
)),
|
54
62
|
));
|
55
|
-
unreachable!();
|
56
63
|
} else if diff < 0 {
|
57
|
-
|
64
|
+
return Err(AnyException::new(
|
58
65
|
"ArgumentError",
|
59
66
|
Some(&format!(
|
60
67
|
"Given {} extra argument(s) when calling `{}`: Expect {} argument(s), given {}.",
|
61
68
|
diff.abs(), method_name, number_of_parameters, number_of_arguments
|
62
69
|
)),
|
63
70
|
));
|
64
|
-
unreachable!();
|
65
71
|
}
|
66
72
|
|
67
73
|
let mut function_arguments =
|
@@ -74,71 +80,66 @@ impl ExportedFunctions {
|
|
74
80
|
argument
|
75
81
|
.try_convert_to::<Fixnum>()
|
76
82
|
.map_err(|_| {
|
77
|
-
|
83
|
+
AnyException::new(
|
78
84
|
"TypeError",
|
79
85
|
Some(&format!(
|
80
86
|
"Cannot convert argument #{} to a WebAssembly i32 value.",
|
81
87
|
nth + 1
|
82
88
|
)),
|
83
|
-
)
|
84
|
-
})
|
85
|
-
.unwrap()
|
89
|
+
)
|
90
|
+
})?
|
86
91
|
.to_i32(),
|
87
92
|
),
|
88
93
|
(Type::I64, ValueType::Fixnum) => runtime::Value::I64(
|
89
94
|
argument
|
90
95
|
.try_convert_to::<Fixnum>()
|
91
96
|
.map_err(|_| {
|
92
|
-
|
97
|
+
AnyException::new(
|
93
98
|
"TypeError",
|
94
99
|
Some(&format!(
|
95
100
|
"Cannot convert argument #{} to a WebAssembly i64 value.",
|
96
101
|
nth + 1
|
97
102
|
)),
|
98
|
-
)
|
99
|
-
})
|
100
|
-
.unwrap()
|
103
|
+
)
|
104
|
+
})?
|
101
105
|
.to_i64(),
|
102
106
|
),
|
103
107
|
(Type::F32, ValueType::Float) => runtime::Value::F32(
|
104
108
|
argument
|
105
109
|
.try_convert_to::<Float>()
|
106
110
|
.map_err(|_| {
|
107
|
-
|
111
|
+
AnyException::new(
|
108
112
|
"TypeError",
|
109
113
|
Some(&format!(
|
110
114
|
"Cannot convert argument #{} to a WebAssembly f32 value.",
|
111
115
|
nth + 1
|
112
116
|
)),
|
113
|
-
)
|
114
|
-
})
|
115
|
-
.unwrap()
|
117
|
+
)
|
118
|
+
})?
|
116
119
|
.to_f64() as f32,
|
117
120
|
),
|
118
121
|
(Type::F64, ValueType::Float) => runtime::Value::F64(
|
119
122
|
argument
|
120
123
|
.try_convert_to::<Float>()
|
121
124
|
.map_err(|_| {
|
122
|
-
|
125
|
+
AnyException::new(
|
123
126
|
"TypeError",
|
124
127
|
Some(&format!(
|
125
128
|
"Cannot convert argument #{} to a WebAssembly f64 value.",
|
126
129
|
nth + 1
|
127
130
|
)),
|
128
|
-
)
|
129
|
-
})
|
130
|
-
.unwrap()
|
131
|
+
)
|
132
|
+
})?
|
131
133
|
.to_f64(),
|
132
134
|
),
|
133
135
|
(_, ty) => {
|
134
|
-
|
136
|
+
return Err(AnyException::new(
|
135
137
|
"ArgumentError",
|
136
138
|
Some(&format!(
|
137
139
|
"Cannot convert argument #{} to a WebAssembly value. Only integers and floats are supported. Given `{:?}`.",
|
138
140
|
nth + 1,
|
139
141
|
ty
|
140
142
|
))));
|
141
|
-
unreachable!()
|
142
143
|
}
|
143
144
|
};
|
144
145
|
|
@@ -147,14 +148,17 @@ impl ExportedFunctions {
|
|
147
148
|
|
148
149
|
let results = function
|
149
150
|
.call(function_arguments.as_slice())
|
150
|
-
.map_err(|e|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
151
|
+
.map_err(|e| AnyException::new("RuntimeError", Some(&format!("{}", e))))?;
|
152
|
+
|
153
|
+
if results.len() > 0 {
|
154
|
+
Ok(match results[0] {
|
155
|
+
runtime::Value::I32(result) => Fixnum::new(result as i64).into(),
|
156
|
+
runtime::Value::I64(result) => Fixnum::new(result).into(),
|
157
|
+
runtime::Value::F32(result) => Float::new(result as f64).into(),
|
158
|
+
runtime::Value::F64(result) => Float::new(result).into(),
|
159
|
+
})
|
160
|
+
} else {
|
161
|
+
Ok(NilClass::new().into())
|
158
162
|
}
|
159
163
|
}
|
160
164
|
}
|
@@ -167,27 +171,45 @@ wrappable_struct!(
|
|
167
171
|
|
168
172
|
class!(RubyExportedFunctions);
|
169
173
|
|
174
|
+
#[rustfmt::skip]
|
175
|
+
methods!(
|
176
|
+
RubyExportedFunctions,
|
177
|
+
itself,
|
178
|
+
|
179
|
+
// Glue code to call the `ExportedFunctions.respond_to` method.
|
180
|
+
fn ruby_exported_functions_method_exists(symbol: Symbol, _include_private: Boolean) -> Boolean {
|
181
|
+
unwrap_or_raise(|| {
|
182
|
+
let symbol = symbol?;
|
183
|
+
let instance = itself.get_data(&*EXPORTED_FUNCTIONS_WRAPPER);
|
184
|
+
|
185
|
+
Ok(Boolean::new(instance.respond_to_missing(symbol.to_str())))
|
186
|
+
})
|
187
|
+
}
|
188
|
+
);
|
189
|
+
|
170
190
|
/// Glue code to call the `ExportedFunctions.method_missing` method.
|
171
191
|
pub extern "C" fn ruby_exported_functions_method_missing(
|
172
192
|
argc: Argc,
|
173
193
|
argv: *const AnyObject,
|
174
194
|
itself: RubyExportedFunctions,
|
175
195
|
) -> AnyObject {
|
176
|
-
|
196
|
+
unwrap_or_raise(|| {
|
197
|
+
let arguments = Value::from(0);
|
177
198
|
|
178
|
-
|
179
|
-
|
199
|
+
unsafe {
|
200
|
+
let argv_pointer: *const Value = mem::transmute(argv);
|
180
201
|
|
181
|
-
|
182
|
-
|
202
|
+
class::rb_scan_args(argc, argv_pointer, str_to_cstring("*").as_ptr(), &arguments)
|
203
|
+
};
|
183
204
|
|
184
|
-
|
185
|
-
|
186
|
-
|
205
|
+
let mut arguments = Array::from(arguments);
|
206
|
+
let method_name = unsafe { arguments.shift().to::<Symbol>() };
|
207
|
+
let method_name = method_name.to_str();
|
187
208
|
|
188
|
-
|
189
|
-
|
190
|
-
|
209
|
+
itself
|
210
|
+
.get_data(&*EXPORTED_FUNCTIONS_WRAPPER)
|
211
|
+
.method_missing(method_name, arguments)
|
212
|
+
})
|
191
213
|
}
|
192
214
|
|
193
215
|
/// The `Instance` Ruby class.
|
@@ -199,20 +221,17 @@ pub struct Instance {
|
|
199
221
|
impl Instance {
|
200
222
|
/// Create a new instance of the `Instance` Ruby class.
|
201
223
|
/// The constructor receives bytes from a string.
|
202
|
-
pub fn new(bytes: &[u8]) -> Self {
|
224
|
+
pub fn new(bytes: &[u8]) -> Result<Self, AnyException> {
|
203
225
|
let import_object = imports! {};
|
204
|
-
let instance = Rc::new(
|
205
|
-
runtime::instantiate(bytes, &import_object)
|
206
|
-
.map_err(|e| {
|
207
|
-
VM::raise_ex(AnyException::new(
|
208
|
-
"RuntimeError",
|
209
|
-
Some(&format!("Failed to instantiate the module:\n {}", e)),
|
210
|
-
))
|
211
|
-
})
|
212
|
-
.unwrap(),
|
213
|
-
);
|
214
226
|
|
215
|
-
Self {
|
227
|
+
Ok(Self {
|
228
|
+
instance: Rc::new(runtime::instantiate(bytes, &import_object).map_err(|e| {
|
229
|
+
AnyException::new(
|
230
|
+
"RuntimeError",
|
231
|
+
Some(&format!("Failed to instantiate the module:\n {}", e)),
|
232
|
+
)
|
233
|
+
})?),
|
234
|
+
})
|
216
235
|
}
|
217
236
|
}
|
218
237
|
|
@@ -224,46 +243,56 @@ class!(RubyInstance);
|
|
224
243
|
methods!(
|
225
244
|
RubyInstance,
|
226
245
|
_itself,
|
246
|
+
|
227
247
|
// Glue code to call the `Instance.new` method.
|
228
248
|
fn ruby_instance_new(bytes: RString) -> AnyObject {
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
249
|
+
unwrap_or_raise(|| {
|
250
|
+
let instance = Instance::new(
|
251
|
+
bytes
|
252
|
+
.map_err(|_| {
|
253
|
+
AnyException::new(
|
254
|
+
"ArgumentError",
|
255
|
+
Some("WebAssembly module must be represented by Ruby bytes only."),
|
256
|
+
)
|
257
|
+
})?
|
258
|
+
.to_bytes_unchecked(),
|
259
|
+
)?;
|
260
|
+
let exported_functions = ExportedFunctions::new(instance.instance.clone());
|
261
|
+
|
262
|
+
let memory = instance
|
263
|
+
.instance
|
264
|
+
.exports()
|
265
|
+
.find_map(|(_, export)| match export {
|
266
|
+
Export::Memory(memory) => Some(Memory::new(Rc::new(memory))),
|
267
|
+
_ => None,
|
236
268
|
})
|
237
|
-
.
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
.find_map(|(_, export)| match export {
|
246
|
-
Export::Memory(memory) => Some(Memory::new(Rc::new(memory))),
|
247
|
-
_ => None,
|
248
|
-
})
|
249
|
-
.ok_or_else(|| VM::raise_ex(AnyException::new("RuntimeError", Some("The WebAssembly module has no exported memory."))))
|
250
|
-
.unwrap();
|
269
|
+
.ok_or_else(|| {
|
270
|
+
AnyException::new(
|
271
|
+
"RuntimeError",
|
272
|
+
Some("The WebAssembly module has no exported memory."),
|
273
|
+
)
|
274
|
+
})?;
|
275
|
+
|
276
|
+
let wasmer_module = Module::from_existing("Wasmer");
|
251
277
|
|
252
|
-
|
253
|
-
|
278
|
+
let mut ruby_instance: AnyObject = wasmer_module
|
279
|
+
.get_nested_class("Instance")
|
280
|
+
.wrap_data(instance, &*INSTANCE_WRAPPER);
|
254
281
|
|
255
|
-
|
256
|
-
|
282
|
+
let ruby_exported_functions: RubyExportedFunctions = wasmer_module
|
283
|
+
.get_nested_class("ExportedFunctions")
|
257
284
|
.wrap_data(exported_functions, &*EXPORTED_FUNCTIONS_WRAPPER);
|
258
285
|
|
259
|
-
|
286
|
+
ruby_instance.instance_variable_set("@exports", ruby_exported_functions);
|
260
287
|
|
261
|
-
|
262
|
-
|
288
|
+
let ruby_memory: RubyMemory = wasmer_module
|
289
|
+
.get_nested_class("Memory")
|
290
|
+
.wrap_data(memory, &*MEMORY_WRAPPER);
|
263
291
|
|
264
|
-
|
292
|
+
ruby_instance.instance_variable_set("@memory", ruby_memory);
|
265
293
|
|
266
|
-
|
294
|
+
Ok(ruby_instance)
|
295
|
+
})
|
267
296
|
}
|
268
297
|
|
269
298
|
// Glue code to call the `Instance.exports` getter method.
|