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.
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
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'minitest', '~> 5.10'
7
+ gem 'minitest-reporters', '~> 1.1'
8
+ gem 'color_pound_spec_reporter', '~> 0.0.6'
9
+ end
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
@@ -0,0 +1,6 @@
1
+ require 'wasmer/version'
2
+ require 'rutie'
3
+
4
+ module Wasmer
5
+ Rutie.new(:wasmer).init 'Init_wasmer', __dir__
6
+ end
@@ -0,0 +1,3 @@
1
+ module Wasmer
2
+ VERSION = "0.1.1"
3
+ end
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
+ );