Ruby.has :red, :background
(window.JavaScript || TypeScript).then("yellow");
Rust::<BackGround>::purple().unwrap();
other_lang { "Lua" = green.background, "and" = so.on }
Other code or command sample has default gray back
ruby.wasm
mec
$ cargo install mec
# fib.rb def fib(n) case n when 0 0 when 1..2 2 else fib(n - 1) + fib(n - 2) end end
# fib.export.rbs def fib: (Integer) -> Integer
※ We have another option, but recommend to make this
$ mec --no-wasi fib.rb ... running: `cd .. && rm -rf work-mrubyedge-bhuxkrgcgOe5TAmDWFiMkgF5uVbnS9lR` [ok] wasm file is generated: fib2.wasm $ file fib.wasm fib.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)
fib
$ wasm-objdump -x -j Export ./fib.wasm fib.wasm: file format wasm 0x1 module name: <mywasm.wasm> Section Details: Export[3]: - memory[0] -> "memory" - func[417] <fib.command_export> -> "fib"
$ wasmedge ./fib.wasm fib 15 610 $ wasmedge ./fib.wasm fib 20 6765 # ...
wasm.html
<script async type="text/javascript"> window.fire = function(e) { WebAssembly.instantiateStreaming(fetch("./fib.wasm"), {}).then(function (o) { let value = document.getElementById("myValue").value; let answer = o.instance.exports.fib(parseInt(value)); document.getElementById("myAnswer").value = answer; });}; </script>
calc fib fib( ) =
# rk2024.rb def runit(arg) answer = arg + 42 show_answer(answer) end
# rk2024.export.rbs def runit: (Integer) -> void
# rk2024.import.rbs def show_answer: (Integer) -> void
// Will be imvoked via main() function show_answer(ans) { console.log("answer = ", ans); } // Specify what func to import const importObject = { env: {show_answer: show_answer} }; WebAssembly.instantiateStreaming(fetch("./rk2024.wasm"), importObject).then( (obj) => { // Call exported main() after load, with arg 21 obj.instance.exports.runit(21); }, );
Show anwser?
0x00 0x61 0x73 0x6D
0x01 0x00 0x00 0x00
$ wasm-objdump -d ./fib.wasm | less ... 0006ad func[12] <fib>: 0006ae: 03 7f | local[2..4] type=i32 0006b0: 01 7c | local[5] type=f64 0006b2: 23 80 80 80 80 00 | global.get 0 <__stack_pointer> 0006b8: 41 b0 02 | i32.const 304 0006bb: 6b | i32.sub 0006bc: 22 01 | local.tee 1 0006be: 24 80 80 80 80 00 | global.set 0 <__stack_pointer> 0006c4: 20 01 | local.get 1 0006c6: 41 38 | i32.const 56 0006c8: 6a | i32.add 0006c9: 41 ff 80 c0 80 00 | i32.const 1048703 0006cf: 41 83 02 | i32.const 259 0006d2: 10 a3 81 80 80 00 | call 163 <_ZN9mrubyedge4rite4rite4load17h9f737249e845f4b1E>
$ wasm-objdump -x -j Function ./fib.wasm | grep fib - func[12] sig=2 <fib> $ wasm-objdump -x -j Type ./fib.wasm Type[23]: - type[0] (i32, i32) -> nil - type[1] (i32, i32, i32) -> i64 - type[2] (i32) -> i32 # => Here's fib(i32) -> i32 ! - type[3] (i32, i32, i32) -> i32 ...
$ mec fib.rb $ wasm-objdump -x -j Import ./fib.wasm ... Import[5]: - func[0] sig=7 <_ZN4wasi13lib_...> <- wasi_snapshot_preview1.fd_write - func[1] sig=5 <_ZN4wasi13lib_...> <- wasi_snapshot_preview1.random_get - func[2] sig=5 <__imported_wasi_...> <- wasi_snapshot_preview1.environ_get - func[3] sig=5 <__imported_wasi_...> <- wasi_snapshot_preview1.environ_sizes_get - func[4] sig=4 <__imported_wasi_...> <- wasi_snapshot_preview1.proc_exit
write(2)
getrandom(2)
_exit(2)
let args = ["bin", "arg1", "arg2"]; //... let wasi = new WASI(args, _env, _fds); let wasm = await WebAssembly.compileStreaming(fetch("bin.wasm")); let inst = await WebAssembly.instantiate(wasm, { // Here specifies the import object "wasi_snapshot_preview1": wasi.wasiImport, }); wasi.start(inst);
// NOTE: time is a pointer to feed result back clock_time_get(id: number, precision: bigint, time: number): number { const buffer = new DataView(self.inst.exports.memory.buffer); if (id === wasi.CLOCKID_REALTIME) { buffer.setBigUint64( time, BigInt(new Date().getTime()) * 1_000_000n, true, ); } else ... ... return 0 }
def test_random Random.rand(10) end
$ mec random.rb $ wasm-objdump -x ./random.wasm ... Export[3]: - func[430] <test_random.command_export> -> "test_random" ... Import[5]: - func[0] sig=5 <_ZN4wasi13lib_generated...> <- wasi_snapshot_preview1.random_get - func[1] ...
const wasiImport = { random_get: function(buf, buf_len) { let buffer8 = new Uint8Array( window.mywasm.exports.memory.buffer ).subarray(buf, buf + buf_len); for (let i = 0; i < buf_len; i++) { buffer8[i] = (Math.random() * 256) | 0; } },...}; const importObject = {"wasi_snapshot_preview1": wasiImport}; WebAssembly.instantiateStreaming(fetch("./random.wasm"), importObject).then( (obj) => { window.mywasm = obj.instance; for( var i = 0; i < 10; i++ ) { console.log("getrandom = ", window.mywasm.exports.test_random()); }} )
--dump=insns
# def hello; p 1 + 2; end $ ruby --dump=insns test.rb == disasm: #<ISeq:<main>@test.rb:1 (1,0)-(5,5)> (catch: FALSE) 0000 definemethod :hello, hello ( 1)[Li] 0003 putself ( 5)[Li] 0004 opt_send_without_block <calldata!mid:hello, argc:0, FCALL|VCALL|ARGS_SIMPLE> 0006 leave == disasm: #<ISeq:[email protected]:1 (1,0)-(3,3)> (catch: FALSE) 0000 putself ( 2)[LiCa] 0001 putobject_INT2FIX_1_ 0002 putobject 2 0004 opt_plus <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr] 0006 opt_send_without_block <calldata!mid:p, argc:1, FCALL|ARGS_SIMPLE> 0008 leave ( 3)[Re]
0001 putobject_INT2FIX_1_
1
0002 putobject 2
2
0004 opt_plus
1 2
3
$ mrbc -v test.rb # ...snip irep 0x60000080c0a0 nregs=3 nlocals=1 pools=0 syms=1 reps=1 ilen=15 file: test.rb 1 000 TCLASS R1 1 002 METHOD R2 I(0:0x60000080c0f0) 1 005 DEF R1 :hello 5 008 SSEND R1 :hello n=0 5 012 RETURN R1 5 014 STOP irep 0x60000080c0f0 nregs=6 nlocals=2 pools=0 syms=1 reps=0 ilen=12 file: test.rb 1 000 ENTER 0:0:0:0:0:0:0 (0x0) 2 004 LOADI_3 R3 (3) 2 006 SSEND R2 :p n=1 2 010 RETURN R2
-l
function hello(a, b) print(a + b) end hello(3, 5)
$ luac -l sample.lua
main <sample.lua:0,0> (8 instructions at 0x600000604000) 0+ params, 3 slots, 1 upvalue, 0 locals, 1 constant, 1 function 1 [1] VARARGPREP 0 2 [3] CLOSURE 0 0 ; 0x600000604080 3 [1] SETTABUP 0 0 0 ; _ENV "hello" 4 [5] GETTABUP 0 0 0 ; _ENV "hello" 5 [5] LOADI 1 3 6 [5] LOADI 2 5 7 [5] CALL 0 3 1 ; 2 in 0 out 8 [5] RETURN 0 1 1 ; 0 out function <sample.lua:1,3> (5 instructions at 0x600000604080) 2 params, 4 slots, 1 upvalue, 2 locals, 1 constant, 0 functions 1 [2] GETTABUP 2 0 0 ; _ENV "print" 2 [2] ADD 3 0 1 3 [2] MMBIN 0 1 6 ; __add 4 [2] CALL 2 2 1 ; 1 in 0 out 5 [3] RETURN0
{C,m}ruby
$ git clone https://github.com/mrubyc/mrubyc.git $ cd mrubyc $ make mrubyc_bin $ ./sample_c/sample_scheduler Usage: ./sample_c/sample_scheduler <xxxx.mrb>
$ mrbc ./test.rb $ # generated ./test.mrb ... $ # result of `p 1 + 2` $ ./sample_c/sample_no_scheduler ./test.mrb 3
$ ls -l fib.wasm -rwxr-xr-x 1 udzura staff 178080 5 12 20:20 fib.wasm
$ wasm-objdump -x -j Export fib.wasm fib.wasm: file format wasm 0x1 module name: <mywasm.wasm> Section Details: Export[4]: - memory[0] -> "memory" - func[414] <__mrbe_grow.command_export> -> "__mrbe_grow" - func[415] <fib.command_export> -> "fib" - func[416] <hello.command_export> -> "hello"
unsafe {}
Type[24]: - type[3] (i32) -> i32 Function[412]: - func[17] sig=3 <fib> - func[415] sig=3 <fib.command_export> Export[4]: - func[415] <fib.command_export> -> "fib"
So, fib has signature (i32) -> i32
(i32) -> i32
def fib(n) if n < 1 return 0 elsif n < 3 return 1 else return fib(n-1)+fib(n-2) end end
file.export.rbs
# Classes can be corresponded with Rust/wasm types # e.g. Integer -> i32 # Float -> f32, ... def fib: (Integer) -> Integer
FILE.rb
FILE.export.rbs
FILE.import.rbs
./ ├── foobar.export.rbs ├── foobar.import.rbs └── foobar.rb
function putSomeString(str) { // Requires start point of memory! var off = window.instance.exports.__some_malloc(); off = off >>> 0; var len = str.length; var buffer = new Uint8Array( window.instance.exports.memory.buffer, off, len); for( var i = 0; i < length; i++ ) { buffer[i] = str.charCodeAt(i); } window.instance.exports.use_string_i_just_put(off, len); }
pub fn use_string_i_just_put(p: *const u8, len: usize) { let s = unsafe { // unsafe! let u8buf = std::slice::from_raw_parts(p, len); std::str::from_utf8(u8buf).unwrap() }; println!("{}", s); }
var off = window.instance.exports.get_my_string_from_wasm(); off = off >>> 0; var len = getAnywayOrFixedLength(window.instance); var buffer = new Uint8Array( window.instance.exports.memory.buffer, off, len); console.log(String.fromCharCode.apply(null, buffer));
def: foo(String) -> void
foo(*const u8, usize)
def: bar() -> String
bar() -> *const u8
(*1) also export __get__bar_size() -> u32 for getting buffer size (*2) the buffer is forced to be ended with \0 automatically
__get__bar_size() -> u32
\0
(*3) also export __set__bar_size(u32) to pass string's size (*4) when __set__bar_size() not set, mruby/edge assumes the buffer to be ended with \0, and tries to detect its length
__set__bar_size(u32)
__set__bar_size()
# @_wasm_expoert def handle_msg: (String) -> void # converted -> handle_msg(ptr, len)
var str = "The WASM Era's emerging"; var len = str.length; var pageLen = Math.ceil(len+1/65536); var off = window.instance.exports.__mrbe_grow(pageLen); var buffer = new Uint8Array( window.instance.exports.memory.buffer, off, len); for( var i = 0; i < length; i++ ) { buffer[i] = str.charCodeAt(i); } // Finally! window.instance.exports.handle_msg()
# @_wasm_import def handle_wasm_msg: (String) -> void # in Ruby script str = "The WASM user's growing" handle_wasm_msg(str) # will pass -> handle_wasm_msg(off, len)
function handle_wasm_msg(off, len) { let instance = window.instance; let buffer = new Uint8Array(instance.exports.memory.buffer, off, len); console.log(String.fromCharCode.apply(null, buffer)); }
string
package wasi:filesystem; interface types { use wasi:clocks.wall-clock.{datetime}; record stat { ino: u64, size: u64, mtime: datetime, // ... } stat-file: func(path: string) -> result<stat>; }
fibonacci
def fib(n) if n < 1 return 0 elsif n < 3 return 1 else return fib(n-1)+fib(n-2) end end def bench(num) # import these JS functions from browser, using performance.now() performance_start fib(num) performance_finish end
※ Browser: Chrome 125.0.6422.41 on MBP-2021 M1 Max
@ruby/3.3-wasm-wasi
def bench(num) console = JS.global[:console] performance = JS.global[:performance] p1 = performance.now n = fib(num) p2 = performance.now console.log("fib(#{num}) = #{n}") console.log("Elapsed #{p2.to_f - p1.to_f} ms") end bench(15) #...
function fib(num) { if (num < 1) { return 0; } if (num < 3) { return 1; } return fib(num-1) + fib(num-2); }
mruby/edge is about x80 ~ x100 slower! Lots of room for growth!
# ruby.wasm ruby-3.3-wasm32-unknown-wasip1 $ rbwasm pack ruby.wasm --dir ./src::/src --dir \ ./ruby-3.3-wasm32-unknown-wasip1-full/usr::/usr -o bench.wasm $ wasmedge bench.wasm /src/bench.rb # mruby/edge 0.1.7 $ wasmedge --reactor bench.wasm bench 15
def fib(n) # ... end def bench(num) start = Time.now.to_f p fib(num) fin = Time.now.to_f p (fin - start) * 1000 end bench(15) # ...
$ time wasmedge --reactor bench2.wasm bench 15 610 200628000 # nanos 0.21s user 0.01s system 96% cpu 0.227 total $ time wasmedge my-ruby-app.wasm /src/bench.rb 610 16.70217514038086 6.67s user 0.19s system 99% cpu 6.906 total
criterion
// e.g. benchmarking eval_insn() fn bm0_eval(c: &mut Criterion) { let bin = include_bytes!("./fib.mrb"); let rite = mrubyedge::rite::load(bin).unwrap(); let mut vm = mrubyedge::vm::VM::open(rite); vm.prelude().unwrap(); c.bench_function("Eval time", |b| { b.iter(|| { vm.eval_insn().unwrap(); }) }); }
Load time time: [148.22 ns 149.17 ns 150.19 ns] Prelude time time: [629.93 ns 631.64 ns 633.60 ns] Eval time time: [1.9061 ns 1.9080 ns 1.9100 ns] Fib 1 time: [758.75 ns 760.10 ns 761.40 ns] # for comparison Fib 5 time: [9.5152 µs 9.5342 µs 9.5528 µs]