wasmer 0.1.1
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 +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
|
+
);
|