From 315cf4544d9574b58e94793da6640344f86d7eb9 Mon Sep 17 00:00:00 2001 From: Andrew Osmond Date: Mon, 27 May 2019 09:20:02 -0400 Subject: [PATCH 1/3] Bug 1554665. r=kats Differential Revision: https://phabricator.services.mozilla.com/D32753 --- toolkit/xre/glxtest.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/toolkit/xre/glxtest.cpp b/toolkit/xre/glxtest.cpp index 67b1c6a75fcb..0f9a44ea1701 100644 --- a/toolkit/xre/glxtest.cpp +++ b/toolkit/xre/glxtest.cpp @@ -255,7 +255,7 @@ void glxtest() { vendorId &= 0xFFFF; deviceId &= 0xFFFF; - length += snprintf(buf + length, bufsize, + length += snprintf(buf + length, bufsize - length, "MESA_VENDOR_ID\n0x%04x\n" "MESA_DEVICE_ID\n0x%04x\n" "MESA_ACCELERATED\n%s\n" @@ -274,7 +274,7 @@ void glxtest() { if (glXGetScreenDriverProc) { const char* driDriver = glXGetScreenDriverProc(dpy, DefaultScreen(dpy)); if (driDriver) { - length += snprintf(buf + length, bufsize, "DRI_DRIVER\n%s\n", driDriver); + length += snprintf(buf + length, bufsize - length, "DRI_DRIVER\n%s\n", driDriver); if (length >= bufsize) fatal_error("GL strings length too large for buffer size"); } From 3da3ee0aa1a2eca14084beddcb1890fe7227e883 Mon Sep 17 00:00:00 2001 From: Andrew Osmond Date: Sun, 26 May 2019 14:39:33 -0400 Subject: [PATCH 2/3] Bug 1548911 - Add support for EGL_MESA_query_driver to detect Mesa driver. r=kats If Wayland is in use, then glXGetScreenDriver doesn't return the Mesa driver name. There is a new API, eglGetDisplayDriverName, which was added in Mesa 19 that works with Wayland. Also expire the driver vendor is supplied for the proprietary NVIDIA and ATI chipsets. Differential Revision: https://phabricator.services.mozilla.com/D32631 --- toolkit/xre/glxtest.cpp | 80 +++++++++++++++++++++++++++++++++++++++++ widget/GfxInfoX11.cpp | 2 ++ 2 files changed, 82 insertions(+) diff --git a/toolkit/xre/glxtest.cpp b/toolkit/xre/glxtest.cpp index 0f9a44ea1701..b349f0614e60 100644 --- a/toolkit/xre/glxtest.cpp +++ b/toolkit/xre/glxtest.cpp @@ -118,6 +118,75 @@ static int x_error_handler(Display*, XErrorEvent* ev) { // care about leaking memory extern "C" { +static int get_egl_status(char* buf, int bufsize) { + void* libegl = dlopen("libEGL.so.1", RTLD_LAZY); + if (!libegl) { + libegl = dlopen("libEGL.so", RTLD_LAZY); + } + if (!libegl) { + return 0; + } + + typedef void* EGLDisplay; + typedef int EGLBoolean; + typedef int EGLint; + + typedef void* (*PFNEGLGETPROCADDRESS)(const char*); + PFNEGLGETPROCADDRESS eglGetProcAddress = + cast(dlsym(libegl, "eglGetProcAddress")); + + if (!eglGetProcAddress) { + dlclose(libegl); + return 0; + } + + typedef EGLDisplay (*PFNEGLGETDISPLAYPROC)(void* native_display); + PFNEGLGETDISPLAYPROC eglGetDisplay = + cast(eglGetProcAddress("eglGetDisplay")); + + typedef EGLBoolean (*PFNEGLINITIALIZEPROC)(EGLDisplay dpy, EGLint * major, + EGLint * minor); + PFNEGLINITIALIZEPROC eglInitialize = + cast(eglGetProcAddress("eglInitialize")); + + typedef EGLBoolean (*PFNEGLTERMINATEPROC)(EGLDisplay dpy); + PFNEGLTERMINATEPROC eglTerminate = + cast(eglGetProcAddress("eglTerminate")); + + typedef const char* (*PFNEGLGETDISPLAYDRIVERNAMEPROC)(EGLDisplay dpy); + PFNEGLGETDISPLAYDRIVERNAMEPROC eglGetDisplayDriverName = + cast( + eglGetProcAddress("eglGetDisplayDriverName")); + + if (!eglGetDisplay || !eglInitialize || !eglTerminate || + !eglGetDisplayDriverName) { + dlclose(libegl); + return 0; + } + + EGLDisplay dpy = eglGetDisplay(nullptr); + if (!dpy) { + dlclose(libegl); + return 0; + } + + EGLint major, minor; + if (!eglInitialize(dpy, &major, &minor)) { + dlclose(libegl); + return 0; + } + + int length = 0; + const char* driDriver = eglGetDisplayDriverName(dpy); + if (driDriver) { + length = snprintf(buf, bufsize, "DRI_DRIVER\n%s\n", driDriver); + } + + eglTerminate(dpy); + dlclose(libegl); + return length; +} + void glxtest() { // we want to redirect to /dev/null stdout, stderr, and while we're at it, // any PR logging file descriptors. To that effect, we redirect all positive @@ -268,12 +337,14 @@ void glxtest() { } // From Mesa's GL/internal/dri_interface.h, to be used by DRI clients. + int gotDriDriver = 0; typedef const char* (*PFNGLXGETSCREENDRIVERPROC)(Display * dpy, int scrNum); PFNGLXGETSCREENDRIVERPROC glXGetScreenDriverProc = cast(glXGetProcAddress("glXGetScreenDriver")); if (glXGetScreenDriverProc) { const char* driDriver = glXGetScreenDriverProc(dpy, DefaultScreen(dpy)); if (driDriver) { + gotDriDriver = 1; length += snprintf(buf + length, bufsize - length, "DRI_DRIVER\n%s\n", driDriver); if (length >= bufsize) fatal_error("GL strings length too large for buffer size"); @@ -304,6 +375,15 @@ void glxtest() { dlclose(libgl); + // If we failed to get the driver name from X, try via EGL_MESA_query_driver. + // We are probably using Wayland. + if (!gotDriDriver) { + length += get_egl_status(buf + length, bufsize - length); + if (length >= bufsize) { + fatal_error("GL strings length too large for buffer size"); + } + } + ///// Finally write data to the pipe mozilla::Unused << write(write_end_of_the_pipe, buf, length); } diff --git a/widget/GfxInfoX11.cpp b/widget/GfxInfoX11.cpp index 0792ebda5092..07e3f93683c1 100644 --- a/widget/GfxInfoX11.cpp +++ b/widget/GfxInfoX11.cpp @@ -274,9 +274,11 @@ void GfxInfo::GetData() { } } else if (glVendor.EqualsLiteral("NVIDIA Corporation")) { CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), mVendorId); + mDriverVendor.AssignLiteral("nvidia/unknown"); // TODO: Use NV-CONTROL X11 extension to query Device ID and VRAM. } else if (glVendor.EqualsLiteral("ATI Technologies Inc.")) { CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(VendorATI), mVendorId); + mDriverVendor.AssignLiteral("ati/unknown"); // TODO: Look into ways to find the device ID on FGLRX. } else { NS_WARNING("Failed to detect GL vendor!"); From 5d02a20efb06da0abaa06690a321a167fbe71a50 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Fri, 10 May 2019 16:49:46 -0500 Subject: [PATCH 3/3] Bug 1546138 - Allow funcref in table operators (r=lth) Differential Revision: https://phabricator.services.mozilla.com/D31166 --HG-- rename : js/src/jit-test/tests/wasm/funcref.js => js/src/jit-test/tests/wasm/gc/funcref.js extra : rebase_source : b1645cb82a197133c2c327cd28c4c2348114680a --- .../jit-test/tests/wasm/gc/anyref-boxing.js | 4 +- .../jit-test/tests/wasm/{ => gc}/funcref.js | 0 js/src/jit-test/tests/wasm/gc/tables-fill.js | 289 ++++++++---------- .../tests/wasm/gc/tables-generalized.js | 123 ++++---- js/src/wasm/WasmInstance.cpp | 70 ++++- js/src/wasm/WasmJS.cpp | 70 +---- js/src/wasm/WasmOpIter.h | 64 ++-- js/src/wasm/WasmTable.cpp | 75 ++++- js/src/wasm/WasmTable.h | 11 +- js/src/wasm/WasmTypes.h | 12 + 10 files changed, 363 insertions(+), 355 deletions(-) rename js/src/jit-test/tests/wasm/{ => gc}/funcref.js (100%) diff --git a/js/src/jit-test/tests/wasm/gc/anyref-boxing.js b/js/src/jit-test/tests/wasm/gc/anyref-boxing.js index 8423900efdff..38ddb9418e09 100644 --- a/js/src/jit-test/tests/wasm/gc/anyref-boxing.js +++ b/js/src/jit-test/tests/wasm/gc/anyref-boxing.js @@ -84,8 +84,8 @@ for (let v of VALUES) // // - through WebAssembly.Table.prototype.set() // - through the table.set, table.copy, and table.grow instructions -// - unimplemented: through table.fill -// - unimplemented: through WebAssembly.Table.prototype.grow() +// - through table.fill +// - through WebAssembly.Table.prototype.grow() // // Their values can be read in several ways: // diff --git a/js/src/jit-test/tests/wasm/funcref.js b/js/src/jit-test/tests/wasm/gc/funcref.js similarity index 100% rename from js/src/jit-test/tests/wasm/funcref.js rename to js/src/jit-test/tests/wasm/gc/funcref.js diff --git a/js/src/jit-test/tests/wasm/gc/tables-fill.js b/js/src/jit-test/tests/wasm/gc/tables-fill.js index 21aaccc3ce70..09e6f009e6e5 100644 --- a/js/src/jit-test/tests/wasm/gc/tables-fill.js +++ b/js/src/jit-test/tests/wasm/gc/tables-fill.js @@ -1,145 +1,145 @@ // |jit-test| skip-if: !wasmReftypesEnabled() -let ins - = wasmEvalText( - `(module - (table 8 anyref) ;; table 0 - (table $t 10 anyref) ;; table 1 +const N = 8; - ;; fill/get for table 0, referenced implicitly - (func (export "fill0") (param $i i32) (param $r anyref) (param $n i32) - (table.fill (local.get $i) (local.get $r) (local.get $n)) - ) - (func (export "get0") (param $i i32) (result anyref) - (table.get (local.get $i)) - ) +function testTableFill(type, obj) { + assertEq(obj.length, N); - ;; fill/get for table 1, referenced explicitly - (func (export "fill1") (param $i i32) (param $r anyref) (param $n i32) - (table.fill $t (local.get $i) (local.get $r) (local.get $n)) - ) - (func (export "get1") (param $i i32) (result anyref) - (table.get $t (local.get $i)) - ) - )`); + let ins + = wasmEvalText( + `(module + (table 8 ${type}) ;; table 0 + (table $t 10 ${type}) ;; table 1 -function Obj(n) { - this.n = n; + ;; fill/get for table 0, referenced implicitly + (func (export "fill0") (param $i i32) (param $r ${type}) (param $n i32) + (table.fill (local.get $i) (local.get $r) (local.get $n)) + ) + (func (export "get0") (param $i i32) (result ${type}) + (table.get (local.get $i)) + ) + + ;; fill/get for table 1, referenced explicitly + (func (export "fill1") (param $i i32) (param $r ${type}) (param $n i32) + (table.fill $t (local.get $i) (local.get $r) (local.get $n)) + ) + (func (export "get1") (param $i i32) (result ${type}) + (table.get $t (local.get $i)) + ) + )`); + + // An initial test to ascertain that tables 0 and 1 are independent + + // Fill in table 0, then check it. + assertEq(ins.exports.fill0(2, obj[6], 5), undefined) + assertEq(ins.exports.fill0(1, obj[7], 3), undefined); + + function check_table0() { + assertEq(ins.exports.get0(0), null); + assertEq(ins.exports.get0(1), obj[7]); + assertEq(ins.exports.get0(2), obj[7]); + assertEq(ins.exports.get0(3), obj[7]); + assertEq(ins.exports.get0(4), obj[6]); + assertEq(ins.exports.get0(5), obj[6]); + assertEq(ins.exports.get0(6), obj[6]); + assertEq(ins.exports.get0(7), null); + } + + // Check that table 0 has the expected content. + check_table0(); + + // Check that messing with table 0 above hasn't changed table 1. + for (let i = 0; i < 10; i++) { + assertEq(ins.exports.get1(i), null); + } + + // Now a bunch of tests involving only table 1. + + // Within the table + assertEq(ins.exports.fill1(2, obj[0], 3), undefined); + assertEq(ins.exports.get1(1), null); + assertEq(ins.exports.get1(2), obj[0]); + assertEq(ins.exports.get1(3), obj[0]); + assertEq(ins.exports.get1(4), obj[0]); + assertEq(ins.exports.get1(5), null); + + // Within the table + assertEq(ins.exports.fill1(4, obj[1], 2), undefined); + assertEq(ins.exports.get1(3), obj[0]); + assertEq(ins.exports.get1(4), obj[1]); + assertEq(ins.exports.get1(5), obj[1]); + assertEq(ins.exports.get1(6), null); + + // Within the table + assertEq(ins.exports.fill1(4, obj[2], 0), undefined); + assertEq(ins.exports.get1(3), obj[0]); + assertEq(ins.exports.get1(4), obj[1]); + assertEq(ins.exports.get1(5), obj[1]); + + // Within the table + assertEq(ins.exports.fill1(8, obj[3], 2), undefined); + assertEq(ins.exports.get1(7), null); + assertEq(ins.exports.get1(8), obj[3]); + assertEq(ins.exports.get1(9), obj[3]); + + // Within the table + assertEq(ins.exports.fill1(9, null, 1), undefined); + assertEq(ins.exports.get1(8), obj[3]); + assertEq(ins.exports.get1(9), null); + + // Within the table + assertEq(ins.exports.fill1(10, obj[4], 0), undefined); + assertEq(ins.exports.get1(9), null); + + // Partly outside the table + assertErrorMessage(() => ins.exports.fill1(8, obj[5], 3), + RangeError, /table index out of bounds/); + + assertEq(ins.exports.get1(7), null); + assertEq(ins.exports.get1(8), obj[5]); + assertEq(ins.exports.get1(9), obj[5]); + + // Boundary tests on table 1: at the edge of the table. + + // Length-zero fill1 at the edge of the table must succeed + assertEq(ins.exports.fill1(10, null, 0), undefined); + + // Length-one fill1 at the edge of the table fails + assertErrorMessage(() => ins.exports.fill1(10, null, 1), + RangeError, /table index out of bounds/); + + // Length-more-than-one fill1 at the edge of the table fails + assertErrorMessage(() => ins.exports.fill1(10, null, 2), + RangeError, /table index out of bounds/); + + + // Boundary tests on table 1: beyond the edge of the table: + + // Length-zero fill1 beyond the edge of the table fails + assertErrorMessage(() => ins.exports.fill1(11, null, 0), + RangeError, /table index out of bounds/); + + // Length-one fill1 beyond the edge of the table fails + assertErrorMessage(() => ins.exports.fill1(11, null, 1), + RangeError, /table index out of bounds/); + + // Length-more-than-one fill1 beyond the edge of the table fails + assertErrorMessage(() => ins.exports.fill1(11, null, 2), + RangeError, /table index out of bounds/); + + // Following all the above tests on table 1, check table 0 hasn't changed. + check_table0(); } -function mkObj(n) { - return new Obj(n); -} +var objs = []; +for (var i = 0; i < N; i++) + objs[i] = {n:i}; +testTableFill('anyref', objs); -const obj1 = mkObj(1); -const obj2 = mkObj(2); -const obj3 = mkObj(3); -const obj4 = mkObj(4); -const obj5 = mkObj(5); -const obj6 = mkObj(6); -const obj7 = mkObj(7); -const obj8 = mkObj(8); - -// An initial test to ascertain that tables 0 and 1 are independent - -// Fill in table 0, then check it. -assertEq(ins.exports.fill0(2, obj7, 5), undefined) -assertEq(ins.exports.fill0(1, obj8, 3), undefined); - -function check_table0() { - assertEq(ins.exports.get0(0), null); - assertEq(ins.exports.get0(1), obj8); - assertEq(ins.exports.get0(2), obj8); - assertEq(ins.exports.get0(3), obj8); - assertEq(ins.exports.get0(4), obj7); - assertEq(ins.exports.get0(5), obj7); - assertEq(ins.exports.get0(6), obj7); - assertEq(ins.exports.get0(7), null); -} - -// Check that table 0 has the expected content. -check_table0(); - -// Check that messing with table 0 above hasn't changed table 1. -for (let i = 0; i < 10; i++) { - assertEq(ins.exports.get1(i), null); -} - - -// Now a bunch of tests involving only table 1. - -// Within the table -assertEq(ins.exports.fill1(2, obj1, 3), undefined); -assertEq(ins.exports.get1(1), null); -assertEq(ins.exports.get1(2), obj1); -assertEq(ins.exports.get1(3), obj1); -assertEq(ins.exports.get1(4), obj1); -assertEq(ins.exports.get1(5), null); - -// Within the table -assertEq(ins.exports.fill1(4, obj2, 2), undefined); -assertEq(ins.exports.get1(3), obj1); -assertEq(ins.exports.get1(4), obj2); -assertEq(ins.exports.get1(5), obj2); -assertEq(ins.exports.get1(6), null); - -// Within the table -assertEq(ins.exports.fill1(4, obj3, 0), undefined); -assertEq(ins.exports.get1(3), obj1); -assertEq(ins.exports.get1(4), obj2); -assertEq(ins.exports.get1(5), obj2); - -// Within the table -assertEq(ins.exports.fill1(8, obj4, 2), undefined); -assertEq(ins.exports.get1(7), null); -assertEq(ins.exports.get1(8), obj4); -assertEq(ins.exports.get1(9), obj4); - -// Within the table -assertEq(ins.exports.fill1(9, null, 1), undefined); -assertEq(ins.exports.get1(8), obj4); -assertEq(ins.exports.get1(9), null); - -// Within the table -assertEq(ins.exports.fill1(10, obj5, 0), undefined); -assertEq(ins.exports.get1(9), null); - -// Partly outside the table -assertErrorMessage(() => ins.exports.fill1(8, obj6, 3), - RangeError, /table index out of bounds/); - -assertEq(ins.exports.get1(7), null); -assertEq(ins.exports.get1(8), obj6); -assertEq(ins.exports.get1(9), obj6); - - -// Boundary tests on table 1: at the edge of the table. - -// Length-zero fill1 at the edge of the table must succeed -assertEq(ins.exports.fill1(10, null, 0), undefined); - -// Length-one fill1 at the edge of the table fails -assertErrorMessage(() => ins.exports.fill1(10, null, 1), - RangeError, /table index out of bounds/); - -// Length-more-than-one fill1 at the edge of the table fails -assertErrorMessage(() => ins.exports.fill1(10, null, 2), - RangeError, /table index out of bounds/); - - -// Boundary tests on table 1: beyond the edge of the table: - -// Length-zero fill1 beyond the edge of the table fails -assertErrorMessage(() => ins.exports.fill1(11, null, 0), - RangeError, /table index out of bounds/); - -// Length-one fill1 beyond the edge of the table fails -assertErrorMessage(() => ins.exports.fill1(11, null, 1), - RangeError, /table index out of bounds/); - -// Length-more-than-one fill1 beyond the edge of the table fails -assertErrorMessage(() => ins.exports.fill1(11, null, 2), - RangeError, /table index out of bounds/); +var funcs = []; +for (var i = 0; i < N; i++) + funcs[i] = wasmEvalText(`(module (func (export "x") (result i32) (i32.const ${i})))`).exports.x; +testTableFill('funcref', funcs); // Type errors. Required sig is: (i32, anyref, i32) -> void @@ -203,24 +203,3 @@ assertErrorMessage(() => wasmEvalText( ))`), WebAssembly.CompileError, /popping value from empty stack/); - -assertErrorMessage(() => wasmEvalText( - `(module - (table $t 0 funcref) - (func $tables-of-funcref-not-allowed (param $r anyref) - (table.fill $t (i32.const 1) (local.get $r) (i32.const 1)) - ))`), - WebAssembly.CompileError, /table.fill only on tables of anyref/); - -assertErrorMessage(() => wasmEvalText( - `(module - (table $t1 1 anyref) - (table $t2 1 funcref) - (func $tables-of-funcref-not-allowed-2 (param $r anyref) - (table.fill $t2 (i32.const 0) (local.get $r) (i32.const 1)) - ))`), - WebAssembly.CompileError, /table.fill only on tables of anyref/); - - -// Following all the above tests on table 1, check table 0 hasn't changed. -check_table0(); diff --git a/js/src/jit-test/tests/wasm/gc/tables-generalized.js b/js/src/jit-test/tests/wasm/gc/tables-generalized.js index 9ac0c1cd3fea..8c4db922684c 100644 --- a/js/src/jit-test/tests/wasm/gc/tables-generalized.js +++ b/js/src/jit-test/tests/wasm/gc/tables-generalized.js @@ -154,22 +154,25 @@ function dummy() { return 37 } // // table.get and table.set +const wasmFun = wasmEvalText(`(module (func (export "x")))`).exports.x; + // table.get in bounds - returns right value type & value // table.get out of bounds - fails -{ +function testTableGet(type, x) { let ins = wasmEvalText( `(module - (table (export "t") 10 anyref) - (func (export "f") (param i32) (result anyref) + (table (export "t") 10 ${type}) + (func (export "f") (param i32) (result ${type}) (table.get (local.get 0))))`); - let x = {}; ins.exports.t.set(0, x); assertEq(ins.exports.f(0), x); assertEq(ins.exports.f(1), null); assertErrorMessage(() => ins.exports.f(10), RangeError, /index out of bounds/); assertErrorMessage(() => ins.exports.f(-5), RangeError, /index out of bounds/); } +testTableGet('anyref', {}); +testTableGet('funcref', wasmFun); // table.get with non-i32 index - fails validation @@ -181,25 +184,6 @@ assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( WebAssembly.CompileError, /type mismatch/); -// table.get on table of funcref - fails validation because funcref is not expressible -// Both with and without anyref support - -assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( - `(module - (table 10 funcref) - (func (export "f") (param i32) - (drop (table.get (local.get 0)))))`)), - WebAssembly.CompileError, - /table.get only on tables of anyref/); - -assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( - `(module - (table 10 funcref) - (func (export "f") (param i32) - (drop (table.get (local.get 0)))))`)), - WebAssembly.CompileError, - /table.get only on tables of anyref/); - // table.get when there are no tables - fails validation assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( @@ -213,15 +197,14 @@ assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( // table.set with null - works // table.set out of bounds - fails -{ +function testTableSet(type, x) { let ins = wasmEvalText( `(module - (table (export "t") 10 anyref) - (func (export "set_anyref") (param i32) (param anyref) + (table (export "t") 10 ${type}) + (func (export "set_anyref") (param i32) (param ${type}) (table.set (local.get 0) (local.get 1))) (func (export "set_null") (param i32) (table.set (local.get 0) (ref.null))))`); - let x = {}; ins.exports.set_anyref(3, x); assertEq(ins.exports.t.get(3), x); ins.exports.set_null(3); @@ -230,6 +213,8 @@ assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( assertErrorMessage(() => ins.exports.set_anyref(10, x), RangeError, /index out of bounds/); assertErrorMessage(() => ins.exports.set_anyref(-1, x), RangeError, /index out of bounds/); } +testTableSet('anyref', {}); +testTableSet('funcref', wasmFun); // table.set with non-i32 index - fails validation @@ -241,7 +226,7 @@ assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( WebAssembly.CompileError, /type mismatch/); -// table.set with non-anyref value - fails validation +// table.set with non-ref value - fails validation assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( `(module @@ -250,16 +235,13 @@ assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( (table.set (i32.const 0) (local.get 0))))`)), WebAssembly.CompileError, /type mismatch/); - -// table.set on table of funcref - fails validation - assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( `(module - (table 10 funcref) - (func (export "f") (param anyref) - (table.set (i32.const 0) (local.get 0))))`)), + (table 10 funcref) + (func (export "f") (param f64) + (table.set (i32.const 0) (local.get 0))))`)), WebAssembly.CompileError, - /table.set only on tables of anyref/); + /type mismatch/); // table.set when there are no tables - fails validation @@ -270,36 +252,43 @@ assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( WebAssembly.CompileError, /table index out of range for table.set/); -// we can grow table of anyref -// table.grow with zero delta - always works even at maximum -// table.grow with delta - works and returns correct old value -// table.grow with delta at upper limit - fails -// table.grow with negative delta - fails +function testTableGrow(type, x) { + let ins = wasmEvalText( + `(module + (table (export "t") 10 20 ${type}) + (func (export "grow") (param i32) (result i32) + (table.grow (ref.null) (local.get 0))) + (func (export "grow2") (param i32) (param ${type}) (result i32) + (table.grow (local.get 1) (local.get 0))))`); -let ins = wasmEvalText( - `(module - (table (export "t") 10 20 anyref) - (func (export "grow") (param i32) (result i32) - (table.grow (ref.null) (local.get 0))))`); -assertEq(ins.exports.grow(0), 10); -assertEq(ins.exports.t.length, 10); -assertEq(ins.exports.grow(1), 10); -assertEq(ins.exports.t.length, 11); -assertEq(ins.exports.t.get(10), null); -assertEq(ins.exports.grow(9), 11); -assertEq(ins.exports.t.length, 20); -assertEq(ins.exports.t.get(19), null); -assertEq(ins.exports.grow(0), 20); + // we can grow table of references + // table.grow with zero delta - always works even at maximum + // table.grow with delta - works and returns correct old value + // table.grow with delta at upper limit - fails + // table.grow with negative delta - fails + assertEq(ins.exports.grow(0), 10); + assertEq(ins.exports.t.length, 10); + assertEq(ins.exports.grow(1), 10); + assertEq(ins.exports.t.length, 11); + assertEq(ins.exports.t.get(10), null); + assertEq(ins.exports.grow2(9, x), 11); + assertEq(ins.exports.t.length, 20); + for (var i = 11; i < 20; i++) + assertEq(ins.exports.t.get(i), x); + assertEq(ins.exports.grow(0), 20); -// The JS API throws if it can't grow -assertErrorMessage(() => ins.exports.t.grow(1), RangeError, /failed to grow table/); -assertErrorMessage(() => ins.exports.t.grow(-1), TypeError, /bad [Tt]able grow delta/); + // The JS API throws if it can't grow + assertErrorMessage(() => ins.exports.t.grow(1), RangeError, /failed to grow table/); + assertErrorMessage(() => ins.exports.t.grow(-1), TypeError, /bad [Tt]able grow delta/); -// The wasm API does not throw if it can't grow, but returns -1 -assertEq(ins.exports.grow(1), -1); -assertEq(ins.exports.t.length, 20); -assertEq(ins.exports.grow(-1), -1); -assertEq(ins.exports.t.length, 20) + // The wasm API does not throw if it can't grow, but returns -1 + assertEq(ins.exports.grow(1), -1); + assertEq(ins.exports.t.length, 20); + assertEq(ins.exports.grow(-1), -1); + assertEq(ins.exports.t.length, 20) +} +testTableGrow('anyref', 42); +testTableGrow('funcref', wasmFun); // Special case for private tables without a maximum @@ -315,16 +304,6 @@ assertEq(ins.exports.t.length, 20) assertEq(ins.exports.grow(0), 20); } -// Can't grow table of funcref yet - -assertErrorMessage(() => wasmEvalText( - `(module - (table $t 2 funcref) - (func $f - (drop (table.grow (ref.null) (i32.const 1)))))`), - WebAssembly.CompileError, - /table.grow only on tables of anyref/); - // table.grow with non-i32 argument - fails validation assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( diff --git a/js/src/wasm/WasmInstance.cpp b/js/src/wasm/WasmInstance.cpp index ce1ed40e1d12..3b4ce3633c95 100644 --- a/js/src/wasm/WasmInstance.cpp +++ b/js/src/wasm/WasmInstance.cpp @@ -886,8 +886,8 @@ void Instance::initElems(uint32_t tableIndex, const ElemSegment& seg, uint32_t tableIndex) { MOZ_ASSERT(SASigTableFill.failureMode == FailureMode::FailOnNegI32); + JSContext* cx = TlsContext.get(); Table& table = *instance->tables()[tableIndex]; - MOZ_RELEASE_ASSERT(table.kind() == TableKind::AnyRef); if (len == 0) { // Even though the length is zero, we must check for a valid offset. But @@ -912,10 +912,17 @@ void Instance::initElems(uint32_t tableIndex, const ElemSegment& seg, mustTrap = true; } - for (uint32_t i = 0; i < len; i++) { - uint32_t index = start + i; - MOZ_ASSERT(index < table.length()); - table.setAnyRef(index, AnyRef::fromCompiledCode(value)); + AnyRef ref = AnyRef::fromCompiledCode(value); + + switch (table.kind()) { + case TableKind::AnyRef: + table.fillAnyRef(start, len, ref); + break; + case TableKind::FuncRef: + table.fillFuncRef(start, len, ref, cx); + break; + case TableKind::AsmJS: + MOZ_CRASH("not asm.js"); } if (!mustTrap) { @@ -923,7 +930,6 @@ void Instance::initElems(uint32_t tableIndex, const ElemSegment& seg, } } - JSContext* cx = TlsContext.get(); JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_TABLE_OUT_OF_BOUNDS); return -1; @@ -932,30 +938,52 @@ void Instance::initElems(uint32_t tableIndex, const ElemSegment& seg, /* static */ void* Instance::tableGet(Instance* instance, uint32_t index, uint32_t tableIndex) { MOZ_ASSERT(SASigTableGet.failureMode == FailureMode::FailOnInvalidRef); + const Table& table = *instance->tables()[tableIndex]; - MOZ_RELEASE_ASSERT(table.kind() == TableKind::AnyRef); if (index >= table.length()) { JS_ReportErrorNumberASCII(TlsContext.get(), GetErrorMessage, nullptr, JSMSG_WASM_TABLE_OUT_OF_BOUNDS); return AnyRef::invalid().forCompiledCode(); } - return table.getAnyRef(index).forCompiledCode(); + + if (table.kind() == TableKind::AnyRef) { + return table.getAnyRef(index).forCompiledCode(); + } + + MOZ_RELEASE_ASSERT(table.kind() == TableKind::FuncRef); + + JSContext* cx = TlsContext.get(); + RootedFunction fun(cx); + if (!table.getFuncRef(cx, index, &fun)) { + return AnyRef::invalid().forCompiledCode(); + } + + return AnyRef::fromJSObject(fun).forCompiledCode(); } /* static */ uint32_t Instance::tableGrow(Instance* instance, void* initValue, uint32_t delta, uint32_t tableIndex) { MOZ_ASSERT(SASigTableGrow.failureMode == FailureMode::Infallible); - RootedAnyRef obj(TlsContext.get(), AnyRef::fromCompiledCode(initValue)); + RootedAnyRef ref(TlsContext.get(), AnyRef::fromCompiledCode(initValue)); Table& table = *instance->tables()[tableIndex]; - MOZ_RELEASE_ASSERT(table.kind() == TableKind::AnyRef); - uint32_t oldSize = table.grow(delta, TlsContext.get()); + JSContext* cx = TlsContext.get(); + uint32_t oldSize = table.grow(delta, cx); + if (oldSize != uint32_t(-1) && initValue != nullptr) { - for (uint32_t i = 0; i < delta; i++) { - table.setAnyRef(oldSize + i, obj.get()); + switch (table.kind()) { + case TableKind::AnyRef: + table.fillAnyRef(oldSize, delta, ref); + break; + case TableKind::FuncRef: + table.fillFuncRef(oldSize, delta, ref, cx); + break; + case TableKind::AsmJS: + MOZ_CRASH("not asm.js"); } } + return oldSize; } @@ -964,13 +992,25 @@ void Instance::initElems(uint32_t tableIndex, const ElemSegment& seg, MOZ_ASSERT(SASigTableSet.failureMode == FailureMode::FailOnNegI32); Table& table = *instance->tables()[tableIndex]; - MOZ_RELEASE_ASSERT(table.kind() == TableKind::AnyRef); if (index >= table.length()) { JS_ReportErrorNumberASCII(TlsContext.get(), GetErrorMessage, nullptr, JSMSG_WASM_TABLE_OUT_OF_BOUNDS); return -1; } - table.setAnyRef(index, AnyRef::fromCompiledCode(value)); + + AnyRef ref = AnyRef::fromCompiledCode(value); + + switch (table.kind()) { + case TableKind::AnyRef: + table.fillAnyRef(index, 1, ref); + break; + case TableKind::FuncRef: + table.fillFuncRef(index, 1, ref, TlsContext.get()); + break; + case TableKind::AsmJS: + MOZ_CRASH("not asm.js"); + } + return 0; } diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp index 993e4989bd94..2f29bf72437c 100644 --- a/js/src/wasm/WasmJS.cpp +++ b/js/src/wasm/WasmJS.cpp @@ -2128,23 +2128,11 @@ bool WasmTableObject::getImpl(JSContext* cx, const CallArgs& args) { switch (table.kind()) { case TableKind::FuncRef: { - const FunctionTableElem& elem = table.getFuncRef(index); - if (!elem.code) { - args.rval().setNull(); - return true; - } - - Instance& instance = *elem.tls->instance; - const CodeRange& codeRange = *instance.code().lookupFuncRange(elem.code); - - RootedWasmInstanceObject instanceObj(cx, instance.object()); RootedFunction fun(cx); - if (!instanceObj->getExportedFunction(cx, instanceObj, - codeRange.funcIndex(), &fun)) { + if (!table.getFuncRef(cx, index, &fun)) { return false; } - - args.rval().setObject(*fun); + args.rval().setObjectOrNull(fun); break; } case TableKind::AnyRef: { @@ -2164,36 +2152,6 @@ bool WasmTableObject::get(JSContext* cx, unsigned argc, Value* vp) { return CallNonGenericMethod(cx, args); } -static void TableFunctionFill(JSContext* cx, Table* table, HandleFunction value, - uint32_t index, uint32_t limit) { - if (!value) { - while (index < limit) { - table->setNull(index++); - } - return; - } - - RootedWasmInstanceObject instanceObj(cx, - ExportedFunctionToInstanceObject(value)); - uint32_t funcIndex = ExportedFunctionToFuncIndex(value); - -#ifdef DEBUG - RootedFunction f(cx); - MOZ_ASSERT(instanceObj->getExportedFunction(cx, instanceObj, funcIndex, &f)); - MOZ_ASSERT(value == f); -#endif - - Instance& instance = instanceObj->instance(); - Tier tier = instance.code().bestTier(); - const MetadataTier& metadata = instance.metadata(tier); - const CodeRange& codeRange = - metadata.codeRange(metadata.lookupFuncExport(funcIndex)); - void* code = instance.codeBase(tier) + codeRange.funcTableEntry(); - while (index < limit) { - table->setFuncRef(index++, code, &instance); - } -} - /* static */ bool WasmTableObject::setImpl(JSContext* cx, const CallArgs& args) { RootedWasmTableObject tableObj( @@ -2218,7 +2176,7 @@ bool WasmTableObject::setImpl(JSContext* cx, const CallArgs& args) { } MOZ_ASSERT(index < MaxTableLength); static_assert(MaxTableLength < UINT32_MAX, "Invariant"); - TableFunctionFill(cx, &table, fun, index, index + 1); + table.fillFuncRef(index, 1, AnyRef::fromJSObject(fun), cx); break; } case TableKind::AnyRef: { @@ -2226,7 +2184,7 @@ bool WasmTableObject::setImpl(JSContext* cx, const CallArgs& args) { if (!BoxAnyRef(cx, fillValue, &tmp)) { return false; } - table.setAnyRef(index, tmp); + table.fillAnyRef(index, 1, tmp); break; } default: { @@ -2246,8 +2204,9 @@ bool WasmTableObject::set(JSContext* cx, unsigned argc, Value* vp) { /* static */ bool WasmTableObject::growImpl(JSContext* cx, const CallArgs& args) { - RootedWasmTableObject table(cx, - &args.thisv().toObject().as()); + RootedWasmTableObject tableObj( + cx, &args.thisv().toObject().as()); + Table& table = tableObj->table(); if (!args.requireAtLeast(cx, "WebAssembly.Table.grow", 1)) { return false; @@ -2258,7 +2217,7 @@ bool WasmTableObject::growImpl(JSContext* cx, const CallArgs& args) { return false; } - uint32_t oldLength = table->table().grow(delta, cx); + uint32_t oldLength = table.grow(delta, cx); if (oldLength == uint32_t(-1)) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW, @@ -2277,12 +2236,12 @@ bool WasmTableObject::growImpl(JSContext* cx, const CallArgs& args) { static_assert(MaxTableLength < UINT32_MAX, "Invariant"); - switch (table->table().kind()) { + switch (table.kind()) { case TableKind::FuncRef: { if (fillValue.isNull()) { #ifdef DEBUG for (uint32_t index = oldLength; index < oldLength + delta; index++) { - MOZ_ASSERT(table->table().getFuncRef(index).code == nullptr); + MOZ_ASSERT(table.getFuncRef(index).code == nullptr); } #endif } else { @@ -2290,8 +2249,7 @@ bool WasmTableObject::growImpl(JSContext* cx, const CallArgs& args) { if (!CheckFuncRefValue(cx, fillValue, &fun)) { return false; } - TableFunctionFill(cx, &table->table(), fun, oldLength, - oldLength + delta); + table.fillFuncRef(oldLength, delta, AnyRef::fromJSObject(fun), cx); } break; } @@ -2301,13 +2259,11 @@ bool WasmTableObject::growImpl(JSContext* cx, const CallArgs& args) { return false; } if (!tmp.get().isNull()) { - for (uint32_t index = oldLength; index < oldLength + delta; index++) { - table->table().setAnyRef(index, tmp); - } + table.fillAnyRef(oldLength, delta, tmp); } else { #ifdef DEBUG for (uint32_t index = oldLength; index < oldLength + delta; index++) { - MOZ_ASSERT(table->table().getAnyRef(index).isNull()); + MOZ_ASSERT(table.getAnyRef(index).isNull()); } #endif } diff --git a/js/src/wasm/WasmOpIter.h b/js/src/wasm/WasmOpIter.h index 5ec06daf7b85..10d42c7a4dff 100644 --- a/js/src/wasm/WasmOpIter.h +++ b/js/src/wasm/WasmOpIter.h @@ -1966,28 +1966,21 @@ inline bool OpIter::readTableFill(uint32_t* tableIndex, Value* start, Value* val, Value* len) { MOZ_ASSERT(Classify(op_) == OpKind::TableFill); - if (!popWithType(ValType::I32, len)) { - return false; - } - - if (!popWithType(ValType::AnyRef, val)) { - return false; - } - - if (!popWithType(ValType::I32, start)) { - return false; - } - if (!readVarU32(tableIndex)) { return false; } - if (*tableIndex >= env_.tables.length()) { return fail("table index out of range for table.fill"); } - if (env_.tables[*tableIndex].kind != TableKind::AnyRef) { - return fail("table.fill only on tables of anyref"); + if (!popWithType(ValType::I32, len)) { + return false; + } + if (!popWithType(ToElemValType(env_.tables[*tableIndex].kind), val)) { + return false; + } + if (!popWithType(ValType::I32, start)) { + return false; } return true; @@ -1997,23 +1990,18 @@ template inline bool OpIter::readTableGet(uint32_t* tableIndex, Value* index) { MOZ_ASSERT(Classify(op_) == OpKind::TableGet); - if (!popWithType(ValType::I32, index)) { - return false; - } - if (!readVarU32(tableIndex)) { return false; } - if (*tableIndex >= env_.tables.length()) { return fail("table index out of range for table.get"); } - if (env_.tables[*tableIndex].kind != TableKind::AnyRef) { - return fail("table.get only on tables of anyref"); + if (!popWithType(ValType::I32, index)) { + return false; } - infalliblePush(ValType::AnyRef); + infalliblePush(ToElemValType(env_.tables[*tableIndex].kind)); return true; } @@ -2022,21 +2010,18 @@ inline bool OpIter::readTableGrow(uint32_t* tableIndex, Value* initValue, Value* delta) { MOZ_ASSERT(Classify(op_) == OpKind::TableGrow); - if (!popWithType(ValType::I32, delta)) { - return false; - } - if (!popWithType(ValType::AnyRef, initValue)) { - return false; - } - if (!readVarU32(tableIndex)) { return false; } if (*tableIndex >= env_.tables.length()) { return fail("table index out of range for table.grow"); } - if (env_.tables[*tableIndex].kind != TableKind::AnyRef) { - return fail("table.grow only on tables of anyref"); + + if (!popWithType(ValType::I32, delta)) { + return false; + } + if (!popWithType(ToElemValType(env_.tables[*tableIndex].kind), initValue)) { + return false; } infalliblePush(ValType::I32); @@ -2048,21 +2033,18 @@ inline bool OpIter::readTableSet(uint32_t* tableIndex, Value* index, Value* value) { MOZ_ASSERT(Classify(op_) == OpKind::TableSet); - if (!popWithType(ValType::AnyRef, value)) { - return false; - } - if (!popWithType(ValType::I32, index)) { - return false; - } - if (!readVarU32(tableIndex)) { return false; } if (*tableIndex >= env_.tables.length()) { return fail("table index out of range for table.set"); } - if (env_.tables[*tableIndex].kind != TableKind::AnyRef) { - return fail("table.set only on tables of anyref"); + + if (!popWithType(ToElemValType(env_.tables[*tableIndex].kind), value)) { + return false; + } + if (!popWithType(ValType::I32, index)) { + return false; } return true; diff --git a/js/src/wasm/WasmTable.cpp b/js/src/wasm/WasmTable.cpp index ade6be66ee57..95d3b5d3ff00 100644 --- a/js/src/wasm/WasmTable.cpp +++ b/js/src/wasm/WasmTable.cpp @@ -139,12 +139,22 @@ const FunctionTableElem& Table::getFuncRef(uint32_t index) const { return functions_[index]; } -AnyRef Table::getAnyRef(uint32_t index) const { - MOZ_ASSERT(!isFunction()); - // TODO/AnyRef-boxing: With boxed immediates and strings, the write barrier - // is going to have to be more complicated. - ASSERT_ANYREF_IS_JSOBJECT; - return AnyRef::fromJSObject(objects_[index]); +bool Table::getFuncRef(JSContext* cx, uint32_t index, + MutableHandleFunction fun) const { + MOZ_ASSERT(isFunction()); + + const FunctionTableElem& elem = getFuncRef(index); + if (!elem.code) { + fun.set(nullptr); + return true; + } + + Instance& instance = *elem.tls->instance; + const CodeRange& codeRange = *instance.code().lookupFuncRange(elem.code); + + RootedWasmInstanceObject instanceObj(cx, instance.object()); + return instanceObj->getExportedFunction(cx, instanceObj, + codeRange.funcIndex(), fun); } void Table::setFuncRef(uint32_t index, void* code, const Instance* instance) { @@ -171,12 +181,57 @@ void Table::setFuncRef(uint32_t index, void* code, const Instance* instance) { } } -void Table::setAnyRef(uint32_t index, AnyRef new_obj) { +void Table::fillFuncRef(uint32_t index, uint32_t fillCount, AnyRef ref, + JSContext* cx) { + MOZ_ASSERT(isFunction()); + + if (ref.isNull()) { + for (uint32_t i = index, end = index + fillCount; i != end; i++) { + setNull(i); + } + return; + } + + RootedFunction fun(cx, &ref.asJSObject()->as()); + MOZ_RELEASE_ASSERT(IsWasmExportedFunction(fun)); + + RootedWasmInstanceObject instanceObj(cx, + ExportedFunctionToInstanceObject(fun)); + uint32_t funcIndex = ExportedFunctionToFuncIndex(fun); + +#ifdef DEBUG + RootedFunction f(cx); + MOZ_ASSERT(instanceObj->getExportedFunction(cx, instanceObj, funcIndex, &f)); + MOZ_ASSERT(fun == f); +#endif + + Instance& instance = instanceObj->instance(); + Tier tier = instance.code().bestTier(); + const MetadataTier& metadata = instance.metadata(tier); + const CodeRange& codeRange = + metadata.codeRange(metadata.lookupFuncExport(funcIndex)); + void* code = instance.codeBase(tier) + codeRange.funcTableEntry(); + for (uint32_t i = index, end = index + fillCount; i != end; i++) { + setFuncRef(i, code, &instance); + } +} + +AnyRef Table::getAnyRef(uint32_t index) const { MOZ_ASSERT(!isFunction()); // TODO/AnyRef-boxing: With boxed immediates and strings, the write barrier // is going to have to be more complicated. ASSERT_ANYREF_IS_JSOBJECT; - objects_[index] = new_obj.asJSObject(); + return AnyRef::fromJSObject(objects_[index]); +} + +void Table::fillAnyRef(uint32_t index, uint32_t fillCount, AnyRef ref) { + MOZ_ASSERT(!isFunction()); + // TODO/AnyRef-boxing: With boxed immediates and strings, the write barrier + // is going to have to be more complicated. + ASSERT_ANYREF_IS_JSOBJECT; + for (uint32_t i = index, end = index + fillCount; i != end; i++) { + objects_[i] = ref.asJSObject(); + } } void Table::setNull(uint32_t index) { @@ -192,7 +247,7 @@ void Table::setNull(uint32_t index) { break; } case TableKind::AnyRef: { - setAnyRef(index, AnyRef::null()); + fillAnyRef(index, 1, AnyRef::null()); break; } case TableKind::AsmJS: { @@ -223,7 +278,7 @@ void Table::copy(const Table& srcTable, uint32_t dstIndex, uint32_t srcIndex) { break; } case TableKind::AnyRef: { - setAnyRef(dstIndex, srcTable.getAnyRef(srcIndex)); + fillAnyRef(dstIndex, 1, srcTable.getAnyRef(srcIndex)); break; } case TableKind::AsmJS: { diff --git a/js/src/wasm/WasmTable.h b/js/src/wasm/WasmTable.h index d2dbba66936f..f5c27ee04a00 100644 --- a/js/src/wasm/WasmTable.h +++ b/js/src/wasm/WasmTable.h @@ -80,14 +80,19 @@ class Table : public ShareableBase { // Only for function values. Raw pointer to the table. uint8_t* functionBase() const; - // get/setFuncRef is allowed only on table-of-funcref. - // get/setAnyRef is allowed only on table-of-anyref. + // set/get/fillFuncRef is allowed only on table-of-funcref. + // get/fillAnyRef is allowed only on table-of-anyref. // setNull is allowed on either. + const FunctionTableElem& getFuncRef(uint32_t index) const; + bool getFuncRef(JSContext* cx, uint32_t index, + MutableHandleFunction fun) const; void setFuncRef(uint32_t index, void* code, const Instance* instance); + void fillFuncRef(uint32_t index, uint32_t fillCount, AnyRef ref, + JSContext* cx); AnyRef getAnyRef(uint32_t index) const; - void setAnyRef(uint32_t index, AnyRef); + void fillAnyRef(uint32_t index, uint32_t fillCount, AnyRef ref); void setNull(uint32_t index); diff --git a/js/src/wasm/WasmTypes.h b/js/src/wasm/WasmTypes.h index 86b3abcae00e..9b202f2fbcce 100644 --- a/js/src/wasm/WasmTypes.h +++ b/js/src/wasm/WasmTypes.h @@ -1980,6 +1980,18 @@ struct Limits { enum class TableKind { AnyRef, FuncRef, AsmJS }; +static inline ValType ToElemValType(TableKind tk) { + switch (tk) { + case TableKind::AnyRef: + return ValType::AnyRef; + case TableKind::FuncRef: + return ValType::FuncRef; + case TableKind::AsmJS: + break; + } + MOZ_CRASH("not used for asm.js"); +} + struct TableDesc { TableKind kind; bool importedOrExported;