Bug 1282618 - Baldr: Implement a simple redundant bounds check elimination pass r=sunfish,bbouvier
authorDimo <dbounov@mozilla.com>
Wed, 20 Jul 2016 13:52:54 -0700
changeset 345981 236e2d22595cac5320a74e506b6cccd002e3d6b2
parent 345980 71c083faa77fa9d20b3c4babd33a5d8be8492484
child 345982 d2c17679b215bb340b1b8051c74526f6301c6253
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssunfish, bbouvier
bugs1282618
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1282618 - Baldr: Implement a simple redundant bounds check elimination pass r=sunfish,bbouvier
js/src/asmjs/WasmTypes.cpp
js/src/jit-test/tests/wasm/basic-bce.js
js/src/jit-test/tests/wasm/basic-memory.js
js/src/jit/Ion.cpp
js/src/jit/JitOptions.cpp
js/src/jit/JitOptions.h
js/src/jit/MIR.h
js/src/jit/WasmBCE.cpp
js/src/jit/WasmBCE.h
js/src/jit/arm/Assembler-arm.h
js/src/jit/arm/CodeGenerator-arm.cpp
js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
js/src/jit/x86-shared/CodeGenerator-x86-shared.h
js/src/jsapi.cpp
js/src/jsapi.h
js/src/moz.build
--- a/js/src/asmjs/WasmTypes.cpp
+++ b/js/src/asmjs/WasmTypes.cpp
@@ -275,17 +275,18 @@ wasm::AddressOf(SymbolicAddress imm, Exc
 
 SignalUsage::SignalUsage()
   :
 #ifdef ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB
     // Signal-handling is only used to eliminate bounds checks when the OS page
     // 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
     forInterrupt(HaveSignalHandlers())
 {}
 
 bool
 SignalUsage::operator==(SignalUsage rhs) const
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/basic-bce.js
@@ -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?
+  }
+}
--- a/js/src/jit-test/tests/wasm/basic-memory.js
+++ b/js/src/jit-test/tests/wasm/basic-memory.js
@@ -99,234 +99,240 @@ function testStore(type, ext, base, offs
 }
 
 function testStoreOOB(type, ext, base, offset, align, value) {
     if (type === 'i64')
         value = createI64(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);
-
-    // Accesses of 2 bytes.
-    lastValidIndex = 0x10000 - 2 - offset;
+  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);
 
-    testLoad('i32', '16_s', lastValidIndex, offset, align, 0);
-    testLoadOOB('i32', '16_s', lastValidIndex + 1, offset, align);
+  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_u', lastValidIndex, offset, align, 0);
-    testLoadOOB('i32', '16_u', lastValidIndex + 1, offset, align);
-
-    testStore('i32', '16', lastValidIndex, offset, align, -32768);
-    testStoreOOB('i32', '16', lastValidIndex + 1, offset, align, -32768);
+  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);
 
-    // Accesses of 4 bytes.
-    lastValidIndex = 0x10000 - 4 - offset;
+  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/);
+
+  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', '8', lastValidIndex, offset, align, -42);
+      testStoreOOB('i32', '8', lastValidIndex + 1, offset, align, -42);
 
-    testStore('i32', '', lastValidIndex, offset, align, 1337);
-    testStoreOOB('i32', '', lastValidIndex + 1, offset, align, 1337);
+      // Accesses of 2 bytes.
+      lastValidIndex = 0x10000 - 2 - offset;
+
+      testLoad('i32', '16_s', lastValidIndex, offset, align, 0);
+      testLoadOOB('i32', '16_s', lastValidIndex + 1, offset, align);
 
-    testStore('f32', '', lastValidIndex, offset, align, Math.fround(13.37));
-    testStoreOOB('f32', '', lastValidIndex + 1, offset, align, Math.fround(13.37));
+      testLoad('i32', '16_u', lastValidIndex, offset, align, 0);
+      testLoadOOB('i32', '16_u', lastValidIndex + 1, offset, align);
+
+      testStore('i32', '16', lastValidIndex, offset, align, -32768);
+      testStoreOOB('i32', '16', lastValidIndex + 1, offset, align, -32768);
 
-    // Accesses of 8 bytes.
-    lastValidIndex = 0x10000 - 8 - offset;
+      // Accesses of 4 bytes.
+      lastValidIndex = 0x10000 - 4 - offset;
+
+      testLoad('i32', '', lastValidIndex, offset, align, 0);
+      testLoadOOB('i32', '', lastValidIndex + 1, offset, align);
 
-    testLoad('f64', '', lastValidIndex, offset, align, 0);
-    testLoadOOB('f64', '', lastValidIndex + 1, offset, align);
+      testLoad('f32', '', lastValidIndex, offset, align, 0);
+      testLoadOOB('f32', '', lastValidIndex + 1, offset, align);
 
-    testStore('f64', '', lastValidIndex, offset, align, 1.23456789);
-    testStoreOOB('f64', '', lastValidIndex + 1, offset, align, 1.23456789);
-}
+      testStore('i32', '', lastValidIndex, offset, align, 1337);
+      testStoreOOB('i32', '', lastValidIndex + 1, offset, align, 1337);
+
+      testStore('f32', '', lastValidIndex, offset, align, Math.fround(13.37));
+      testStoreOOB('f32', '', lastValidIndex + 1, offset, align, Math.fround(13.37));
 
-// 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 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);
+  }
 
-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"));
+  // 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 (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"));
+  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"));
 
-wasmEvalText('(module (memory 0 65535))')
-assertErrorMessage(() => wasmEvalText('(module (memory 0 65536))'), TypeError, /maximum memory size too big/);
+  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")
-       )
-       (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
+  // 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
-            (get_local 2)
-            (get_local 3)
-           )
-           (i32.add
-            (get_local 4)
-            (i32.reinterpret/f32 (get_local 5))
+            (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();
+         ) (export "" 0))`
+      )(1), 50464523);
+  }
 
-if (hasI64()) {
-    setJitCompilerOption('wasm.test-mode', 1);
+  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', '', 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');
+      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);
+      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;
+      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_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);
+          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);
+          testStore('i64', '8', lastValidIndex, offset, align, -42);
+          testStoreOOB('i64', '8', lastValidIndex + 1, offset, align, -42);
 
-        // Accesses of 2 bytes.
-        lastValidIndex = 0x10000 - 2 - offset;
+          // 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_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);
+          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);
+          testStore('i64', '16', lastValidIndex, offset, align, -32768);
+          testStoreOOB('i64', '16', lastValidIndex + 1, offset, align, -32768);
 
-        // Accesses of 4 bytes.
-        lastValidIndex = 0x10000 - 4 - offset;
+          // 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_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);
+          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);
+          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;
+          // Accesses of 8 bytes.
+          lastValidIndex = 0x10000 - 8 - offset;
 
-        testLoad('i64', '', lastValidIndex, offset, align, 0);
-        testLoadOOB('i64', '', lastValidIndex + 1, offset, align);
+          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');
-    }
+          testStore('i64', '', lastValidIndex, offset, align, '0x1234567887654321');
+          testStoreOOB('i64', '', lastValidIndex + 1, offset, align, '0x1234567887654321');
+      }
 
-    setJitCompilerOption('wasm.test-mode', 0);
+      setJitCompilerOption('wasm.test-mode', 0);
+  }
 }
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -39,16 +39,17 @@
 #include "jit/LoopUnroller.h"
 #include "jit/Lowering.h"
 #include "jit/PerfSpewer.h"
 #include "jit/RangeAnalysis.h"
 #include "jit/ScalarReplacement.h"
 #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"
 
 #include "jscompartmentinlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
@@ -1890,16 +1891,23 @@ OptimizeMIR(MIRGenerator* mir)
     if (!mir->compilingAsmJS()) {
         AutoTraceLog log(logger, TraceLogger_AddKeepAliveInstructions);
         if (!AddKeepAliveInstructions(graph))
             return false;
         gs.spewPass("Add KeepAlive Instructions");
         AssertGraphCoherency(graph);
     }
 
+    if (mir->compilingAsmJS()) {
+        if (!EliminateBoundsChecks(mir, graph))
+            return false;
+        gs.spewPass("Redundant Bounds Check Elimination");
+        AssertGraphCoherency(graph);
+    }
+
     return true;
 }
 
 LIRGraph*
 GenerateLIR(MIRGenerator* mir)
 {
     MIRGraph& graph = mir->graph();
     GraphSpewer& gs = mir->graphSpewer();
--- a/js/src/jit/JitOptions.cpp
+++ b/js/src/jit/JitOptions.cpp
@@ -215,16 +215,20 @@ DefaultJitOptions::DefaultJitOptions()
             Warn(forcedRegisterAllocatorEnv, env);
     }
 
     // Toggles whether unboxed plain objects can be created by the VM.
     SET_DEFAULT(disableUnboxedObjects, false);
 
     // 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
 DefaultJitOptions::isSmallFunction(JSScript* script) const
 {
     return script->length() <= smallFunctionMaxBytecodeLength_;
 }
 
--- a/js/src/jit/JitOptions.h
+++ b/js/src/jit/JitOptions.h
@@ -64,16 +64,17 @@ struct DefaultJitOptions
     bool disableSharedStubs;
     bool disableSincos;
     bool disableSink;
     bool eagerCompilation;
     bool forceInlineCaches;
     bool limitScriptSize;
     bool osr;
     bool wasmTestMode;
+    bool wasmExplicitBoundsChecks;
     uint32_t baselineWarmUpThreshold;
     uint32_t exceptionBailoutThreshold;
     uint32_t frequentBailoutThreshold;
     uint32_t maxStackArgs;
     uint32_t osrPcMismatchesBeforeRecompile;
     uint32_t smallFunctionMaxBytecodeLength_;
     uint32_t jumpThreshold;
     uint32_t branchPruningHitCountFactor;
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -13052,19 +13052,22 @@ class MWasmMemoryAccess
     void setOffset(uint32_t o) { offset_ = o; }
 };
 
 class MWasmBoundsCheck
   : public MUnaryInstruction,
     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.
     }
 
   public:
     INSTRUCTION_HEADER(WasmBoundsCheck)
     TRIVIAL_NEW_WRAPPERS
@@ -13076,16 +13079,24 @@ class MWasmBoundsCheck
         return accessType() == other->accessType() &&
                offset() == other->offset() &&
                align() == other->align();
     }
 
     AliasSet getAliasSet() const override {
         return AliasSet::None();
     }
+
+    bool isRedundant() const {
+        return redundant_;
+    }
+
+    void setRedundant(bool val) {
+        redundant_ = val;
+    }
 };
 
 class MWasmLoad
   : public MUnaryInstruction,
     public MWasmMemoryAccess,
     public NoTypePolicy::Data
 {
     MWasmLoad(MDefinition* base, const MWasmMemoryAccess& access, bool isInt64)
new file mode 100644
--- /dev/null
+++ b/js/src/jit/WasmBCE.cpp
@@ -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;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit/WasmBCE.h
@@ -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 */
--- a/js/src/jit/arm/Assembler-arm.h
+++ b/js/src/jit/arm/Assembler-arm.h
@@ -1202,16 +1202,17 @@ class Assembler : public AssemblerShared
         Below = CC,
         BelowOrEqual = LS,
         GreaterThan = GT,
         GreaterThanOrEqual = GE,
         LessThan = LT,
         LessThanOrEqual = LE,
         Overflow = VS,
         CarrySet = CS,
+        CarryClear = CC,
         Signed = MI,
         NotSigned = PL,
         Zero = EQ,
         NonZero = NE,
         Always  = AL,
 
         VFP_NotEqualOrUnordered = NE,
         VFP_Equal = EQ,
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -2194,32 +2194,56 @@ CodeGeneratorARM::visitWasmBoundsCheck(L
     MWasmBoundsCheck* mir = ins->mir();
 
     uint32_t offset = mir->offset();
     if (offset > INT32_MAX) {
         masm.as_b(wasm::JumpTarget::OutOfBounds);
         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();
-
-    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(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);
+    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());
+
+        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);
+
+        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
 CodeGeneratorARM::visitWasmLoad(LWasmLoad* lir)
 {
     const MWasmLoad* mir = lir->mir();
 
     MOZ_ASSERT(!mir->barrierBefore() && !mir->barrierAfter(), "atomics NYI");
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
@@ -476,31 +476,45 @@ CodeGeneratorX86Shared::visitWasmBoundsC
     const MWasmBoundsCheck* mir = ins->mir();
     MOZ_ASSERT(gen->needsBoundsCheckBranch(mir));
     if (mir->offset() > INT32_MAX) {
         masm.jump(wasm::JumpTarget::OutOfBounds);
         return;
     }
 
     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
 CodeGeneratorX86Shared::maybeEmitThrowingAsmJSBoundsCheck(const MWasmMemoryAccess* access,
                                                           const MInstruction* mir,
                                                           const LAllocation* ptr)
 {
     if (!gen->needsBoundsCheckBranch(access))
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.h
+++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.h
@@ -93,17 +93,18 @@ class CodeGeneratorX86Shared : public Co
         }
     };
 
   private:
     void emitAsmJSBoundsCheckBranch(const MWasmMemoryAccess* mir, const MInstruction* ins,
                                     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):
     bool maybeEmitThrowingAsmJSBoundsCheck(const MWasmMemoryAccess* mir, const MInstruction* ins,
                                            const LAllocation* ptr);
 
     // For asm.js plain and atomic loads that possibly require a bounds check:
     bool maybeEmitAsmJSLoadBoundsCheck(const MAsmJSLoadHeap* mir, LAsmJSLoadHeap* ins,
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -6142,16 +6142,19 @@ JS_SetGlobalJitCompilerOption(JSContext*
             jit::DefaultJitOptions defaultValues;
             value = defaultValues.jumpThreshold;
         }
         jit::JitOptions.jumpThreshold = value;
         break;
       case JSJITCOMPILER_WASM_TEST_MODE:
         jit::JitOptions.wasmTestMode = !!value;
         break;
+      case JSJITCOMPILER_WASM_EXPLICIT_BOUNDS_CHECKS:
+        jit::JitOptions.wasmExplicitBoundsChecks = !!value;
+        break;
       default:
         break;
     }
 }
 
 JS_PUBLIC_API(int)
 JS_GetGlobalJitCompilerOption(JSContext* cx, JSJitCompilerOption opt)
 {
@@ -6169,16 +6172,18 @@ JS_GetGlobalJitCompilerOption(JSContext*
       case JSJITCOMPILER_ION_ENABLE:
         return JS::ContextOptionsRef(cx).ion();
       case JSJITCOMPILER_BASELINE_ENABLE:
         return JS::ContextOptionsRef(cx).baseline();
       case JSJITCOMPILER_OFFTHREAD_COMPILATION_ENABLE:
         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;
     }
 #endif
     return 0;
 }
 
 /************************************************************************/
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -5551,17 +5551,18 @@ JS_SetOffthreadIonCompilationEnabled(JSC
     Register(BASELINE_WARMUP_TRIGGER, "baseline.warmup.trigger")           \
     Register(ION_WARMUP_TRIGGER, "ion.warmup.trigger")                     \
     Register(ION_GVN_ENABLE, "ion.gvn.enable")                             \
     Register(ION_FORCE_IC, "ion.forceinlineCaches")                        \
     Register(ION_ENABLE, "ion.enable")                                     \
     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) \
     JSJITCOMPILER_ ## key,
 
     JIT_COMPILER_OPTIONS(JIT_COMPILER_DECLARE)
 #undef JIT_COMPILER_DECLARE
 
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -280,16 +280,17 @@ UNIFIED_SOURCES += [
     'jit/SharedIC.cpp',
     'jit/Sink.cpp',
     'jit/Snapshots.cpp',
     'jit/StupidAllocator.cpp',
     'jit/TypedObjectPrediction.cpp',
     'jit/TypePolicy.cpp',
     'jit/ValueNumbering.cpp',
     'jit/VMFunctions.cpp',
+    'jit/WasmBCE.cpp',
     'jsalloc.cpp',
     'jsapi.cpp',
     'jsbool.cpp',
     'jscntxt.cpp',
     'jscompartment.cpp',
     'jsdate.cpp',
     'jsexn.cpp',
     'jsfriendapi.cpp',