WebAssembly: running out of executable memory should throw OoM
https://bugs.webkit.org/show_bug.cgi?id=171537
<rdar://problem/32963338>

Reviewed by Saam Barati.

JSTests:

* wasm.yaml:
* wasm/lowExecutableMemory/executable-memory-oom.js: Added.
(const.invoke):
(failCount.0.catch):
(failCount.0.module.undefined.catch):
* wasm/lowExecutableMemory/exports-oom.js: Added.
(const.type):
(const.params):
(const.randomProgram):
(failCount.0.catch):
(failCount.0.module.undefined.catch):
* wasm/lowExecutableMemory/imports-oom.js: Added.
(const.type):
(const.params):
(const.randomProgram):
(f.imports.push):
(failCount.0.catch):
(failCount.0.module.undefined.catch):

Source/JavaScriptCore:

Both on first compile with BBQ as well as on tier-up with OMG,
running out of X memory shouldn't cause the entire program to
terminate. An exception will do when compiling initial code (since
we don't have any other fallback at the moment), and refusal to
tier up will do as well (it'll just be slower).

This is useful because programs which generate huge amounts of
code simply look like crashes, which developers report to
us. Getting a JavaScript exception instead is much clearer.

* jit/ExecutableAllocator.cpp:
(JSC::ExecutableAllocator::allocate):
* llint/LLIntSlowPaths.cpp:
(JSC::LLInt::shouldJIT):
* runtime/Options.h:
* wasm/WasmBBQPlan.cpp:
(JSC::Wasm::BBQPlan::prepare):
(JSC::Wasm::BBQPlan::complete):
* wasm/WasmBinding.cpp:
(JSC::Wasm::wasmToJs):
(JSC::Wasm::wasmToWasm):
* wasm/WasmBinding.h:
* wasm/WasmOMGPlan.cpp:
(JSC::Wasm::OMGPlan::work):
* wasm/js/JSWebAssemblyCodeBlock.cpp:
(JSC::JSWebAssemblyCodeBlock::JSWebAssemblyCodeBlock):
* wasm/js/JSWebAssemblyCodeBlock.h:
* wasm/js/JSWebAssemblyInstance.cpp:
(JSC::JSWebAssemblyInstance::finalizeCreation):

Tools:

* Scripts/run-jsc-stress-tests: add a configuration which runs the
tests under limited executable memory and avoids non-WebAssembly
code generation so that we more reliably run out of executable
memory in WebAssembly.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@218868 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/JSTests/ChangeLog b/JSTests/ChangeLog
index c598175..7eda751 100644
--- a/JSTests/ChangeLog
+++ b/JSTests/ChangeLog
@@ -1,3 +1,30 @@
+2017-06-27  JF Bastien  <jfbastien@apple.com>
+
+        WebAssembly: running out of executable memory should throw OoM
+        https://bugs.webkit.org/show_bug.cgi?id=171537
+        <rdar://problem/32963338>
+
+        Reviewed by Saam Barati.
+
+        * wasm.yaml:
+        * wasm/lowExecutableMemory/executable-memory-oom.js: Added.
+        (const.invoke):
+        (failCount.0.catch):
+        (failCount.0.module.undefined.catch):
+        * wasm/lowExecutableMemory/exports-oom.js: Added.
+        (const.type):
+        (const.params):
+        (const.randomProgram):
+        (failCount.0.catch):
+        (failCount.0.module.undefined.catch):
+        * wasm/lowExecutableMemory/imports-oom.js: Added.
+        (const.type):
+        (const.params):
+        (const.randomProgram):
+        (f.imports.push):
+        (failCount.0.catch):
+        (failCount.0.module.undefined.catch):
+
 2017-06-27  Caio Lima  <ticaiolima@gmail.com>
 
         [ESnext] Implement Object Rest - Implementing Object Rest Destructuring
diff --git a/JSTests/wasm.yaml b/JSTests/wasm.yaml
index d22d9b3..f5a3a05 100644
--- a/JSTests/wasm.yaml
+++ b/JSTests/wasm.yaml
@@ -33,6 +33,8 @@
   cmd: runWebAssembly unless parseRunCommands
 - path: wasm/stress
   cmd: runWebAssembly unless parseRunCommands
+- path: wasm/lowExecutableMemory
+  cmd: runWebAssemblyLowExecutableMemory unless parseRunCommands
 
 - path: wasm/spec-tests/address.wast.js
   cmd: runWebAssemblySpecTest :normal
diff --git a/JSTests/wasm/lowExecutableMemory/executable-memory-oom.js b/JSTests/wasm/lowExecutableMemory/executable-memory-oom.js
new file mode 100644
index 0000000..e00aada
--- /dev/null
+++ b/JSTests/wasm/lowExecutableMemory/executable-memory-oom.js
@@ -0,0 +1,121 @@
+import * as assert from '../assert.js'
+import Builder from '../Builder.js'
+
+const verbose = false;
+const maxInstructionCount = 500;
+const instancesTotal = 8;
+const invocationsTotal = 8;
+const tierUpCalls = 20000; // Call enough to trigger tier up and get it to compile.
+
+// This test starts running with a few bytes of executable memory available. Try
+// to create and instantiate a module which will fail to fit.
+
+const randomProgram = instructionCount => {
+    let b = new Builder()
+        .Type().End()
+        .Function().End()
+        .Export()
+            .Function("foo")
+            .Function("bar")
+        .End()
+        .Code()
+            .Function("foo", { params: [], ret: "f32" })
+                .F32Const(2.0)
+                .Return()
+            .End()
+            .Function("bar", { params: ["f32", "f32"], ret: "f32" })
+              .GetLocal(0);
+
+    // Insert a bunch of dependent instructions in a single basic block so that
+    // our compiler won't be able to strength-reduce.
+    const actions = [
+        b => b.GetLocal(0).F32Sub(),
+        b => b.GetLocal(1).F32Sub(),
+        b => b.GetLocal(0).F32Add(),
+        b => b.GetLocal(1).F32Add(),
+        b => b.GetLocal(0).F32Mul(),
+        b => b.GetLocal(1).F32Mul(),
+    ];
+
+    while (--instructionCount)
+        b = actions[(Math.random() * actions.length) | 0](b);
+
+    b = b.Return().End().End();
+
+    return b.WebAssembly().get();
+}
+
+let failCount = 0;
+let callCount = 0;
+let instances = [];
+
+const invoke = (instance, count) => {
+    if (verbose)
+        print(`Invoking`);
+    for (let i = 0; i < count; ++i)
+        assert.eq(instance.exports["foo"](), 2.0);
+    for (let i = 0; i < count; ++i)
+        instance.exports["bar"](2.0, 6.0);
+    ++callCount;
+};
+
+while (failCount === 0) {
+    const instructionCount = (Math.random() * maxInstructionCount + 1) | 0;
+
+    if (verbose)
+        print(`Trying module with ${instructionCount} instructions.`);
+
+    const buf = randomProgram(instructionCount);
+    let module;
+
+    try {
+        module = new WebAssembly.Module(buf);
+    } catch (e) {
+        if (e instanceof WebAssembly.CompileError) {
+            if (verbose)
+                print(`Caught: ${e}`);
+            ++failCount;
+        }
+        else
+            throw new Error(`Expected a WebAssembly.CompileError, got ${e}`);
+    }
+
+    if (module !== undefined) {
+        if (verbose)
+            print(`Creating instance`);
+
+        let instance;
+        try {
+            instance = new WebAssembly.Instance(module);
+        } catch (e) {
+            if (e instanceof WebAssembly.LinkError) {
+                if (verbose)
+                    print(`Caught: ${e}`);
+                ++failCount;
+            }
+            else
+                throw new Error(`Expected a WebAssembly.LinkError, got ${e}`);
+        }
+
+        if (instance !== undefined) {
+            instances.push(instance);
+            invoke(instance, 1);
+        }
+    }
+}
+
+if (callCount === 0)
+    throw new Error(`Expected to be able to allocate a WebAssembly module, instantiate it, and call its exports at least once`);
+
+// Make sure we can still call all the instances we create, even after going
+// OOM. This will try to force tier-up as well, which should fail.
+
+if (verbose)
+    print(`Invoking all previously created instances`);
+
+for (let instance of instances)
+    invoke(instance, tierUpCalls);
+
+// Do it twice to revisit what should have gotten tiered up.
+for (let instance of instances)
+    invoke(instance, 1);
diff --git a/JSTests/wasm/lowExecutableMemory/exports-oom.js b/JSTests/wasm/lowExecutableMemory/exports-oom.js
new file mode 100644
index 0000000..0c3270a
--- /dev/null
+++ b/JSTests/wasm/lowExecutableMemory/exports-oom.js
@@ -0,0 +1,107 @@
+import * as assert from '../assert.js'
+import Builder from '../Builder.js'
+
+const verbose = false;
+const numFunctions = 2;
+const maxParams = 128;
+
+// This test starts running with a few bytes of executable memory available. Try
+// to create and instantiate modules which have way more exports than anything
+// else. Hopefully they'll fail when trying to instantiate their entrypoints.
+
+const type = () => {
+    const types = ["i32", "f32", "f64"]; // Can't export i64.
+    return types[(Math.random() * types.length) | 0];
+};
+
+const params = () => {
+    let p = [];
+    let count = (Math.random() * maxParams) | 0;
+    while (count--)
+        p.push(type());
+    return p;
+};
+
+const randomProgram = () => {
+    let b = new Builder()
+        .Type().End()
+        .Function().End()
+        .Export();
+    for (let f = 0; f < numFunctions; ++f)
+        b = b.Function(`f${f}`);
+    b = b.End().Code();
+    for (let f = 0; f < numFunctions; ++f)
+        b = b.Function(`f${f}`, { params: params() }).Return().End();
+    b = b.End();
+    return b.WebAssembly().get();
+}
+
+let failCount = 0;
+let callCount = 0;
+let instances = [];
+
+const invoke = instance => {
+    let result = 0;
+    for (let f = 0; f < numFunctions; ++f) {
+        const name = `f${f}`;
+        if (verbose)
+            print(`Invoking ${name}`);
+        result += instance.exports[name]();
+        ++callCount;
+    }
+    return result;
+};
+
+while (failCount === 0) {
+    if (verbose)
+        print(`Trying...`);
+
+    const buf = randomProgram();
+    let module;
+
+    try {
+        module = new WebAssembly.Module(buf);
+    } catch (e) {
+        if (e instanceof WebAssembly.CompileError) {
+            if (verbose)
+                print(`Caught: ${e}`);
+            ++failCount;
+        }
+        else
+            throw new Error(`Expected a WebAssembly.CompileError, got ${e}`);
+    }
+
+    if (module !== undefined) {
+        if (verbose)
+            print(`Creating instance`);
+
+        let instance;
+        try {
+            instance = new WebAssembly.Instance(module);
+        } catch (e) {
+            if (e instanceof WebAssembly.LinkError) {
+                if (verbose)
+                    print(`Caught: ${e}`);
+                ++failCount;
+            }
+            else
+                throw new Error(`Expected a WebAssembly.LinkError, got ${e}`);
+        }
+
+        if (instance !== undefined) {
+            instances.push(instance);
+            invoke(instance);
+        }
+    }
+}
+
+if (callCount === 0)
+    throw new Error(`Expected to be able to allocate a WebAssembly module, instantiate it, and call its exports at least once`);
+
+// Make sure we can still call all the instances we create, even after going OOM.
+
+if (verbose)
+    print(`Invoking all previously created instances`);
+
+for (let instance of instances)
+    invoke(instance);
diff --git a/JSTests/wasm/lowExecutableMemory/imports-oom.js b/JSTests/wasm/lowExecutableMemory/imports-oom.js
new file mode 100644
index 0000000..44cd973
--- /dev/null
+++ b/JSTests/wasm/lowExecutableMemory/imports-oom.js
@@ -0,0 +1,124 @@
+import * as assert from '../assert.js'
+import Builder from '../Builder.js'
+
+const verbose = false;
+const numFunctions = 2;
+const maxParams = 32;
+
+// This test starts running with a few bytes of executable memory available. Try
+// to create and instantiate modules which have way more imports than anything
+// else. Hopefully they'll fail when trying to instantiate their entrypoints.
+
+const type = () => {
+    const types = ["i32", "f32", "f64"];
+    return types[(Math.random() * types.length) | 0];
+};
+
+const params = () => {
+    let p = [];
+    let count = (Math.random() * maxParams) | 0;
+    while (count--)
+        p.push(type());
+    return p;
+};
+
+const randomProgram = () => {
+    let b = new Builder()
+        .Type().End()
+        .Import();
+    const ps = params();
+    for (let f = 0; f < numFunctions; ++f)
+        b = b.Function("imp", `${f}`, { params: ps });
+    b = b.End()
+        .Function().End()
+        .Export();
+    for (let f = 0; f < numFunctions; ++f)
+        b = b.Function(`call${f}`);
+    b = b.End()
+        .Code();
+    for (let f = 0; f < numFunctions; ++f) {
+        b = b.Function(`call${f}`, { params: ps });
+        for (let p = 0; p < ps.length; ++p)
+            b = b.GetLocal(p);
+        b = b.Call(f).End();
+    }
+    b = b.End();
+    return b.WebAssembly().get();
+}
+
+let failCount = 0;
+let callCount = 0;
+let instances = [];
+
+let imports = [];
+for (let f = 0; f < numFunctions; ++f)
+    imports.push((...args) => {
+        if (verbose)
+            print(`Invoked ${f} with: ${args}`);
+        ++callCount;
+    });
+
+const invoke = instance => {
+    let result = 0;
+    for (let f = 0; f < numFunctions; ++f) {
+        const name = `call${f}`;
+        if (verbose)
+            print(`Invoking ${name}`);
+        result += instance.exports[name]();
+    }
+    return result;
+};
+
+while (failCount === 0) {
+    if (verbose)
+        print(`Trying...`);
+
+    const buf = randomProgram();
+    let module;
+
+    try {
+        module = new WebAssembly.Module(buf);
+    } catch (e) {
+        if (e instanceof WebAssembly.CompileError) {
+            if (verbose)
+                print(`Caught: ${e}`);
+            ++failCount;
+        }
+        else
+            throw new Error(`Expected a WebAssembly.CompileError, got ${e}`);
+    }
+
+    if (module !== undefined) {
+        if (verbose)
+            print(`Creating instance`);
+
+        let instance;
+        try {
+            instance = new WebAssembly.Instance(module, { imp: imports });
+        } catch (e) {
+            if (e instanceof WebAssembly.LinkError) {
+                if (verbose)
+                    print(`Caught: ${e}`);
+                ++failCount;
+            }
+            else
+                throw new Error(`Expected a WebAssembly.LinkError, got ${e}`);
+        }
+
+        if (instance !== undefined) {
+            instances.push(instance);
+            invoke(instance);
+        }
+    }
+}
+
+if (callCount === 0)
+    throw new Error(`Expected to be able to allocate a WebAssembly module, instantiate it, and call its exports at least once`);
+
+// Make sure we can still call all the instances we create, even after going OOM.
+
+if (verbose)
+    print(`Invoking all previously created instances`);
+
+for (let instance of instances)
+    invoke(instance);
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index bbaea17..b12f1fb 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -1,3 +1,41 @@
+2017-06-27  JF Bastien  <jfbastien@apple.com>
+
+        WebAssembly: running out of executable memory should throw OoM
+        https://bugs.webkit.org/show_bug.cgi?id=171537
+        <rdar://problem/32963338>
+
+        Reviewed by Saam Barati.
+
+        Both on first compile with BBQ as well as on tier-up with OMG,
+        running out of X memory shouldn't cause the entire program to
+        terminate. An exception will do when compiling initial code (since
+        we don't have any other fallback at the moment), and refusal to
+        tier up will do as well (it'll just be slower).
+
+        This is useful because programs which generate huge amounts of
+        code simply look like crashes, which developers report to
+        us. Getting a JavaScript exception instead is much clearer.
+
+        * jit/ExecutableAllocator.cpp:
+        (JSC::ExecutableAllocator::allocate):
+        * llint/LLIntSlowPaths.cpp:
+        (JSC::LLInt::shouldJIT):
+        * runtime/Options.h:
+        * wasm/WasmBBQPlan.cpp:
+        (JSC::Wasm::BBQPlan::prepare):
+        (JSC::Wasm::BBQPlan::complete):
+        * wasm/WasmBinding.cpp:
+        (JSC::Wasm::wasmToJs):
+        (JSC::Wasm::wasmToWasm):
+        * wasm/WasmBinding.h:
+        * wasm/WasmOMGPlan.cpp:
+        (JSC::Wasm::OMGPlan::work):
+        * wasm/js/JSWebAssemblyCodeBlock.cpp:
+        (JSC::JSWebAssemblyCodeBlock::JSWebAssemblyCodeBlock):
+        * wasm/js/JSWebAssemblyCodeBlock.h:
+        * wasm/js/JSWebAssemblyInstance.cpp:
+        (JSC::JSWebAssemblyInstance::finalizeCreation):
+
 2017-06-27  Saam Barati  <sbarati@apple.com>
 
         JITStubRoutine::passesFilter should use isJITPC
diff --git a/Source/JavaScriptCore/jit/ExecutableAllocator.cpp b/Source/JavaScriptCore/jit/ExecutableAllocator.cpp
index b4a1e87..3e7b66a 100644
--- a/Source/JavaScriptCore/jit/ExecutableAllocator.cpp
+++ b/Source/JavaScriptCore/jit/ExecutableAllocator.cpp
@@ -412,8 +412,11 @@
         size_t bytesAllocated = statistics.bytesAllocated + sizeInBytes;
         size_t bytesAvailable = static_cast<size_t>(
             statistics.bytesReserved * (1 - executablePoolReservationFraction));
-        if (bytesAllocated > bytesAvailable)
+        if (bytesAllocated > bytesAvailable) {
+            if (Options::logExecutableAllocation())
+                dataLog("Allocation failed because bytes allocated ", bytesAllocated,  " > ", bytesAvailable, " bytes available.\n");
             return nullptr;
+        }
     }
     
     RefPtr<ExecutableMemoryHandle> result = allocator->allocate(sizeInBytes, ownerUID);
diff --git a/Source/JavaScriptCore/llint/LLIntSlowPaths.cpp b/Source/JavaScriptCore/llint/LLIntSlowPaths.cpp
index c613605..ee4929c 100644
--- a/Source/JavaScriptCore/llint/LLIntSlowPaths.cpp
+++ b/Source/JavaScriptCore/llint/LLIntSlowPaths.cpp
@@ -318,8 +318,7 @@
         || !ensureGlobalJITWhitelist().contains(codeBlock))
         return false;
 
-    // You can modify this to turn off JITting without rebuilding the world.
-    return exec->vm().canUseJIT();
+    return exec->vm().canUseJIT() && Options::useBaselineJIT();
 }
 
 // Returns true if we should try to OSR.
diff --git a/Source/JavaScriptCore/runtime/Options.h b/Source/JavaScriptCore/runtime/Options.h
index f72f109..70d7e1a 100644
--- a/Source/JavaScriptCore/runtime/Options.h
+++ b/Source/JavaScriptCore/runtime/Options.h
@@ -112,7 +112,8 @@
     v(optionString, configFile, nullptr, Normal, "file to configure JSC options and logging location") \
     \
     v(bool, useLLInt,  true, Normal, "allows the LLINT to be used if true") \
-    v(bool, useJIT,    true, Normal, "allows the baseline JIT to be used if true") \
+    v(bool, useJIT,    true, Normal, "allows the executable pages to be allocated for JIT and thunks if true") \
+    v(bool, useBaselineJIT, true, Normal, "allows the baseline JIT to be used if true") \
     v(bool, useDFGJIT, true, Normal, "allows the DFG JIT to be used if true") \
     v(bool, useRegExpJIT, true, Normal, "allows the RegExp JIT to be used if true") \
     v(bool, useDOMJIT, true, Normal, "allows the DOMJIT to be used if true") \
diff --git a/Source/JavaScriptCore/wasm/WasmBBQPlan.cpp b/Source/JavaScriptCore/wasm/WasmBBQPlan.cpp
index b3b3198..50feb73 100644
--- a/Source/JavaScriptCore/wasm/WasmBBQPlan.cpp
+++ b/Source/JavaScriptCore/wasm/WasmBBQPlan.cpp
@@ -175,7 +175,15 @@
             continue;
         unsigned importFunctionIndex = m_wasmToWasmExitStubs.size();
         dataLogLnIf(verbose, "Processing import function number ", importFunctionIndex, ": ", makeString(import->module), ": ", makeString(import->field));
-        m_wasmToWasmExitStubs.uncheckedAppend(wasmToWasm(importFunctionIndex));
+        auto binding = wasmToWasm(importFunctionIndex);
+        if (UNLIKELY(!binding)) {
+            switch (binding.error()) {
+            case BindingFailure::OutOfMemory:
+                return fail(holdLock(m_lock), makeString("Out of executable memory at import ", String::number(importIndex)));
+            }
+            RELEASE_ASSERT_NOT_REACHED();
+        }
+        m_wasmToWasmExitStubs.uncheckedAppend(binding.value());
     }
 
     const uint32_t importFunctionCount = m_moduleInformation->importFunctionCount();
@@ -288,19 +296,29 @@
     ASSERT(m_state != State::Compiled || m_currentIndex >= m_moduleInformation->functionLocationInBinary.size());
     dataLogLnIf(verbose, "Starting Completion");
 
-    if (m_state == State::Compiled) {
+    if (!failed() && m_state == State::Compiled) {
         for (uint32_t functionIndex = 0; functionIndex < m_moduleInformation->functionLocationInBinary.size(); functionIndex++) {
             CompilationContext& context = m_compilationContexts[functionIndex];
             SignatureIndex signatureIndex = m_moduleInformation->internalFunctionSignatureIndices[functionIndex];
             {
-                LinkBuffer linkBuffer(*context.wasmEntrypointJIT, nullptr);
+                LinkBuffer linkBuffer(*context.wasmEntrypointJIT, nullptr, JITCompilationCanFail);
+                if (UNLIKELY(linkBuffer.didFailToAllocate())) {
+                    Base::fail(locker, makeString("Out of executable memory in function at index ", String::number(functionIndex)));
+                    return;
+                }
+
                 m_wasmInternalFunctions[functionIndex]->entrypoint.compilation = std::make_unique<B3::Compilation>(
                     FINALIZE_CODE(linkBuffer, ("WebAssembly function[%i] %s", functionIndex, SignatureInformation::get(signatureIndex).toString().ascii().data())),
                     WTFMove(context.wasmEntrypointByproducts));
             }
 
             if (auto jsToWasmInternalFunction = m_jsToWasmInternalFunctions.get(functionIndex)) {
-                LinkBuffer linkBuffer(*context.jsEntrypointJIT, nullptr);
+                LinkBuffer linkBuffer(*context.jsEntrypointJIT, nullptr, JITCompilationCanFail);
+                if (UNLIKELY(linkBuffer.didFailToAllocate())) {
+                    Base::fail(locker, makeString("Out of executable memory in function entrypoint at index ", String::number(functionIndex)));
+                    return;
+                }
+
                 jsToWasmInternalFunction->entrypoint.compilation = std::make_unique<B3::Compilation>(
                     FINALIZE_CODE(linkBuffer, ("JavaScript->WebAssembly entrypoint[%i] %s", functionIndex, SignatureInformation::get(signatureIndex).toString().ascii().data())),
                     WTFMove(context.jsEntrypointByproducts));
diff --git a/Source/JavaScriptCore/wasm/WasmBinding.cpp b/Source/JavaScriptCore/wasm/WasmBinding.cpp
index 36d8b15..8f9aa0c 100644
--- a/Source/JavaScriptCore/wasm/WasmBinding.cpp
+++ b/Source/JavaScriptCore/wasm/WasmBinding.cpp
@@ -51,7 +51,7 @@
     jit.loadPtr(JIT::Address(result, JSWebAssemblyInstance::offsetOfImportFunction(importIndex)), result);
 }
 
-MacroAssemblerCodeRef wasmToJs(VM* vm, Bag<CallLinkInfo>& callLinkInfos, SignatureIndex signatureIndex, unsigned importIndex)
+Expected<MacroAssemblerCodeRef, BindingFailure> wasmToJs(VM* vm, Bag<CallLinkInfo>& callLinkInfos, SignatureIndex signatureIndex, unsigned importIndex)
 {
     // FIXME: This function doesn't properly abstract away the calling convention.
     // It'd be super easy to do so: https://bugs.webkit.org/show_bug.cgi?id=169401
@@ -119,7 +119,10 @@
                 ASSERT(!!vm->callFrameForCatch);
             };
 
-            LinkBuffer linkBuffer(jit, GLOBAL_THUNK_ID);
+            LinkBuffer linkBuffer(jit, GLOBAL_THUNK_ID, JITCompilationCanFail);
+            if (UNLIKELY(linkBuffer.didFailToAllocate()))
+                return makeUnexpected(BindingFailure::OutOfMemory);
+
             linkBuffer.link(call, throwBadI64);
             return FINALIZE_CODE(linkBuffer, ("WebAssembly->JavaScript invalid i64 use in import[%i]", importIndex));
         }
@@ -303,7 +306,10 @@
         jit.emitFunctionEpilogue();
         jit.ret();
 
-        LinkBuffer linkBuffer(jit, GLOBAL_THUNK_ID);
+        LinkBuffer linkBuffer(jit, GLOBAL_THUNK_ID, JITCompilationCanFail);
+        if (UNLIKELY(linkBuffer.didFailToAllocate()))
+            return makeUnexpected(BindingFailure::OutOfMemory);
+
         linkBuffer.link(call, callFunc);
         linkBuffer.link(exceptionCall, doUnwinding);
 
@@ -600,7 +606,10 @@
         });
     }
 
-    LinkBuffer patchBuffer(jit, GLOBAL_THUNK_ID);
+    LinkBuffer patchBuffer(jit, GLOBAL_THUNK_ID, JITCompilationCanFail);
+    if (UNLIKELY(patchBuffer.didFailToAllocate()))
+        return makeUnexpected(BindingFailure::OutOfMemory);
+
     patchBuffer.link(slowCall, FunctionPtr(vm->getCTIStub(linkCallThunkGenerator).code().executableAddress()));
     CodeLocationLabel callReturnLocation(patchBuffer.locationOfNearCall(slowCall));
     CodeLocationLabel hotPathBegin(patchBuffer.locationOf(targetToCheck));
@@ -610,7 +619,7 @@
     return FINALIZE_CODE(patchBuffer, ("WebAssembly->JavaScript import[%i] %s", importIndex, signature.toString().ascii().data()));
 }
 
-MacroAssemblerCodeRef wasmToWasm(unsigned importIndex)
+Expected<MacroAssemblerCodeRef, BindingFailure> wasmToWasm(unsigned importIndex)
 {
     const PinnedRegisterInfo& pinnedRegs = PinnedRegisterInfo::get();
     JIT jit;
@@ -653,7 +662,10 @@
     jit.loadPtr(scratch, scratch);
     jit.jump(scratch);
 
-    LinkBuffer patchBuffer(jit, GLOBAL_THUNK_ID);
+    LinkBuffer patchBuffer(jit, GLOBAL_THUNK_ID, JITCompilationCanFail);
+    if (UNLIKELY(patchBuffer.didFailToAllocate()))
+        return makeUnexpected(BindingFailure::OutOfMemory);
+
     return FINALIZE_CODE(patchBuffer, ("WebAssembly->WebAssembly import[%i]", importIndex));
 }
 
diff --git a/Source/JavaScriptCore/wasm/WasmBinding.h b/Source/JavaScriptCore/wasm/WasmBinding.h
index 9e29a33..4e262ba 100644
--- a/Source/JavaScriptCore/wasm/WasmBinding.h
+++ b/Source/JavaScriptCore/wasm/WasmBinding.h
@@ -31,6 +31,7 @@
 #include "VM.h"
 #include "WasmFormat.h"
 #include <wtf/Bag.h>
+#include <wtf/Expected.h>
 
 namespace JSC {
 
@@ -38,8 +39,12 @@
 
 namespace Wasm {
 
-MacroAssemblerCodeRef wasmToWasm(unsigned importIndex);
-MacroAssemblerCodeRef wasmToJs(VM*, Bag<CallLinkInfo>& callLinkInfos, SignatureIndex, unsigned importIndex);
+enum class BindingFailure {
+    OutOfMemory,
+};
+
+Expected<MacroAssemblerCodeRef, BindingFailure> wasmToWasm(unsigned importIndex);
+Expected<MacroAssemblerCodeRef, BindingFailure> wasmToJs(VM*, Bag<CallLinkInfo>& callLinkInfos, SignatureIndex, unsigned importIndex);
 
 } } // namespace JSC::Wasm
 
diff --git a/Source/JavaScriptCore/wasm/WasmOMGPlan.cpp b/Source/JavaScriptCore/wasm/WasmOMGPlan.cpp
index 9563ae2..9576c98 100644
--- a/Source/JavaScriptCore/wasm/WasmOMGPlan.cpp
+++ b/Source/JavaScriptCore/wasm/WasmOMGPlan.cpp
@@ -88,7 +88,12 @@
     }
 
     Entrypoint omgEntrypoint;
-    LinkBuffer linkBuffer(*context.wasmEntrypointJIT, nullptr);
+    LinkBuffer linkBuffer(*context.wasmEntrypointJIT, nullptr, JITCompilationCanFail);
+    if (UNLIKELY(linkBuffer.didFailToAllocate())) {
+        Base::fail(holdLock(m_lock), makeString("Out of executable memory while tiering up function at index ", String::number(m_functionIndex)));
+        return;
+    }
+
     omgEntrypoint.compilation = std::make_unique<B3::Compilation>(
         FINALIZE_CODE(linkBuffer, ("WebAssembly OMG function[%i] %s", m_functionIndex, SignatureInformation::get(signatureIndex).toString().ascii().data())),
         WTFMove(context.wasmEntrypointByproducts));
diff --git a/Source/JavaScriptCore/wasm/js/JSWebAssemblyCodeBlock.cpp b/Source/JavaScriptCore/wasm/js/JSWebAssemblyCodeBlock.cpp
index b721e0e..f91e6c7 100644
--- a/Source/JavaScriptCore/wasm/js/JSWebAssemblyCodeBlock.cpp
+++ b/Source/JavaScriptCore/wasm/js/JSWebAssemblyCodeBlock.cpp
@@ -58,7 +58,16 @@
     m_wasmToJSExitStubs.reserveCapacity(m_codeBlock->functionImportCount());
     for (unsigned importIndex = 0; importIndex < m_codeBlock->functionImportCount(); ++importIndex) {
         Wasm::SignatureIndex signatureIndex = moduleInformation.importFunctionSignatureIndices.at(importIndex);
-        m_wasmToJSExitStubs.uncheckedAppend(Wasm::wasmToJs(&vm, m_callLinkInfos, signatureIndex, importIndex));
+        auto binding = Wasm::wasmToJs(&vm, m_callLinkInfos, signatureIndex, importIndex);
+        if (UNLIKELY(!binding)) {
+            switch (binding.error()) {
+            case Wasm::BindingFailure::OutOfMemory:
+                m_errorMessage = ASCIILiteral("Out of executable memory");
+                return;
+            }
+            RELEASE_ASSERT_NOT_REACHED();
+        }
+        m_wasmToJSExitStubs.uncheckedAppend(binding.value());
         importWasmToJSStub(importIndex) = m_wasmToJSExitStubs[importIndex].code().executableAddress();
     }
 }
diff --git a/Source/JavaScriptCore/wasm/js/JSWebAssemblyCodeBlock.h b/Source/JavaScriptCore/wasm/js/JSWebAssemblyCodeBlock.h
index c1e511b..52703f8 100644
--- a/Source/JavaScriptCore/wasm/js/JSWebAssemblyCodeBlock.h
+++ b/Source/JavaScriptCore/wasm/js/JSWebAssemblyCodeBlock.h
@@ -75,15 +75,18 @@
 
     Wasm::Callee& jsEntrypointCalleeFromFunctionIndexSpace(unsigned functionIndexSpace)
     {
+        ASSERT(runnable());
         return m_codeBlock->jsEntrypointCalleeFromFunctionIndexSpace(functionIndexSpace);
     }
     Wasm::WasmEntrypointLoadLocation wasmEntrypointLoadLocationFromFunctionIndexSpace(unsigned functionIndexSpace)
     {
+        ASSERT(runnable());
         return m_codeBlock->wasmEntrypointLoadLocationFromFunctionIndexSpace(functionIndexSpace);
     }
 
     Wasm::WasmEntrypointLoadLocation wasmToJsCallStubForImport(unsigned importIndex)
     {
+        ASSERT(runnable());
         return &importWasmToJSStub(importIndex);
     }
 
@@ -96,6 +99,14 @@
 
     void clearJSCallICs(VM&);
 
+    bool runnable() const { return !m_errorMessage; }
+
+    String errorMessage()
+    {
+        ASSERT(!runnable());
+        return m_errorMessage;
+    }
+
 private:
     JSWebAssemblyCodeBlock(VM&, Ref<Wasm::CodeBlock>&&, const Wasm::ModuleInformation&);
     DECLARE_EXPORT_INFO;
@@ -127,6 +138,7 @@
     Vector<MacroAssemblerCodeRef> m_wasmToJSExitStubs;
     UnconditionalFinalizer m_unconditionalFinalizer;
     Bag<CallLinkInfo> m_callLinkInfos;
+    String m_errorMessage;
 };
 
 } // namespace JSC
diff --git a/Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.cpp b/Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.cpp
index 35a5b3c..0e87737 100644
--- a/Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.cpp
+++ b/Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.cpp
@@ -112,6 +112,10 @@
         m_codeBlock.set(vm, this, codeBlock);
     } else {
         codeBlock = JSWebAssemblyCodeBlock::create(vm, wasmCodeBlock.copyRef(), m_module.get());
+        if (UNLIKELY(!codeBlock->runnable())) {
+            throwException(exec, scope, JSWebAssemblyLinkError::create(exec, vm, globalObject()->WebAssemblyLinkErrorStructure(), codeBlock->errorMessage()));
+            return;
+        }
         m_codeBlock.set(vm, this, codeBlock);
         module()->setCodeBlock(vm, memoryMode(), codeBlock);
     }
diff --git a/Tools/ChangeLog b/Tools/ChangeLog
index 4d95cd0..be4d22f 100644
--- a/Tools/ChangeLog
+++ b/Tools/ChangeLog
@@ -1,3 +1,16 @@
+2017-06-27  JF Bastien  <jfbastien@apple.com>
+
+        WebAssembly: running out of executable memory should throw OoM
+        https://bugs.webkit.org/show_bug.cgi?id=171537
+        <rdar://problem/32963338>
+
+        Reviewed by Saam Barati.
+
+        * Scripts/run-jsc-stress-tests: add a configuration which runs the
+        tests under limited executable memory and avoids non-WebAssembly
+        code generation so that we more reliably run out of executable
+        memory in WebAssembly.
+
 2017-06-27  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         [iOS DnD] Support dragging out of contenteditable areas without a prior selection
diff --git a/Tools/Scripts/run-jsc-stress-tests b/Tools/Scripts/run-jsc-stress-tests
index c09f218..d142d3d 100755
--- a/Tools/Scripts/run-jsc-stress-tests
+++ b/Tools/Scripts/run-jsc-stress-tests
@@ -1234,7 +1234,6 @@
     when :skip
         return
     end
-
     return if !$jitTests
     return if !$isFTLPlatform
     prepareExtraAbsoluteFiles(WASMTESTS_PATH, ["wasm.json"])
@@ -1246,10 +1245,22 @@
     prepareExtraRelativeFiles(harness.map { |f| "../../spec-harness/" + f }, $collection)
 
     runWithOutputHandler("default-wasm", noisyOutputHandler, "../spec-harness.js", *FTL_OPTIONS)
-    runWithOutputHandler("wasm-no-cjit-yes-tls-context", noisyOutputHandler, "../spec-harness.js",  "--useFastTLSForWasmContext=true", *(FTL_OPTIONS + NO_CJIT_OPTIONS))
-    runWithOutputHandler("wasm-eager-jettison", noisyOutputHandler, "../spec-harness.js", "--forceCodeBlockToJettisonDueToOldAge=true", *FTL_OPTIONS)
-    runWithOutputHandler("wasm-no-call-ic", noisyOutputHandler, "../spec-harness.js", "--useCallICsForWebAssemblyToJSCalls=false", *FTL_OPTIONS)
-    runWithOutputHandler("wasm-no-tls-context", noisyOutputHandler, "../spec-harness.js", "--useFastTLSForWasmContext=false", *FTL_OPTIONS)
+    if !$quickMode
+      runWithOutputHandler("wasm-no-cjit-yes-tls-context", noisyOutputHandler, "../spec-harness.js",  "--useFastTLSForWasmContext=true", *(FTL_OPTIONS + NO_CJIT_OPTIONS))
+      runWithOutputHandler("wasm-eager-jettison", noisyOutputHandler, "../spec-harness.js", "--forceCodeBlockToJettisonDueToOldAge=true", *FTL_OPTIONS)
+      runWithOutputHandler("wasm-no-call-ic", noisyOutputHandler, "../spec-harness.js", "--useCallICsForWebAssemblyToJSCalls=false", *FTL_OPTIONS)
+      runWithOutputHandler("wasm-no-tls-context", noisyOutputHandler, "../spec-harness.js", "--useFastTLSForWasmContext=false", *FTL_OPTIONS)
+    end
+end
+
+def runWebAssemblyLowExecutableMemory(*optionalTestSpecificOptions)
+    return if !$jitTests
+    return if !$isFTLPlatform
+    modules = Dir[WASMTESTS_PATH + "*.js"].map { |f| File.basename(f) }
+    prepareExtraAbsoluteFiles(WASMTESTS_PATH, ["wasm.json"])
+    prepareExtraRelativeFiles(modules.map { |f| "../" + f }, $collection)
+    # Only let WebAssembly get executable memory.
+    run("default-wasm", "--useConcurrentGC=0" , "--useConcurrentJIT=0", "--jitMemoryReservationSize=15000", "--useBaselineJIT=0", "--useDFGJIT=0", "--useFTLJIT=0", "-m")
 end
 
 def runChakra(mode, exception, baselineFile, extraFiles)