wasmer 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|