From 3b3e310b73a02a8034731da080d3ee6e5f94eb77 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Wed, 7 Oct 2020 03:44:45 +0000 Subject: [PATCH] Bug 1655042: Support Cranelift-based Wasm validation. r=jseward This patch is an update of Benjamin Bouvier's WIP patch that supports Cranelift's new Wasm validator functionality, as introduced in bytecodealliance/wasmtime#2059. Previously, Baldrdash allowed SpiderMonkey to validate Wasm function bodies before invoking Cranelift. The Cranelift PR now causes validation to happen in the function-body compiler. Because the Cranelift backend now takes on this responsibility, we no longer need to invoke the SpiderMonkey function-body validator. This requires some significant new glue in the Baldrdash bindings to allow Cranelift's Wasm frontend to access module type information. Co-authored-by: Benjamin Bouvier Differential Revision: https://phabricator.services.mozilla.com/D92504 --- js/src/jit-test/lib/wasm.js | 20 +- js/src/jit-test/tests/wasm/basic.js | 10 +- js/src/jit-test/tests/wasm/binary.js | 12 +- js/src/jit-test/tests/wasm/control-flow.js | 28 +- js/src/jit-test/tests/wasm/float.js | 2 +- js/src/jit-test/tests/wasm/gc/disabled.js | 2 +- js/src/jit-test/tests/wasm/globals.js | 4 +- js/src/jit-test/tests/wasm/memory.js | 4 +- .../tests/wasm/passive-segs-boundary.js | 32 +- .../tests/wasm/passive-segs-nonboundary.js | 12 +- .../jit-test/tests/wasm/ref-types/ref-func.js | 6 +- .../tests/wasm/ref-types/tables-fill.js | 16 +- .../wasm/ref-types/tables-generalized.js | 20 +- .../tests/wasm/ref-types/tables-multiple.js | 18 +- .../tests/wasm/regress/misc-control-flow.js | 8 +- js/src/jit-test/tests/wasm/select.js | 6 +- js/src/jit-test/tests/wasm/simd/disabled.js | 2 +- js/src/jit-test/tests/wasm/text.js | 2 +- js/src/wasm/WasmCraneliftCompile.cpp | 101 +++++-- js/src/wasm/WasmValidate.cpp | 7 + js/src/wasm/WasmValidate.h | 1 + js/src/wasm/cranelift/baldrapi.h | 22 +- js/src/wasm/cranelift/clifapi.h | 10 +- js/src/wasm/cranelift/src/bindings/mod.rs | 276 ++++++++++++++---- js/src/wasm/cranelift/src/compile.rs | 68 ++--- js/src/wasm/cranelift/src/lib.rs | 29 +- js/src/wasm/cranelift/src/wasm2clif.rs | 60 ++-- 27 files changed, 528 insertions(+), 250 deletions(-) diff --git a/js/src/jit-test/lib/wasm.js b/js/src/jit-test/lib/wasm.js index ab557890443f..2bc491ec480c 100644 --- a/js/src/jit-test/lib/wasm.js +++ b/js/src/jit-test/lib/wasm.js @@ -59,13 +59,27 @@ function wasmCompilationShouldFail(bin, compile_error_regex) { } } +function typeToCraneliftName(ty) { + switch(ty) { + case 'externref': + return 'ExternRef'; + case 'funcref': + return 'FuncRef'; + default: + return ty.toUpperCase(); + } +} + function mismatchError(actual, expect) { - var str = `type mismatch: expression has type ${actual} but expected ${expect}`; + let actualCL = typeToCraneliftName(actual); + let expectCL = typeToCraneliftName(expect); + var str = `(type mismatch: expression has type ${actual} but expected ${expect})|` + + `(type mismatch: expected Some\\(${expectCL}\\), found Some\\(${actualCL}\\))`; return RegExp(str); } -const emptyStackError = /from empty stack/; -const unusedValuesError = /unused values not explicitly dropped by end of block/; +const emptyStackError = /(from empty stack)|(nothing on stack)/; +const unusedValuesError = /(unused values not explicitly dropped by end of block)|(values remaining on stack at end of block)/; function jsify(wasmVal) { if (wasmVal === 'nan') diff --git a/js/src/jit-test/tests/wasm/basic.js b/js/src/jit-test/tests/wasm/basic.js index dc36522d62bc..4c775fbe6cc3 100644 --- a/js/src/jit-test/tests/wasm/basic.js +++ b/js/src/jit-test/tests/wasm/basic.js @@ -158,7 +158,7 @@ assertEq(wasmEvalText('(module (func (param i32) (result i32) (local.get 0)) (ex assertEq(wasmEvalText('(module (func (param i32) (param i32) (result i32) (local.get 0)) (export "" (func 0)))').exports[""](42, 43), 42); assertEq(wasmEvalText('(module (func (param i32) (param i32) (result i32) (local.get 1)) (export "" (func 0)))').exports[""](42, 43), 43); -wasmFailValidateText('(module (func (local.get 0)))', /local.get index out of range/); +wasmFailValidateText('(module (func (local.get 0)))', /(local.get index out of range)|(local index out of bounds)/); wasmFailValidateText('(module (func (result f32) (local i32) (local.get 0)))', mismatchError("i32", "f32")); wasmFailValidateText('(module (func (result i32) (local f32) (local.get 0)))', mismatchError("f32", "i32")); wasmFailValidateText('(module (func (param i32) (result f32) (local f32) (local.get 0)))', mismatchError("i32", "f32")); @@ -171,7 +171,7 @@ wasmFullPass('(module (func (result i32) (local i32) (local.get 0)) (export "run wasmFullPass('(module (func (param i32) (result i32) (local f32) (local.get 0)) (export "run" (func 0)))', 0); wasmFullPass('(module (func (param i32) (result f32) (local f32) (local.get 1)) (export "run" (func 0)))', 0); -wasmFailValidateText('(module (func (local.set 0 (i32.const 0))))', /local.set index out of range/); +wasmFailValidateText('(module (func (local.set 0 (i32.const 0))))', /(local.set index out of range)|(local index out of bounds)/); wasmFailValidateText('(module (func (local f32) (local.set 0 (i32.const 0))))', mismatchError("i32", "f32")); wasmFailValidateText('(module (func (local f32) (local.set 0 (nop))))', emptyStackError); wasmFailValidateText('(module (func (local i32) (local f32) (local.set 0 (local.get 1))))', mismatchError("f32", "i32")); @@ -215,7 +215,7 @@ wasmFailValidateText('(module (func (nop)) (func (call 0 (i32.const 0))))', unus wasmFailValidateText('(module (func (param i32) (nop)) (func (call 0)))', emptyStackError); wasmFailValidateText('(module (func (param f32) (nop)) (func (call 0 (i32.const 0))))', mismatchError("i32", "f32")); -wasmFailValidateText('(module (func (nop)) (func (call 3)))', /callee index out of range/); +wasmFailValidateText('(module (func (nop)) (func (call 3)))', /(callee index out of range)|(function index out of bounds)/); wasmValidateText('(module (func (nop)) (func (call 0)))'); wasmValidateText('(module (func (param i32) (nop)) (func (call 0 (i32.const 0))))'); @@ -277,7 +277,7 @@ wasmFullPass(`(module (import "" "evalcx" (func (param i32) (result i32))) (func if (typeof evaluate === 'function') evaluate(`new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module)'))) `, { fileName: null }); -wasmFailValidateText(`(module (type $t (func)) (func (call_indirect (type $t) (i32.const 0))))`, /can't call_indirect without a table/); +wasmFailValidateText(`(module (type $t (func)) (func (call_indirect (type $t) (i32.const 0))))`, /(can't call_indirect without a table)|(unknown table)/); var {v2i, i2i, i2v} = wasmEvalText(`(module (type (func (result i32))) @@ -359,4 +359,4 @@ wasmValidateText('(module (func (call $foo)) (func $foo (nop)))'); wasmValidateText('(module (import "" "a" (func $bar)) (func (call $bar)) (func $foo (nop)))'); var exp = wasmEvalText(`(module (func (export "f")))`).exports; -assertEq(Object.isFrozen(exp), true); \ No newline at end of file +assertEq(Object.isFrozen(exp), true); diff --git a/js/src/jit-test/tests/wasm/binary.js b/js/src/jit-test/tests/wasm/binary.js index 1f1409f46a40..91ccc47a62b4 100644 --- a/js/src/jit-test/tests/wasm/binary.js +++ b/js/src/jit-test/tests/wasm/binary.js @@ -133,7 +133,7 @@ wasmEval(moduleWithSections([memorySection0()])); assertErrorMessage(() => wasmEval(moduleWithSections([invalidMemorySection2()])), CompileError, /number of memories must be at most one/); // Test early 'end' -const bodyMismatch = /function body length mismatch/; +const bodyMismatch = /(function body length mismatch)|(operators remaining after end of function)/; assertErrorMessage(() => wasmEval(moduleWithSections([v2vSigSection, declSection([0]), bodySection([funcBody({locals:[], body:[EndCode]})])])), CompileError, bodyMismatch); assertErrorMessage(() => wasmEval(moduleWithSections([v2vSigSection, declSection([0]), bodySection([funcBody({locals:[], body:[UnreachableCode,EndCode]})])])), CompileError, bodyMismatch); assertErrorMessage(() => wasmEval(moduleWithSections([v2vSigSection, declSection([0]), bodySection([funcBody({locals:[], body:[EndCode,UnreachableCode]})])])), CompileError, bodyMismatch); @@ -217,20 +217,20 @@ assertErrorMessage(() => wasmEval(moduleWithSections([ // Diagnose invalid block signature types. for (var bad of [0xff, 1, 0x3f]) - assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[BlockCode, bad, EndCode]})])])), CompileError, /invalid .*block type/); + assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[BlockCode, bad, EndCode]})])])), CompileError, /(invalid .*block type)|(unknown type)/); const multiValueModule = moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[BlockCode, 0, EndCode]})])]); if (wasmMultiValueEnabled()) { // In this test module, 0 denotes a void-to-void block type. assertEq(WebAssembly.validate(multiValueModule), true); } else { - assertErrorMessage(() => wasmEval(multiValueModule), CompileError, /invalid .*block type/); + assertErrorMessage(() => wasmEval(multiValueModule), CompileError, /(invalid .*block type)|(unknown type)/); } // Ensure all invalid opcodes rejected for (let op of undefinedOpcodes) { let binary = moduleWithSections([v2vSigSection, declSection([0]), bodySection([funcBody({locals:[], body:[op]})])]); - assertErrorMessage(() => wasmEval(binary), CompileError, /unrecognized opcode/); + assertErrorMessage(() => wasmEval(binary), CompileError, /((unrecognized|Unknown) opcode)|(tail calls support is not enabled)|(Unexpected EOF)/); assertEq(WebAssembly.validate(binary), false); } @@ -241,7 +241,7 @@ function checkIllegalPrefixed(prefix, opcode) { declSection([0]), bodySection([funcBody({locals:[], body:[prefix, ...varU32(opcode)]})])]); - assertErrorMessage(() => wasmEval(binary), CompileError, /unrecognized opcode/); + assertErrorMessage(() => wasmEval(binary), CompileError, /((unrecognized|Unknown) opcode)|(Unknown.*subopcode)|(Unexpected EOF)|(SIMD support is not enabled)|(invalid lane index)/); assertEq(WebAssembly.validate(binary), false); } @@ -330,7 +330,7 @@ for (let prefix of [ThreadPrefix, MiscPrefix, SimdPrefix, MozPrefix]) { // Prefix without a subsequent opcode. We must ask funcBody not to add an // End code after the prefix, so the body really is just the prefix byte. let binary = moduleWithSections([v2vSigSection, declSection([0]), bodySection([funcBody({locals:[], body:[prefix]}, /*withEndCode=*/false)])]); - assertErrorMessage(() => wasmEval(binary), CompileError, /unable to read opcode/); + assertErrorMessage(() => wasmEval(binary), CompileError, /(unable to read opcode)|(Unexpected EOF)|(Unknown opcode)/); assertEq(WebAssembly.validate(binary), false); } diff --git a/js/src/jit-test/tests/wasm/control-flow.js b/js/src/jit-test/tests/wasm/control-flow.js index 3afba34592b5..87c7593ff0ff 100644 --- a/js/src/jit-test/tests/wasm/control-flow.js +++ b/js/src/jit-test/tests/wasm/control-flow.js @@ -20,7 +20,7 @@ assertEq(wasmEvalText('(module (func (result i32) (if (result i32) (i32.const 0) // Even if we don't yield, sub expressions types still have to match. wasmFailValidateText('(module (func (param f32) (if (result i32) (i32.const 42) (i32.const 1) (local.get 0))) (export "" (func 0)))', mismatchError('f32', 'i32')); -wasmFailValidateText('(module (func (if (result i32) (i32.const 42) (i32.const 1) (i32.const 0))) (export "" (func 0)))', /unused values not explicitly dropped by end of block/); +wasmFailValidateText('(module (func (if (result i32) (i32.const 42) (i32.const 1) (i32.const 0))) (export "" (func 0)))', /(unused values not explicitly dropped by end of block)|(values remaining on stack at end of block)/); wasmFullPass('(module (func (drop (if (result i32) (i32.const 42) (i32.const 1) (i32.const 0)))) (export "run" (func 0)))', undefined); wasmFullPass('(module (func (param f32) (if (i32.const 42) (drop (i32.const 1)) (drop (local.get 0)))) (export "run" (func 0)))', undefined, {}, 13.37); @@ -157,13 +157,13 @@ wasmFullPass(`(module assertEq(counter, 0); // "if" doesn't return an expression value -wasmFailValidateText('(module (func (result i32) (if (result i32) (i32.const 42) (i32.const 0))))', /if without else with a result value/); +wasmFailValidateText('(module (func (result i32) (if (result i32) (i32.const 42) (i32.const 0))))', /(if without else with a result value)|(type mismatch: expected Some\(I32\) but nothing on stack)/); wasmFailValidateText('(module (func (result i32) (if (result i32) (i32.const 42) (drop (i32.const 0)))))', emptyStackError); -wasmFailValidateText('(module (func (result i32) (if (result i32) (i32.const 1) (i32.const 0) (if (result i32) (i32.const 1) (i32.const 1)))))', /if without else with a result value/); +wasmFailValidateText('(module (func (result i32) (if (result i32) (i32.const 1) (i32.const 0) (if (result i32) (i32.const 1) (i32.const 1)))))', /(if without else with a result value)|(type mismatch: expected Some\(I32\) but nothing on stack)/); wasmFailValidateText('(module (func (result i32) (if (result i32) (i32.const 1) (drop (i32.const 0)) (if (i32.const 1) (drop (i32.const 1))))))', emptyStackError); -wasmFailValidateText('(module (func (if (result i32) (i32.const 1) (i32.const 0) (if (result i32) (i32.const 1) (i32.const 1)))))', /if without else with a result value/); +wasmFailValidateText('(module (func (if (result i32) (i32.const 1) (i32.const 0) (if (result i32) (i32.const 1) (i32.const 1)))))', /(if without else with a result value)|(type mismatch: expected Some\(I32\) but nothing on stack)/); wasmFailValidateText('(module (func (if (result i32) (i32.const 1) (i32.const 0) (if (i32.const 1) (drop (i32.const 1))))))', emptyStackError); -wasmFailValidateText('(module (func (if (i32.const 1) (drop (i32.const 0)) (if (result i32) (i32.const 1) (i32.const 1)))))', /if without else with a result value/); +wasmFailValidateText('(module (func (if (i32.const 1) (drop (i32.const 0)) (if (result i32) (i32.const 1) (i32.const 1)))))', /(if without else with a result value)|(type mismatch: expected Some\(I32\) but nothing on stack)/); wasmEvalText('(module (func (if (i32.const 1) (drop (i32.const 0)) (if (i32.const 1) (drop (i32.const 1))))))'); // ---------------------------------------------------------------------------- @@ -184,7 +184,7 @@ wasmFailValidateText('(module (func (result i32) (block (br 0))) (export "" (fun wasmFailValidateText('(module (func (result i32) (br 0)) (export "" (func 0)))', emptyStackError); wasmFailValidateText('(module (func (result i32) (block (br_if 0 (i32.const 0)))) (export "" (func 0)))', emptyStackError); -const DEPTH_OUT_OF_BOUNDS = /branch depth exceeds current nesting level/; +const DEPTH_OUT_OF_BOUNDS = /(branch depth exceeds current nesting level)|(branch depth too large)/; wasmFailValidateText('(module (func (br 1)))', DEPTH_OUT_OF_BOUNDS); wasmFailValidateText('(module (func (block (br 2))))', DEPTH_OUT_OF_BOUNDS); @@ -315,7 +315,7 @@ wasmFailValidateText('(module (func (result i32) (br 0 (f32.const 42))))', misma wasmFailValidateText('(module (func (result i32) (block (br 0))))', emptyStackError); wasmFailValidateText('(module (func (result i32) (block (result f32) (br 0 (f32.const 42)))))', mismatchError("f32", "i32")); -wasmFailValidateText(`(module (func (param i32) (result i32) (block (if (result i32) (local.get 0) (br 0 (i32.const 42))))) (export "" (func 0)))`, /if without else with a result value/); +wasmFailValidateText(`(module (func (param i32) (result i32) (block (if (result i32) (local.get 0) (br 0 (i32.const 42))))) (export "" (func 0)))`, /(if without else with a result value)|(type mismatch: expected Some\(I32\) but nothing on stack)/); wasmFailValidateText(`(module (func (param i32) (result i32) (block (result i32) (if (local.get 0) (drop (i32.const 42))) (br 0 (f32.const 42)))) (export "" (func 0)))`, mismatchError("f32", "i32")); wasmFullPass('(module (func (result i32) (br 0 (i32.const 42)) (i32.const 13)) (export "run" (func 0)))', 42); @@ -328,13 +328,13 @@ var f = wasmEvalText(`(module (func (param i32) (result i32) (block (result i32) assertEq(f(0), 43); assertEq(f(1), 43); -wasmFailValidateText(`(module (func (param i32) (result i32) (block (result i32) (if (result i32) (local.get 0) (br 0 (i32.const 42))) (i32.const 43))) (export "" (func 0)))`, /if without else with a result value/); +wasmFailValidateText(`(module (func (param i32) (result i32) (block (result i32) (if (result i32) (local.get 0) (br 0 (i32.const 42))) (i32.const 43))) (export "" (func 0)))`, /(if without else with a result value)|(type mismatch: expected Some\(I32\) but nothing on stack)/); var f = wasmEvalText(`(module (func (param i32) (result i32) (block (result i32) (if (local.get 0) (br 1 (i32.const 42))) (i32.const 43))) (export "" (func 0)))`).exports[""]; assertEq(f(0), 43); assertEq(f(1), 42); -wasmFailValidateText(`(module (func (param i32) (result i32) (block (br_if 0 (i32.const 42) (local.get 0)) (i32.const 43))) (export "" (func 0)))`, /unused values not explicitly dropped by end of block/); +wasmFailValidateText(`(module (func (param i32) (result i32) (block (br_if 0 (i32.const 42) (local.get 0)) (i32.const 43))) (export "" (func 0)))`, /(unused values not explicitly dropped by end of block)|(values remaining on stack at end of block)/); var f = wasmEvalText(`(module (func (param i32) (result i32) (block (result i32) (drop (br_if 0 (i32.const 42) (local.get 0))) (i32.const 43))) (export "" (func 0)))`).exports[""]; assertEq(f(0), 43); @@ -344,7 +344,7 @@ var f = wasmEvalText(`(module (func (param i32) (result i32) (block (result i32) assertEq(f(0), 43); assertEq(f(1), 43); -wasmFailValidateText(`(module (func (param i32) (result i32) (block (result i32) (if (result i32) (local.get 0) (br 0 (i32.const 42))) (br 0 (i32.const 43)))) (export "" (func 0)))`, /if without else with a result value/); +wasmFailValidateText(`(module (func (param i32) (result i32) (block (result i32) (if (result i32) (local.get 0) (br 0 (i32.const 42))) (br 0 (i32.const 43)))) (export "" (func 0)))`, /(if without else with a result value)|(type mismatch: expected Some\(I32\) but nothing on stack)/); var f = wasmEvalText(`(module (func (param i32) (result i32) (if (local.get 0) (br 1 (i32.const 42))) (br 0 (i32.const 43))) (export "" (func 0)))`).exports[""]; assertEq(f(0), 43); @@ -366,13 +366,13 @@ var f = wasmEvalText(`(module (func (param i32) (result i32) (i32.add (i32.const assertEq(f(0), 0); assertEq(f(1), 0); -wasmFailValidateText(`(module (func (param i32) (result i32) (i32.add (i32.const 1) (block (result i32) (if (result i32) (local.get 0) (br 0 (i32.const 99))) (i32.const -1)))) (export "" (func 0)))`, /if without else with a result value/); +wasmFailValidateText(`(module (func (param i32) (result i32) (i32.add (i32.const 1) (block (result i32) (if (result i32) (local.get 0) (br 0 (i32.const 99))) (i32.const -1)))) (export "" (func 0)))`, /(if without else with a result value)|(type mismatch: expected Some\(I32\) but nothing on stack)/); var f = wasmEvalText(`(module (func (param i32) (result i32) (i32.add (i32.const 1) (block (result i32) (if (local.get 0) (br 1 (i32.const 99))) (i32.const -1)))) (export "" (func 0)))`).exports[""]; assertEq(f(0), 0); assertEq(f(1), 100); -wasmFailValidateText(`(module (func (param i32) (result i32) (i32.add (i32.const 1) (block (br_if 0 (i32.const 99) (local.get 0)) (i32.const -1)))) (export "" (func 0)))`, /unused values not explicitly dropped by end of block/); +wasmFailValidateText(`(module (func (param i32) (result i32) (i32.add (i32.const 1) (block (br_if 0 (i32.const 99) (local.get 0)) (i32.const -1)))) (export "" (func 0)))`, /(unused values not explicitly dropped by end of block)|(values remaining on stack at end of block)/); var f = wasmEvalText(`(module (func (param i32) (result i32) (i32.add (i32.const 1) (block (result i32) (drop (br_if 0 (i32.const 99) (local.get 0))) (i32.const -1)))) (export "" (func 0)))`).exports[""]; assertEq(f(0), 0); @@ -429,7 +429,7 @@ wasmFailValidateText(`(module (func (param i32) (result i32) (i32.const 7) ) ) -) (export "" (func 0)))`, /unused values not explicitly dropped by end of block/); +) (export "" (func 0)))`, /(unused values not explicitly dropped by end of block)|(values remaining on stack at end of block)/); wasmFullPass(`(module (func @@ -467,7 +467,7 @@ wasmFullPass(`(module wasmFailValidateText('(module (func (loop (br 2))))', DEPTH_OUT_OF_BOUNDS); -wasmFailValidateText('(module (func (result i32) (drop (loop (i32.const 2))) (i32.const 1)) (export "" (func 0)))', /unused values not explicitly dropped by end of block/); +wasmFailValidateText('(module (func (result i32) (drop (loop (i32.const 2))) (i32.const 1)) (export "" (func 0)))', /(unused values not explicitly dropped by end of block)|(values remaining on stack at end of block)/); wasmFullPass('(module (func (loop)) (export "run" (func 0)))', undefined); wasmFullPass('(module (func (result i32) (loop (drop (i32.const 2))) (i32.const 1)) (export "run" (func 0)))', 1); diff --git a/js/src/jit-test/tests/wasm/float.js b/js/src/jit-test/tests/wasm/float.js index a1790fe8f2f2..f24e5ed5b895 100644 --- a/js/src/jit-test/tests/wasm/float.js +++ b/js/src/jit-test/tests/wasm/float.js @@ -84,7 +84,7 @@ wasmFailValidateText('(module (func (param i32) (result i32) (f32.sqrt (local.ge wasmFailValidateText('(module (func (param i32) (result f64) (f64.sqrt (local.get 0))))', mismatchError("i32", "f64")); wasmFailValidateText('(module (func (param f64) (result i32) (f64.sqrt (local.get 0))))', mismatchError("f64", "i32")); wasmFailValidateText('(module (func (param i32) (result i32) (f64.sqrt (local.get 0))))', mismatchError("i32", "f64")); -wasmFailValidateText('(module (func (f32.sqrt (nop))))', /popping value from empty stack/); +wasmFailValidateText('(module (func (f32.sqrt (nop))))', /(popping value from empty stack)|(type mismatch: expected Some\(F32\) but nothing on stack)/); wasmFailValidateText('(module (func (param i32) (param f32) (result f32) (f32.add (local.get 0) (local.get 1))))', mismatchError("i32", "f32")); wasmFailValidateText('(module (func (param f32) (param i32) (result f32) (f32.add (local.get 0) (local.get 1))))', mismatchError("i32", "f32")); diff --git a/js/src/jit-test/tests/wasm/gc/disabled.js b/js/src/jit-test/tests/wasm/gc/disabled.js index 2403bae9fb53..5ce320ee22ab 100644 --- a/js/src/jit-test/tests/wasm/gc/disabled.js +++ b/js/src/jit-test/tests/wasm/gc/disabled.js @@ -2,7 +2,7 @@ const { CompileError, validate } = WebAssembly; -const UNRECOGNIZED_OPCODE_OR_BAD_TYPE = /unrecognized opcode|(Structure|reference|gc) types not enabled|invalid heap type|invalid inline block type|bad type|\(ref T\) types not enabled|Cranelift error in clifFunc/; +const UNRECOGNIZED_OPCODE_OR_BAD_TYPE = /unrecognized opcode|(Structure|reference|gc) types not enabled|invalid heap type|invalid inline block type|bad type|\(ref T\) types not enabled|Invalid type|invalid function type/; let simpleTests = [ "(module (func (drop (ref.null any))))", diff --git a/js/src/jit-test/tests/wasm/globals.js b/js/src/jit-test/tests/wasm/globals.js index eaa988542471..1767b432551a 100644 --- a/js/src/jit-test/tests/wasm/globals.js +++ b/js/src/jit-test/tests/wasm/globals.js @@ -44,8 +44,8 @@ testInner('f32', 13.37, 0.1989, Math.fround); testInner('f64', 13.37, 0.1989, x => +x); // Semantic errors. -wasmFailValidateText(`(module (global (mut i32) (i32.const 1337)) (func (global.set 1 (i32.const 0))))`, /out of range/); -wasmFailValidateText(`(module (global i32 (i32.const 1337)) (func (global.set 0 (i32.const 0))))`, /can't write an immutable global/); +wasmFailValidateText(`(module (global (mut i32) (i32.const 1337)) (func (global.set 1 (i32.const 0))))`, /(out of range)|(global index out of bounds)/); +wasmFailValidateText(`(module (global i32 (i32.const 1337)) (func (global.set 0 (i32.const 0))))`, /(can't write an immutable global)|(global is immutable)/); // Big module with many variables: test that setting one doesn't overwrite the // other ones. diff --git a/js/src/jit-test/tests/wasm/memory.js b/js/src/jit-test/tests/wasm/memory.js index b6645ef39267..8363ee4ccbac 100644 --- a/js/src/jit-test/tests/wasm/memory.js +++ b/js/src/jit-test/tests/wasm/memory.js @@ -122,11 +122,11 @@ function testStoreOOB(type, ext, base, offset, align, value) { } function badLoadModule(type, ext) { - wasmFailValidateText( `(module (func (param i32) (${type}.load${ext} (local.get 0))) (export "" (func 0)))`, /can't touch memory/); + wasmFailValidateText( `(module (func (param i32) (${type}.load${ext} (local.get 0))) (export "" (func 0)))`, /(can't touch memory)|(unknown memory 0)/); } function badStoreModule(type, ext) { - wasmFailValidateText(`(module (func (param i32) (${type}.store${ext} (local.get 0) (${type}.const 0))) (export "" (func 0)))`, /can't touch memory/); + wasmFailValidateText(`(module (func (param i32) (${type}.store${ext} (local.get 0) (${type}.const 0))) (export "" (func 0)))`, /(can't touch memory)|(unknown memory 0)/); } // Can't touch memory. diff --git a/js/src/jit-test/tests/wasm/passive-segs-boundary.js b/js/src/jit-test/tests/wasm/passive-segs-boundary.js index 5a9c29ebc35f..01b06a042e8b 100644 --- a/js/src/jit-test/tests/wasm/passive-segs-boundary.js +++ b/js/src/jit-test/tests/wasm/passive-segs-boundary.js @@ -142,7 +142,7 @@ function tab_test_nofail(insn1, insn2, // drop with no memory and no data segments mem_test("data.drop 0", "", - WebAssembly.CompileError, /data.drop segment index out of range/, + WebAssembly.CompileError, /(data.drop segment index out of range)|(unknown data segment 0)/, /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/false); // drop with no memory but with both passive and active segments, ix in range @@ -154,7 +154,7 @@ mem_test("data.drop 3", "", // drop with no memory but with passive segments only, ix out of range mem_test("data.drop 2", "", - WebAssembly.CompileError, /data.drop segment index out of range/, + WebAssembly.CompileError, /(data.drop segment index out of range)|(unknown data segment 2)/, /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/true); // drop with no memory but with passive segments only, ix in range @@ -164,16 +164,16 @@ mem_test_nofail("data.drop 1", "", // init with no memory and no data segs mem_test("(memory.init 1 (i32.const 1234) (i32.const 1) (i32.const 1))", "", - WebAssembly.CompileError, /can't touch memory without memory/, + WebAssembly.CompileError, /(can't touch memory without memory)|(unknown memory 0)/, /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/false); // drop with data seg ix out of range mem_test("data.drop 4", "", - WebAssembly.CompileError, /data.drop segment index out of range/); + WebAssembly.CompileError, /(data.drop segment index out of range)|(unknown data segment 4)/); // init with data seg ix out of range mem_test("(memory.init 4 (i32.const 1234) (i32.const 1) (i32.const 1))", "", - WebAssembly.CompileError, /memory.init segment index out of range/); + WebAssembly.CompileError, /(memory.init segment index out of range)|(unknown data segment 4)/); // drop with data seg ix indicating an active segment mem_test("data.drop 2", ""); @@ -236,17 +236,17 @@ mem_test("", // drop: too many args mem_test("data.drop 1 (i32.const 42)", "", WebAssembly.CompileError, - /unused values not explicitly dropped by end of block/); + /(unused values not explicitly dropped by end of block)|(values remaining on stack at end of block)/); // init: too many args mem_test("(memory.init 1 (i32.const 1) (i32.const 1) (i32.const 1) (i32.const 1))", "", - WebAssembly.CompileError, /unused values/); + WebAssembly.CompileError, /(unused values)|(values remaining on stack at end of block)/); // init: too few args mem_test("(memory.init 1 (i32.const 1) (i32.const 1))", "", WebAssembly.CompileError, - /popping value from empty stack/); + /(popping value from empty stack)|(expected Some\(I32\) but nothing on stack)/); // invalid argument types { @@ -268,7 +268,7 @@ mem_test("(memory.init 1 (i32.const 1) (i32.const 1))", "", // drop with no tables and no elem segments tab_test("elem.drop 0", "", WebAssembly.CompileError, - /element segment index out of range for elem.drop/, + /(element segment index out of range for elem.drop)|(segment index out of bounds)/, /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/false); // drop with no tables but with both passive and active segments, ix in range @@ -281,7 +281,7 @@ tab_test("elem.drop 3", "", // drop with no tables but with passive segments only, ix out of range tab_test("elem.drop 2", "", WebAssembly.CompileError, - /element segment index out of range for elem.drop/, + /(element segment index out of range for elem.drop)|(segment index out of bounds)/, /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/true); // drop with no tables but with passive segments only, ix in range @@ -291,16 +291,16 @@ tab_test_nofail("elem.drop 1", "", // init with no table tab_test("(table.init 1 (i32.const 12) (i32.const 1) (i32.const 1))", "", - WebAssembly.CompileError, /table index out of range/, + WebAssembly.CompileError, /(table index out of range)|(table index out of bounds)/, /*haveStorage=*/false, /*haveInitA=*/false, /*haveInitP=*/false); // drop with elem seg ix out of range tab_test("elem.drop 4", "", - WebAssembly.CompileError, /element segment index out of range for elem.drop/); + WebAssembly.CompileError, /(element segment index out of range for elem.drop)|(segment index out of bounds)/); // init with elem seg ix out of range tab_test("(table.init 4 (i32.const 12) (i32.const 1) (i32.const 1))", "", - WebAssembly.CompileError, /table.init segment index out of range/); + WebAssembly.CompileError, /(table.init segment index out of range)|(segment index out of bounds)/); // drop with elem seg ix indicating an active segment tab_test("elem.drop 2", ""); @@ -362,17 +362,17 @@ tab_test("", // drop: too many args tab_test("elem.drop 1 (i32.const 42)", "", WebAssembly.CompileError, - /unused values not explicitly dropped by end of block/); + /(unused values not explicitly dropped by end of block)|(values remaining on stack at end of block)/); // init: too many args tab_test("(table.init 1 (i32.const 1) (i32.const 1) (i32.const 1) (i32.const 1))", "", - WebAssembly.CompileError, /unused values/); + WebAssembly.CompileError, /(unused values)|(values remaining on stack at end of block)/); // init: too few args tab_test("(table.init 1 (i32.const 1) (i32.const 1))", "", WebAssembly.CompileError, - /popping value from empty stack/); + /(popping value from empty stack)|(expected Some\(I32\) but nothing on stack)/); // invalid argument types { diff --git a/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js b/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js index 4cbb9615b3dd..336dfb5b3f0d 100644 --- a/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js +++ b/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js @@ -294,10 +294,10 @@ checkNoDataCount([I32ConstCode, 0, I32ConstCode, 0, I32ConstCode, 0, MiscPrefix, MemoryInitCode, 0, 0], - /memory.init requires a DataCount section/); + /(memory.init requires a DataCount section)|(unknown data segment)/); checkNoDataCount([MiscPrefix, DataDropCode, 0], - /data.drop requires a DataCount section/); + /(data.drop requires a DataCount section)|(unknown data segment)/); //---------------------------------------------------------------------// //---------------------------------------------------------------------// @@ -318,7 +318,7 @@ function checkMiscPrefixed(opcode, expect_failure) { MiscPrefix, ...opcode]})])]); if (expect_failure) { assertErrorMessage(() => new WebAssembly.Module(binary), - WebAssembly.CompileError, /unrecognized opcode/); + WebAssembly.CompileError, /(unrecognized opcode)|(Unknown.*subopcode)/); } else { assertEq(WebAssembly.validate(binary), true); } @@ -370,7 +370,7 @@ checkMiscPrefixed([0x13], true); // table.size+1, which is currently unas )`; assertErrorMessage(() => wasmEvalText(text1), WebAssembly.CompileError, - /popping value from empty stack/); + /(popping value from empty stack)|(expected Some\(I32\) but nothing on stack)/); let text2 = `(module (memory (export "memory") 1 1) @@ -384,7 +384,7 @@ checkMiscPrefixed([0x13], true); // table.size+1, which is currently unas )`; assertErrorMessage(() => wasmEvalText(text2), WebAssembly.CompileError, - /unused values not explicitly dropped by end of block/); + /(unused values not explicitly dropped by end of block)|(values remaining on stack at end of block)/); } } @@ -399,7 +399,7 @@ checkMiscPrefixed([0x13], true); // table.size+1, which is currently unas )`; assertErrorMessage(() => wasmEvalText(text), WebAssembly.CompileError, - /can't touch memory without memory/); + /(can't touch memory without memory)|(unknown memory)/); } } diff --git a/js/src/jit-test/tests/wasm/ref-types/ref-func.js b/js/src/jit-test/tests/wasm/ref-types/ref-func.js index edd51e560128..9fbda7e83f8b 100644 --- a/js/src/jit-test/tests/wasm/ref-types/ref-func.js +++ b/js/src/jit-test/tests/wasm/ref-types/ref-func.js @@ -74,7 +74,7 @@ assertErrorMessage(() => { (func (result funcref) ref.func 10) ) `); -}, WebAssembly.CompileError, /function index out of range/); +}, WebAssembly.CompileError, /(function index out of range)|(function index out of bounds)/); function validFuncRefText(forwardDeclare, tbl_type) { return wasmEvalText(` @@ -88,7 +88,7 @@ function validFuncRefText(forwardDeclare, tbl_type) { } // referenced function must be forward declared somehow -assertErrorMessage(() => validFuncRefText('', 'funcref'), WebAssembly.CompileError, /function index is not declared in a section before the code section/); +assertErrorMessage(() => validFuncRefText('', 'funcref'), WebAssembly.CompileError, /(function index is not declared in a section before the code section)|(undeclared function reference)/); // referenced function can be forward declared via segments assertEq(validFuncRefText('(elem 0 (i32.const 0) func $referenced)', 'funcref') instanceof WebAssembly.Instance, true); @@ -106,7 +106,7 @@ assertEq(validFuncRefText('(global funcref (ref.func $referenced))', 'externref' assertEq(validFuncRefText('(export "referenced" (func $referenced))', 'externref') instanceof WebAssembly.Instance, true); // reference function cannot be forward declared via start -assertErrorMessage(() => validFuncRefText('(start $referenced)', 'externref'), WebAssembly.CompileError, /function index is not declared in a section before the code section/); +assertErrorMessage(() => validFuncRefText('(start $referenced)', 'externref'), WebAssembly.CompileError, /(function index is not declared in a section before the code section)|(undeclared function reference)/); // Tests not expressible in the text format. diff --git a/js/src/jit-test/tests/wasm/ref-types/tables-fill.js b/js/src/jit-test/tests/wasm/ref-types/tables-fill.js index d16b2d18e41f..4e2a33361a30 100644 --- a/js/src/jit-test/tests/wasm/ref-types/tables-fill.js +++ b/js/src/jit-test/tests/wasm/ref-types/tables-fill.js @@ -150,7 +150,7 @@ assertErrorMessage(() => wasmEvalText( (func $expected-3-args-got-0 (table.fill $t) ))`), - WebAssembly.CompileError, /popping value from empty stack/); + WebAssembly.CompileError, /(popping value from empty stack)|(nothing on stack)/); assertErrorMessage(() => wasmEvalText( `(module @@ -158,7 +158,7 @@ assertErrorMessage(() => wasmEvalText( (func $expected-3-args-got-1 (table.fill $t (i32.const 0)) ))`), - WebAssembly.CompileError, /popping value from empty stack/); + WebAssembly.CompileError, /(popping value from empty stack)|(nothing on stack)/); assertErrorMessage(() => wasmEvalText( `(module @@ -166,7 +166,7 @@ assertErrorMessage(() => wasmEvalText( (func $expected-3-args-got-2 (table.fill $t (ref.null extern) (i32.const 0)) ))`), - WebAssembly.CompileError, /popping value from empty stack/); + WebAssembly.CompileError, /(popping value from empty stack)|(nothing on stack)/); assertErrorMessage(() => wasmEvalText( `(module @@ -175,7 +175,7 @@ assertErrorMessage(() => wasmEvalText( (table.fill $t (i32.const 0) (ref.null extern) (f64.const 0)) ))`), WebAssembly.CompileError, - /type mismatch: expression has type f64 but expected i32/); + /(type mismatch: expression has type f64 but expected i32)|(type mismatch: expected Some\(I32\), found Some\(F64\))/); assertErrorMessage(() => wasmEvalText( `(module @@ -184,7 +184,7 @@ assertErrorMessage(() => wasmEvalText( (table.fill $t (i32.const 0) (f32.const 0) (i32.const 0)) ))`), WebAssembly.CompileError, - /type mismatch: expression has type f32 but expected externref/); + /(type mismatch: expression has type f32 but expected externref)|(type mismatch: expected Some\(ExternRef\), found Some\(F32\))/); assertErrorMessage(() => wasmEvalText( `(module @@ -193,7 +193,7 @@ assertErrorMessage(() => wasmEvalText( (table.fill $t (i64.const 0) (ref.null extern) (i32.const 0)) ))`), WebAssembly.CompileError, - /type mismatch: expression has type i64 but expected i32/); + /(type mismatch: expression has type i64 but expected i32)|(type mismatch: expected Some\(I32\), found Some\(I64\))/); assertErrorMessage(() => wasmEvalText( `(module @@ -202,7 +202,7 @@ assertErrorMessage(() => wasmEvalText( (table.fill $t (i32.const 0) (ref.null extern) (i32.const 0)) ))`), WebAssembly.CompileError, - /popping value from empty stack/); + /(popping value from empty stack)|(nothing on stack)/); assertErrorMessage(() => wasmEvalText( `(module @@ -211,4 +211,4 @@ assertErrorMessage(() => wasmEvalText( (table.fill (i32.const 0) (local.get $v) (i32.const 0))) )`), WebAssembly.CompileError, - /expression has type externref but expected funcref/); + /(expression has type externref but expected funcref)|(type mismatch: expected Some\(FuncRef\), found Some\(ExternRef\))/); diff --git a/js/src/jit-test/tests/wasm/ref-types/tables-generalized.js b/js/src/jit-test/tests/wasm/ref-types/tables-generalized.js index 3c9048204bb4..849c47d6f384 100644 --- a/js/src/jit-test/tests/wasm/ref-types/tables-generalized.js +++ b/js/src/jit-test/tests/wasm/ref-types/tables-generalized.js @@ -92,7 +92,7 @@ assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( (func (export "f") (table.copy 0 1 (i32.const 5) (i32.const 0) (i32.const 5))))`)), WebAssembly.CompileError, - /expression has type funcref but expected externref/); + /(expression has type funcref but expected externref)|(type mismatch)/); // Wasm: table.copy from table(externref) to table(funcref) should not work @@ -103,7 +103,7 @@ assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( (func (export "f") (table.copy 0 1 (i32.const 0) (i32.const 0) (i32.const 5))))`)), WebAssembly.CompileError, - /expression has type externref but expected funcref/); + /(expression has type externref but expected funcref)|(type mismatch)/); // Wasm: Element segments of funcref can't target tables of externref @@ -136,7 +136,7 @@ assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( (func (table.init 0 (i32.const 0) (i32.const 0) (i32.const 0))))`)), WebAssembly.CompileError, - /expression has type funcref but expected externref/); + /(expression has type funcref but expected externref)|(type mismatch)/); // Wasm: table.init on table-of-funcref is not allowed when the segment has @@ -149,7 +149,7 @@ assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( (func (table.init 0 (i32.const 0) (i32.const 0) (i32.const 0))))`)), WebAssembly.CompileError, - /expression has type externref but expected funcref/); + /(expression has type externref but expected funcref)|(type mismatch)/); // Wasm: table types must match at link time @@ -170,7 +170,7 @@ assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( (func (result i32) (call_indirect (type $t) (i32.const 37))))`)), WebAssembly.CompileError, - /indirect calls must go through a table of 'funcref'/); + /(indirect calls must go through a table of 'funcref')|(type mismatch)/); /////////////////////////////////////////////////////////////////////////// // @@ -243,7 +243,7 @@ assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( (func (export "f") (param i32) (drop (table.get (local.get 0)))))`)), WebAssembly.CompileError, - /table index out of range for table.get/); + /(table index out of range for table.get)|(table index out of bounds)/); // table.set in bounds with i32 x externref - works, no value generated // table.set with null - works @@ -276,7 +276,7 @@ assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( (func (export "set_ref") (param i32) (param externref) (table.set (local.get 0) (local.get 1))))`)), WebAssembly.CompileError, - /type mismatch: expression has type externref but expected funcref/); + /(type mismatch: expression has type externref but expected funcref)|(type mismatch: expected Some\(FuncRef\), found Some\(ExternRef\))/); // table.set with non-i32 index - fails validation @@ -312,7 +312,7 @@ assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( (func (export "f") (param externref) (table.set (i32.const 0) (local.get 0))))`)), WebAssembly.CompileError, - /table index out of range for table.set/); + /(table index out of range for table.set)|(table index out of bounds)/); function testTableGrow(lhs_type, lhs_reftype, rhs_type, x) { let ins = wasmEvalText( @@ -360,7 +360,7 @@ assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( (func (export "grow2") (param i32) (param externref) (result i32) (table.grow (local.get 1) (local.get 0))))`)), WebAssembly.CompileError, - /type mismatch: expression has type externref but expected funcref/); + /(type mismatch: expression has type externref but expected funcref)|(type mismatch: expected Some\(FuncRef\), found Some\(ExternRef\))/); // Special case for private tables without a maximum @@ -393,7 +393,7 @@ assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary( (func (export "f") (param i32) (table.grow (ref.null extern) (local.get 0))))`)), WebAssembly.CompileError, - /table index out of range for table.grow/); + /(table index out of range for table.grow)|(table index out of bounds)/); // table.size on table of externref diff --git a/js/src/jit-test/tests/wasm/ref-types/tables-multiple.js b/js/src/jit-test/tests/wasm/ref-types/tables-multiple.js index 48577b398db8..cedecd591b69 100644 --- a/js/src/jit-test/tests/wasm/ref-types/tables-multiple.js +++ b/js/src/jit-test/tests/wasm/ref-types/tables-multiple.js @@ -326,7 +326,7 @@ assertErrorMessage(() => wasmEvalText( (func $f (result externref) (table.get 2 (i32.const 0))))`), WebAssembly.CompileError, - /table index out of range for table.get/); + /(table index out of range for table.get)|(table index out of bounds)/); assertErrorMessage(() => wasmEvalText( `(module @@ -335,7 +335,7 @@ assertErrorMessage(() => wasmEvalText( (func $f (param externref) (table.set 2 (i32.const 0) (local.get 0))))`), WebAssembly.CompileError, - /table index out of range for table.set/); + /(table index out of range for table.set)|(table index out of bounds)/); assertErrorMessage(() => wasmEvalText( `(module @@ -344,7 +344,7 @@ assertErrorMessage(() => wasmEvalText( (func $f (param externref) (table.copy 0 2 (i32.const 0) (i32.const 0) (i32.const 2))))`), WebAssembly.CompileError, - /table index out of range for table.copy/); + /(table index out of range for table.copy)|(table index out of bounds)/); assertErrorMessage(() => wasmEvalText( `(module @@ -353,7 +353,7 @@ assertErrorMessage(() => wasmEvalText( (func $f (param externref) (table.copy 2 0 (i32.const 0) (i32.const 0) (i32.const 2))))`), WebAssembly.CompileError, - /table index out of range for table.copy/); + /(table index out of range for table.copy)|(table index out of bounds)/); assertErrorMessage(() => wasmEvalText( `(module @@ -362,7 +362,7 @@ assertErrorMessage(() => wasmEvalText( (func $f (result i32) (table.size 2)))`), WebAssembly.CompileError, - /table index out of range for table.size/); + /(table index out of range for table.size)|(table index out of bounds)/); assertErrorMessage(() => wasmEvalText( `(module @@ -371,7 +371,7 @@ assertErrorMessage(() => wasmEvalText( (func $f (result i32) (table.grow 2 (ref.null extern) (i32.const 1))))`), WebAssembly.CompileError, - /table index out of range for table.grow/); + /(table index out of range for table.grow)|(table index out of bounds)/); assertErrorMessage(() => wasmEvalText( `(module @@ -380,14 +380,14 @@ assertErrorMessage(() => wasmEvalText( (func $f (result i32) (table.init 2 0 (i32.const 0) (i32.const 0) (i32.const 0))))`), WebAssembly.CompileError, - /table index out of range for table.init/); + /(table index out of range for table.init)|(table index out of bounds)/); assertErrorMessage(() => wasmEvalText( `(module (table $t0 2 funcref) (elem 2 (i32.const 0) func))`), WebAssembly.CompileError, - /table index out of range for element segment/); + /(table index out of range for element segment)|(table index out of bounds)/); assertErrorMessage(() => wasmEvalText( `(module @@ -396,7 +396,7 @@ assertErrorMessage(() => wasmEvalText( (func $f (result i32) (call_indirect 2 (type $ft) (f64.const 3.14) (i32.const 0))))`), WebAssembly.CompileError, - /table index out of range for call_indirect/); + /(table index out of range for call_indirect)|(table index out of bounds)/); // Syntax errors when parsing text diff --git a/js/src/jit-test/tests/wasm/regress/misc-control-flow.js b/js/src/jit-test/tests/wasm/regress/misc-control-flow.js index 1db03559a8d1..6feafcee1bf6 100644 --- a/js/src/jit-test/tests/wasm/regress/misc-control-flow.js +++ b/js/src/jit-test/tests/wasm/regress/misc-control-flow.js @@ -2,13 +2,13 @@ wasmFailValidateText(`(module (func (param i32) (result i32) (loop (if (i32.const 0) (br 0)) (local.get 0))) (export "" (func 0)) -)`, /unused values not explicitly dropped by end of block/); +)`, /(unused values not explicitly dropped by end of block)|(values remaining on stack at end of block)/); wasmFailValidateText(`(module (func (param i32) (loop (if (i32.const 0) (br 0)) (local.get 0))) (export "" (func 0)) -)`, /unused values not explicitly dropped by end of block/); +)`, /(unused values not explicitly dropped by end of block)|(values remaining on stack at end of block)/); wasmFailValidateText(`(module (func (param i32) (result i32) @@ -204,7 +204,7 @@ wasmFailValidateText(` (br_table 1 0 (i32.const 15)) ) ) -)`, /br_table targets must all have the same arity/); +)`, /(br_table targets must all have the same arity)|(br_table target labels have different types)/); wasmFailValidateText(` (module @@ -214,7 +214,7 @@ wasmFailValidateText(` (br_table 1 0 (i32.const 15)) ) ) -)`, /br_table targets must all have the same arity/); +)`, /(br_table targets must all have the same arity)|(br_table target labels have different types)/); wasmValidateText(` (module diff --git a/js/src/jit-test/tests/wasm/select.js b/js/src/jit-test/tests/wasm/select.js index 67b39b976561..13df6002f739 100644 --- a/js/src/jit-test/tests/wasm/select.js +++ b/js/src/jit-test/tests/wasm/select.js @@ -2,7 +2,7 @@ wasmFailValidateText('(module (func (select (i32.const 0) (i32.const 0) (f32.const 0))))', mismatchError("f32", "i32")); -wasmFailValidateText('(module (func (select (i32.const 0) (f32.const 0) (i32.const 0))) (export "" (func 0)))', /select operand types must match/); +wasmFailValidateText('(module (func (select (i32.const 0) (f32.const 0) (i32.const 0))) (export "" (func 0)))', /(select operand types must match)|(type mismatch: expected Some\(F32\), found Some\(I32\))/); wasmFailValidateText('(module (func (select (block ) (i32.const 0) (i32.const 0))) (export "" (func 0)))', emptyStackError); wasmFailValidateText('(module (func (select (return) (i32.const 0) (i32.const 0))) (export "" (func 0)))', unusedValuesError); assertEq(wasmEvalText('(module (func (drop (select (return) (i32.const 0) (i32.const 0)))) (export "" (func 0)))').exports[""](), undefined); @@ -169,14 +169,14 @@ if (wasmReftypesEnabled()) { (select (local.get 0) (local.get 0) (i32.const 0)) drop ))`, - /untyped select/); + /(untyped select)|(type mismatch: select only takes integral types)/); wasmFailValidateText(`(module (func (param funcref) (select (local.get 0) (local.get 0) (i32.const 0)) drop ))`, - /untyped select/); + /(untyped select)|(type mismatch: select only takes integral types)/); function testRefSelect(type, trueVal, falseVal) { // Always true condition diff --git a/js/src/jit-test/tests/wasm/simd/disabled.js b/js/src/jit-test/tests/wasm/simd/disabled.js index 67e5b8851d12..3091faec375f 100644 --- a/js/src/jit-test/tests/wasm/simd/disabled.js +++ b/js/src/jit-test/tests/wasm/simd/disabled.js @@ -15,7 +15,7 @@ wasmFailValidateText(`(module (func (result v128)))`, /(v128 not enabled)|(bad type)/); wasmFailValidateText(`(module (func (local v128)))`, - /(v128 not enabled)|(bad type)/); + /(v128 not enabled)|(bad type)|(SIMD support is not enabled)/); wasmFailValidateText(`(module (global (import "m" "g") v128))`, /expected global type/); diff --git a/js/src/jit-test/tests/wasm/text.js b/js/src/jit-test/tests/wasm/text.js index de677c4ef153..5bf72cb8c01b 100644 --- a/js/src/jit-test/tests/wasm/text.js +++ b/js/src/jit-test/tests/wasm/text.js @@ -20,7 +20,7 @@ assertErrorMessage(() => wasmEvalText('(module (type $a (func)) (func (call_indi assertErrorMessage(() => wasmEvalText('(module (func (br $a)))'), SyntaxError, /failed to find label/); assertErrorMessage(() => wasmEvalText('(module (func (block $a ) (br $a)))'), SyntaxError, /failed to find label/); -assertErrorMessage(() => wasmEvalText(`(module (func (call ${0xffffffff})))`), WebAssembly.CompileError, /callee index out of range/); +assertErrorMessage(() => wasmEvalText(`(module (func (call ${0xffffffff})))`), WebAssembly.CompileError, /(callee index out of range)|(function index out of bounds)/); assertErrorMessage(() => wasmEvalText(`(module (export "func" ${0xffffffff}))`), SyntaxError, parsingError); wasmEvalText('(module (func (param $a i32)))'); diff --git a/js/src/wasm/WasmCraneliftCompile.cpp b/js/src/wasm/WasmCraneliftCompile.cpp index edd5ea7e95e0..597b565baab2 100644 --- a/js/src/wasm/WasmCraneliftCompile.cpp +++ b/js/src/wasm/WasmCraneliftCompile.cpp @@ -292,6 +292,7 @@ class CraneliftContext { explicit CraneliftContext(const ModuleEnvironment& moduleEnv) : moduleEnv_(moduleEnv), compiler_(nullptr) { staticEnv_.ref_types_enabled = moduleEnv.refTypesEnabled(); + staticEnv_.threads_enabled = true; #ifdef WASM_SUPPORTS_HUGE_MEMORY if (moduleEnv.hugeMemoryEnabled()) { // In the huge memory configuration, we always reserve the full 4 GB @@ -386,50 +387,86 @@ CraneliftModuleEnvironment::CraneliftModuleEnvironment( : env(&env), min_memory_length(env.minMemoryLength) {} TypeCode env_unpack(BD_ValType valType) { - return TypeCode(UnpackTypeCodeType(PackedTypeCode(valType.packed))); + return UnpackTypeCodeType(PackedTypeCode(valType.packed)); } -bool env_uses_shared_memory(const CraneliftModuleEnvironment* wrapper) { - return wrapper->env->usesSharedMemory(); +size_t env_num_datas(const CraneliftModuleEnvironment* env) { + return env->env->dataCount.valueOr(0); } -size_t env_num_types(const CraneliftModuleEnvironment* wrapper) { - return wrapper->env->types.length(); +size_t env_num_elems(const CraneliftModuleEnvironment* env) { + return env->env->elemSegments.length(); } -const FuncTypeWithId* env_type(const CraneliftModuleEnvironment* wrapper, +TypeCode env_elem_typecode(const CraneliftModuleEnvironment* env, + uint32_t index) { + return UnpackTypeCodeType(env->env->elemSegments[index]->elemType.packed()); +} + +uint32_t env_max_memory(const CraneliftModuleEnvironment* env) { + return env->env->maxMemoryLength.valueOr(UINT32_MAX); +} + +bool env_uses_shared_memory(const CraneliftModuleEnvironment* env) { + return env->env->usesSharedMemory(); +} + +bool env_has_memory(const CraneliftModuleEnvironment* env) { + return env->env->usesMemory(); +} + +size_t env_num_types(const CraneliftModuleEnvironment* env) { + return env->env->types.length(); +} +const FuncTypeWithId* env_type(const CraneliftModuleEnvironment* env, size_t typeIndex) { - return &wrapper->env->types[typeIndex].funcType(); + return &env->env->types[typeIndex].funcType(); } -const FuncTypeWithId* env_func_sig(const CraneliftModuleEnvironment* wrapper, +size_t env_num_funcs(const CraneliftModuleEnvironment* env) { + return env->env->funcTypes.length(); +} +const FuncTypeWithId* env_func_sig(const CraneliftModuleEnvironment* env, size_t funcIndex) { - return wrapper->env->funcTypes[funcIndex]; + return env->env->funcTypes[funcIndex]; +} +size_t env_func_sig_index(const CraneliftModuleEnvironment* env, + size_t funcIndex) { + return env->env->funcTypeIndices[funcIndex]; +} +bool env_is_func_valid_for_ref(const CraneliftModuleEnvironment* env, + uint32_t index) { + return env->env->validForRefFunc.getBit(index); } -size_t env_func_import_tls_offset(const CraneliftModuleEnvironment* wrapper, +size_t env_func_import_tls_offset(const CraneliftModuleEnvironment* env, size_t funcIndex) { - return globalToTlsOffset( - wrapper->env->funcImportGlobalDataOffsets[funcIndex]); + return globalToTlsOffset(env->env->funcImportGlobalDataOffsets[funcIndex]); } -bool env_func_is_import(const CraneliftModuleEnvironment* wrapper, +bool env_func_is_import(const CraneliftModuleEnvironment* env, size_t funcIndex) { - return wrapper->env->funcIsImport(funcIndex); + return env->env->funcIsImport(funcIndex); } -const FuncTypeWithId* env_signature(const CraneliftModuleEnvironment* wrapper, +const FuncTypeWithId* env_signature(const CraneliftModuleEnvironment* env, size_t funcTypeIndex) { - return &wrapper->env->types[funcTypeIndex].funcType(); + return &env->env->types[funcTypeIndex].funcType(); } -const TableDesc* env_table(const CraneliftModuleEnvironment* wrapper, +size_t env_num_tables(const CraneliftModuleEnvironment* env) { + return env->env->tables.length(); +} +const TableDesc* env_table(const CraneliftModuleEnvironment* env, size_t tableIndex) { - return &wrapper->env->tables[tableIndex]; + return &env->env->tables[tableIndex]; } -const GlobalDesc* env_global(const CraneliftModuleEnvironment* wrapper, +size_t env_num_globals(const CraneliftModuleEnvironment* env) { + return env->env->globals.length(); +} +const GlobalDesc* env_global(const CraneliftModuleEnvironment* env, size_t globalIndex) { - return &wrapper->env->globals[globalIndex]; + return &env->env->globals[globalIndex]; } bool wasm::CraneliftCompileFunctions(const ModuleEnvironment& moduleEnv, @@ -484,9 +521,6 @@ bool wasm::CraneliftCompileFunctions(const ModuleEnvironment& moduleEnv, Decoder d(func.begin, func.end, func.lineOrBytecode, error); size_t funcBytecodeSize = func.end - func.begin; - if (!ValidateFunctionBody(moduleEnv, func.index, funcBytecodeSize, d)) { - return false; - } size_t previousStackmapCount = code->stackMaps.length(); @@ -495,8 +529,11 @@ bool wasm::CraneliftCompileFunctions(const ModuleEnvironment& moduleEnv, CraneliftCompiledFunc clifFunc; - if (!cranelift_compile_function(*compiler, &clifInput, &clifFunc)) { - *error = JS_smprintf("Cranelift error in clifFunc #%u", clifInput.index); + char* clifError = nullptr; + if (!cranelift_compile_function(*compiler, &clifInput, &clifFunc, + &clifError)) { + *error = JS_smprintf("%s", clifError); + cranelift_compiler_free_error(clifError); return false; } @@ -633,6 +670,16 @@ size_t table_tlsOffset(const TableDesc* table) { return globalToTlsOffset(table->globalDataOffset); } +uint32_t table_initialLimit(const TableDesc* table) { + return table->initialLength; +} +uint32_t table_maximumLimit(const TableDesc* table) { + return table->maximumLength.valueOr(UINT32_MAX); +} +TypeCode table_elementTypeCode(const TableDesc* table) { + return UnpackTypeCodeType(table->elemType.packed()); +} + // Sig size_t funcType_numArgs(const FuncTypeWithId* funcType) { @@ -641,7 +688,7 @@ size_t funcType_numArgs(const FuncTypeWithId* funcType) { const BD_ValType* funcType_args(const FuncTypeWithId* funcType) { static_assert(sizeof(BD_ValType) == sizeof(ValType), "update BD_ValType"); - return (const BD_ValType*)&funcType->args()[0]; + return (const BD_ValType*)funcType->args().begin(); } size_t funcType_numResults(const FuncTypeWithId* funcType) { @@ -650,7 +697,7 @@ size_t funcType_numResults(const FuncTypeWithId* funcType) { const BD_ValType* funcType_results(const FuncTypeWithId* funcType) { static_assert(sizeof(BD_ValType) == sizeof(ValType), "update BD_ValType"); - return (const BD_ValType*)&funcType->results()[0]; + return (const BD_ValType*)funcType->results().begin(); } FuncTypeIdDescKind funcType_idKind(const FuncTypeWithId* funcType) { diff --git a/js/src/wasm/WasmValidate.cpp b/js/src/wasm/WasmValidate.cpp index a0f832210d6a..226b61492742 100644 --- a/js/src/wasm/WasmValidate.cpp +++ b/js/src/wasm/WasmValidate.cpp @@ -2069,6 +2069,9 @@ static bool DecodeImport(Decoder& d, ModuleEnvironment* env) { if (!env->funcTypes.append(&env->types[funcTypeIndex].funcType())) { return false; } + if (!env->funcTypeIndices.append(funcTypeIndex)) { + return false; + } if (env->funcTypes.length() > MaxFuncs) { return d.fail("too many functions"); } @@ -2173,6 +2176,9 @@ static bool DecodeFunctionSection(Decoder& d, ModuleEnvironment* env) { if (!env->funcTypes.reserve(numFuncs.value())) { return false; } + if (!env->funcTypeIndices.reserve(numFuncs.value())) { + return false; + } for (uint32_t i = 0; i < numDefs; i++) { uint32_t funcTypeIndex; @@ -2180,6 +2186,7 @@ static bool DecodeFunctionSection(Decoder& d, ModuleEnvironment* env) { return false; } env->funcTypes.infallibleAppend(&env->types[funcTypeIndex].funcType()); + env->funcTypeIndices.infallibleAppend(funcTypeIndex); } return d.finishSection(*range, "function"); diff --git a/js/src/wasm/WasmValidate.h b/js/src/wasm/WasmValidate.h index 79c7ae115566..23fa1ce2b2c8 100644 --- a/js/src/wasm/WasmValidate.h +++ b/js/src/wasm/WasmValidate.h @@ -138,6 +138,7 @@ struct ModuleEnvironment { uint32_t numStructTypes; TypeDefVector types; FuncTypeWithIdPtrVector funcTypes; + Uint32Vector funcTypeIndices; Uint32Vector funcImportGlobalDataOffsets; GlobalDescVector globals; TableDescVector tables; diff --git a/js/src/wasm/cranelift/baldrapi.h b/js/src/wasm/cranelift/baldrapi.h index 7cc4c5c4bc7b..1f8190c8fd09 100644 --- a/js/src/wasm/cranelift/baldrapi.h +++ b/js/src/wasm/cranelift/baldrapi.h @@ -68,6 +68,7 @@ struct CraneliftStaticEnvironment { bool has_lzcnt; bool platform_is_windows; bool ref_types_enabled; + bool threads_enabled; size_t static_memory_bound; size_t memory_guard_size; size_t memory_base_tls_offset; @@ -214,12 +215,27 @@ enum class BD_SymbolicAddress : uint32_t { extern "C" { js::wasm::TypeCode env_unpack(BD_ValType type); -bool env_uses_shared_memory(const CraneliftModuleEnvironment* env); +size_t env_num_tables(const CraneliftModuleEnvironment* env); +size_t env_num_globals(const CraneliftModuleEnvironment* env); size_t env_num_types(const CraneliftModuleEnvironment* env); +size_t env_num_funcs(const CraneliftModuleEnvironment* env); +size_t env_num_elems(const CraneliftModuleEnvironment* env); +size_t env_num_datas(const CraneliftModuleEnvironment* env); +js::wasm::TypeCode env_elem_typecode(const CraneliftModuleEnvironment* env, + uint32_t index); +bool env_is_func_valid_for_ref(const CraneliftModuleEnvironment* env, + uint32_t index); +/// Returns the maximum memory size as an uint32, or UINT32_MAX if not defined. +uint32_t env_max_memory(const CraneliftModuleEnvironment* env); + +bool env_uses_shared_memory(const CraneliftModuleEnvironment* env); +bool env_has_memory(const CraneliftModuleEnvironment* env); const js::wasm::FuncTypeWithId* env_type(const CraneliftModuleEnvironment* env, size_t typeIndex); const js::wasm::FuncTypeWithId* env_func_sig( const CraneliftModuleEnvironment* env, size_t funcIndex); +size_t env_func_sig_index(const CraneliftModuleEnvironment* env, + size_t funcIndex); size_t env_func_import_tls_offset(const CraneliftModuleEnvironment* env, size_t funcIndex); bool env_func_is_import(const CraneliftModuleEnvironment* env, @@ -238,6 +254,10 @@ js::wasm::TypeCode global_type(const js::wasm::GlobalDesc*); size_t global_tlsOffset(const js::wasm::GlobalDesc*); size_t table_tlsOffset(const js::wasm::TableDesc*); +uint32_t table_initialLimit(const js::wasm::TableDesc*); +// Returns the maximum limit as an uint32, or UINT32_MAX if not defined. +uint32_t table_maximumLimit(const js::wasm::TableDesc*); +js::wasm::TypeCode table_elementTypeCode(const js::wasm::TableDesc*); size_t funcType_numArgs(const js::wasm::FuncTypeWithId*); const BD_ValType* funcType_args(const js::wasm::FuncTypeWithId*); diff --git a/js/src/wasm/cranelift/clifapi.h b/js/src/wasm/cranelift/clifapi.h index ec6c759cb6e1..f792ecc10419 100644 --- a/js/src/wasm/cranelift/clifapi.h +++ b/js/src/wasm/cranelift/clifapi.h @@ -61,9 +61,17 @@ void cranelift_compiler_destroy(CraneliftCompiler* compiler); // The function described by `data` is compiled. // // Returns true on success. +// +// If this function returns false, an error message is returned in `*error`. +// This string must be freed by `cranelift_compiler_free_error()` (it is on the +// Rust heap so must not be freed by `free()` or similar). bool cranelift_compile_function(CraneliftCompiler* compiler, const CraneliftFuncCompileInput* data, - CraneliftCompiledFunc* result); + CraneliftCompiledFunc* result, char** error); + +// Free an error string returned by `cranelift_compile_function()`. +void cranelift_compiler_free_error(char* error); + } // extern "C" #endif // wasm_cranelift_clifapi_h diff --git a/js/src/wasm/cranelift/src/bindings/mod.rs b/js/src/wasm/cranelift/src/bindings/mod.rs index 807e739b76a0..11b5bb5daa01 100644 --- a/js/src/wasm/cranelift/src/bindings/mod.rs +++ b/js/src/wasm/cranelift/src/bindings/mod.rs @@ -24,9 +24,8 @@ use cranelift_codegen::entity::EntityRef; use cranelift_codegen::ir::immediates::{Ieee32, Ieee64}; use cranelift_codegen::ir::{self, InstBuilder, SourceLoc}; use cranelift_codegen::isa; -use cranelift_wasm::{FuncIndex, GlobalIndex, SignatureIndex, TableIndex, WasmResult}; -use smallvec::SmallVec; +use cranelift_wasm::{wasmparser, FuncIndex, GlobalIndex, SignatureIndex, TableIndex, WasmResult}; use crate::compile; use crate::utils::BasicError; @@ -63,17 +62,10 @@ fn typecode_to_type(type_code: TypeCode) -> WasmResult> { /// Convert a non-void `TypeCode` into the equivalent Cranelift type. #[inline] -fn typecode_to_nonvoid_type(type_code: TypeCode) -> WasmResult { +pub(crate) fn typecode_to_nonvoid_type(type_code: TypeCode) -> WasmResult { Ok(typecode_to_type(type_code)?.expect("unexpected void type")) } -/// Convert a `TypeCode` into the equivalent Cranelift type. -#[inline] -fn valtype_to_type(val_type: BD_ValType) -> WasmResult { - let type_code = unsafe { low_level::env_unpack(val_type) }; - typecode_to_nonvoid_type(type_code) -} - /// Convert a u32 into a `BD_SymbolicAddress`. impl From for SymbolicAddress { fn from(x: u32) -> SymbolicAddress { @@ -121,6 +113,10 @@ impl GlobalDesc { pub fn tls_offset(self) -> usize { unsafe { low_level::global_tlsOffset(self.0) } } + + pub fn content_type(self) -> wasmparser::Type { + typecode_to_parser_type(unsafe { low_level::global_type(self.0) }) + } } #[derive(Clone, Copy)] @@ -131,81 +127,156 @@ impl TableDesc { pub fn tls_offset(self) -> usize { unsafe { low_level::table_tlsOffset(self.0) } } -} -#[derive(Clone, Copy)] -pub struct FuncTypeWithId(*const low_level::FuncTypeWithId); + pub fn element_type(self) -> wasmparser::Type { + typecode_to_parser_type(unsafe { low_level::table_elementTypeCode(self.0) }) + } -impl FuncTypeWithId { - pub fn args<'a>(self) -> WasmResult> { - let num_args = unsafe { low_level::funcType_numArgs(self.0) }; - // The `funcType_args` callback crashes when there are no arguments. Also note that - // `slice::from_raw_parts()` requires a non-null pointer for empty slices. - // TODO: We should get all the parts of a signature in a single callback that returns a - // struct. - if num_args == 0 { - Ok(SmallVec::new()) + pub fn resizable_limits(self) -> wasmparser::ResizableLimits { + let initial = unsafe { low_level::table_initialLimit(self.0) }; + let maximum = unsafe { low_level::table_initialLimit(self.0) }; + let maximum = if maximum == u32::max_value() { + None } else { - let args = unsafe { slice::from_raw_parts(low_level::funcType_args(self.0), num_args) }; - let mut ret = SmallVec::new(); - for &arg in args { - ret.push(valtype_to_type(arg)?); - } - Ok(ret) - } + Some(maximum) + }; + wasmparser::ResizableLimits { initial, maximum } } +} - pub fn results<'a>(self) -> WasmResult> { - let num_results = unsafe { low_level::funcType_numResults(self.0) }; - // The same comments as FuncTypeWithId::args apply here. - if num_results == 0 { - Ok(Vec::new()) - } else { - let results = - unsafe { slice::from_raw_parts(low_level::funcType_results(self.0), num_results) }; - let mut ret = Vec::new(); - for &result in results { - ret.push(valtype_to_type(result)?); - } - Ok(ret) - } +#[derive(Clone)] +pub struct FuncTypeWithId { + ptr: *const low_level::FuncTypeWithId, + args: Vec, + results: Vec, +} + +impl FuncTypeWithId { + /// Creates a new FuncTypeWithId, caching all the values it requires. + pub(crate) fn new(ptr: *const low_level::FuncTypeWithId) -> Self { + let num_args = unsafe { low_level::funcType_numArgs(ptr) }; + let args = unsafe { slice::from_raw_parts(low_level::funcType_args(ptr), num_args) }; + let args = args + .iter() + .map(|val_type| unsafe { low_level::env_unpack(*val_type) }) + .collect(); + + let num_results = unsafe { low_level::funcType_numResults(ptr) }; + let results = + unsafe { slice::from_raw_parts(low_level::funcType_results(ptr), num_results) }; + let results = results + .iter() + .map(|val_type| unsafe { low_level::env_unpack(*val_type) }) + .collect(); + + Self { ptr, args, results } } - pub fn id_kind(self) -> FuncTypeIdDescKind { - unsafe { low_level::funcType_idKind(self.0) } + pub(crate) fn id_kind(&self) -> FuncTypeIdDescKind { + unsafe { low_level::funcType_idKind(self.ptr) } + } + pub(crate) fn id_immediate(&self) -> usize { + unsafe { low_level::funcType_idImmediate(self.ptr) } } + pub(crate) fn id_tls_offset(&self) -> usize { + unsafe { low_level::funcType_idTlsOffset(self.ptr) } + } + pub(crate) fn args(&self) -> &[TypeCode] { + &self.args + } + pub(crate) fn results(&self) -> &[TypeCode] { + &self.results + } +} - pub fn id_immediate(self) -> usize { - unsafe { low_level::funcType_idImmediate(self.0) } +fn typecode_to_parser_type(ty: TypeCode) -> wasmparser::Type { + match ty { + TypeCode::I32 => wasmparser::Type::I32, + TypeCode::I64 => wasmparser::Type::I64, + TypeCode::F32 => wasmparser::Type::F32, + TypeCode::F64 => wasmparser::Type::F64, + TypeCode::FuncRef => wasmparser::Type::FuncRef, + TypeCode::ExternRef => wasmparser::Type::ExternRef, + TypeCode::BlockVoid => wasmparser::Type::EmptyBlockType, + _ => panic!("unknown type code: {:?}", ty), } +} - pub fn id_tls_offset(self) -> usize { - unsafe { low_level::funcType_idTlsOffset(self.0) } +impl wasmparser::WasmFuncType for FuncTypeWithId { + fn len_inputs(&self) -> usize { + self.args.len() + } + fn len_outputs(&self) -> usize { + self.results.len() + } + fn input_at(&self, at: u32) -> Option { + self.args + .get(at as usize) + .map(|ty| typecode_to_parser_type(*ty)) + } + fn output_at(&self, at: u32) -> Option { + self.results + .get(at as usize) + .map(|ty| typecode_to_parser_type(*ty)) } } /// Thin wrapper for the CraneliftModuleEnvironment structure. -#[derive(Clone, Copy)] pub struct ModuleEnvironment<'a> { env: &'a CraneliftModuleEnvironment, + /// The `WasmModuleResources` trait requires us to return a borrow to a `FuncTypeWithId`, so we + /// eagerly construct these. + types: Vec, + /// Similar to `types`, we need to have a persistently-stored `FuncTypeWithId` to return. The + /// types in `func_sigs` are a subset of those in `types`, but we don't want to have to + /// maintain an index from function to signature ID, so we store these directly. + func_sigs: Vec, } impl<'a> ModuleEnvironment<'a> { - pub fn new(env: &'a CraneliftModuleEnvironment) -> Self { - Self { env } + pub(crate) fn new(env: &'a CraneliftModuleEnvironment) -> Self { + let num_types = unsafe { low_level::env_num_types(env) }; + let mut types = Vec::with_capacity(num_types); + for i in 0..num_types { + let t = FuncTypeWithId::new(unsafe { low_level::env_signature(env, i) }); + types.push(t); + } + let num_func_sigs = unsafe { low_level::env_num_funcs(env) }; + let mut func_sigs = Vec::with_capacity(num_func_sigs); + for i in 0..num_func_sigs { + let t = FuncTypeWithId::new(unsafe { low_level::env_func_sig(env, i) }); + func_sigs.push(t); + } + Self { + env, + types, + func_sigs, + } + } + pub fn has_memory(&self) -> bool { + unsafe { low_level::env_has_memory(self.env) } } pub fn uses_shared_memory(&self) -> bool { unsafe { low_level::env_uses_shared_memory(self.env) } } + pub fn num_tables(&self) -> usize { + unsafe { low_level::env_num_tables(self.env) } + } pub fn num_types(&self) -> usize { - unsafe { low_level::env_num_types(self.env) } + self.types.len() } pub fn type_(&self, index: usize) -> FuncTypeWithId { - FuncTypeWithId(unsafe { low_level::env_type(self.env, index) }) + self.types[index].clone() + } + pub fn num_func_sigs(&self) -> usize { + self.func_sigs.len() } pub fn func_sig(&self, func_index: FuncIndex) -> FuncTypeWithId { - FuncTypeWithId(unsafe { low_level::env_func_sig(self.env, func_index.index()) }) + self.func_sigs[func_index.index()].clone() + } + pub fn func_sig_index(&self, func_index: FuncIndex) -> SignatureIndex { + SignatureIndex::new(unsafe { low_level::env_func_sig_index(self.env, func_index.index()) }) } pub fn func_import_tls_offset(&self, func_index: FuncIndex) -> usize { unsafe { low_level::env_func_import_tls_offset(self.env, func_index.index()) } @@ -214,7 +285,7 @@ impl<'a> ModuleEnvironment<'a> { unsafe { low_level::env_func_is_import(self.env, func_index.index()) } } pub fn signature(&self, sig_index: SignatureIndex) -> FuncTypeWithId { - FuncTypeWithId(unsafe { low_level::env_signature(self.env, sig_index.index()) }) + FuncTypeWithId::new(unsafe { low_level::env_signature(self.env, sig_index.index()) }) } pub fn table(&self, table_index: TableIndex) -> TableDesc { TableDesc(unsafe { low_level::env_table(self.env, table_index.index()) }) @@ -222,8 +293,97 @@ impl<'a> ModuleEnvironment<'a> { pub fn global(&self, global_index: GlobalIndex) -> GlobalDesc { GlobalDesc(unsafe { low_level::env_global(self.env, global_index.index()) }) } - pub fn min_memory_length(&self) -> i64 { - i64::from(self.env.min_memory_length) + pub fn min_memory_length(&self) -> u32 { + self.env.min_memory_length + } + pub fn max_memory_length(&self) -> Option { + let max = unsafe { low_level::env_max_memory(self.env) }; + if max == u32::max_value() { + None + } else { + Some(max) + } + } +} + +impl<'module> wasmparser::WasmModuleResources for ModuleEnvironment<'module> { + type FuncType = FuncTypeWithId; + fn table_at(&self, at: u32) -> Option { + if (at as usize) < self.num_tables() { + let desc = TableDesc(unsafe { low_level::env_table(self.env, at as usize) }); + let element_type = desc.element_type(); + let limits = desc.resizable_limits(); + Some(wasmparser::TableType { + element_type, + limits, + }) + } else { + None + } + } + fn memory_at(&self, at: u32) -> Option { + if at == 0 { + let has_memory = self.has_memory(); + if has_memory { + let shared = self.uses_shared_memory(); + let initial = self.min_memory_length() as u32; + let maximum = self.max_memory_length(); + Some(wasmparser::MemoryType::M32 { + limits: wasmparser::ResizableLimits { initial, maximum }, + shared, + }) + } else { + None + } + } else { + None + } + } + fn global_at(&self, at: u32) -> Option { + let num_globals = unsafe { low_level::env_num_globals(self.env) }; + if (at as usize) < num_globals { + let desc = self.global(GlobalIndex::new(at as usize)); + let mutable = !desc.is_constant(); + let content_type = desc.content_type(); + Some(wasmparser::GlobalType { + mutable, + content_type, + }) + } else { + None + } + } + fn func_type_at(&self, type_idx: u32) -> Option<&Self::FuncType> { + if (type_idx as usize) < self.types.len() { + Some(&self.types[type_idx as usize]) + } else { + None + } + } + fn type_of_function(&self, func_idx: u32) -> Option<&Self::FuncType> { + if (func_idx as usize) < self.func_sigs.len() { + Some(&self.func_sigs[func_idx as usize]) + } else { + None + } + } + fn element_type_at(&self, at: u32) -> Option { + let num_elems = self.element_count(); + if at < num_elems { + let elem_type = unsafe { low_level::env_elem_typecode(self.env, at) }; + Some(typecode_to_parser_type(elem_type)) + } else { + None + } + } + fn element_count(&self) -> u32 { + unsafe { low_level::env_num_elems(self.env) as u32 } + } + fn data_count(&self) -> u32 { + unsafe { low_level::env_num_datas(self.env) as u32 } + } + fn is_function_referenced(&self, idx: u32) -> bool { + unsafe { low_level::env_is_func_valid_for_ref(self.env, idx) } } } diff --git a/js/src/wasm/cranelift/src/compile.rs b/js/src/wasm/cranelift/src/compile.rs index 1e811e3aba13..a64344ef521a 100644 --- a/js/src/wasm/cranelift/src/compile.rs +++ b/js/src/wasm/cranelift/src/compile.rs @@ -21,6 +21,7 @@ use log::{debug, info}; use std::fmt; use std::mem; +use std::rc::Rc; use cranelift_codegen::binemit::{ Addend, CodeInfo, CodeOffset, NullStackMapSink, Reloc, RelocSink, TrapSink, @@ -28,13 +29,14 @@ use cranelift_codegen::binemit::{ use cranelift_codegen::entity::EntityRef; use cranelift_codegen::ir::{ self, constant::ConstantOffset, stackslot::StackSize, ExternalName, JumpTable, SourceLoc, - TrapCode, Type, + TrapCode, }; use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::machinst::MachStackMap; use cranelift_codegen::CodegenResult; use cranelift_codegen::Context; -use cranelift_wasm::{FuncIndex, FuncTranslator, ModuleTranslationState, WasmResult}; +use cranelift_wasm::wasmparser::{FuncValidator, WasmFeatures}; +use cranelift_wasm::{FuncIndex, FuncTranslator, WasmResult}; use crate::bindings; use crate::isa::make_isa; @@ -91,10 +93,9 @@ impl CompiledFunc { /// compilations. pub struct BatchCompiler<'static_env, 'module_env> { // Attributes that are constant accross multiple compilations. - static_environ: &'static_env bindings::StaticEnvironment, + static_env: &'static_env bindings::StaticEnvironment, - environ: bindings::ModuleEnvironment<'module_env>, - module_state: ModuleTranslationState, + module_env: Rc>, isa: Box, @@ -117,17 +118,17 @@ pub struct BatchCompiler<'static_env, 'module_env> { impl<'static_env, 'module_env> BatchCompiler<'static_env, 'module_env> { pub fn new( - static_environ: &'static_env bindings::StaticEnvironment, - environ: bindings::ModuleEnvironment<'module_env>, + static_env: &'static_env bindings::StaticEnvironment, + module_env: bindings::ModuleEnvironment<'module_env>, ) -> DashResult { - let isa = make_isa(static_environ)?; - let trans_env = TransEnv::new(&*isa, environ, static_environ); + let isa = make_isa(static_env)?; + let module_env = Rc::new(module_env); + let trans_env = TransEnv::new(&*isa, module_env.clone(), static_env); Ok(BatchCompiler { - static_environ, - environ, + static_env, + module_env, isa, func_translator: FuncTranslator::new(), - module_state: create_module_translation_state(&environ)?, context: Context::new(), trap_relocs: Traps::new(), trans_env, @@ -156,11 +157,27 @@ impl<'static_env, 'module_env> BatchCompiler<'static_env, 'module_env> { let index = FuncIndex::new(func.index as usize); self.context.func.signature = - init_sig(&self.environ, self.static_environ.call_conv(), index)?; + init_sig(&*self.module_env, self.static_env.call_conv(), index)?; self.context.func.name = wasm_function_name(index); + let features = WasmFeatures { + reference_types: self.static_env.ref_types_enabled, + module_linking: false, + simd: false, // TODO + multi_value: true, + threads: self.static_env.threads_enabled, + tail_call: false, + bulk_memory: true, + deterministic_only: true, + memory64: false, + multi_memory: false, + }; + let sig_index = self.module_env.func_sig_index(index); + let mut validator = + FuncValidator::new(sig_index.index() as u32, 0, &*self.module_env, &features)?; + self.func_translator.translate( - &self.module_state, + &mut validator, func.bytecode(), func.offset_in_module as usize, &mut self.context.func, @@ -222,7 +239,7 @@ impl<'static_env, 'module_env> BatchCompiler<'static_env, 'module_env> { .append(&mut self.trap_relocs.metadata); } - if self.static_environ.ref_types_enabled { + if self.static_env.ref_types_enabled { self.emit_stackmaps(stackmaps); } @@ -287,27 +304,6 @@ impl<'static_env, 'module_env> fmt::Display for BatchCompiler<'static_env, 'modu } } -fn create_module_translation_state( - env: &bindings::ModuleEnvironment, -) -> WasmResult { - let num_sig = env.num_types(); - - let mut arg_vecs = vec![]; - let mut result_vecs = vec![]; - for i in 0..num_sig { - let sig = env.type_(i); - arg_vecs.push(sig.args()?); - result_vecs.push(sig.results()?); - } - let types: Vec<(&[Type], &[Type])> = arg_vecs - .iter() - .zip(result_vecs.iter()) - .map(|(args, results)| (&args[..], &results[..])) - .collect(); - - ModuleTranslationState::from_func_sigs(&types[..]) -} - /// Create a Cranelift function name representing a WebAssembly function with `index`. pub fn wasm_function_name(func: FuncIndex) -> ExternalName { ExternalName::User { diff --git a/js/src/wasm/cranelift/src/lib.rs b/js/src/wasm/cranelift/src/lib.rs index 36b6cb81fdbc..ef6fd7e684fd 100644 --- a/js/src/wasm/cranelift/src/lib.rs +++ b/js/src/wasm/cranelift/src/lib.rs @@ -136,7 +136,10 @@ mod utils; mod wasm2clif; use log::{self, error, info}; +use std::ffi::CString; +use std::os::raw::c_char; use std::ptr; +use std::fmt::Display; use crate::bindings::{CompiledFunc, FuncCompileInput, ModuleEnvironment, StaticEnvironment}; use crate::compile::BatchCompiler; @@ -197,14 +200,26 @@ pub unsafe extern "C" fn cranelift_compiler_destroy(compiler: *mut BatchCompiler let _box = Box::from_raw(compiler); } +fn error_to_cstring(err: D) -> *mut c_char { + use std::fmt::Write; + let mut s = String::new(); + write!(&mut s, "{}", err); + let cstr = CString::new(s).unwrap(); + cstr.into_raw() +} + /// Compile a single function. /// /// This is declared in `clifapi.h`. +/// +/// If a Wasm validation error is returned in *error, then it *must* be later +/// freed by `cranelift_compiler_free_error()`. #[no_mangle] pub unsafe extern "C" fn cranelift_compile_function( compiler: *mut BatchCompiler, data: *const FuncCompileInput, result: *mut CompiledFunc, + error: *mut *mut c_char, ) -> bool { let compiler = compiler.as_mut().unwrap(); let data = data.as_ref().unwrap(); @@ -213,8 +228,8 @@ pub unsafe extern "C" fn cranelift_compile_function( compiler.clear(); if let Err(e) = compiler.translate_wasm(data) { - error!("Wasm translation error: {}\n", e); - info!("Translated function: {}", compiler); + let cstr = error_to_cstring(e); + unsafe { *error = cstr; } return false; }; @@ -229,8 +244,8 @@ pub unsafe extern "C" fn cranelift_compile_function( CodegenError::ImplLimitExceeded | CodegenError::CodeTooLarge | CodegenError::Unsupported(_) => { - error!("Cranelift compilation error: {}\n", e); - info!("Compiled function: {}", compiler); + let cstr = error_to_cstring(e); + unsafe { *error = cstr; } return false; } } @@ -244,6 +259,12 @@ pub unsafe extern "C" fn cranelift_compile_function( true } +#[no_mangle] +pub unsafe extern "C" fn cranelift_compiler_free_error(s: *mut c_char) { + // Convert back into a `CString` and then let it drop. + let _cstr = unsafe { CString::from_raw(s) }; +} + /// Returns true whether a platform (target ISA) is supported or not. #[no_mangle] pub unsafe extern "C" fn cranelift_supports_platform() -> bool { diff --git a/js/src/wasm/cranelift/src/wasm2clif.rs b/js/src/wasm/cranelift/src/wasm2clif.rs index 99b05e00f018..2d09c1582b63 100644 --- a/js/src/wasm/cranelift/src/wasm2clif.rs +++ b/js/src/wasm/cranelift/src/wasm2clif.rs @@ -19,6 +19,7 @@ //! internal data structures. use std::collections::HashMap; +use std::rc::Rc; use cranelift_codegen::cursor::{Cursor, FuncCursor}; use cranelift_codegen::entity::{EntityRef, PrimaryMap, SecondaryMap}; @@ -36,6 +37,7 @@ use cranelift_wasm::{ use crate::bindings::{self, GlobalDesc, SymbolicAddress}; use crate::compile::{symbolic_function_name, wasm_function_name}; use crate::isa::{platform::USES_HEAP_REG, POINTER_SIZE}; +use bindings::typecode_to_nonvoid_type; #[cfg(target_pointer_width = "64")] pub const POINTER_TYPE: ir::Type = ir::types::I64; @@ -71,27 +73,29 @@ fn imm64(offset: usize) -> ir::immediates::Imm64 { /// a callee. fn init_sig_from_wsig( call_conv: CallConv, - wsig: bindings::FuncTypeWithId, + wsig: &bindings::FuncTypeWithId, ) -> WasmResult { let mut sig = ir::Signature::new(call_conv); - for arg_type in wsig.args()? { - let arg = match arg_type { + for arg_type in wsig.args() { + let ty = typecode_to_nonvoid_type(*arg_type)?; + let arg = match ty { // SpiderMonkey requires i32 arguments to callees (e.g., from Wasm // back into JS or native code) to have their high 32 bits zero so // that it can directly box them. - ir::types::I32 => ir::AbiParam::new(arg_type).uext(), - _ => ir::AbiParam::new(arg_type), + ir::types::I32 => ir::AbiParam::new(ty).uext(), + _ => ir::AbiParam::new(ty), }; sig.params.push(arg); } - for ret_type in wsig.results()? { - let ret = match ret_type { + for ret_type in wsig.results() { + let ty = typecode_to_nonvoid_type(*ret_type)?; + let ret = match ty { // SpiderMonkey requires i32 returns to have their high 32 bits // zero so that it can directly box them. - ir::types::I32 => ir::AbiParam::new(ret_type).uext(), - _ => ir::AbiParam::new(ret_type), + ir::types::I32 => ir::AbiParam::new(ty).uext(), + _ => ir::AbiParam::new(ty), }; sig.returns.push(ret); } @@ -113,7 +117,7 @@ pub fn init_sig( func_index: FuncIndex, ) -> WasmResult { let wsig = env.func_sig(func_index); - init_sig_from_wsig(call_conv, wsig) + init_sig_from_wsig(call_conv, &wsig) } /// An instance call may return a special value to indicate that the operation @@ -299,13 +303,13 @@ pub const TRAP_THROW_REPORTED: u16 = 1; /// A translation context that implements `FuncEnvironment` for the specific Spidermonkey /// translation bits. pub struct TransEnv<'static_env, 'module_env> { - env: bindings::ModuleEnvironment<'module_env>, static_env: &'static_env bindings::StaticEnvironment, + module_env: Rc>, target_frontend_config: TargetFrontendConfig, - /// Information about the function pointer tables `self.env` knowns about. Indexed by table - /// index. + /// Information about the function pointer tables `self.module_env` knowns about. Indexed by + /// table index. tables: PrimaryMap, /// For those signatures whose ID is stored in a global, keep track of the globals we have @@ -349,12 +353,12 @@ pub struct TransEnv<'static_env, 'module_env> { impl<'static_env, 'module_env> TransEnv<'static_env, 'module_env> { pub fn new( isa: &dyn TargetIsa, - env: bindings::ModuleEnvironment<'module_env>, + module_env: Rc>, static_env: &'static_env bindings::StaticEnvironment, ) -> Self { TransEnv { - env, static_env, + module_env, target_frontend_config: isa.frontend_config(), tables: PrimaryMap::new(), signatures: HashMap::new(), @@ -401,7 +405,7 @@ impl<'static_env, 'module_env> TransEnv<'static_env, 'module_env> { // Allocate all tables up to the requested index. let vmctx = self.get_vmctx_gv(func); while self.tables.len() <= table.index() { - let wtab = self.env.table(TableIndex::new(self.tables.len())); + let wtab = self.module_env.table(TableIndex::new(self.tables.len())); self.tables.push(TableInfo::new(wtab, func, vmctx)); } self.tables[table].clone() @@ -429,7 +433,7 @@ impl<'static_env, 'module_env> TransEnv<'static_env, 'module_env> { let vmctx = self.get_vmctx_gv(func); let gv = func.create_global_value(ir::GlobalValueData::IAddImm { base: vmctx, - offset: imm64(self.env.func_import_tls_offset(index)), + offset: imm64(self.module_env.func_import_tls_offset(index)), global_type: POINTER_TYPE, }); // Save it for next time. @@ -724,7 +728,7 @@ impl<'static_env, 'module_env> FuncEnvironment for TransEnv<'static_env, 'module func: &mut ir::Function, index: GlobalIndex, ) -> WasmResult { - let global = self.env.global(index); + let global = self.module_env.global(index); if global.is_constant() { // Constant globals have a known value at compile time. We insert an instruction to // materialize the constant at the front of the entry block. @@ -787,7 +791,7 @@ impl<'static_env, 'module_env> FuncEnvironment for TransEnv<'static_env, 'module ir::HeapStyle::Dynamic { bound_gv } }; - let min_size = (self.env.min_memory_length() as u64).into(); + let min_size = (self.module_env.min_memory_length() as u64).into(); let offset_guard_size = (self.static_env.memory_guard_size as u64).into(); Ok(func.create_heap(ir::HeapData { @@ -804,8 +808,8 @@ impl<'static_env, 'module_env> FuncEnvironment for TransEnv<'static_env, 'module func: &mut ir::Function, index: SignatureIndex, ) -> WasmResult { - let wsig = self.env.signature(index); - let mut sigdata = init_sig_from_wsig(self.static_env.call_conv(), wsig)?; + let wsig = self.module_env.signature(index); + let mut sigdata = init_sig_from_wsig(self.static_env.call_conv(), &wsig)?; if wsig.id_kind() != bindings::FuncTypeIdDescKind::None { // A signature to be used for an indirect call also takes a signature id. @@ -852,7 +856,7 @@ impl<'static_env, 'module_env> FuncEnvironment for TransEnv<'static_env, 'module index: FuncIndex, ) -> WasmResult { // Create a signature. - let sigdata = init_sig(&self.env, self.static_env.call_conv(), index)?; + let sigdata = init_sig(&*self.module_env, self.static_env.call_conv(), index)?; let signature = func.import_signature(sigdata); Ok(func.import_function(ir::ExtFuncData { @@ -872,7 +876,7 @@ impl<'static_env, 'module_env> FuncEnvironment for TransEnv<'static_env, 'module callee: ir::Value, call_args: &[ir::Value], ) -> WasmResult { - let wsig = self.env.signature(sig_index); + let wsig = self.module_env.signature(sig_index); let wtable = self.get_table(pos.func, table_index); @@ -973,7 +977,7 @@ impl<'static_env, 'module_env> FuncEnvironment for TransEnv<'static_env, 'module args.extend(call_args.iter().cloned(), &mut pos.func.dfg.value_lists); // Is this an imported function in a different instance, or a local function? - if self.env.func_is_import(callee_index) { + if self.module_env.func_is_import(callee_index) { // This is a call to an imported function. We need to load the callee address and vmctx // from the associated `FuncImportTls` struct in a global. let gv = self.func_import_global(pos.func, callee_index); @@ -1063,7 +1067,7 @@ impl<'static_env, 'module_env> FuncEnvironment for TransEnv<'static_env, 'module // We have a specialized version of `memory.copy` when we are using // shared memory or not. - let ret = if self.env.uses_shared_memory() { + let ret = if self.module_env.uses_shared_memory() { self.instance_call(&mut pos, &FN_MEMORY_COPY_SHARED, &[dst, src, len, mem_base]) } else { self.instance_call(&mut pos, &FN_MEMORY_COPY, &[dst, src, len, mem_base]) @@ -1086,7 +1090,7 @@ impl<'static_env, 'module_env> FuncEnvironment for TransEnv<'static_env, 'module // We have a specialized version of `memory.fill` when we are using // shared memory or not. - let ret = if self.env.uses_shared_memory() { + let ret = if self.module_env.uses_shared_memory() { self.instance_call(&mut pos, &FN_MEMORY_FILL_SHARED, &[dst, val, len, mem_base]) } else { self.instance_call(&mut pos, &FN_MEMORY_FILL, &[dst, val, len, mem_base]) @@ -1258,7 +1262,7 @@ impl<'static_env, 'module_env> FuncEnvironment for TransEnv<'static_env, 'module mut pos: FuncCursor, global_index: GlobalIndex, ) -> WasmResult { - let global = self.env.global(global_index); + let global = self.module_env.global(global_index); let ty = global.value_type()?; debug_assert!(ty == ir::types::R32 || ty == ir::types::R64); @@ -1274,7 +1278,7 @@ impl<'static_env, 'module_env> FuncEnvironment for TransEnv<'static_env, 'module global_index: GlobalIndex, val: ir::Value, ) -> WasmResult<()> { - let global = self.env.global(global_index); + let global = self.module_env.global(global_index); let ty = global.value_type()?; debug_assert!(ty == ir::types::R32 || ty == ir::types::R64);