wasmer 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.cargo/config +6 -0
- data/.circleci/config.yml +70 -0
- data/.github/ISSUE_TEMPLATE/---bug-report.md +36 -0
- data/.github/ISSUE_TEMPLATE/---feature-request.md +26 -0
- data/.github/ISSUE_TEMPLATE/--question.md +16 -0
- data/.gitignore +3 -0
- data/Cargo.lock +1230 -0
- data/Cargo.toml +21 -0
- data/Gemfile +9 -0
- data/README.md +279 -0
- data/Rakefile +21 -0
- data/justfile +23 -0
- data/lib/wasmer.rb +6 -0
- data/lib/wasmer/version.rb +3 -0
- data/src/instance.rs +282 -0
- data/src/lib.rs +98 -0
- data/src/memory/mod.rs +120 -0
- data/src/memory/view.rs +127 -0
- data/src/module.rs +28 -0
- data/wasmer.gemspec +29 -0
- metadata +120 -0
data/Cargo.toml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
[package]
|
2
|
+
publish = false
|
3
|
+
name = "ruby-ext-wasm"
|
4
|
+
version = "0.1.1"
|
5
|
+
authors = ["Ivan Enderlin <ivan.enderlin@hoa-project.net>"]
|
6
|
+
edition = "2018"
|
7
|
+
description = "Ruby extension to run WebAssembly binaries"
|
8
|
+
readme = "README.md"
|
9
|
+
repository = "https://github.com/wasmerio/ruby-ext-wasm"
|
10
|
+
keywords = ["ruby", "extension", "webassembly"]
|
11
|
+
categories = ["wasm"]
|
12
|
+
|
13
|
+
[lib]
|
14
|
+
name = "wasmer"
|
15
|
+
crate-type = ["dylib"]
|
16
|
+
|
17
|
+
[dependencies]
|
18
|
+
wasmer-runtime = "0.3.0"
|
19
|
+
wasmer-runtime-core = "0.3.0"
|
20
|
+
rutie = "0.5.4"
|
21
|
+
lazy_static = "1.3.0"
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,279 @@
|
|
1
|
+
<p align="center">
|
2
|
+
<a href="https://wasmer.io" target="_blank" rel="noopener noreferrer">
|
3
|
+
<img width="400" src="https://raw.githubusercontent.com/wasmerio/wasmer/master/logo.png" alt="Wasmer logo">
|
4
|
+
</a>
|
5
|
+
</p>
|
6
|
+
|
7
|
+
<p align="center">
|
8
|
+
<a href="https://spectrum.chat/wasmer">
|
9
|
+
<img src="https://withspectrum.github.io/badge/badge.svg" alt="Join the Wasmer Community"></a>
|
10
|
+
<a href="https://github.com/wasmerio/wasmer/blob/master/LICENSE">
|
11
|
+
<img src="https://img.shields.io/github/license/wasmerio/wasmer.svg" alt="License"></a>
|
12
|
+
</p>
|
13
|
+
|
14
|
+
# The Ruby extension to run WebAssembly
|
15
|
+
|
16
|
+
The goal of the project is to be able to run WebAssembly binaries from
|
17
|
+
Ruby directly. So much fun coming!
|
18
|
+
|
19
|
+
_Under Development, don't use it in product yet_.
|
20
|
+
|
21
|
+
## What is WebAssembly?
|
22
|
+
|
23
|
+
Quoting [the WebAssembly site](https://webassembly.org/):
|
24
|
+
|
25
|
+
> WebAssembly (abbreviated Wasm) is a binary instruction format for a
|
26
|
+
> stack-based virtual machine. Wasm is designed as a portable target
|
27
|
+
> for compilation of high-level languages like C/C++/Rust, enabling
|
28
|
+
> deployment on the web for client and server applications.
|
29
|
+
|
30
|
+
About speed:
|
31
|
+
|
32
|
+
> WebAssembly aims to execute at native speed by taking advantage of
|
33
|
+
> [common hardware
|
34
|
+
> capabilities](https://webassembly.org/docs/portability/#assumptions-for-efficient-execution)
|
35
|
+
> available on a wide range of platforms.
|
36
|
+
|
37
|
+
About safety:
|
38
|
+
|
39
|
+
> WebAssembly describes a memory-safe, sandboxed [execution
|
40
|
+
> environment](https://webassembly.org/docs/semantics/#linear-memory) […].
|
41
|
+
|
42
|
+
## Example
|
43
|
+
|
44
|
+
There is a toy program in `examples/simple.rs`, written in Rust (or
|
45
|
+
any other language that compiles to Wasm):
|
46
|
+
|
47
|
+
```rust
|
48
|
+
#[no_mangle]
|
49
|
+
pub extern fn sum(x: i32, y: i32) -> i32 {
|
50
|
+
x + y
|
51
|
+
}
|
52
|
+
```
|
53
|
+
|
54
|
+
Once this program compiled to WebAssembly, we end up with a
|
55
|
+
`examples/simple.wasm` binary file.
|
56
|
+
|
57
|
+
Then, we can execute it in Ruby (!) with the `examples/simple.rb` file:
|
58
|
+
|
59
|
+
```rb
|
60
|
+
require "wasmer"
|
61
|
+
|
62
|
+
bytes = IO.read "simple.wasm", mode: "rb"
|
63
|
+
instance = Instance.new bytes
|
64
|
+
puts instance.exports.sum 1, 2
|
65
|
+
```
|
66
|
+
|
67
|
+
And then, finally, enjoy by running:
|
68
|
+
|
69
|
+
```sh
|
70
|
+
$ ruby simple.rb
|
71
|
+
3
|
72
|
+
```
|
73
|
+
|
74
|
+
## API documentation
|
75
|
+
|
76
|
+
### The `Instance` class
|
77
|
+
|
78
|
+
Instantiates a WebAssembly module represented by bytes, and calls exported functions on it:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
require "wasmer"
|
82
|
+
|
83
|
+
# Get the Wasm module as bytes.
|
84
|
+
wasm_bytes = IO.read "my_program.wasm", mode: "rb"
|
85
|
+
|
86
|
+
# Instantiates the Wasm module.
|
87
|
+
instance = Instance.new wasm_bytes
|
88
|
+
|
89
|
+
# Call a function on it.
|
90
|
+
result = instance.exports.sum 1, 2
|
91
|
+
|
92
|
+
puts result # 3
|
93
|
+
```
|
94
|
+
|
95
|
+
All exported functions are accessible on the `exports`
|
96
|
+
getter. Arguments of these functions are automatically casted to
|
97
|
+
WebAssembly values.
|
98
|
+
|
99
|
+
The `memory` getter exposes the `Memory` class representing the memory
|
100
|
+
of that particular instance, e.g.:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
view = instance.memory.uint8_view
|
104
|
+
```
|
105
|
+
|
106
|
+
See below for more information.
|
107
|
+
|
108
|
+
### The `Memory` class
|
109
|
+
|
110
|
+
A WebAssembly instance has its own memory, represented by the `Memory`
|
111
|
+
class. It is accessible by the `Instance.memory` getter.
|
112
|
+
|
113
|
+
The `Memory` class offers methods to create views of the memory
|
114
|
+
internal buffer, e.g. `uint8_view`, `int8_view`, `uint16_view`
|
115
|
+
etc. All these methods accept one optional argument: `offset`, to
|
116
|
+
subset the memory buffer at a particular offset. These methods return
|
117
|
+
respectively a `*Array` object, i.e. `uint8_view` returns a
|
118
|
+
`Uint8Array` object etc.
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
offset = 7
|
122
|
+
view = instance.memory.uint8_view offset
|
123
|
+
|
124
|
+
puts view[0]
|
125
|
+
```
|
126
|
+
|
127
|
+
#### The `*Array` classes
|
128
|
+
|
129
|
+
These classes represent views over a memory buffer of an instance.
|
130
|
+
|
131
|
+
| Class | View buffer as a sequence of… | Bytes per element |
|
132
|
+
|-|-|-|
|
133
|
+
| `Int8Array` | `int8` | 1 |
|
134
|
+
| `Uint8Array` | `uint8` | 1 |
|
135
|
+
| `Int16Array` | `int16` | 2 |
|
136
|
+
| `Uint16Array` | `uint16` | 2 |
|
137
|
+
| `Int32Array` | `int32` | 4 |
|
138
|
+
| `Uint32Array` | `uint32` | 4 |
|
139
|
+
|
140
|
+
All these classes share the same implementation. Taking the example of
|
141
|
+
`Uint8Array`, the class looks like this:
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
class Uint8Array
|
145
|
+
def bytes_per_element
|
146
|
+
def length
|
147
|
+
def [](index)
|
148
|
+
def []=(index, value)
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
Let's see it in action:
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
require "wasmer"
|
156
|
+
|
157
|
+
# Get the Wasm module as bytes.
|
158
|
+
wasm_bytes = IO.read "my_program.wasm", mode: "rb"
|
159
|
+
|
160
|
+
# Instantiates the Wasm module.
|
161
|
+
instance = Instance.new wasm_bytes
|
162
|
+
|
163
|
+
# Call a function that returns a pointer to a string for instance.
|
164
|
+
pointer = instance.exports.return_string
|
165
|
+
|
166
|
+
# Get the memory view, with the offset set to `pointer` (default is 0).
|
167
|
+
memory = instance.memory.uint8_view pointer
|
168
|
+
|
169
|
+
# Read the string pointed by the pointer.
|
170
|
+
nth = 0
|
171
|
+
string = ""
|
172
|
+
|
173
|
+
while true
|
174
|
+
char = memory[nth]
|
175
|
+
|
176
|
+
if 0 == char
|
177
|
+
break
|
178
|
+
end
|
179
|
+
|
180
|
+
string += char.chr
|
181
|
+
nth += 1
|
182
|
+
end
|
183
|
+
|
184
|
+
puts string # Hello, World!
|
185
|
+
```
|
186
|
+
|
187
|
+
Notice that `*Array` treat bytes in little-endian, as required by the
|
188
|
+
WebAssembly specification, [Chapter Structure, Section Instructions,
|
189
|
+
Sub-Section Memory
|
190
|
+
Instructions](https://webassembly.github.io/spec/core/syntax/instructions.html#memory-instructions):
|
191
|
+
|
192
|
+
> All values are read and written in [little
|
193
|
+
> endian](https://en.wikipedia.org/wiki/Endianness#Little-endian) byte
|
194
|
+
> order.
|
195
|
+
|
196
|
+
Each view shares the same memory buffer internally. Let's have some fun:
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
int8 = instance.memory.int8_view
|
200
|
+
int16 = instance.memory.int16_view
|
201
|
+
int32 = instance.memory.int32_view
|
202
|
+
|
203
|
+
b₁
|
204
|
+
┌┬┬┬┬┬┬┐
|
205
|
+
int8[0] = 0b00000001
|
206
|
+
b₂
|
207
|
+
┌┬┬┬┬┬┬┐
|
208
|
+
int8[1] = 0b00000100
|
209
|
+
b₃
|
210
|
+
┌┬┬┬┬┬┬┐
|
211
|
+
int8[2] = 0b00010000
|
212
|
+
b₄
|
213
|
+
┌┬┬┬┬┬┬┐
|
214
|
+
int8[3] = 0b01000000
|
215
|
+
|
216
|
+
// No surprise with the following assertions.
|
217
|
+
b₁
|
218
|
+
┌┬┬┬┬┬┬┐
|
219
|
+
assert_equal 0b00000001, int8[0]
|
220
|
+
b₂
|
221
|
+
┌┬┬┬┬┬┬┐
|
222
|
+
assert_equal 0b00000100, int8[1]
|
223
|
+
b₃
|
224
|
+
┌┬┬┬┬┬┬┐
|
225
|
+
assert_equal 0b00010000, int8[2]
|
226
|
+
b₄
|
227
|
+
┌┬┬┬┬┬┬┐
|
228
|
+
assert_equal 0b01000000, int8[3]
|
229
|
+
|
230
|
+
// The `int16` view reads 2 bytes.
|
231
|
+
b₂ b₁
|
232
|
+
┌┬┬┬┬┬┬┐ ┌┬┬┬┬┬┬┐
|
233
|
+
assert_equal 0b00000100_00000001, int16[0]
|
234
|
+
b₄ b₃
|
235
|
+
┌┬┬┬┬┬┬┐ ┌┬┬┬┬┬┬┐
|
236
|
+
assert_equal 0b01000000_00010000, int16[1]
|
237
|
+
|
238
|
+
// The `int32` view reads 4 bytes.
|
239
|
+
b₄ b₃ b₂ b₁
|
240
|
+
┌┬┬┬┬┬┬┐ ┌┬┬┬┬┬┬┐ ┌┬┬┬┬┬┬┐ ┌┬┬┬┬┬┬┐
|
241
|
+
assert_equal 0b01000000_00010000_00000100_00000001, int32[0]
|
242
|
+
```
|
243
|
+
|
244
|
+
### The `Module` class
|
245
|
+
|
246
|
+
The `Module` class contains one static method `validate`, that checks
|
247
|
+
whether the given bytes represent valid WebAssembly bytes:
|
248
|
+
|
249
|
+
```ruby
|
250
|
+
require "wasmer"
|
251
|
+
|
252
|
+
wasm_bytes = IO.read "my_program.wasm", mode: "rb"
|
253
|
+
|
254
|
+
if not Module.validate wasm_bytes
|
255
|
+
puts "The program seems corrupted."
|
256
|
+
end
|
257
|
+
```
|
258
|
+
|
259
|
+
This function returns a boolean.
|
260
|
+
|
261
|
+
## Install and Testing
|
262
|
+
|
263
|
+
To compile the entire project, run the following commands:
|
264
|
+
|
265
|
+
```sh
|
266
|
+
$ just build
|
267
|
+
$ just test
|
268
|
+
$ ruby examples/simple.rb
|
269
|
+
```
|
270
|
+
|
271
|
+
(Yes, you need [`just`](https://github.com/casey/just/)).
|
272
|
+
|
273
|
+
## License
|
274
|
+
|
275
|
+
The entire project is under the BSD-3-Clause license. Please read [the
|
276
|
+
`LICENSE` file][license].
|
277
|
+
|
278
|
+
|
279
|
+
[license]: https://github.com/wasmerio/wasmer/blob/master/LICENSE
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require "rbconfig"
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require "rake/testtask"
|
4
|
+
|
5
|
+
desc 'Build the Rust extension'
|
6
|
+
task :build_lib do
|
7
|
+
sh 'cargo build --release'
|
8
|
+
end
|
9
|
+
|
10
|
+
desc 'Install the bundle'
|
11
|
+
task :bundle_install do
|
12
|
+
sh 'bundle install'
|
13
|
+
end
|
14
|
+
|
15
|
+
Rake::TestTask.new(test: [:bundle_install, :build_lib]) do |t|
|
16
|
+
t.libs << "tests"
|
17
|
+
t.libs << "lib"
|
18
|
+
t.test_files = FileList["tests/**/*_test.rb"]
|
19
|
+
end
|
20
|
+
|
21
|
+
task :default => :test
|
data/justfile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Compile and install the Ruby extension.
|
2
|
+
build:
|
3
|
+
rake build_lib
|
4
|
+
|
5
|
+
# Run the tests.
|
6
|
+
test:
|
7
|
+
rake test
|
8
|
+
|
9
|
+
# Build the `.gem` file.
|
10
|
+
gem:
|
11
|
+
rake build
|
12
|
+
|
13
|
+
# Clean the project.
|
14
|
+
clean:
|
15
|
+
cargo clean
|
16
|
+
rake clean
|
17
|
+
rake clobber
|
18
|
+
|
19
|
+
|
20
|
+
# Local Variables:
|
21
|
+
# mode: makefile
|
22
|
+
# End:
|
23
|
+
# vim: set ft=make :
|
data/lib/wasmer.rb
ADDED
data/src/instance.rs
ADDED
@@ -0,0 +1,282 @@
|
|
1
|
+
//! The `Instance` WebAssembly class.
|
2
|
+
|
3
|
+
use crate::memory::{Memory, RubyMemory, MEMORY_WRAPPER};
|
4
|
+
use lazy_static::lazy_static;
|
5
|
+
use rutie::{
|
6
|
+
class, methods,
|
7
|
+
rubysys::{class, value::ValueType},
|
8
|
+
types::{Argc, Value},
|
9
|
+
util::str_to_cstring,
|
10
|
+
wrappable_struct, AnyException, AnyObject, Array, Class, Exception, Fixnum, Float, Object,
|
11
|
+
RString, Symbol, VM,
|
12
|
+
};
|
13
|
+
use std::{mem, rc::Rc};
|
14
|
+
use wasmer_runtime::{self as runtime, imports, Export};
|
15
|
+
use wasmer_runtime_core::types::Type;
|
16
|
+
|
17
|
+
/// The `ExportedFunctions` Ruby class.
|
18
|
+
pub struct ExportedFunctions {
|
19
|
+
/// The WebAssembly runtime.
|
20
|
+
instance: Rc<runtime::Instance>,
|
21
|
+
}
|
22
|
+
|
23
|
+
impl ExportedFunctions {
|
24
|
+
/// Create a new instance of the `ExportedFunctions` Ruby class.
|
25
|
+
pub fn new(instance: Rc<runtime::Instance>) -> Self {
|
26
|
+
Self { instance }
|
27
|
+
}
|
28
|
+
|
29
|
+
/// Call an exported function on the given WebAssembly instance.
|
30
|
+
pub fn method_missing(&self, method_name: &str, arguments: Array) -> AnyObject {
|
31
|
+
let function = self
|
32
|
+
.instance
|
33
|
+
.dyn_func(method_name)
|
34
|
+
.map_err(|_| {
|
35
|
+
VM::raise_ex(AnyException::new(
|
36
|
+
"RuntimeError",
|
37
|
+
Some(&format!("Function `{}` does not exist.", method_name)),
|
38
|
+
))
|
39
|
+
})
|
40
|
+
.unwrap();
|
41
|
+
let signature = function.signature();
|
42
|
+
let parameters = signature.params();
|
43
|
+
let number_of_parameters = parameters.len() as isize;
|
44
|
+
let number_of_arguments = arguments.length() as isize;
|
45
|
+
let diff: isize = number_of_parameters - number_of_arguments;
|
46
|
+
|
47
|
+
if diff > 0 {
|
48
|
+
VM::raise_ex(AnyException::new(
|
49
|
+
"ArgumentError",
|
50
|
+
Some(&format!(
|
51
|
+
"Missing {} argument(s) when calling `{}`: Expect {} argument(s), given {}.",
|
52
|
+
diff, method_name, number_of_parameters, number_of_arguments
|
53
|
+
)),
|
54
|
+
));
|
55
|
+
unreachable!();
|
56
|
+
} else if diff < 0 {
|
57
|
+
VM::raise_ex(AnyException::new(
|
58
|
+
"ArgumentError",
|
59
|
+
Some(&format!(
|
60
|
+
"Given {} extra argument(s) when calling `{}`: Expect {} argument(s), given {}.",
|
61
|
+
diff.abs(), method_name, number_of_parameters, number_of_arguments
|
62
|
+
)),
|
63
|
+
));
|
64
|
+
unreachable!();
|
65
|
+
}
|
66
|
+
|
67
|
+
let mut function_arguments =
|
68
|
+
Vec::<runtime::Value>::with_capacity(number_of_parameters as usize);
|
69
|
+
|
70
|
+
for (nth, (parameter, argument)) in parameters.iter().zip(arguments.into_iter()).enumerate()
|
71
|
+
{
|
72
|
+
let value = match (parameter, argument.ty()) {
|
73
|
+
(Type::I32, ValueType::Fixnum) => runtime::Value::I32(
|
74
|
+
argument
|
75
|
+
.try_convert_to::<Fixnum>()
|
76
|
+
.map_err(|_| {
|
77
|
+
VM::raise_ex(AnyException::new(
|
78
|
+
"TypeError",
|
79
|
+
Some(&format!(
|
80
|
+
"Cannot convert argument #{} to a WebAssembly i32 value.",
|
81
|
+
nth + 1
|
82
|
+
)),
|
83
|
+
))
|
84
|
+
})
|
85
|
+
.unwrap()
|
86
|
+
.to_i32(),
|
87
|
+
),
|
88
|
+
(Type::I64, ValueType::Fixnum) => runtime::Value::I64(
|
89
|
+
argument
|
90
|
+
.try_convert_to::<Fixnum>()
|
91
|
+
.map_err(|_| {
|
92
|
+
VM::raise_ex(AnyException::new(
|
93
|
+
"TypeError",
|
94
|
+
Some(&format!(
|
95
|
+
"Cannot convert argument #{} to a WebAssembly i64 value.",
|
96
|
+
nth + 1
|
97
|
+
)),
|
98
|
+
))
|
99
|
+
})
|
100
|
+
.unwrap()
|
101
|
+
.to_i64(),
|
102
|
+
),
|
103
|
+
(Type::F32, ValueType::Float) => runtime::Value::F32(
|
104
|
+
argument
|
105
|
+
.try_convert_to::<Float>()
|
106
|
+
.map_err(|_| {
|
107
|
+
VM::raise_ex(AnyException::new(
|
108
|
+
"TypeError",
|
109
|
+
Some(&format!(
|
110
|
+
"Cannot convert argument #{} to a WebAssembly f32 value.",
|
111
|
+
nth + 1
|
112
|
+
)),
|
113
|
+
))
|
114
|
+
})
|
115
|
+
.unwrap()
|
116
|
+
.to_f64() as f32,
|
117
|
+
),
|
118
|
+
(Type::F64, ValueType::Float) => runtime::Value::F64(
|
119
|
+
argument
|
120
|
+
.try_convert_to::<Float>()
|
121
|
+
.map_err(|_| {
|
122
|
+
VM::raise_ex(AnyException::new(
|
123
|
+
"TypeError",
|
124
|
+
Some(&format!(
|
125
|
+
"Cannot convert argument #{} to a WebAssembly f64 value.",
|
126
|
+
nth + 1
|
127
|
+
)),
|
128
|
+
))
|
129
|
+
})
|
130
|
+
.unwrap()
|
131
|
+
.to_f64(),
|
132
|
+
),
|
133
|
+
(_, ty) => {
|
134
|
+
VM::raise_ex(AnyException::new(
|
135
|
+
"ArgumentError",
|
136
|
+
Some(&format!(
|
137
|
+
"Cannot convert argument #{} to a WebAssembly value. Only integers and floats are supported. Given `{:?}`.",
|
138
|
+
nth + 1,
|
139
|
+
ty
|
140
|
+
))));
|
141
|
+
unreachable!()
|
142
|
+
}
|
143
|
+
};
|
144
|
+
|
145
|
+
function_arguments.push(value);
|
146
|
+
}
|
147
|
+
|
148
|
+
let results = function
|
149
|
+
.call(function_arguments.as_slice())
|
150
|
+
.map_err(|e| VM::raise_ex(AnyException::new("RuntimeError", Some(&format!("{}", e)))))
|
151
|
+
.unwrap();
|
152
|
+
|
153
|
+
match results[0] {
|
154
|
+
runtime::Value::I32(result) => Fixnum::new(result as i64).into(),
|
155
|
+
runtime::Value::I64(result) => Fixnum::new(result).into(),
|
156
|
+
runtime::Value::F32(result) => Float::new(result as f64).into(),
|
157
|
+
runtime::Value::F64(result) => Float::new(result).into(),
|
158
|
+
}
|
159
|
+
}
|
160
|
+
}
|
161
|
+
|
162
|
+
wrappable_struct!(
|
163
|
+
ExportedFunctions,
|
164
|
+
ExportedFunctionsWrapper,
|
165
|
+
EXPORTED_FUNCTIONS_WRAPPER
|
166
|
+
);
|
167
|
+
|
168
|
+
class!(RubyExportedFunctions);
|
169
|
+
|
170
|
+
/// Glue code to call the `ExportedFunctions.method_missing` method.
|
171
|
+
pub extern "C" fn ruby_exported_functions_method_missing(
|
172
|
+
argc: Argc,
|
173
|
+
argv: *const AnyObject,
|
174
|
+
itself: RubyExportedFunctions,
|
175
|
+
) -> AnyObject {
|
176
|
+
let arguments = Value::from(0);
|
177
|
+
|
178
|
+
unsafe {
|
179
|
+
let argv_pointer: *const Value = mem::transmute(argv);
|
180
|
+
|
181
|
+
class::rb_scan_args(argc, argv_pointer, str_to_cstring("*").as_ptr(), &arguments)
|
182
|
+
};
|
183
|
+
|
184
|
+
let mut arguments = Array::from(arguments);
|
185
|
+
let method_name = unsafe { arguments.shift().to::<Symbol>() };
|
186
|
+
let method_name = method_name.to_str();
|
187
|
+
|
188
|
+
itself
|
189
|
+
.get_data(&*EXPORTED_FUNCTIONS_WRAPPER)
|
190
|
+
.method_missing(method_name, arguments)
|
191
|
+
}
|
192
|
+
|
193
|
+
/// The `Instance` Ruby class.
|
194
|
+
pub struct Instance {
|
195
|
+
/// The WebAssembly instance.
|
196
|
+
instance: Rc<runtime::Instance>,
|
197
|
+
}
|
198
|
+
|
199
|
+
impl Instance {
|
200
|
+
/// Create a new instance of the `Instance` Ruby class.
|
201
|
+
/// The constructor receives bytes from a string.
|
202
|
+
pub fn new(bytes: &[u8]) -> Self {
|
203
|
+
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
|
+
|
215
|
+
Self { instance }
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
219
|
+
wrappable_struct!(Instance, InstanceWrapper, INSTANCE_WRAPPER);
|
220
|
+
|
221
|
+
class!(RubyInstance);
|
222
|
+
|
223
|
+
#[rustfmt::skip]
|
224
|
+
methods!(
|
225
|
+
RubyInstance,
|
226
|
+
_itself,
|
227
|
+
// Glue code to call the `Instance.new` method.
|
228
|
+
fn ruby_instance_new(bytes: RString) -> AnyObject {
|
229
|
+
let instance = Instance::new(
|
230
|
+
bytes
|
231
|
+
.map_err(|_| {
|
232
|
+
VM::raise_ex(AnyException::new(
|
233
|
+
"ArgumentError",
|
234
|
+
Some("WebAssembly module must be represented by Ruby bytes only."),
|
235
|
+
))
|
236
|
+
})
|
237
|
+
.unwrap()
|
238
|
+
.to_bytes_unchecked(),
|
239
|
+
);
|
240
|
+
let exported_functions = ExportedFunctions::new(instance.instance.clone());
|
241
|
+
|
242
|
+
let memory = instance
|
243
|
+
.instance
|
244
|
+
.exports()
|
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();
|
251
|
+
|
252
|
+
let mut ruby_instance: AnyObject =
|
253
|
+
Class::from_existing("Instance").wrap_data(instance, &*INSTANCE_WRAPPER);
|
254
|
+
|
255
|
+
let ruby_exported_functions: RubyExportedFunctions =
|
256
|
+
Class::from_existing("ExportedFunctions")
|
257
|
+
.wrap_data(exported_functions, &*EXPORTED_FUNCTIONS_WRAPPER);
|
258
|
+
|
259
|
+
ruby_instance.instance_variable_set("@exports", ruby_exported_functions);
|
260
|
+
|
261
|
+
let ruby_memory: RubyMemory =
|
262
|
+
Class::from_existing("Memory").wrap_data(memory, &*MEMORY_WRAPPER);
|
263
|
+
|
264
|
+
ruby_instance.instance_variable_set("@memory", ruby_memory);
|
265
|
+
|
266
|
+
ruby_instance
|
267
|
+
}
|
268
|
+
|
269
|
+
// Glue code to call the `Instance.exports` getter method.
|
270
|
+
fn ruby_instance_exported_functions() -> RubyExportedFunctions {
|
271
|
+
unsafe {
|
272
|
+
_itself
|
273
|
+
.instance_variable_get("@exports")
|
274
|
+
.to::<RubyExportedFunctions>()
|
275
|
+
}
|
276
|
+
}
|
277
|
+
|
278
|
+
// Glue code to call the `Instance.memory` getter method.
|
279
|
+
fn ruby_instance_memory() -> RubyMemory {
|
280
|
+
unsafe { _itself.instance_variable_get("@memory").to::<RubyMemory>() }
|
281
|
+
}
|
282
|
+
);
|