Bug 1282618 - Baldr: Implement a simple redundant bounds check elimination pass r=sunfish,bbouvier

This commit is contained in:
Dimo 2016-07-20 13:52:54 -07:00
Родитель 5c3ccb71c2
Коммит 0e12359ca9
16 изменённых файлов: 641 добавлений и 223 удалений

Просмотреть файл

@ -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

105
js/src/jit/WasmBCE.cpp Normal file
Просмотреть файл

@ -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;
}

33
js/src/jit/WasmBCE.h Normal file
Просмотреть файл

@ -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',