Skip to content

Commit

Permalink
[JSC] Supports Type Reflection for WebAssembly.Module.imports and `…
Browse files Browse the repository at this point in the history
…WebAssembly.Module.exports`

https://bugs.webkit.org/show_bug.cgi?id=277608

Reviewed by Yusuke Suzuki.

The current JSC implements part of the WebAssembly Type Reflection proposal[1]. It allows to get
type informations through methods like `WebAssembly.Memory.prototype.type()` and
`WebAssembly.Table.prototype.type()`.

The Type Reflection API should also add a new `type` field to the elements of the arrays returned by
`WebAssembly.Module.imports` and `WebAssembly.Module.exports`[2]. However, this feature is not
currently implemented in JSC. This feature has been implemented to V8[3] and SpiderMonkey[4].

This patch implements Type Reflection for `WebAssembly.Module.imports` and
`WebAssembly.Module.exports`.

[1]: https://github.com/WebAssembly/js-types
[2]: https://webassembly.github.io/js-types/js-api/#modules
[3]: https://chromium-review.googlesource.com/c/v8/v8/+/1763527
[4]: https://hg.mozilla.org/mozilla-central/rev/91e0c6d26de8ba365ec3d462f98d28c75055a89b

* JSTests/wasm/js-api/Module.exports.js:
(assert.truthy):
* JSTests/wasm/js-api/Module.imports.js:
(assert.truthy):
* JSTests/wasm/js-api/type-reflection-concrete-types.js: Added.
(import.as.assert.from.string_appeared_here.assert.throws):
(WebAssembly.Module.exports):
(assert.throws):
* JSTests/wasm/js-api/type-reflection-exports.js: Added.
(import.as.assert.from.string_appeared_here.sameValue):
(async test):
* JSTests/wasm/js-api/type-reflection-imports.js: Added.
(import.as.assert.from.string_appeared_here.sameValue):
* Source/JavaScriptCore/wasm/WasmModuleInformation.h:
(JSC::Wasm::ModuleInformation::global const):
* Source/JavaScriptCore/wasm/js/WebAssemblyModuleConstructor.cpp:
(JSC::createTypeReflectionObject):
(JSC::JSC_DEFINE_HOST_FUNCTION):

Canonical link: https://commits.webkit.org/281974@main
  • Loading branch information
sosukesuzuki authored and Constellation committed Aug 8, 2024
1 parent 1e5569c commit 6880a8b
Show file tree
Hide file tree
Showing 7 changed files with 333 additions and 2 deletions.
10 changes: 10 additions & 0 deletions JSTests/wasm/js-api/Module.exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,20 @@ assert.eq(WebAssembly.Module.exports.length, 1);
assert.eq(WebAssembly.Module.exports(m).length, 4);
assert.eq(WebAssembly.Module.exports(m)[0].name, "func");
assert.eq(WebAssembly.Module.exports(m)[0].kind, "function");
assert.eq(WebAssembly.Module.exports(m)[0].type.parameters, []);
assert.eq(WebAssembly.Module.exports(m)[0].type.results, []);
assert.eq(WebAssembly.Module.exports(m)[1].name, "tab");
assert.eq(WebAssembly.Module.exports(m)[1].kind, "table");
assert.eq(WebAssembly.Module.exports(m)[1].type.minimum, 20);
assert.eq(WebAssembly.Module.exports(m)[1].type.maximum, 30);
assert.eq(WebAssembly.Module.exports(m)[1].type.element, "funcref");
assert.eq(WebAssembly.Module.exports(m)[2].name, "mem");
assert.eq(WebAssembly.Module.exports(m)[2].kind, "memory");
assert.eq(WebAssembly.Module.exports(m)[2].type.minimum, 1);
assert.eq(WebAssembly.Module.exports(m)[2].type.maximum, 1);
assert.eq(WebAssembly.Module.exports(m)[2].type.shared, false);
assert.eq(WebAssembly.Module.exports(m)[3].name, "glob");
assert.eq(WebAssembly.Module.exports(m)[3].kind, "global");
assert.eq(WebAssembly.Module.exports(m)[3].type.value, "i32");
assert.eq(WebAssembly.Module.exports(m)[3].type.mutable, false);
}
10 changes: 10 additions & 0 deletions JSTests/wasm/js-api/Module.imports.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,23 @@ assert.eq(WebAssembly.Module.imports.length, 1);
assert.eq(WebAssembly.Module.imports(m)[0].module, "fooFunction");
assert.eq(WebAssembly.Module.imports(m)[0].name, "barFunction");
assert.eq(WebAssembly.Module.imports(m)[0].kind, "function");
assert.eq(WebAssembly.Module.imports(m)[0].type.parameters, []);
assert.eq(WebAssembly.Module.imports(m)[0].type.results, []);
assert.eq(WebAssembly.Module.imports(m)[1].module, "fooTable");
assert.eq(WebAssembly.Module.imports(m)[1].name, "barTable");
assert.eq(WebAssembly.Module.imports(m)[1].kind, "table");
assert.eq(WebAssembly.Module.imports(m)[1].type.minimum, 20);
assert.eq(WebAssembly.Module.imports(m)[1].type.maximum, undefined);
assert.eq(WebAssembly.Module.imports(m)[1].type.element, "funcref");
assert.eq(WebAssembly.Module.imports(m)[2].module, "fooMemory");
assert.eq(WebAssembly.Module.imports(m)[2].name, "barMemory");
assert.eq(WebAssembly.Module.imports(m)[2].kind, "memory");
assert.eq(WebAssembly.Module.imports(m)[2].type.minimum, 20);
assert.eq(WebAssembly.Module.imports(m)[2].type.maximum, undefined);
assert.eq(WebAssembly.Module.imports(m)[2].type.shared, false);
assert.eq(WebAssembly.Module.imports(m)[3].module, "fooGlobal");
assert.eq(WebAssembly.Module.imports(m)[3].name, "barGlobal");
assert.eq(WebAssembly.Module.imports(m)[3].kind, "global");
assert.eq(WebAssembly.Module.imports(m)[3].type.value, "i32");
assert.eq(WebAssembly.Module.imports(m)[3].type.mutable, false);
}
63 changes: 63 additions & 0 deletions JSTests/wasm/js-api/type-reflection-concrete-types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// @requireOptions("--useWasmTypedFunctionReferences=true")

import * as assert from "../assert.js"

// https://github.com/WebAssembly/function-references/blob/main/proposals/function-references/Overview.md#type-reflection

{
// (module
// (table (export "t") 10 (ref func) (ref.func 0))
// (func)
// )
const bytes = new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,4,10,1,64,0,100,112,0,10,210,0,11,7,5,1,1,116,1,0,10,4,1,2,0,11]);
const module = new WebAssembly.Module(bytes);
assert.throws(function () {
WebAssembly.Module.exports(module);
}, TypeError, "WebAssembly.Module.exports unable to produce export descriptors for the given module");
}

{
// (module
// (global (export "g") (ref func) (ref.func 0))
// (func)
// )
const bytes = new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,6,7,1,100,112,0,210,0,11,7,5,1,1,103,3,0,10,4,1,2,0,11]);
const module = new WebAssembly.Module(bytes);
assert.throws(function () {
WebAssembly.Module.exports(module);
}, TypeError, "WebAssembly.Module.exports unable to produce export descriptors for the given module");
}

{
// (module
// (func (export "f") (param (ref func)))
// (func)
// )
const bytes = new Uint8Array([0,97,115,109,1,0,0,0,1,9,2,96,1,100,112,0,96,0,0,3,3,2,0,1,7,5,1,1,102,0,0,10,7,2,2,0,11,2,0,11]);
const module = new WebAssembly.Module(bytes);
assert.throws(function () {
WebAssembly.Module.exports(module);
}, TypeError, "WebAssembly.Module.exports unable to produce export descriptors for the given module");
}

{
// (module
// (import "m" "g" (global (ref func)))
// )
const bytes = new Uint8Array([0,97,115,109,1,0,0,0,2,9,1,1,109,1,103,3,100,112,0]);
const module = new WebAssembly.Module(bytes);
assert.throws(function () {
WebAssembly.Module.imports(module);
}, TypeError, "WebAssembly.Module.imports unable to produce import descriptors for the given module");
}

{
// (module
// (import "m" "g" (func (param (ref func))))
// )
const bytes = new Uint8Array([0,97,115,109,1,0,0,0,1,6,1,96,1,100,112,0,2,7,1,1,109,1,103,0,0]);
const module = new WebAssembly.Module(bytes);
assert.throws(function () {
WebAssembly.Module.imports(module);
}, TypeError, "WebAssembly.Module.imports unable to produce import descriptors for the given module");
}
73 changes: 73 additions & 0 deletions JSTests/wasm/js-api/type-reflection-exports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { compile } from "../wabt-wrapper.js";
import * as assert from "../assert.js"

function sameValue(a, b) {
if (JSON.stringify(a) !== JSON.stringify(b))
throw new Error(`Expected ${JSON.stringify(b)} but got ${JSON.stringify(a)}`);
}

const wat = `
(module
(func $log1 (export "fn1") (param i64) (param i32) (result i32)
(i32.const 0))
(func $log2 (export "fn2") (param i32)
(drop (local.get 0)))
(func $log3 (export "fn3") (param i32) (param i64) (result i64)
(local.get 1))
(func $log4 (export "fn4") (param f32) (param f64) (result f64)
(local.get 1))
(func $log5 (export "fn5") (param i32) (param i32) (param i32) (result i32)
(local.get 0))
(func $log6 (export "fn6") (param f32) (param f32) (result f32)
(local.get 0))
(func $log7 (export "fn7") (param i32) (result i32)
(local.get 0))
(func $log8 (export "fn8") (param f64) (result f64)
(local.get 0))
(memory (export "memory") 1 8)
(table (export "table_funcref") 1 2 funcref)
(table (export "table_externref") 1 2 externref)
(global $global_i32_mut (export "global_i32_mut") (mut i32) (i32.const 0))
(global $global_i32_imut (export "global_i32_imut") i32 (i32.const 0))
(global $global_f32_mut (export "global_f32_mut") (mut f32) (f32.const 0.0))
(global $global_f64_imut (export "global_f64_imut") f64 (f64.const 0.0))
(global $global_i64_mut (export "global_i64_mut") (mut i64) (i64.const 0))
(global $global_i64_imut (export "global_i64_imut") i64 (i64.const 0))
(global $global_f64_mut (export "global_f64_mut") (mut f64) (f64.const 0.0))
(global $global_f32_imut (export "global_f32_imut") f32 (f32.const 0.0))
)
`;

async function test() {
const module = await compile(wat);
const expected = [
{ parameters: ["i64", "i32"], results: ["i32"] },
{ parameters: ["i32"], results: [] },
{ parameters: ["i32", "i64"], results: ["i64"] },
{ parameters: ["f32", "f64"], results: ["f64"] },
{ parameters: ["i32", "i32", "i32"], results: ["i32"] },
{ parameters: ["f32", "f32"], results: ["f32"] },
{ parameters: ["i32"], results: ["i32"] },
{ parameters: ["f64"], results: ["f64"] },
{ maximum: 8, minimum: 1, shared: false },
{ maximum: 2, minimum: 1, element: "funcref" },
{ maximum: 2, minimum: 1, element: "externref" },
{ mutable: true, value: "i32" },
{ mutable: false, value: "i32" },
{ mutable: true, value: "f32" },
{ mutable: false, value: "f64" },
{ mutable: true, value: "i64" },
{ mutable: false, value: "i64" },
{ mutable: true, value: "f64" },
{ mutable: false, value: "f32" }
];
const result = WebAssembly.Module.exports(module).map(({ type }) => type);
for (let i = 0; i < expected.length; ++i) {
sameValue(result[i], expected[i]);
}
}

await assert.asyncTest(test());
64 changes: 64 additions & 0 deletions JSTests/wasm/js-api/type-reflection-imports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { compile } from "../wabt-wrapper.js";
import * as assert from "../assert.js"

function sameValue(a, b) {
if (JSON.stringify(a) !== JSON.stringify(b))
throw new Error(`Expected ${JSON.stringify(b)} but got ${JSON.stringify(a)}`);
}

const wat = `
(module
(import "mod" "fn1" (func $log1 (param i64) (param i32) (result i32)))
(import "mod" "fn2" (func $log2 (param i32)))
(import "mod" "fn3" (func $log3 (param i32) (param i64) (result i64)))
(import "mod" "fn4" (func $log4 (param f32) (param f64) (result f64)))
(import "mod" "fn5" (func $log5 (param i32) (param i32) (param i32) (result i32)))
(import "mod" "fn6" (func $log6 (param f32) (param f32) (result f32)))
(import "mod" "fn7" (func $log7 (param i32) (result i32)))
(import "mod" "fn8" (func $log8 (param f64) (result f64)))
(import "mod" "memory" (memory 1 8))
(import "mod" "table_funcref" (table 1 2 funcref))
(import "mod" "table_externref" (table 1 2 externref))
(import "mod" "global_i32_mut" (global $global_i32_mut (mut i32)))
(import "mod" "global_i32_imut" (global $global_i32_imut i32))
(import "mod" "global_f32_mut" (global $global_f32_mut (mut f32)))
(import "mod" "global_f64_imut" (global $global_f64_imut f64))
(import "mod" "global_i64_mut" (global $global_i64_mut (mut i64)))
(import "mod" "global_i64_imut" (global $global_i64_imut i64))
(import "mod" "global_f64_mut" (global $global_f64_mut (mut f64)))
(import "mod" "global_f32_imut" (global $global_f32_imut f32))
)
`
async function test() {
const module = await compile(wat);
const expected = [
{ parameters: ["i64", "i32"], results: ["i32"] },
{ parameters: ["i32"], results: [] },
{ parameters: ["i32", "i64"], results: ["i64"] },
{ parameters: ["f32", "f64"], results: ["f64"] },
{ parameters: ["i32", "i32", "i32"], results: ["i32"] },
{ parameters: ["f32", "f32"], results: ["f32"] },
{ parameters: ["i32"], results: ["i32"] },
{ parameters: ["f64"], results: ["f64"] },
{ maximum: 8, minimum: 1, shared: false },
{ maximum: 2, minimum: 1, element: "funcref" },
{ maximum: 2, minimum: 1, element: "externref" },
{ mutable: true, value: "i32" },
{ mutable: false, value: "i32" },
{ mutable: true, value: "f32" },
{ mutable: false, value: "f64" },
{ mutable: true, value: "i64" },
{ mutable: false, value: "i64" },
{ mutable: true, value: "f64" },
{ mutable: false, value: "f32" }
];
const result = WebAssembly.Module.imports(module).map(({ type }) => type);
for (let i = 0; i < expected.length; ++i) {
sameValue(result[i], expected[i]);
}
}

await assert.asyncTest(test());
1 change: 1 addition & 0 deletions Source/JavaScriptCore/wasm/WasmModuleInformation.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ struct ModuleInformation : public ThreadSafeRefCounted<ModuleInformation> {
uint32_t dataSegmentsCount() const { return numberOfDataSegments.value_or(0); }

const TableInformation& table(unsigned index) const { return tables[index]; }
const GlobalInformation& global(unsigned index) const { return globals[index]; }

void initializeFunctionTrackers() const
{
Expand Down
Loading

0 comments on commit 6880a8b

Please sign in to comment.