From 7837497dbaff21213a892fbc2968c24f430e3cf0 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 19 May 2022 04:44:53 +0000 Subject: [PATCH 1/5] Allow to wrap arbitrary Ruby Object by JS's RbValue When passing a non-interoperated Ruby Object like `Object` to JS method from Ruby world, the object has to be JS object. This patch provides a way to wrap a Ruby object by `RbValue`, which is a Ruby object representation in JS world. --- ext/js/bindgen/rb-js-abi-host.wit | 1 + ext/js/js-core.c | 19 ++++++++++++++++--- .../npm-packages/ruby-wasm-wasi/src/index.ts | 13 +++++++++++-- .../ruby-wasm-wasi/test/js_from_rb.test.ts | 18 ++++++++++++++++++ 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/ext/js/bindgen/rb-js-abi-host.wit b/ext/js/bindgen/rb-js-abi-host.wit index 9bbad82a99..e7582ba9d8 100644 --- a/ext/js/bindgen/rb-js-abi-host.wit +++ b/ext/js/bindgen/rb-js-abi-host.wit @@ -7,6 +7,7 @@ global-this: function() -> js-abi-value int-to-js-number: function(value: s32) -> js-abi-value string-to-js-string: function(value: string) -> js-abi-value bool-to-js-bool: function(value: bool) -> js-abi-value +rb-object-to-js-rb-value: function(raw-rb-abi-value: u32) -> js-abi-value js-value-to-string: function(value: js-abi-value) -> string diff --git a/ext/js/js-core.c b/ext/js/js-core.c index 195be58cbf..b3b93d01c7 100644 --- a/ext/js/js-core.c +++ b/ext/js/js-core.c @@ -102,9 +102,10 @@ static VALUE _rb_js_is_js(VALUE _, VALUE obj) { * Try to convert the given object to a JS::Object using to_js * method. Returns nil if the object cannot be converted. * - * p JS.try_convert(1) # => 1 - * p JS.try_convert("foo") # => "foo" - * p JS.try_convert(Object.new) # => nil + * p JS.try_convert(1) # => JS::Object + * p JS.try_convert("foo") # => JS::Object + * p JS.try_convert(Object.new) # => nil + * p JS.try_convert(JS::Object.wrap(Object.new)) # => JS::Object */ VALUE _rb_js_try_convert(VALUE klass, VALUE obj) { if (_rb_js_is_js(klass, obj)) { @@ -242,6 +243,17 @@ static VALUE _rb_js_export_to_js(VALUE obj) { return Qnil; } + +/* + * call-seq: + * JS::Object.wrap(obj) -> JS::Object + * + * Returns +obj+ wrapped by JS class RbValue. + */ +static VALUE _rb_js_obj_wrap(VALUE obj, VALUE wrapping) { + return jsvalue_s_new(rb_js_abi_host_rb_object_to_js_rb_value((uint32_t)wrapping)); +} + /* * call-seq: * to_js -> JS::Object @@ -306,6 +318,7 @@ void Init_js() { rb_define_method(rb_cJS_Object, "call", _rb_js_obj_call, -1); rb_define_method(rb_cJS_Object, "__export_to_js", _rb_js_export_to_js, 0); rb_define_method(rb_cJS_Object, "inspect", _rb_js_obj_inspect, 0); + rb_define_singleton_method(rb_cJS_Object, "wrap", _rb_js_obj_wrap, 1); rb_define_method(rb_cInteger, "to_js", _rb_js_integer_to_js, 0); rb_define_method(rb_cString, "to_js", _rb_js_string_to_js, 0); diff --git a/packages/npm-packages/ruby-wasm-wasi/src/index.ts b/packages/npm-packages/ruby-wasm-wasi/src/index.ts index 2e56be1752..35acb46ec5 100644 --- a/packages/npm-packages/ruby-wasm-wasi/src/index.ts +++ b/packages/npm-packages/ruby-wasm-wasi/src/index.ts @@ -72,7 +72,8 @@ export class RubyVM { return Function(code)(); }, isJs: (value) => { - return value == null || !(value instanceof RbValue); + // Just for compatibility with the old JS API + return true; }, globalThis: () => { if (typeof globalThis !== "undefined") { @@ -93,6 +94,10 @@ export class RubyVM { boolToJsBool: (value) => { return value; }, + rbObjectToJsRbValue: (rawRbAbiValue) => { + const abiValue = new (RbAbi.RbAbiValue as any)(rawRbAbiValue, this.guest); + return new RbValue(abiValue, this, this.privateObject()); + }, jsValueToString: (value) => { return value.toString(); }, @@ -172,7 +177,11 @@ export class RubyVM { * */ eval(code: string): RbValue { - return evalRbCode(this, { exporter: this.exporter, exceptionFormatter: this.exceptionFormatter }, code); + return evalRbCode(this, this.privateObject(), code); + } + + private privateObject(): RubyVMPrivate { + return { exporter: this.exporter, exceptionFormatter: this.exceptionFormatter } } } diff --git a/packages/npm-packages/ruby-wasm-wasi/test/js_from_rb.test.ts b/packages/npm-packages/ruby-wasm-wasi/test/js_from_rb.test.ts index 2571cc0175..ec50b0c1d1 100644 --- a/packages/npm-packages/ruby-wasm-wasi/test/js_from_rb.test.ts +++ b/packages/npm-packages/ruby-wasm-wasi/test/js_from_rb.test.ts @@ -147,4 +147,22 @@ describe("Manipulation of JS from Ruby", () => { `); expect(result.toJS()).toEqual(expected); }); + + test("Wrap arbitrary Ruby object to JS::Object", async () => { + const vm = await initRubyVM(); + const results = vm.eval(` + require "js" + intrinsics = JS.eval(<<-JS) + return { + identity(v) { return v } + } + JS + o1 = Object.new + o1_clone = intrinsics.call(:identity, JS::Object.wrap(o1)) + [o1.object_id, o1_clone.call("call", "object_id").inspect] + `); + const o1 = results.call("at", vm.eval("0")); + const o1Clone = results.call("at", vm.eval("1")); + expect(o1.toString()).toEqual(o1Clone.toString()); + }); }); From 6c605ba280ec5c17e303f0fb54713a777e92db3a Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 19 May 2022 06:12:48 +0000 Subject: [PATCH 2/5] Allow to wrap arbitrary JS Object by Ruby's JS::Object --- ext/js/bindgen/rb-js-abi-host.wit | 3 +- ext/js/js-core.c | 6 ++- .../npm-packages/ruby-wasm-wasi/src/index.ts | 50 +++++++++++++++---- .../ruby-wasm-wasi/test/vm.test.ts | 17 +++++++ 4 files changed, 63 insertions(+), 13 deletions(-) diff --git a/ext/js/bindgen/rb-js-abi-host.wit b/ext/js/bindgen/rb-js-abi-host.wit index e7582ba9d8..481d19e6e2 100644 --- a/ext/js/bindgen/rb-js-abi-host.wit +++ b/ext/js/bindgen/rb-js-abi-host.wit @@ -11,7 +11,8 @@ rb-object-to-js-rb-value: function(raw-rb-abi-value: u32) -> js-abi-value js-value-to-string: function(value: js-abi-value) -> string -take-js-value: function(value: js-abi-value) +export-js-value-to-host: function(value: js-abi-value) +import-js-value-from-host: function() -> js-abi-value reflect-apply: function(target: js-abi-value, this-argument: js-abi-value, arguments: list) -> js-abi-value reflect-construct: function(target: js-abi-value, arguments: list) -> js-abi-value diff --git a/ext/js/js-core.c b/ext/js/js-core.c index b3b93d01c7..728ec57383 100644 --- a/ext/js/js-core.c +++ b/ext/js/js-core.c @@ -239,10 +239,13 @@ static VALUE _rb_js_obj_inspect(VALUE obj) { */ static VALUE _rb_js_export_to_js(VALUE obj) { struct jsvalue *p = check_jsvalue(obj); - rb_js_abi_host_take_js_value(p->abi); + rb_js_abi_host_export_js_value_to_host(p->abi); return Qnil; } +static VALUE _rb_js_import_from_js(VALUE obj) { + return jsvalue_s_new(rb_js_abi_host_import_js_value_from_host()); +} /* * call-seq: @@ -317,6 +320,7 @@ void Init_js() { rb_define_method(rb_cJS_Object, "[]=", _rb_js_obj_aset, 2); rb_define_method(rb_cJS_Object, "call", _rb_js_obj_call, -1); rb_define_method(rb_cJS_Object, "__export_to_js", _rb_js_export_to_js, 0); + rb_define_singleton_method(rb_cJS_Object, "__import_from_js", _rb_js_import_from_js, 0); rb_define_method(rb_cJS_Object, "inspect", _rb_js_obj_inspect, 0); rb_define_singleton_method(rb_cJS_Object, "wrap", _rb_js_obj_wrap, 1); diff --git a/packages/npm-packages/ruby-wasm-wasi/src/index.ts b/packages/npm-packages/ruby-wasm-wasi/src/index.ts index 35acb46ec5..03b7ebed57 100644 --- a/packages/npm-packages/ruby-wasm-wasi/src/index.ts +++ b/packages/npm-packages/ruby-wasm-wasi/src/index.ts @@ -23,12 +23,12 @@ import { addRbJsAbiHostToImports, JsAbiValue } from "./bindgen/rb-js-abi-host"; export class RubyVM { guest: RbAbi.RbAbiGuest; private instance: WebAssembly.Instance | null = null; - private exporter: JsValueExporter; + private transport: JsValueTransport; private exceptionFormatter: RbExceptionFormatter; constructor() { this.guest = new RbAbi.RbAbiGuest(); - this.exporter = new JsValueExporter(); + this.transport = new JsValueTransport(); this.exceptionFormatter = new RbExceptionFormatter(); } @@ -101,9 +101,12 @@ export class RubyVM { jsValueToString: (value) => { return value.toString(); }, - takeJsValue: (value) => { + exportJsValueToHost: (value) => { // See `JsValueExporter` for the reason why we need to do this - this.exporter.takeJsValue(value); + this.transport.takeJsValue(value); + }, + importJsValueFromHost: () => { + return this.transport.consumeJsValue(); }, instanceOf: (value, klass) => { if (typeof klass === "function") { @@ -180,8 +183,21 @@ export class RubyVM { return evalRbCode(this, this.privateObject(), code); } + /** + * Wrap a JavaScript value into a Ruby JS::Object + * @param value The value to convert to RbValue + * @returns the RbValue object representing the given JS value + * + * @example + * const hash = vm.eval(`Hash.new`) + * hash.call("store", vm.eval(`"key1"`), vm.wrap(new Object())); + */ + wrap(value: any): RbValue { + return this.transport.importJsValue(value, this); + } + private privateObject(): RubyVMPrivate { - return { exporter: this.exporter, exceptionFormatter: this.exceptionFormatter } + return { transport: this.transport, exceptionFormatter: this.exceptionFormatter } } } @@ -204,14 +220,26 @@ export class RubyVM { * * Note that `exportJsValue` is not reentrant. */ -class JsValueExporter { - private _takenJsValues: JsAbiValue = null; +class JsValueTransport { + private _takenJsValue: JsAbiValue = null; takeJsValue(value: JsAbiValue) { - this._takenJsValues = value; + this._takenJsValue = value; + } + consumeJsValue(): JsAbiValue { + return this._takenJsValue; } + exportJsValue(value: RbValue): JsAbiValue { value.call("__export_to_js"); - return this._takenJsValues; + return this._takenJsValue; + } + + importJsValue(value: JsAbiValue, vm: RubyVM): RbValue { + this._takenJsValue = value; + return vm.eval(` + require "js" + JS::Object.__import_from_js + `); } } @@ -284,7 +312,7 @@ export class RbValue { if (jsValue.call("nil?").toString() === "true") { return null; } - return this.privateObject.exporter.exportJsValue(jsValue); + return this.privateObject.transport.exportJsValue(jsValue); } } @@ -302,7 +330,7 @@ enum ruby_tag_type { } type RubyVMPrivate = { - exporter: JsValueExporter, + transport: JsValueTransport, exceptionFormatter: RbExceptionFormatter, }; diff --git a/packages/npm-packages/ruby-wasm-wasi/test/vm.test.ts b/packages/npm-packages/ruby-wasm-wasi/test/vm.test.ts index f787928e31..e6f06b5b89 100644 --- a/packages/npm-packages/ruby-wasm-wasi/test/vm.test.ts +++ b/packages/npm-packages/ruby-wasm-wasi/test/vm.test.ts @@ -170,4 +170,21 @@ eval:11:in \`
'`); expect(o1.call("hash").toString()).toBe(o2.call("hash").toString()); expect(o2.call("hash").toString()).toBe(o3.call("hash").toString()); }); + + test("Wrap arbitrary JS object to RbValue", async () => { + const vm = await initRubyVM(); + const o1 = { v() { return 42 } } + const X = vm.eval(` + module X + def self.identity(x) = x + end + X + `); + const o1Clone = X.call("identity", vm.wrap(o1)); + expect(o1Clone.call("call", vm.eval(`"v"`)).toJS().toString()).toBe("42"); + + // Check that JS object can be stored in Ruby Hash + const hash = vm.eval(`Hash.new`) + hash.call("store", vm.eval(`"key1"`), vm.wrap(new Object())); + }); }); From efcf5c40421bd0766c3a0fcf1a7635c820425699 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 19 May 2022 06:26:18 +0000 Subject: [PATCH 3/5] Add GC limitation note in README.md --- .../ruby-head-wasm-wasi/README.md | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/npm-packages/ruby-head-wasm-wasi/README.md b/packages/npm-packages/ruby-head-wasm-wasi/README.md index 28289891ba..894a200358 100644 --- a/packages/npm-packages/ruby-head-wasm-wasi/README.md +++ b/packages/npm-packages/ruby-head-wasm-wasi/README.md @@ -85,6 +85,34 @@ See [the example project](https://github.com/ruby/ruby.wasm/tree/main/packages/n ``` + +## GC limitation with JavaScript interoperability + +Since JavaScript's GC system and Ruby's GC system are separated and not cooperative, they cannot collect cyclic references between JavaScript and Ruby objects. + +The following code will cause a memory leak: + +```javascript +class JNode { + setRNode(rnode) { + this.rnode = rnode; + } +} +jnode = new JNode(); + +rnode = vm.eval(` +class RNode + def set_jnode(jnode) + @jnode = jnode + end +end +RNode.new +`) + +rnode.call("set_jnode", vm.wrap(jnode)) +jnode.setRNode(rnode); +``` + ## APIs From a35575e7034a00a0a0925ee697a63b9e1c87b56f Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 19 May 2022 06:28:45 +0000 Subject: [PATCH 4/5] Apply npm run format --- .../npm-packages/ruby-wasm-wasi/README.md | 5 ++- .../npm-packages/ruby-wasm-wasi/src/index.ts | 31 ++++++++++++------- .../ruby-wasm-wasi/test/package.test.ts | 5 ++- .../ruby-wasm-wasi/test/vm.test.ts | 8 +++-- 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/packages/npm-packages/ruby-wasm-wasi/README.md b/packages/npm-packages/ruby-wasm-wasi/README.md index b94b6c3057..1de43e1d91 100644 --- a/packages/npm-packages/ruby-wasm-wasi/README.md +++ b/packages/npm-packages/ruby-wasm-wasi/README.md @@ -2,7 +2,6 @@ This package is a template for each channel-specific package. -| Channel | Package | -| ------- | ------- | +| Channel | Package | +| ------- | ------------------------------------------------- | | `head` | [`ruby-head-wasm-wasi`](./../ruby-head-wasm-wasi) | - diff --git a/packages/npm-packages/ruby-wasm-wasi/src/index.ts b/packages/npm-packages/ruby-wasm-wasi/src/index.ts index 03b7ebed57..2f0cfadd70 100644 --- a/packages/npm-packages/ruby-wasm-wasi/src/index.ts +++ b/packages/npm-packages/ruby-wasm-wasi/src/index.ts @@ -95,8 +95,11 @@ export class RubyVM { return value; }, rbObjectToJsRbValue: (rawRbAbiValue) => { - const abiValue = new (RbAbi.RbAbiValue as any)(rawRbAbiValue, this.guest); - return new RbValue(abiValue, this, this.privateObject()); + const abiValue = new (RbAbi.RbAbiValue as any)( + rawRbAbiValue, + this.guest + ); + return new RbValue(abiValue, this, this.privateObject()); }, jsValueToString: (value) => { return value.toString(); @@ -197,7 +200,10 @@ export class RubyVM { } private privateObject(): RubyVMPrivate { - return { transport: this.transport, exceptionFormatter: this.exceptionFormatter } + return { + transport: this.transport, + exceptionFormatter: this.exceptionFormatter, + }; } } @@ -330,11 +336,10 @@ enum ruby_tag_type { } type RubyVMPrivate = { - transport: JsValueTransport, - exceptionFormatter: RbExceptionFormatter, + transport: JsValueTransport; + exceptionFormatter: RbExceptionFormatter; }; - class RbExceptionFormatter { private literalsCache: [RbValue, RbValue, RbValue] | null = null; @@ -344,7 +349,7 @@ class RbExceptionFormatter { const zeroOneNewLine: [RbValue, RbValue, RbValue] = [ evalRbCode(vm, privateObject, "0"), evalRbCode(vm, privateObject, "1"), - evalRbCode(vm, privateObject, `"\n"`) + evalRbCode(vm, privateObject, `"\n"`), ]; this.literalsCache = zeroOneNewLine; return zeroOneNewLine; @@ -355,11 +360,13 @@ class RbExceptionFormatter { const backtrace = error.call("backtrace"); const firstLine = backtrace.call("at", zeroLiteral); - const restLines = backtrace.call("drop", oneLiteral).call("join", newLineLiteral); + const restLines = backtrace + .call("drop", oneLiteral) + .call("join", newLineLiteral); return this.formatString(error.call("class").toString(), error.toString(), [ firstLine.toString(), restLines.toString(), - ]) + ]); } formatString( @@ -368,7 +375,7 @@ class RbExceptionFormatter { backtrace: [string, string] ): string { return `${backtrace[0]}: ${message} (${klass})\n${backtrace[1]}`; - }; + } } const checkStatusTag = ( @@ -399,7 +406,9 @@ const checkStatusTag = ( } // clear errinfo if got exception due to no rb_jump_tag vm.guest.rbClearErrinfo(); - throw new RbError(privateObject.exceptionFormatter.format(error, vm, privateObject)); + throw new RbError( + privateObject.exceptionFormatter.format(error, vm, privateObject) + ); default: throw new RbError(`unknown error tag: ${rawTag}`); } diff --git a/packages/npm-packages/ruby-wasm-wasi/test/package.test.ts b/packages/npm-packages/ruby-wasm-wasi/test/package.test.ts index 46663f25ae..3c6ab40161 100644 --- a/packages/npm-packages/ruby-wasm-wasi/test/package.test.ts +++ b/packages/npm-packages/ruby-wasm-wasi/test/package.test.ts @@ -38,11 +38,10 @@ describe("Packaging validation", () => { const mod = await WebAssembly.compile(binary.buffer); const { vm } = await initRubyVM(mod, ["ruby.wasm", "-e_=0"]); // Check loading ext library - vm.eval(`require "stringio"`) + vm.eval(`require "stringio"`); if (stdlib) { // Check loading stdlib gem - vm.eval(`require "English"`) + vm.eval(`require "English"`); } }); - }); diff --git a/packages/npm-packages/ruby-wasm-wasi/test/vm.test.ts b/packages/npm-packages/ruby-wasm-wasi/test/vm.test.ts index e6f06b5b89..e21b3f3539 100644 --- a/packages/npm-packages/ruby-wasm-wasi/test/vm.test.ts +++ b/packages/npm-packages/ruby-wasm-wasi/test/vm.test.ts @@ -173,7 +173,11 @@ eval:11:in \`
'`); test("Wrap arbitrary JS object to RbValue", async () => { const vm = await initRubyVM(); - const o1 = { v() { return 42 } } + const o1 = { + v() { + return 42; + }, + }; const X = vm.eval(` module X def self.identity(x) = x @@ -184,7 +188,7 @@ eval:11:in \`
'`); expect(o1Clone.call("call", vm.eval(`"v"`)).toJS().toString()).toBe("42"); // Check that JS object can be stored in Ruby Hash - const hash = vm.eval(`Hash.new`) + const hash = vm.eval(`Hash.new`); hash.call("store", vm.eval(`"key1"`), vm.wrap(new Object())); }); }); From d0db2858827178057b114a49bed333a9a0c5ee33 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 19 May 2022 06:33:07 +0000 Subject: [PATCH 5/5] Update README.md in ruby-head-wasm-wasi --- .../ruby-head-wasm-wasi/README.md | 117 ++++++++++++------ 1 file changed, 76 insertions(+), 41 deletions(-) diff --git a/packages/npm-packages/ruby-head-wasm-wasi/README.md b/packages/npm-packages/ruby-head-wasm-wasi/README.md index 894a200358..f8c110fc21 100644 --- a/packages/npm-packages/ruby-head-wasm-wasi/README.md +++ b/packages/npm-packages/ruby-head-wasm-wasi/README.md @@ -26,9 +26,9 @@ import { DefaultRubyVM } from "ruby-head-wasm-wasi/dist/node.cjs.js"; const main = async () => { const binary = await fs.readFile( -// Tips: Replace the binary with debug info if you want symbolicated stack trace. -// (only nightly release for now) -// "./node_modules/ruby-head-wasm-wasi/dist/ruby.debug.wasm" + // Tips: Replace the binary with debug info if you want symbolicated stack trace. + // (only nightly release for now) + // "./node_modules/ruby-head-wasm-wasi/dist/ruby.debug.wasm" "./node_modules/ruby-head-wasm-wasi/dist/ruby.wasm" ); const module = await WebAssembly.compile(binary); @@ -62,9 +62,9 @@ See [the example project](https://github.com/ruby/ruby.wasm/tree/main/packages/n const main = async () => { // Fetch and instntiate WebAssembly binary const response = await fetch( -// Tips: Replace the binary with debug info if you want symbolicated stack trace. -// (only nightly release for now) -// "https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@latest/dist/ruby.debug.wasm" + // Tips: Replace the binary with debug info if you want symbolicated stack trace. + // (only nightly release for now) + // "https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@latest/dist/ruby.debug.wasm" "https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@latest/dist/ruby.wasm" ); const buffer = await response.arrayBuffer(); @@ -85,7 +85,6 @@ See [the example project](https://github.com/ruby/ruby.wasm/tree/main/packages/n ``` - ## GC limitation with JavaScript interoperability Since JavaScript's GC system and Ruby's GC system are separated and not cooperative, they cannot collect cyclic references between JavaScript and Ruby objects. @@ -107,13 +106,13 @@ class RNode end end RNode.new -`) +`); -rnode.call("set_jnode", vm.wrap(jnode)) +rnode.call("set_jnode", vm.wrap(jnode)); jnode.setRNode(rnode); ``` - + ## APIs @@ -121,36 +120,33 @@ jnode.setRNode(rnode); #### Table of Contents -- [ruby-head-wasm-wasi](#ruby-head-wasm-wasi) - - [Installation](#installation) - - [Quick Start (for Node.js)](#quick-start-for-nodejs) - - [Quick Start (for Browser)](#quick-start-for-browser) - - [APIs](#apis) - - [Table of Contents](#table-of-contents) - - [RubyVM](#rubyvm) - - [Examples](#examples) - - [initialize](#initialize) - - [Parameters](#parameters) - - [setInstance](#setinstance) - - [Parameters](#parameters-1) - - [addToImports](#addtoimports) - - [Parameters](#parameters-2) - - [printVersion](#printversion) - - [eval](#eval) - - [Parameters](#parameters-3) - - [Examples](#examples-1) - - [RbValue](#rbvalue) - - [Parameters](#parameters-4) - - [call](#call) - - [Parameters](#parameters-5) - - [Examples](#examples-2) - - [toPrimitive](#toprimitive) - - [Parameters](#parameters-6) - - [toString](#tostring) - - [toJS](#tojs) - - [RbError](#rberror) - - [Parameters](#parameters-7) - - [Building the package from source](#building-the-package-from-source) +- [RubyVM](#rubyvm) + - [Examples](#examples) + - [initialize](#initialize) + - [Parameters](#parameters) + - [setInstance](#setinstance) + - [Parameters](#parameters-1) + - [addToImports](#addtoimports) + - [Parameters](#parameters-2) + - [printVersion](#printversion) + - [eval](#eval) + - [Parameters](#parameters-3) + - [Examples](#examples-1) + - [wrap](#wrap) + - [Parameters](#parameters-4) + - [Examples](#examples-2) +- [JsValueTransport](#jsvaluetransport) +- [RbValue](#rbvalue) + - [Parameters](#parameters-5) + - [call](#call) + - [Parameters](#parameters-6) + - [Examples](#examples-3) + - [toPrimitive](#toprimitive) + - [Parameters](#parameters-7) + - [toString](#tostring) + - [toJS](#tojs) +- [RbError](#rberror) + - [Parameters](#parameters-8) ### RubyVM @@ -170,6 +166,7 @@ vm.addToImports(imports); const instance = await WebAssembly.instantiate(rubyModule, imports); await vm.setInstance(instance); wasi.initialize(instance); +vm.initialize(); ``` #### initialize @@ -224,6 +221,44 @@ console.log(result.toString()); // 3 Returns **any** the result of the last expression +#### wrap + +Wrap a JavaScript value into a Ruby JS::Object + +##### Parameters + +- `value` The value to convert to RbValue + +##### Examples + +```javascript +const hash = vm.eval(`Hash.new`); +hash.call("store", vm.eval(`"key1"`), vm.wrap(new Object())); +``` + +Returns **any** the RbValue object representing the given JS value + +### JsValueTransport + +Export a JS value held by the Ruby VM to the JS environment. +This is implemented in a dirty way since wit cannot reference resources +defined in other interfaces. +In our case, we can't express `function(v: rb-abi-value) -> js-abi-value` +because `rb-js-abi-host.wit`, that defines `js-abi-value`, is implemented +by embedder side (JS) but `rb-abi-guest.wit`, that defines `rb-abi-value` +is implemented by guest side (Wasm). + +This class is a helper to export by: + +1. Call `function __export_to_js(v: rb-abi-value)` defined in guest from embedder side. +2. Call `function takeJsValue(v: js-abi-value)` defined in embedder from guest side with + underlying JS value of given `rb-abi-value`. +3. Then `takeJsValue` implementation escapes the given JS value to the `_takenJsValues` + stored in embedder side. +4. Finally, embedder side can take `_takenJsValues`. + +Note that `exportJsValue` is not reentrant. + ### RbValue A RbValue is an object that represents a value in Ruby @@ -232,7 +267,7 @@ A RbValue is an object that represents a value in Ruby - `inner` - `vm` -- `exporter` +- `privateObject` #### call