Title: [218868] trunk
Revision
218868
Author
jfbast...@apple.com
Date
2017-06-27 23:42:13 -0700 (Tue, 27 Jun 2017)

Log Message

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/_javascript_Core:

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.

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (218867 => 218868)


--- trunk/JSTests/ChangeLog	2017-06-28 06:23:23 UTC (rev 218867)
+++ trunk/JSTests/ChangeLog	2017-06-28 06:42:13 UTC (rev 218868)
@@ -1,3 +1,30 @@
+2017-06-27  JF Bastien  <jfbast...@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  <ticaiol...@gmail.com>
 
         [ESnext] Implement Object Rest - Implementing Object Rest Destructuring

Added: trunk/JSTests/wasm/lowExecutableMemory/executable-memory-oom.js (0 => 218868)


--- trunk/JSTests/wasm/lowExecutableMemory/executable-memory-oom.js	                        (rev 0)
+++ trunk/JSTests/wasm/lowExecutableMemory/executable-memory-oom.js	2017-06-28 06:42:13 UTC (rev 218868)
@@ -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);

Added: trunk/JSTests/wasm/lowExecutableMemory/exports-oom.js (0 => 218868)


--- trunk/JSTests/wasm/lowExecutableMemory/exports-oom.js	                        (rev 0)
+++ trunk/JSTests/wasm/lowExecutableMemory/exports-oom.js	2017-06-28 06:42:13 UTC (rev 218868)
@@ -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);

Added: trunk/JSTests/wasm/lowExecutableMemory/imports-oom.js (0 => 218868)


--- trunk/JSTests/wasm/lowExecutableMemory/imports-oom.js	                        (rev 0)
+++ trunk/JSTests/wasm/lowExecutableMemory/imports-oom.js	2017-06-28 06:42:13 UTC (rev 218868)
@@ -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);

Modified: trunk/JSTests/wasm.yaml (218867 => 218868)


--- trunk/JSTests/wasm.yaml	2017-06-28 06:23:23 UTC (rev 218867)
+++ trunk/JSTests/wasm.yaml	2017-06-28 06:42:13 UTC (rev 218868)
@@ -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

Modified: trunk/Source/_javascript_Core/ChangeLog (218867 => 218868)


--- trunk/Source/_javascript_Core/ChangeLog	2017-06-28 06:23:23 UTC (rev 218867)
+++ trunk/Source/_javascript_Core/ChangeLog	2017-06-28 06:42:13 UTC (rev 218868)
@@ -1,3 +1,41 @@
+2017-06-27  JF Bastien  <jfbast...@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  <sbar...@apple.com>
 
         JITStubRoutine::passesFilter should use isJITPC

Modified: trunk/Source/_javascript_Core/jit/ExecutableAllocator.cpp (218867 => 218868)


--- trunk/Source/_javascript_Core/jit/ExecutableAllocator.cpp	2017-06-28 06:23:23 UTC (rev 218867)
+++ trunk/Source/_javascript_Core/jit/ExecutableAllocator.cpp	2017-06-28 06:42:13 UTC (rev 218868)
@@ -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);

Modified: trunk/Source/_javascript_Core/llint/LLIntSlowPaths.cpp (218867 => 218868)


--- trunk/Source/_javascript_Core/llint/LLIntSlowPaths.cpp	2017-06-28 06:23:23 UTC (rev 218867)
+++ trunk/Source/_javascript_Core/llint/LLIntSlowPaths.cpp	2017-06-28 06:42:13 UTC (rev 218868)
@@ -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.

Modified: trunk/Source/_javascript_Core/runtime/Options.h (218867 => 218868)


--- trunk/Source/_javascript_Core/runtime/Options.h	2017-06-28 06:23:23 UTC (rev 218867)
+++ trunk/Source/_javascript_Core/runtime/Options.h	2017-06-28 06:42:13 UTC (rev 218868)
@@ -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") \

Modified: trunk/Source/_javascript_Core/wasm/WasmBBQPlan.cpp (218867 => 218868)


--- trunk/Source/_javascript_Core/wasm/WasmBBQPlan.cpp	2017-06-28 06:23:23 UTC (rev 218867)
+++ trunk/Source/_javascript_Core/wasm/WasmBBQPlan.cpp	2017-06-28 06:42:13 UTC (rev 218868)
@@ -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,12 +296,17 @@
     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));
@@ -300,7 +313,12 @@
             }
 
             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));

Modified: trunk/Source/_javascript_Core/wasm/WasmBinding.cpp (218867 => 218868)


--- trunk/Source/_javascript_Core/wasm/WasmBinding.cpp	2017-06-28 06:23:23 UTC (rev 218867)
+++ trunk/Source/_javascript_Core/wasm/WasmBinding.cpp	2017-06-28 06:42:13 UTC (rev 218868)
@@ -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));
 }
 

Modified: trunk/Source/_javascript_Core/wasm/WasmBinding.h (218867 => 218868)


--- trunk/Source/_javascript_Core/wasm/WasmBinding.h	2017-06-28 06:23:23 UTC (rev 218867)
+++ trunk/Source/_javascript_Core/wasm/WasmBinding.h	2017-06-28 06:42:13 UTC (rev 218868)
@@ -31,6 +31,7 @@
 #include "VM.h"
 #include "WasmFormat.h"
 #include <wtf/Bag.h>
+#include <wtf/Expected.h>
 
 namespace JSC {
 
@@ -38,9 +39,13 @@
 
 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
 
 #endif // ENABLE(WEBASSEMBLY)

Modified: trunk/Source/_javascript_Core/wasm/WasmOMGPlan.cpp (218867 => 218868)


--- trunk/Source/_javascript_Core/wasm/WasmOMGPlan.cpp	2017-06-28 06:23:23 UTC (rev 218867)
+++ trunk/Source/_javascript_Core/wasm/WasmOMGPlan.cpp	2017-06-28 06:42:13 UTC (rev 218868)
@@ -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));

Modified: trunk/Source/_javascript_Core/wasm/js/JSWebAssemblyCodeBlock.cpp (218867 => 218868)


--- trunk/Source/_javascript_Core/wasm/js/JSWebAssemblyCodeBlock.cpp	2017-06-28 06:23:23 UTC (rev 218867)
+++ trunk/Source/_javascript_Core/wasm/js/JSWebAssemblyCodeBlock.cpp	2017-06-28 06:42:13 UTC (rev 218868)
@@ -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();
     }
 }

Modified: trunk/Source/_javascript_Core/wasm/js/JSWebAssemblyCodeBlock.h (218867 => 218868)


--- trunk/Source/_javascript_Core/wasm/js/JSWebAssemblyCodeBlock.h	2017-06-28 06:23:23 UTC (rev 218867)
+++ trunk/Source/_javascript_Core/wasm/js/JSWebAssemblyCodeBlock.h	2017-06-28 06:42:13 UTC (rev 218868)
@@ -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

Modified: trunk/Source/_javascript_Core/wasm/js/JSWebAssemblyInstance.cpp (218867 => 218868)


--- trunk/Source/_javascript_Core/wasm/js/JSWebAssemblyInstance.cpp	2017-06-28 06:23:23 UTC (rev 218867)
+++ trunk/Source/_javascript_Core/wasm/js/JSWebAssemblyInstance.cpp	2017-06-28 06:42:13 UTC (rev 218868)
@@ -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);
     }

Modified: trunk/Tools/ChangeLog (218867 => 218868)


--- trunk/Tools/ChangeLog	2017-06-28 06:23:23 UTC (rev 218867)
+++ trunk/Tools/ChangeLog	2017-06-28 06:42:13 UTC (rev 218868)
@@ -1,3 +1,16 @@
+2017-06-27  JF Bastien  <jfbast...@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_hs...@apple.com>
 
         [iOS DnD] Support dragging out of contenteditable areas without a prior selection

Modified: trunk/Tools/Scripts/run-jsc-stress-tests (218867 => 218868)


--- trunk/Tools/Scripts/run-jsc-stress-tests	2017-06-28 06:23:23 UTC (rev 218867)
+++ trunk/Tools/Scripts/run-jsc-stress-tests	2017-06-28 06:42:13 UTC (rev 218868)
@@ -1234,7 +1234,6 @@
     when :skip
         return
     end
-
     return if !$jitTests
     return if !$isFTLPlatform
     prepareExtraAbsoluteFiles(WASMTESTS_PATH, ["wasm.json"])
@@ -1246,12 +1245,24 @@
     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)
     raise unless $benchmark.to_s =~ /\.js$/
     failsWithException = exception != "NoException"
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to