зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1282618 - Baldr: Implement a simple redundant bounds check elimination pass r=sunfish,bbouvier
This commit is contained in:
Родитель
5c3ccb71c2
Коммит
0e12359ca9
|
@ -280,7 +280,8 @@ SignalUsage::SignalUsage()
|
|||
// size is an even divisor of the WebAssembly page size.
|
||||
forOOB(HaveSignalHandlers() &&
|
||||
gc::SystemPageSize() <= PageSize &&
|
||||
PageSize % gc::SystemPageSize() == 0),
|
||||
PageSize % gc::SystemPageSize() == 0 &&
|
||||
!JitOptions.wasmExplicitBoundsChecks),
|
||||
#else
|
||||
forOOB(false),
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,202 @@
|
|||
// |jit-test| test-also-wasm-baseline
|
||||
load(libdir + "wasm.js");
|
||||
|
||||
mem='\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'+
|
||||
'\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff'+
|
||||
'\x00'.repeat(65488) +
|
||||
'\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff'
|
||||
|
||||
print (mem.lengt)
|
||||
|
||||
let accessWidth = {
|
||||
'8_s': 1,
|
||||
'8_u': 1,
|
||||
'16_s': 2,
|
||||
'16_u': 2,
|
||||
'': 4,
|
||||
'f32': 4,
|
||||
'f64': 8,
|
||||
}
|
||||
|
||||
let baseOp = {
|
||||
'8_s': 'i32',
|
||||
'8_u': 'i32',
|
||||
'16_s': 'i32',
|
||||
'16_u': 'i32',
|
||||
'': 'i32',
|
||||
'f32': 'f32',
|
||||
'f64': 'f64',
|
||||
}
|
||||
|
||||
function toSigned(width, num) {
|
||||
let unsignedMax = 2 ** (accessWidth[width] * 8) - 1;
|
||||
let signedMax = 2 ** (accessWidth[width] * 8 - 1) - 1;
|
||||
|
||||
return (num <= signedMax ? num : -(unsignedMax + 1 - num));
|
||||
}
|
||||
|
||||
function fromLittleEndianNum(width, bytes) {
|
||||
let base = 1;
|
||||
var res = 0;
|
||||
for (var i = 0; i < accessWidth[width]; i++) {
|
||||
res += base * bytes[i];
|
||||
base *= 256;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function getInt(width, offset, mem) {
|
||||
var bytes = [ ];
|
||||
for (var i = offset; i < offset + accessWidth[width]; i++) {
|
||||
if (i < mem.length)
|
||||
bytes.push(mem.charCodeAt(i));
|
||||
else
|
||||
bytes.push(0);
|
||||
}
|
||||
|
||||
var res = fromLittleEndianNum(width, bytes);
|
||||
if (width == '8_s' || width == '16_s' || width == '')
|
||||
res = toSigned(width, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
function loadTwiceModule(type, ext, offset, align) {
|
||||
// TODO: Generate memory from byte string
|
||||
return wasmEvalText(
|
||||
`(module
|
||||
(memory 1
|
||||
(segment 0 "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\0a\\0b\\0c\\0d\\0e\\0f")
|
||||
(segment 16 "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")
|
||||
(segment 65520 "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")
|
||||
)
|
||||
(func (param i32) (param i32) (result ${type})
|
||||
(${type}.load${ext}
|
||||
offset=${offset}
|
||||
${align != 0 ? 'align=' + align : ''}
|
||||
(get_local 0)
|
||||
)
|
||||
(${type}.load${ext}
|
||||
offset=${offset}
|
||||
${align != 0 ? 'align=' + align : ''}
|
||||
(get_local 1)
|
||||
)
|
||||
) (export "" 0))`
|
||||
);
|
||||
}
|
||||
|
||||
function loadTwiceSameBasePlusConstModule(type, ext, offset, align, addConst) {
|
||||
return wasmEvalText(
|
||||
`(module
|
||||
(memory 1
|
||||
(segment 0 "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\0a\\0b\\0c\\0d\\0e\\0f")
|
||||
(segment 16 "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")
|
||||
(segment 65520 "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")
|
||||
)
|
||||
(func (param i32) (result ${type})
|
||||
(${type}.load${ext}
|
||||
offset=${offset}
|
||||
${align != 0 ? 'align=' + align : ''}
|
||||
(get_local 0)
|
||||
)
|
||||
(${type}.load${ext}
|
||||
offset=${offset}
|
||||
${align != 0 ? 'align=' + align : ''}
|
||||
(i32.add (get_local 0) (i32.const ${addConst}))
|
||||
)
|
||||
) (export "" 0))`
|
||||
);
|
||||
}
|
||||
|
||||
function loadTwiceSameBasePlusNonConstModule(type, ext, offset, align) {
|
||||
return wasmEvalText(
|
||||
`(module
|
||||
(memory 1
|
||||
(segment 0 "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\0a\\0b\\0c\\0d\\0e\\0f")
|
||||
(segment 16 "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")
|
||||
(segment 65520 "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")
|
||||
)
|
||||
(func (param i32) (param i32) (result ${type})
|
||||
(${type}.load${ext}
|
||||
offset=${offset}
|
||||
${align != 0 ? 'align=' + align : ''}
|
||||
(get_local 0)
|
||||
)
|
||||
(${type}.load${ext}
|
||||
offset=${offset}
|
||||
${align != 0 ? 'align=' + align : ''}
|
||||
(i32.add (get_local 0) (get_local 1))
|
||||
)
|
||||
) (export "" 0))`
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* On x64 falsely removed bounds checks will be masked by the signal handlers.
|
||||
* Thus it is important that these tests be run on x86.
|
||||
*/
|
||||
|
||||
function testOOB(mod, args) {
|
||||
assertErrorMessage(() => mod(...args), Error, /invalid or out-of-range index/);
|
||||
}
|
||||
|
||||
function testOk(mod, args, expected, expectedType) {
|
||||
if (expectedType === 'i64')
|
||||
assertEqI64(mod(...args), createI64(expected));
|
||||
else
|
||||
assertEq(mod(...args), expected);
|
||||
}
|
||||
|
||||
// TODO: It would be nice to verify how many BCs are eliminated on positive tests.
|
||||
|
||||
const align = 0;
|
||||
for (let offset of [0, 1, 2, 3, 4, 8, 16, 41, 0xfff8]) {
|
||||
|
||||
var widths = ['8_s', '8_u', '16_s', '16_u', '']
|
||||
|
||||
for(let width of widths) {
|
||||
// Accesses of 1 byte.
|
||||
let lastValidIndex = 0x10000 - offset - accessWidth[width];
|
||||
let op = baseOp[width];
|
||||
|
||||
print("Width: " + width + " offset: " + offset + " op: " + op)
|
||||
var mod = loadTwiceModule(op, width, offset, align);
|
||||
|
||||
// Two consecutive loads from two different bases
|
||||
testOk(mod, [lastValidIndex, lastValidIndex], getInt(width, lastValidIndex + offset, mem), op);
|
||||
testOOB(mod, [lastValidIndex + 42, lastValidIndex + 42]);
|
||||
testOOB(mod, [lastValidIndex, lastValidIndex + 42]);
|
||||
|
||||
mod = loadTwiceSameBasePlusConstModule(op, width, offset, align, 1);
|
||||
|
||||
testOk(mod, [lastValidIndex-1], getInt(width, lastValidIndex + offset, mem), op);
|
||||
testOOB(mod, [lastValidIndex]);
|
||||
|
||||
// Two consecutive loads from same base with different offsets
|
||||
mod = loadTwiceSameBasePlusConstModule(op, width, offset, align, 2);
|
||||
|
||||
testOk(mod, [lastValidIndex-2], getInt(width, lastValidIndex + offset, mem), op);
|
||||
testOOB(mod, [lastValidIndex-1, 2]);
|
||||
|
||||
mod = loadTwiceSameBasePlusConstModule(op, width, offset, align, lastValidIndex);
|
||||
|
||||
testOk(mod, [0], getInt(width, lastValidIndex + offset, mem), op);
|
||||
testOOB(mod, [1]);
|
||||
|
||||
mod = loadTwiceSameBasePlusNonConstModule(op, width, offset, align);
|
||||
testOk(mod, [0, 1], getInt(width, 1 + offset, mem), op);
|
||||
testOk(mod, [0, lastValidIndex], getInt(width, lastValidIndex + offset, mem), op);
|
||||
testOOB(mod, [1, lastValidIndex])
|
||||
|
||||
// TODO: All of the above with mixed loads and stores
|
||||
|
||||
// TODO: Branching - what do we want?
|
||||
|
||||
// TODO: Just loops
|
||||
// - loop invariant checks
|
||||
// - loop dependant checks remaining inbounds
|
||||
// - loop dependant checks going out-of bounds.
|
||||
//
|
||||
// TODO: Loops + branching
|
||||
// - loop invariant checks guarded by a loop invariant branch?
|
||||
}
|
||||
}
|
|
@ -104,229 +104,235 @@ function testStoreOOB(type, ext, base, offset, align, value) {
|
|||
assertErrorMessage(() => storeModule(type, ext, offset, align).store(base, value), Error, /invalid or out-of-range index/);
|
||||
}
|
||||
|
||||
testLoad('i32', '', 0, 0, 0, 0x03020100);
|
||||
testLoad('i32', '', 1, 0, 0, 0x04030201);
|
||||
testLoad('i32', '', 0, 4, 0, 0x07060504);
|
||||
testLoad('i32', '', 1, 3, 4, 0x07060504);
|
||||
testLoad('f32', '', 0, 0, 0, 3.820471434542632e-37);
|
||||
testLoad('f32', '', 1, 0, 0, 1.539989614439558e-36);
|
||||
testLoad('f32', '', 0, 4, 0, 1.0082513512365273e-34);
|
||||
testLoad('f32', '', 1, 3, 4, 1.0082513512365273e-34);
|
||||
testLoad('f64', '', 0, 0, 0, 7.949928895127363e-275);
|
||||
testLoad('f64', '', 1, 0, 0, 5.447603722011605e-270);
|
||||
testLoad('f64', '', 0, 8, 0, 3.6919162048650923e-236);
|
||||
testLoad('f64', '', 1, 7, 4, 3.6919162048650923e-236);
|
||||
|
||||
testLoad('i32', '8_s', 16, 0, 0, -0x10);
|
||||
testLoad('i32', '8_u', 16, 0, 0, 0xf0);
|
||||
testLoad('i32', '16_s', 16, 0, 0, -0xe10);
|
||||
testLoad('i32', '16_u', 16, 0, 0, 0xf1f0);
|
||||
|
||||
testStore('i32', '', 0, 0, 0, -0x3f3e2c2c);
|
||||
testStore('i32', '', 1, 0, 0, -0x3f3e2c2c);
|
||||
testStore('i32', '', 0, 1, 0, -0x3f3e2c2c);
|
||||
testStore('i32', '', 1, 1, 4, -0x3f3e2c2c);
|
||||
|
||||
testStore('f32', '', 0, 0, 0, 0.01234566979110241);
|
||||
testStore('f32', '', 1, 0, 0, 0.01234566979110241);
|
||||
testStore('f32', '', 0, 4, 0, 0.01234566979110241);
|
||||
testStore('f32', '', 1, 3, 4, 0.01234566979110241);
|
||||
testStore('f64', '', 0, 0, 0, 0.89012345);
|
||||
testStore('f64', '', 1, 0, 0, 0.89012345);
|
||||
testStore('f64', '', 0, 8, 0, 0.89012345);
|
||||
testStore('f64', '', 1, 7, 4, 0.89012345);
|
||||
|
||||
testStore('i32', '8', 0, 0, 0, 0x23);
|
||||
testStore('i32', '16', 0, 0, 0, 0x2345);
|
||||
|
||||
assertErrorMessage(() => wasmEvalText('(module (memory 2 1))'), TypeError, /maximum memory size less than initial memory size/);
|
||||
|
||||
// Test bounds checks and edge cases.
|
||||
const align = 0;
|
||||
for (let offset of [0, 1, 2, 3, 4, 8, 16, 41, 0xfff8]) {
|
||||
// Accesses of 1 byte.
|
||||
let lastValidIndex = 0x10000 - 1 - offset;
|
||||
|
||||
testLoad('i32', '8_s', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i32', '8_s', lastValidIndex + 1, offset, align);
|
||||
for (var ind = 0; ind < 2; ind++) {
|
||||
if (ind == 1)
|
||||
setJitCompilerOption('wasm.explicit-bounds-checks', 1);
|
||||
|
||||
testLoad('i32', '8_u', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i32', '8_u', lastValidIndex + 1, offset, align);
|
||||
testLoad('i32', '', 0, 0, 0, 0x03020100);
|
||||
testLoad('i32', '', 1, 0, 0, 0x04030201);
|
||||
testLoad('i32', '', 0, 4, 0, 0x07060504);
|
||||
testLoad('i32', '', 1, 3, 4, 0x07060504);
|
||||
testLoad('f32', '', 0, 0, 0, 3.820471434542632e-37);
|
||||
testLoad('f32', '', 1, 0, 0, 1.539989614439558e-36);
|
||||
testLoad('f32', '', 0, 4, 0, 1.0082513512365273e-34);
|
||||
testLoad('f32', '', 1, 3, 4, 1.0082513512365273e-34);
|
||||
testLoad('f64', '', 0, 0, 0, 7.949928895127363e-275);
|
||||
testLoad('f64', '', 1, 0, 0, 5.447603722011605e-270);
|
||||
testLoad('f64', '', 0, 8, 0, 3.6919162048650923e-236);
|
||||
testLoad('f64', '', 1, 7, 4, 3.6919162048650923e-236);
|
||||
|
||||
testStore('i32', '8', lastValidIndex, offset, align, -42);
|
||||
testStoreOOB('i32', '8', lastValidIndex + 1, offset, align, -42);
|
||||
testLoad('i32', '8_s', 16, 0, 0, -0x10);
|
||||
testLoad('i32', '8_u', 16, 0, 0, 0xf0);
|
||||
testLoad('i32', '16_s', 16, 0, 0, -0xe10);
|
||||
testLoad('i32', '16_u', 16, 0, 0, 0xf1f0);
|
||||
|
||||
// Accesses of 2 bytes.
|
||||
lastValidIndex = 0x10000 - 2 - offset;
|
||||
testStore('i32', '', 0, 0, 0, -0x3f3e2c2c);
|
||||
testStore('i32', '', 1, 0, 0, -0x3f3e2c2c);
|
||||
testStore('i32', '', 0, 1, 0, -0x3f3e2c2c);
|
||||
testStore('i32', '', 1, 1, 4, -0x3f3e2c2c);
|
||||
|
||||
testLoad('i32', '16_s', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i32', '16_s', lastValidIndex + 1, offset, align);
|
||||
testStore('f32', '', 0, 0, 0, 0.01234566979110241);
|
||||
testStore('f32', '', 1, 0, 0, 0.01234566979110241);
|
||||
testStore('f32', '', 0, 4, 0, 0.01234566979110241);
|
||||
testStore('f32', '', 1, 3, 4, 0.01234566979110241);
|
||||
testStore('f64', '', 0, 0, 0, 0.89012345);
|
||||
testStore('f64', '', 1, 0, 0, 0.89012345);
|
||||
testStore('f64', '', 0, 8, 0, 0.89012345);
|
||||
testStore('f64', '', 1, 7, 4, 0.89012345);
|
||||
|
||||
testLoad('i32', '16_u', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i32', '16_u', lastValidIndex + 1, offset, align);
|
||||
testStore('i32', '8', 0, 0, 0, 0x23);
|
||||
testStore('i32', '16', 0, 0, 0, 0x2345);
|
||||
|
||||
testStore('i32', '16', lastValidIndex, offset, align, -32768);
|
||||
testStoreOOB('i32', '16', lastValidIndex + 1, offset, align, -32768);
|
||||
assertErrorMessage(() => wasmEvalText('(module (memory 2 1))'), TypeError, /maximum memory size less than initial memory size/);
|
||||
|
||||
// Accesses of 4 bytes.
|
||||
lastValidIndex = 0x10000 - 4 - offset;
|
||||
for (let offset of [0, 1, 2, 3, 4, 8, 16, 41, 0xfff8]) {
|
||||
// Accesses of 1 byte.
|
||||
let lastValidIndex = 0x10000 - 1 - offset;
|
||||
|
||||
testLoad('i32', '', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i32', '', lastValidIndex + 1, offset, align);
|
||||
testLoad('i32', '8_s', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i32', '8_s', lastValidIndex + 1, offset, align);
|
||||
|
||||
testLoad('f32', '', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('f32', '', lastValidIndex + 1, offset, align);
|
||||
testLoad('i32', '8_u', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i32', '8_u', lastValidIndex + 1, offset, align);
|
||||
|
||||
testStore('i32', '', lastValidIndex, offset, align, 1337);
|
||||
testStoreOOB('i32', '', lastValidIndex + 1, offset, align, 1337);
|
||||
testStore('i32', '8', lastValidIndex, offset, align, -42);
|
||||
testStoreOOB('i32', '8', lastValidIndex + 1, offset, align, -42);
|
||||
|
||||
testStore('f32', '', lastValidIndex, offset, align, Math.fround(13.37));
|
||||
testStoreOOB('f32', '', lastValidIndex + 1, offset, align, Math.fround(13.37));
|
||||
// Accesses of 2 bytes.
|
||||
lastValidIndex = 0x10000 - 2 - offset;
|
||||
|
||||
// Accesses of 8 bytes.
|
||||
lastValidIndex = 0x10000 - 8 - offset;
|
||||
testLoad('i32', '16_s', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i32', '16_s', lastValidIndex + 1, offset, align);
|
||||
|
||||
testLoad('f64', '', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('f64', '', lastValidIndex + 1, offset, align);
|
||||
testLoad('i32', '16_u', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i32', '16_u', lastValidIndex + 1, offset, align);
|
||||
|
||||
testStore('f64', '', lastValidIndex, offset, align, 1.23456789);
|
||||
testStoreOOB('f64', '', lastValidIndex + 1, offset, align, 1.23456789);
|
||||
}
|
||||
testStore('i32', '16', lastValidIndex, offset, align, -32768);
|
||||
testStoreOOB('i32', '16', lastValidIndex + 1, offset, align, -32768);
|
||||
|
||||
// Ensure wrapping doesn't apply.
|
||||
offset = 0x7fffffff; // maximum allowed offset that doesn't always throw.
|
||||
for (let index of [0, 1, 2, 3, 0x7fffffff, 0x80000000, 0x80000001]) {
|
||||
testLoadOOB('i32', '8_s', index, offset, align);
|
||||
testLoadOOB('i32', '16_s', index, offset, align);
|
||||
testLoadOOB('i32', '', index, offset, align);
|
||||
testLoadOOB('f32', '', index, offset, align);
|
||||
testLoadOOB('f64', '', index, offset, align);
|
||||
}
|
||||
// Accesses of 4 bytes.
|
||||
lastValidIndex = 0x10000 - 4 - offset;
|
||||
|
||||
assertErrorMessage(() => wasmEvalText('(module (memory 1) (func (f64.store offset=0 (i32.const 0) (i32.const 0))))'), TypeError, mismatchError("i32", "f64"));
|
||||
assertErrorMessage(() => wasmEvalText('(module (memory 1) (func (f64.store offset=0 (i32.const 0) (f32.const 0))))'), TypeError, mismatchError("f32", "f64"));
|
||||
testLoad('i32', '', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i32', '', lastValidIndex + 1, offset, align);
|
||||
|
||||
assertErrorMessage(() => wasmEvalText('(module (memory 1) (func (f32.store offset=0 (i32.const 0) (i32.const 0))))'), TypeError, mismatchError("i32", "f32"));
|
||||
assertErrorMessage(() => wasmEvalText('(module (memory 1) (func (f32.store offset=0 (i32.const 0) (f64.const 0))))'), TypeError, mismatchError("f64", "f32"));
|
||||
testLoad('f32', '', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('f32', '', lastValidIndex + 1, offset, align);
|
||||
|
||||
assertErrorMessage(() => wasmEvalText('(module (memory 1) (func (i32.store offset=0 (i32.const 0) (f32.const 0))))'), TypeError, mismatchError("f32", "i32"));
|
||||
assertErrorMessage(() => wasmEvalText('(module (memory 1) (func (i32.store offset=0 (i32.const 0) (f64.const 0))))'), TypeError, mismatchError("f64", "i32"));
|
||||
testStore('i32', '', lastValidIndex, offset, align, 1337);
|
||||
testStoreOOB('i32', '', lastValidIndex + 1, offset, align, 1337);
|
||||
|
||||
wasmEvalText('(module (memory 0 65535))')
|
||||
assertErrorMessage(() => wasmEvalText('(module (memory 0 65536))'), TypeError, /maximum memory size too big/);
|
||||
testStore('f32', '', lastValidIndex, offset, align, Math.fround(13.37));
|
||||
testStoreOOB('f32', '', lastValidIndex + 1, offset, align, Math.fround(13.37));
|
||||
|
||||
// Test high charge of registers
|
||||
function testRegisters() {
|
||||
assertEq(wasmEvalText(
|
||||
`(module
|
||||
(memory 1
|
||||
(segment 0 "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\0a\\0b\\0c\\0d\\0e\\0f")
|
||||
(segment 16 "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")
|
||||
)
|
||||
(func (param i32) (local i32 i32 i32 i32 f32 f64) (result i32)
|
||||
(set_local 1 (i32.load8_s offset=4 (get_local 0)))
|
||||
(set_local 2 (i32.load16_s (get_local 1)))
|
||||
(i32.store8 offset=4 (get_local 0) (get_local 1))
|
||||
(set_local 3 (i32.load16_u (get_local 2)))
|
||||
(i32.store16 (get_local 1) (get_local 2))
|
||||
(set_local 4 (i32.load (get_local 2)))
|
||||
(i32.store (get_local 1) (get_local 2))
|
||||
(set_local 5 (f32.load (get_local 4)))
|
||||
(f32.store (get_local 4) (get_local 5))
|
||||
(set_local 6 (f64.load (get_local 4)))
|
||||
(f64.store (get_local 4) (get_local 6))
|
||||
(i32.add
|
||||
(i32.add
|
||||
(get_local 0)
|
||||
(get_local 1)
|
||||
)
|
||||
(i32.add
|
||||
(i32.add
|
||||
(get_local 2)
|
||||
(get_local 3)
|
||||
)
|
||||
(i32.add
|
||||
(get_local 4)
|
||||
(i32.reinterpret/f32 (get_local 5))
|
||||
)
|
||||
)
|
||||
// Accesses of 8 bytes.
|
||||
lastValidIndex = 0x10000 - 8 - offset;
|
||||
|
||||
testLoad('f64', '', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('f64', '', lastValidIndex + 1, offset, align);
|
||||
|
||||
testStore('f64', '', lastValidIndex, offset, align, 1.23456789);
|
||||
testStoreOOB('f64', '', lastValidIndex + 1, offset, align, 1.23456789);
|
||||
}
|
||||
|
||||
// Ensure wrapping doesn't apply.
|
||||
offset = 0x7fffffff; // maximum allowed offset that doesn't always throw.
|
||||
for (let index of [0, 1, 2, 3, 0x7fffffff, 0x80000000, 0x80000001]) {
|
||||
testLoadOOB('i32', '8_s', index, offset, align);
|
||||
testLoadOOB('i32', '16_s', index, offset, align);
|
||||
testLoadOOB('i32', '', index, offset, align);
|
||||
testLoadOOB('f32', '', index, offset, align);
|
||||
testLoadOOB('f64', '', index, offset, align);
|
||||
}
|
||||
|
||||
assertErrorMessage(() => wasmEvalText('(module (memory 1) (func (f64.store offset=0 (i32.const 0) (i32.const 0))))'), TypeError, mismatchError("i32", "f64"));
|
||||
assertErrorMessage(() => wasmEvalText('(module (memory 1) (func (f64.store offset=0 (i32.const 0) (f32.const 0))))'), TypeError, mismatchError("f32", "f64"));
|
||||
|
||||
assertErrorMessage(() => wasmEvalText('(module (memory 1) (func (f32.store offset=0 (i32.const 0) (i32.const 0))))'), TypeError, mismatchError("i32", "f32"));
|
||||
assertErrorMessage(() => wasmEvalText('(module (memory 1) (func (f32.store offset=0 (i32.const 0) (f64.const 0))))'), TypeError, mismatchError("f64", "f32"));
|
||||
|
||||
assertErrorMessage(() => wasmEvalText('(module (memory 1) (func (i32.store offset=0 (i32.const 0) (f32.const 0))))'), TypeError, mismatchError("f32", "i32"));
|
||||
assertErrorMessage(() => wasmEvalText('(module (memory 1) (func (i32.store offset=0 (i32.const 0) (f64.const 0))))'), TypeError, mismatchError("f64", "i32"));
|
||||
|
||||
wasmEvalText('(module (memory 0 65535))')
|
||||
assertErrorMessage(() => wasmEvalText('(module (memory 0 65536))'), TypeError, /maximum memory size too big/);
|
||||
|
||||
// Test high charge of registers
|
||||
function testRegisters() {
|
||||
assertEq(wasmEvalText(
|
||||
`(module
|
||||
(memory 1
|
||||
(segment 0 "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\0a\\0b\\0c\\0d\\0e\\0f")
|
||||
(segment 16 "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")
|
||||
)
|
||||
) (export "" 0))`
|
||||
)(1), 50464523);
|
||||
}
|
||||
|
||||
testRegisters();
|
||||
|
||||
if (hasI64()) {
|
||||
setJitCompilerOption('wasm.test-mode', 1);
|
||||
|
||||
testLoad('i64', '', 0, 0, 0, '0x0706050403020100');
|
||||
testLoad('i64', '', 1, 0, 0, '0x0807060504030201');
|
||||
testLoad('i64', '', 0, 1, 0, '0x0807060504030201');
|
||||
testLoad('i64', '', 1, 1, 4, '0x0908070605040302');
|
||||
|
||||
testLoad('i64', '8_s', 16, 0, 0, -0x10);
|
||||
testLoad('i64', '8_u', 16, 0, 0, 0xf0);
|
||||
testLoad('i64', '16_s', 16, 0, 0, -0xe10);
|
||||
testLoad('i64', '16_u', 16, 0, 0, 0xf1f0);
|
||||
testLoad('i64', '32_s', 16, 0, 0, 0xf3f2f1f0 | 0);
|
||||
testLoad('i64', '32_u', 16, 0, 0, '0xf3f2f1f0');
|
||||
|
||||
testStore('i64', '', 0, 0, 0, '0xc0c1d3d4e6e7090a');
|
||||
testStore('i64', '', 1, 0, 0, '0xc0c1d3d4e6e7090a');
|
||||
testStore('i64', '', 0, 1, 0, '0xc0c1d3d4e6e7090a');
|
||||
testStore('i64', '', 1, 1, 4, '0xc0c1d3d4e6e7090a');
|
||||
testStore('i64', '8', 0, 0, 0, 0x23);
|
||||
testStore('i64', '16', 0, 0, 0, 0x23);
|
||||
testStore('i64', '32', 0, 0, 0, 0x23);
|
||||
|
||||
let align = 0;
|
||||
for (let offset of [0, 1, 2, 3, 4, 8, 16, 41, 0xfff8]) {
|
||||
// Accesses of 1 byte.
|
||||
let lastValidIndex = 0x10000 - 1 - offset;
|
||||
|
||||
testLoad('i64', '8_s', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i64', '8_s', lastValidIndex + 1, offset, align);
|
||||
|
||||
testLoad('i64', '8_u', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i64', '8_u', lastValidIndex + 1, offset, align);
|
||||
|
||||
testStore('i64', '8', lastValidIndex, offset, align, -42);
|
||||
testStoreOOB('i64', '8', lastValidIndex + 1, offset, align, -42);
|
||||
|
||||
// Accesses of 2 bytes.
|
||||
lastValidIndex = 0x10000 - 2 - offset;
|
||||
|
||||
testLoad('i64', '16_s', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i64', '16_s', lastValidIndex + 1, offset, align);
|
||||
|
||||
testLoad('i64', '16_u', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i64', '16_u', lastValidIndex + 1, offset, align);
|
||||
|
||||
testStore('i64', '16', lastValidIndex, offset, align, -32768);
|
||||
testStoreOOB('i64', '16', lastValidIndex + 1, offset, align, -32768);
|
||||
|
||||
// Accesses of 4 bytes.
|
||||
lastValidIndex = 0x10000 - 4 - offset;
|
||||
|
||||
testLoad('i64', '32_s', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i64', '32_s', lastValidIndex + 1, offset, align);
|
||||
|
||||
testLoad('i64', '32_u', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i64', '32_u', lastValidIndex + 1, offset, align);
|
||||
|
||||
testStore('i64', '32', lastValidIndex, offset, align, 0xf1231337 | 0);
|
||||
testStoreOOB('i64', '32', lastValidIndex + 1, offset, align, 0xf1231337 | 0);
|
||||
|
||||
// Accesses of 8 bytes.
|
||||
lastValidIndex = 0x10000 - 8 - offset;
|
||||
|
||||
testLoad('i64', '', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i64', '', lastValidIndex + 1, offset, align);
|
||||
|
||||
testStore('i64', '', lastValidIndex, offset, align, '0x1234567887654321');
|
||||
testStoreOOB('i64', '', lastValidIndex + 1, offset, align, '0x1234567887654321');
|
||||
}
|
||||
|
||||
setJitCompilerOption('wasm.test-mode', 0);
|
||||
(func (param i32) (local i32 i32 i32 i32 f32 f64) (result i32)
|
||||
(set_local 1 (i32.load8_s offset=4 (get_local 0)))
|
||||
(set_local 2 (i32.load16_s (get_local 1)))
|
||||
(i32.store8 offset=4 (get_local 0) (get_local 1))
|
||||
(set_local 3 (i32.load16_u (get_local 2)))
|
||||
(i32.store16 (get_local 1) (get_local 2))
|
||||
(set_local 4 (i32.load (get_local 2)))
|
||||
(i32.store (get_local 1) (get_local 2))
|
||||
(set_local 5 (f32.load (get_local 4)))
|
||||
(f32.store (get_local 4) (get_local 5))
|
||||
(set_local 6 (f64.load (get_local 4)))
|
||||
(f64.store (get_local 4) (get_local 6))
|
||||
(i32.add
|
||||
(i32.add
|
||||
(get_local 0)
|
||||
(get_local 1)
|
||||
)
|
||||
(i32.add
|
||||
(i32.add
|
||||
(get_local 2)
|
||||
(get_local 3)
|
||||
)
|
||||
(i32.add
|
||||
(get_local 4)
|
||||
(i32.reinterpret/f32 (get_local 5))
|
||||
)
|
||||
)
|
||||
)
|
||||
) (export "" 0))`
|
||||
)(1), 50464523);
|
||||
}
|
||||
|
||||
testRegisters();
|
||||
|
||||
if (hasI64()) {
|
||||
setJitCompilerOption('wasm.test-mode', 1);
|
||||
|
||||
testLoad('i64', '', 0, 0, 0, '0x0706050403020100');
|
||||
testLoad('i64', '', 1, 0, 0, '0x0807060504030201');
|
||||
testLoad('i64', '', 0, 1, 0, '0x0807060504030201');
|
||||
testLoad('i64', '', 1, 1, 4, '0x0908070605040302');
|
||||
|
||||
testLoad('i64', '8_s', 16, 0, 0, -0x10);
|
||||
testLoad('i64', '8_u', 16, 0, 0, 0xf0);
|
||||
testLoad('i64', '16_s', 16, 0, 0, -0xe10);
|
||||
testLoad('i64', '16_u', 16, 0, 0, 0xf1f0);
|
||||
testLoad('i64', '32_s', 16, 0, 0, 0xf3f2f1f0 | 0);
|
||||
testLoad('i64', '32_u', 16, 0, 0, '0xf3f2f1f0');
|
||||
|
||||
testStore('i64', '', 0, 0, 0, '0xc0c1d3d4e6e7090a');
|
||||
testStore('i64', '', 1, 0, 0, '0xc0c1d3d4e6e7090a');
|
||||
testStore('i64', '', 0, 1, 0, '0xc0c1d3d4e6e7090a');
|
||||
testStore('i64', '', 1, 1, 4, '0xc0c1d3d4e6e7090a');
|
||||
testStore('i64', '8', 0, 0, 0, 0x23);
|
||||
testStore('i64', '16', 0, 0, 0, 0x23);
|
||||
testStore('i64', '32', 0, 0, 0, 0x23);
|
||||
|
||||
let align = 0;
|
||||
for (let offset of [0, 1, 2, 3, 4, 8, 16, 41, 0xfff8]) {
|
||||
// Accesses of 1 byte.
|
||||
let lastValidIndex = 0x10000 - 1 - offset;
|
||||
|
||||
testLoad('i64', '8_s', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i64', '8_s', lastValidIndex + 1, offset, align);
|
||||
|
||||
testLoad('i64', '8_u', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i64', '8_u', lastValidIndex + 1, offset, align);
|
||||
|
||||
testStore('i64', '8', lastValidIndex, offset, align, -42);
|
||||
testStoreOOB('i64', '8', lastValidIndex + 1, offset, align, -42);
|
||||
|
||||
// Accesses of 2 bytes.
|
||||
lastValidIndex = 0x10000 - 2 - offset;
|
||||
|
||||
testLoad('i64', '16_s', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i64', '16_s', lastValidIndex + 1, offset, align);
|
||||
|
||||
testLoad('i64', '16_u', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i64', '16_u', lastValidIndex + 1, offset, align);
|
||||
|
||||
testStore('i64', '16', lastValidIndex, offset, align, -32768);
|
||||
testStoreOOB('i64', '16', lastValidIndex + 1, offset, align, -32768);
|
||||
|
||||
// Accesses of 4 bytes.
|
||||
lastValidIndex = 0x10000 - 4 - offset;
|
||||
|
||||
testLoad('i64', '32_s', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i64', '32_s', lastValidIndex + 1, offset, align);
|
||||
|
||||
testLoad('i64', '32_u', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i64', '32_u', lastValidIndex + 1, offset, align);
|
||||
|
||||
testStore('i64', '32', lastValidIndex, offset, align, 0xf1231337 | 0);
|
||||
testStoreOOB('i64', '32', lastValidIndex + 1, offset, align, 0xf1231337 | 0);
|
||||
|
||||
// Accesses of 8 bytes.
|
||||
lastValidIndex = 0x10000 - 8 - offset;
|
||||
|
||||
testLoad('i64', '', lastValidIndex, offset, align, 0);
|
||||
testLoadOOB('i64', '', lastValidIndex + 1, offset, align);
|
||||
|
||||
testStore('i64', '', lastValidIndex, offset, align, '0x1234567887654321');
|
||||
testStoreOOB('i64', '', lastValidIndex + 1, offset, align, '0x1234567887654321');
|
||||
}
|
||||
|
||||
setJitCompilerOption('wasm.test-mode', 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
#include "jit/Sink.h"
|
||||
#include "jit/StupidAllocator.h"
|
||||
#include "jit/ValueNumbering.h"
|
||||
#include "jit/WasmBCE.h"
|
||||
#include "vm/Debugger.h"
|
||||
#include "vm/HelperThreads.h"
|
||||
#include "vm/TraceLogging.h"
|
||||
|
@ -1895,6 +1896,13 @@ OptimizeMIR(MIRGenerator* mir)
|
|||
AssertGraphCoherency(graph);
|
||||
}
|
||||
|
||||
if (mir->compilingAsmJS()) {
|
||||
if (!EliminateBoundsChecks(mir, graph))
|
||||
return false;
|
||||
gs.spewPass("Redundant Bounds Check Elimination");
|
||||
AssertGraphCoherency(graph);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -220,6 +220,10 @@ DefaultJitOptions::DefaultJitOptions()
|
|||
|
||||
// Test whether wasm int64 / double NaN bits testing is enabled.
|
||||
SET_DEFAULT(wasmTestMode, false);
|
||||
|
||||
// Determines whether explicit bounds check will be used for OOB
|
||||
// instead of signals (even when signals are available).
|
||||
SET_DEFAULT(wasmExplicitBoundsChecks, false);
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
|
@ -69,6 +69,7 @@ struct DefaultJitOptions
|
|||
bool limitScriptSize;
|
||||
bool osr;
|
||||
bool wasmTestMode;
|
||||
bool wasmExplicitBoundsChecks;
|
||||
uint32_t baselineWarmUpThreshold;
|
||||
uint32_t exceptionBailoutThreshold;
|
||||
uint32_t frequentBailoutThreshold;
|
||||
|
|
|
@ -13057,9 +13057,12 @@ class MWasmBoundsCheck
|
|||
public MWasmMemoryAccess,
|
||||
public NoTypePolicy::Data
|
||||
{
|
||||
bool redundant_;
|
||||
|
||||
explicit MWasmBoundsCheck(MDefinition* index, const MWasmMemoryAccess& access)
|
||||
: MUnaryInstruction(index),
|
||||
MWasmMemoryAccess(access)
|
||||
MWasmMemoryAccess(access),
|
||||
redundant_(false)
|
||||
{
|
||||
setMovable();
|
||||
setGuard(); // Effectful: throws for OOB.
|
||||
|
@ -13081,6 +13084,14 @@ class MWasmBoundsCheck
|
|||
AliasSet getAliasSet() const override {
|
||||
return AliasSet::None();
|
||||
}
|
||||
|
||||
bool isRedundant() const {
|
||||
return redundant_;
|
||||
}
|
||||
|
||||
void setRedundant(bool val) {
|
||||
redundant_ = val;
|
||||
}
|
||||
};
|
||||
|
||||
class MWasmLoad
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "jit/WasmBCE.h"
|
||||
#include "jit/MIRGenerator.h"
|
||||
#include "jit/MIRGraph.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::jit;
|
||||
using namespace mozilla;
|
||||
|
||||
struct DefAndOffset {
|
||||
MDefinition* loc;
|
||||
uint32_t endOffset;
|
||||
};
|
||||
|
||||
typedef js::HashMap<uint32_t, DefAndOffset, DefaultHasher<uint32_t>, SystemAllocPolicy>
|
||||
LastSeenMap;
|
||||
|
||||
// The Wasm Bounds Check Elimination (BCE) pass looks for bounds checks
|
||||
// on SSA values that have already been checked. (in the same block or in a
|
||||
// dominating block). These bounds checks are redundant and thus eliminated.
|
||||
//
|
||||
// Note: This is safe in the presense of dynamic memory sizes as long as they
|
||||
// can ONLY GROW. If we allow SHRINKING the heap, this pass should be
|
||||
// RECONSIDERED.
|
||||
//
|
||||
// TODO (dbounov): Are there a lot of cases where there is no single dominating
|
||||
// check, but a set of checks that together dominate a redundant check?
|
||||
//
|
||||
// TODO (dbounov): Generalize to constant additions relative to one base
|
||||
bool jit::EliminateBoundsChecks(MIRGenerator* mir, MIRGraph& graph) {
|
||||
// Map for dominating block where a given definition was checked
|
||||
LastSeenMap lastSeen;
|
||||
if (!lastSeen.init())
|
||||
return false;
|
||||
|
||||
for (ReversePostorderIterator bIter(graph.rpoBegin()); bIter != graph.rpoEnd(); bIter++) {
|
||||
MBasicBlock* block = *bIter;
|
||||
for (MDefinitionIterator dIter(block); dIter;) {
|
||||
MDefinition* def = *dIter++;
|
||||
|
||||
switch (def->op()) {
|
||||
case MDefinition::Op_WasmBoundsCheck: {
|
||||
MWasmBoundsCheck* bc = def->toWasmBoundsCheck();
|
||||
MDefinition* addr = def->getOperand(0);
|
||||
LastSeenMap::Ptr checkPtr = lastSeen.lookup(addr->id());
|
||||
|
||||
if (checkPtr &&
|
||||
checkPtr->value().endOffset >= bc->endOffset() &&
|
||||
checkPtr->value().loc->block()->dominates(block)) {
|
||||
// Address already checked. Discard current check
|
||||
bc->setRedundant(true);
|
||||
} else {
|
||||
DefAndOffset defOff = { def, bc->endOffset() };
|
||||
// Address not previously checked - remember current check
|
||||
if (!lastSeen.put(addr->id(), defOff))
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MDefinition::Op_Phi: {
|
||||
MPhi* phi = def->toPhi();
|
||||
bool phiChecked = true;
|
||||
uint32_t off = UINT32_MAX;
|
||||
|
||||
MOZ_ASSERT(phi->numOperands() > 0);
|
||||
|
||||
// If all incoming values to a phi node are safe (i.e. have a
|
||||
// check that dominates this block) then we can consider this
|
||||
// phi node checked.
|
||||
//
|
||||
// Note that any phi that is part of a cycle
|
||||
// will not be "safe" since the value coming on the backedge
|
||||
// cannot be in lastSeen because its block hasn't been traversed yet.
|
||||
for (int i = 0, nOps = phi->numOperands(); i < nOps; i++) {
|
||||
MDefinition* src = phi->getOperand(i);
|
||||
LastSeenMap::Ptr checkPtr = lastSeen.lookup(src->id());
|
||||
|
||||
if (!checkPtr || !checkPtr->value().loc->block()->dominates(block)) {
|
||||
phiChecked = false;
|
||||
break;
|
||||
} else {
|
||||
off = Min(off, checkPtr->value().endOffset);
|
||||
}
|
||||
}
|
||||
|
||||
if (phiChecked) {
|
||||
DefAndOffset defOff = { def, off };
|
||||
if (!lastSeen.put(def->id(), defOff))
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
*
|
||||
* Copyright 2016 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef jit_wasmbce_h
|
||||
#define jit_wasmbce_h
|
||||
|
||||
namespace js {
|
||||
namespace jit {
|
||||
|
||||
class MIRGenerator;
|
||||
class MIRGraph;
|
||||
|
||||
bool EliminateBoundsChecks(MIRGenerator* mir, MIRGraph& graph);
|
||||
|
||||
} // namespace jit
|
||||
} // namespace js
|
||||
|
||||
#endif /* jit_wasmbce_h */
|
|
@ -1207,6 +1207,7 @@ class Assembler : public AssemblerShared
|
|||
LessThanOrEqual = LE,
|
||||
Overflow = VS,
|
||||
CarrySet = CS,
|
||||
CarryClear = CC,
|
||||
Signed = MI,
|
||||
NotSigned = PL,
|
||||
Zero = EQ,
|
||||
|
|
|
@ -2199,22 +2199,46 @@ CodeGeneratorARM::visitWasmBoundsCheck(LWasmBoundsCheck* ins)
|
|||
return;
|
||||
}
|
||||
|
||||
// No guarantee that heapBase + endOffset can be properly encoded in
|
||||
// the cmp immediate in ma_BoundsCheck, so use an explicit add instead.
|
||||
uint32_t endOffset = mir->endOffset();
|
||||
if (!mir->isRedundant()) {
|
||||
// No guarantee that heapBase + endOffset can be properly encoded in
|
||||
// the cmp immediate in ma_BoundsCheck, so use an explicit add instead.
|
||||
uint32_t endOffset = mir->endOffset();
|
||||
|
||||
Register ptr = ToRegister(ins->ptr());
|
||||
Register ptr = ToRegister(ins->ptr());
|
||||
|
||||
ScratchRegisterScope ptrPlusOffset(masm);
|
||||
masm.move32(Imm32(endOffset), ptrPlusOffset);
|
||||
masm.ma_add(ptr, ptrPlusOffset, SetCC);
|
||||
ScratchRegisterScope ptrPlusOffset(masm);
|
||||
masm.move32(Imm32(endOffset), ptrPlusOffset);
|
||||
masm.ma_add(ptr, ptrPlusOffset, SetCC);
|
||||
|
||||
// Detect unsigned overflow by checking the carry bit.
|
||||
masm.as_b(wasm::JumpTarget::OutOfBounds, Assembler::CarrySet);
|
||||
// Detect unsigned overflow by checking the carry bit.
|
||||
masm.as_b(wasm::JumpTarget::OutOfBounds, Assembler::CarrySet);
|
||||
|
||||
uint32_t cmpOffset = masm.ma_BoundsCheck(ptrPlusOffset).getOffset();
|
||||
masm.append(wasm::BoundsCheck(cmpOffset));
|
||||
masm.as_b(wasm::JumpTarget::OutOfBounds, Assembler::Above);
|
||||
uint32_t cmpOffset = masm.ma_BoundsCheck(ptrPlusOffset).getOffset();
|
||||
masm.append(wasm::BoundsCheck(cmpOffset));
|
||||
masm.as_b(wasm::JumpTarget::OutOfBounds, Assembler::Above);
|
||||
} else {
|
||||
#ifdef DEBUG
|
||||
Label ok1, ok2;
|
||||
uint32_t endOffset = mir->endOffset();
|
||||
|
||||
Register ptr = ToRegister(ins->ptr());
|
||||
|
||||
ScratchRegisterScope ptrPlusOffset(masm);
|
||||
masm.move32(Imm32(endOffset), ptrPlusOffset);
|
||||
masm.ma_add(ptr, ptrPlusOffset, SetCC);
|
||||
|
||||
// Detect unsigned overflow by checking the carry bit.
|
||||
masm.as_b(&ok1, Assembler::CarryClear);
|
||||
masm.assumeUnreachable("Redundant bounds check failed!");
|
||||
masm.bind(&ok1);
|
||||
|
||||
uint32_t cmpOffset = masm.ma_BoundsCheck(ptrPlusOffset).getOffset();
|
||||
masm.append(wasm::BoundsCheck(cmpOffset));
|
||||
masm.as_b(&ok2, Assembler::BelowOrEqual);
|
||||
masm.assumeUnreachable("Redundant bounds check failed!");
|
||||
masm.bind(&ok2);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -481,21 +481,35 @@ CodeGeneratorX86Shared::visitWasmBoundsCheck(LWasmBoundsCheck* ins)
|
|||
}
|
||||
|
||||
Register ptrReg = ToRegister(ins->ptr());
|
||||
maybeEmitWasmBoundsCheckBranch(mir, ptrReg);
|
||||
maybeEmitWasmBoundsCheckBranch(mir, ptrReg, mir->isRedundant());
|
||||
}
|
||||
|
||||
void
|
||||
CodeGeneratorX86Shared::maybeEmitWasmBoundsCheckBranch(const MWasmMemoryAccess* mir, Register ptr)
|
||||
CodeGeneratorX86Shared::maybeEmitWasmBoundsCheckBranch(const MWasmMemoryAccess* mir, Register ptr,
|
||||
bool redundant)
|
||||
{
|
||||
if (!mir->needsBoundsCheck())
|
||||
return;
|
||||
|
||||
MOZ_ASSERT(mir->endOffset() >= 1,
|
||||
"need to subtract 1 to use JAE, see also AssemblerX86Shared::UpdateBoundsCheck");
|
||||
|
||||
uint32_t cmpOffset = masm.cmp32WithPatch(ptr, Imm32(1 - mir->endOffset())).offset();
|
||||
masm.j(Assembler::AboveOrEqual, wasm::JumpTarget::OutOfBounds);
|
||||
masm.append(wasm::BoundsCheck(cmpOffset));
|
||||
/*
|
||||
* TODO: See 1287224 Unify MWasmBoundsCheck::redunant_ and needsBoundsCheck
|
||||
*/
|
||||
if (!redundant) {
|
||||
uint32_t cmpOffset = masm.cmp32WithPatch(ptr, Imm32(1 - mir->endOffset())).offset();
|
||||
masm.j(Assembler::AboveOrEqual, wasm::JumpTarget::OutOfBounds);
|
||||
masm.append(wasm::BoundsCheck(cmpOffset));
|
||||
} else {
|
||||
#ifdef DEBUG
|
||||
Label ok;
|
||||
uint32_t cmpOffset = masm.cmp32WithPatch(ptr, Imm32(1 - mir->endOffset())).offset();
|
||||
masm.j(Assembler::Below, &ok);
|
||||
masm.assumeUnreachable("Redundant bounds check failed!");
|
||||
masm.bind(&ok);
|
||||
masm.append(wasm::BoundsCheck(cmpOffset));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
|
@ -98,7 +98,8 @@ class CodeGeneratorX86Shared : public CodeGeneratorShared
|
|||
Register ptr, Label* fail);
|
||||
|
||||
protected:
|
||||
void maybeEmitWasmBoundsCheckBranch(const MWasmMemoryAccess* mir, Register ptr);
|
||||
void maybeEmitWasmBoundsCheckBranch(const MWasmMemoryAccess* mir, Register ptr,
|
||||
bool redundant = false);
|
||||
|
||||
public:
|
||||
// For SIMD and atomic loads and stores (which throw on out-of-bounds):
|
||||
|
|
|
@ -6147,6 +6147,9 @@ JS_SetGlobalJitCompilerOption(JSContext* cx, JSJitCompilerOption opt, uint32_t v
|
|||
case JSJITCOMPILER_WASM_TEST_MODE:
|
||||
jit::JitOptions.wasmTestMode = !!value;
|
||||
break;
|
||||
case JSJITCOMPILER_WASM_EXPLICIT_BOUNDS_CHECKS:
|
||||
jit::JitOptions.wasmExplicitBoundsChecks = !!value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -6174,6 +6177,8 @@ JS_GetGlobalJitCompilerOption(JSContext* cx, JSJitCompilerOption opt)
|
|||
return rt->canUseOffthreadIonCompilation();
|
||||
case JSJITCOMPILER_WASM_TEST_MODE:
|
||||
return jit::JitOptions.wasmTestMode ? 1 : 0;
|
||||
case JSJITCOMPILER_WASM_EXPLICIT_BOUNDS_CHECKS:
|
||||
return jit::JitOptions.wasmExplicitBoundsChecks ? 1 : 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -5556,7 +5556,8 @@ JS_SetOffthreadIonCompilationEnabled(JSContext* cx, bool enabled);
|
|||
Register(BASELINE_ENABLE, "baseline.enable") \
|
||||
Register(OFFTHREAD_COMPILATION_ENABLE, "offthread-compilation.enable") \
|
||||
Register(JUMP_THRESHOLD, "jump-threshold") \
|
||||
Register(WASM_TEST_MODE, "wasm.test-mode")
|
||||
Register(WASM_TEST_MODE, "wasm.test-mode") \
|
||||
Register(WASM_EXPLICIT_BOUNDS_CHECKS, "wasm.explicit-bounds-checks")
|
||||
|
||||
typedef enum JSJitCompilerOption {
|
||||
#define JIT_COMPILER_DECLARE(key, str) \
|
||||
|
|
|
@ -285,6 +285,7 @@ UNIFIED_SOURCES += [
|
|||
'jit/TypePolicy.cpp',
|
||||
'jit/ValueNumbering.cpp',
|
||||
'jit/VMFunctions.cpp',
|
||||
'jit/WasmBCE.cpp',
|
||||
'jsalloc.cpp',
|
||||
'jsapi.cpp',
|
||||
'jsbool.cpp',
|
||||
|
|
Загрузка…
Ссылка в новой задаче