Skip to content

Commit

Permalink
Bug 1655042: Support Cranelift-based Wasm validation. r=jseward
Browse files Browse the repository at this point in the history
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 <benj@benj.me>

Differential Revision: https://phabricator.services.mozilla.com/D92504
  • Loading branch information
cfallin committed Oct 7, 2020
1 parent 59f1066 commit 3b3e310
Show file tree
Hide file tree
Showing 27 changed files with 528 additions and 250 deletions.
20 changes: 17 additions & 3 deletions js/src/jit-test/lib/wasm.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
10 changes: 5 additions & 5 deletions js/src/jit-test/tests/wasm/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand All @@ -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"));
Expand Down Expand Up @@ -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))))');
Expand Down Expand Up @@ -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)))
Expand Down Expand Up @@ -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);
assertEq(Object.isFrozen(exp), true);
12 changes: 6 additions & 6 deletions js/src/jit-test/tests/wasm/binary.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
}

Expand Down Expand Up @@ -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);
}

Expand Down
28 changes: 14 additions & 14 deletions js/src/jit-test/tests/wasm/control-flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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))))))');

// ----------------------------------------------------------------------------
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion js/src/jit-test/tests/wasm/float.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand Down
2 changes: 1 addition & 1 deletion js/src/jit-test/tests/wasm/gc/disabled.js
Original file line number Diff line number Diff line change
Expand Up @@ -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))))",
Expand Down
Loading

0 comments on commit 3b3e310

Please sign in to comment.