WebAssembly: manage memory better
authorjfbastien@apple.com <jfbastien@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 13 Apr 2017 21:48:42 +0000 (21:48 +0000)
committerjfbastien@apple.com <jfbastien@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 13 Apr 2017 21:48:42 +0000 (21:48 +0000)
https://bugs.webkit.org/show_bug.cgi?id=170628

Reviewed by Keith Miller, Michael Saboff.

JSTests:

* wasm/Builder.js: move a helper out so tests can use it
(export.default.Builder.prototype._registerSectionBuilders.const.section.in.WASM.description.section.switch.section.case.string_appeared_here.this.section):
* wasm/WASM.js: add utilities to classify opcodes
(export.opcodes):
(export.const.memoryAccessInfo.op.const.sign):
* wasm/function-tests/memory-access-past-4gib.js: Added. This test
fails before this patch.
(const.op.of.WASM.opcodes):
* wasm/function-tests/memory-many.js: Added. This simple tests
just shouldn't crash. In verbose mode it's useful at determining
if the GC falls behind or not.
* wasm/function-tests/memory-multiagent.js: Added. Emulate postMessage.
(const.startAgents.numAgentsToStart.a.agent.receiveBroadcast):
(const.startAgents.numAgentsToStart.a.write.const.idx.Math.random):
(const.broadcastToAgents):
* wasm/js-api/extension-MemoryMode.js: verbose logging.
(testMemoryNoMax):
(testMemory):
(testInstanceNoMemory):
(testInstanceNoMax):
(testInstance):
* wasm/utilities.js: move a utility here.

Source/JavaScriptCore:

WebAssembly fast memories weren't managed very well. This patch
refactors it and puts us in a good position to further improve our
fast memory handling in the future.

We now cache fast memories at a process granularity, but make sure
that they don't consume dirty pages. We add a cap to the total
number of allocated fast memories to avoid ASLR degradation.

We teach the GC about memories as a kind of resource it should
care about because it didn't have visibility into the amount of
memory each represented. This allows benchmarks which allocate
memories back-to-back to reliably get fast memories 100% of the
time, even on a system under load, which wasn't the case
before. This reliability yields roughly 8% perf bump on x86-64
WasmBench.

The GC heuristic is as follows: each time we allocate a fast
memory we notify the GC, which then keeps track of the total
number of fast memories allocated since it last GC'd. We
separately keep track of the total number of fast memories which
have ever existed at any point in time (cached + allocated). This
is a monotonically-increasing high watermark. The GC will force a
full collection if, since it last ran, half or more of the high
watermark of fast memories was allocated.

At the same time, if we fail obtaining a fast memory from the
cache we do a GC to try to find one. If that fails we'll allocate
a new one (this can also fail, then we go to slow memory). This
can also be improved, but it's a good start.

This currently disables fast memories on iOS because getting fast
memories isn't a guaranteed thing. Rather, we get quite a few of
them and achieve significant speedups, but benchmarks which
allocate memories back-to-back end up falling behind because the
GC can conservatively hold onto memories, which then yields a perf
cliff. That cliff isn't reliable, WasmBench gets roughly 10 of 18
fast memories when in theory it should get all of them fast (as
MacOS does). The patch significantly improves the state of iOS
though, and in a follow-up we could re-enable fast memories.

Part of this good positioning is a facility to pre-allocate fast
memories very early at startup, before any fragmentation
occurs. This is currently disabled but worked extremely reliably
on iOS. Once we fix the above issues we'll want to re-visit and
turn on pre-allocation.

We also avoid locking for fast memory identification when
performing signal handling. I'm very nervous about acquiring locks
in a signal handler because in general signals can happen when
we've messed up. This isn't the case with fast memories: we're
raising a signal on purpose and handling it. However this doesn't
mean we won't mess up elsewhere! This will get more complicated
once we add support for multiple threads sharing memories and
being able to grow their memories. One example: the code calls
CRASH(), which executes the following code in release:

    *(int *)(uintptr_t)0xbbadbeef = 0;

This is a segfault, which our fast memory signal handler tries to
handle. It does so by first figuring out whether 0xbbadbeef is in
a fast memory region, reqiring a lock. If we CRASH() while holding
the lock then our thread self-deadlocks, giving us no crash report
and a bad user experience.

Avoiding a lock therefore it's not about speed or reduced
contention. In fact, I'd use something else than a FIFO if these
were a concern. We're also doing syscalls, which dwarf any locking
cost.

We now only allocate 4GiB + redzone of 64k * 128 for fast memories
instead of 8GiB. This patch reuses the logic from
B3::WasmBoundsCheck to perform bounds checks when accesses could
exceed the redzone. We'll therefore benefit from CSE goodness when
it reaches WasmBoundsCheck. See bug #163469.

* b3/B3LowerToAir.cpp: fix a baaaaddd bug where unsigned->signed
conversion allowed out-of-bounds reads by -2GiB. I'll follow-up in
bug #170692 to prevent this type of bug once and for all.
(JSC::B3::Air::LowerToAir::lower):
* b3/B3Validate.cpp: update WasmBoundsCheck validation.
* b3/B3Value.cpp:
(JSC::B3::Value::effects): update WasmBoundsCheck effects.
* b3/B3WasmBoundsCheckValue.cpp:
(JSC::B3::WasmBoundsCheckValue::WasmBoundsCheckValue):
(JSC::B3::WasmBoundsCheckValue::redzoneLimit):
(JSC::B3::WasmBoundsCheckValue::dumpMeta):
* b3/B3WasmBoundsCheckValue.h:
(JSC::B3::WasmBoundsCheckValue::maximum):
* b3/air/AirCustom.cpp:
(JSC::B3::Air::WasmBoundsCheckCustom::isValidForm):
* b3/testb3.cpp:
(JSC::B3::testWasmBoundsCheck):
* heap/Heap.cpp:
(JSC::Heap::Heap):
(JSC::Heap::reportWebAssemblyFastMemoriesAllocated):
(JSC::Heap::webAssemblyFastMemoriesThisCycleAtThreshold):
(JSC::Heap::updateAllocationLimits):
(JSC::Heap::didAllocateWebAssemblyFastMemories):
(JSC::Heap::shouldDoFullCollection):
(JSC::Heap::collectIfNecessaryOrDefer):
* heap/Heap.h:
* runtime/InitializeThreading.cpp:
(JSC::initializeThreading):
* runtime/Options.cpp:
* runtime/Options.h:
* wasm/WasmB3IRGenerator.cpp:
(JSC::Wasm::B3IRGenerator::fixupPointerPlusOffset):
(JSC::Wasm::B3IRGenerator::B3IRGenerator):
(JSC::Wasm::B3IRGenerator::emitCheckAndPreparePointer):
(JSC::Wasm::B3IRGenerator::emitLoadOp):
(JSC::Wasm::B3IRGenerator::emitStoreOp):
(JSC::Wasm::createJSToWasmWrapper):
* wasm/WasmFaultSignalHandler.cpp:
(JSC::Wasm::trapHandler):
* wasm/WasmMemory.cpp: Rewrite.
(JSC::Wasm::makeString):
(JSC::Wasm::Memory::initializePreallocations):
(JSC::Wasm::Memory::createImpl):
(JSC::Wasm::Memory::create):
(JSC::Wasm::Memory::~Memory):
(JSC::Wasm::Memory::fastMappedRedzoneBytes):
(JSC::Wasm::Memory::fastMappedBytes):
(JSC::Wasm::Memory::maxFastMemoryCount):
(JSC::Wasm::Memory::addressIsInActiveFastMemory):
(JSC::Wasm::Memory::grow):
* wasm/WasmMemory.h:
(Memory::maxFastMemoryCount):
(Memory::addressIsInActiveFastMemory):
* wasm/js/JSWebAssemblyInstance.cpp:
(JSC::JSWebAssemblyInstance::finishCreation):
(JSC::JSWebAssemblyInstance::visitChildren):
(JSC::JSWebAssemblyInstance::globalMemoryByteSize):
* wasm/js/JSWebAssemblyInstance.h:
* wasm/js/JSWebAssemblyMemory.cpp:
(JSC::JSWebAssemblyMemory::grow):
(JSC::JSWebAssemblyMemory::finishCreation):
(JSC::JSWebAssemblyMemory::visitChildren):

Source/WebCore:

Re-use a VM tag which was intended for JavaScript core, was then
used by our GC, and is now unused. If I don't do this then
WebAssembly fast memories will make vmmap look super weird because
it'll look like multi-gigabyte of virtual memory are allocated as
part of our process' regular memory!

Separately I need to update vmmap and other tools to print the
right name. Right now this tag gets identified as "JS garbage
collector".

* page/ResourceUsageData.cpp:
(WebCore::ResourceUsageData::ResourceUsageData):
* page/ResourceUsageData.h:
* page/cocoa/ResourceUsageOverlayCocoa.mm:
(WebCore::HistoricResourceUsageData::HistoricResourceUsageData):
* page/cocoa/ResourceUsageThreadCocoa.mm:
(WebCore::displayNameForVMTag):
(WebCore::categoryForVMTag):

Source/WTF:

Re-use a VM tag which was intended for JavaScript core, was then
used by our GC, and is now unused. If I don't do this then
WebAssembly fast memories will make vmmap look super weird because
it'll look like multi-gigabyte of virtual memory are allocated as
part of our process' regular memory!

Separately I need to update vmmap and other tools to print the
right name. Right now this tag gets identified as "JS garbage
collector".

* wtf/OSAllocator.h:
* wtf/VMTags.h:

Websites/webkit.org:

* docs/b3/intermediate-representation.html: typos

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@215340 268f45cc-cd09-0410-ab3c-d52691b4dbfc

38 files changed:
JSTests/ChangeLog
JSTests/wasm/Builder.js
JSTests/wasm/WASM.js
JSTests/wasm/function-tests/memory-access-past-4gib.js [new file with mode: 0644]
JSTests/wasm/function-tests/memory-many.js [new file with mode: 0644]
JSTests/wasm/function-tests/memory-multiagent.js [new file with mode: 0644]
JSTests/wasm/js-api/extension-MemoryMode.js
JSTests/wasm/utilities.js
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/b3/B3LowerToAir.cpp
Source/JavaScriptCore/b3/B3Validate.cpp
Source/JavaScriptCore/b3/B3Value.cpp
Source/JavaScriptCore/b3/B3WasmBoundsCheckValue.cpp
Source/JavaScriptCore/b3/B3WasmBoundsCheckValue.h
Source/JavaScriptCore/b3/air/AirCustom.cpp
Source/JavaScriptCore/b3/testb3.cpp
Source/JavaScriptCore/heap/Heap.cpp
Source/JavaScriptCore/heap/Heap.h
Source/JavaScriptCore/runtime/InitializeThreading.cpp
Source/JavaScriptCore/runtime/Options.cpp
Source/JavaScriptCore/runtime/Options.h
Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp
Source/JavaScriptCore/wasm/WasmFaultSignalHandler.cpp
Source/JavaScriptCore/wasm/WasmMemory.cpp
Source/JavaScriptCore/wasm/WasmMemory.h
Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.cpp
Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.h
Source/JavaScriptCore/wasm/js/JSWebAssemblyMemory.cpp
Source/WTF/ChangeLog
Source/WTF/wtf/OSAllocator.h
Source/WTF/wtf/VMTags.h
Source/WebCore/ChangeLog
Source/WebCore/page/ResourceUsageData.cpp
Source/WebCore/page/ResourceUsageData.h
Source/WebCore/page/cocoa/ResourceUsageOverlayCocoa.mm
Source/WebCore/page/cocoa/ResourceUsageThreadCocoa.mm
Websites/webkit.org/ChangeLog
Websites/webkit.org/docs/b3/intermediate-representation.html

index 4a60068..88bad46 100644 (file)
@@ -1,3 +1,33 @@
+2017-04-13  JF Bastien  <jfbastien@apple.com>
+
+        WebAssembly: manage memory better
+        https://bugs.webkit.org/show_bug.cgi?id=170628
+
+        Reviewed by Keith Miller, Michael Saboff.
+
+        * wasm/Builder.js: move a helper out so tests can use it
+        (export.default.Builder.prototype._registerSectionBuilders.const.section.in.WASM.description.section.switch.section.case.string_appeared_here.this.section):
+        * wasm/WASM.js: add utilities to classify opcodes
+        (export.opcodes):
+        (export.const.memoryAccessInfo.op.const.sign):
+        * wasm/function-tests/memory-access-past-4gib.js: Added. This test
+        fails before this patch.
+        (const.op.of.WASM.opcodes):
+        * wasm/function-tests/memory-many.js: Added. This simple tests
+        just shouldn't crash. In verbose mode it's useful at determining
+        if the GC falls behind or not.
+        * wasm/function-tests/memory-multiagent.js: Added. Emulate postMessage.
+        (const.startAgents.numAgentsToStart.a.agent.receiveBroadcast):
+        (const.startAgents.numAgentsToStart.a.write.const.idx.Math.random):
+        (const.broadcastToAgents):
+        * wasm/js-api/extension-MemoryMode.js: verbose logging.
+        (testMemoryNoMax):
+        (testMemory):
+        (testInstanceNoMemory):
+        (testInstanceNoMax):
+        (testInstance):
+        * wasm/utilities.js: move a utility here.
+
 2017-04-12  Joseph Pecoraro  <pecoraro@apple.com>
 
         test262: test262/test/built-ins/NativeErrors/EvalError/proto.js
index 8ff5dce..dee786d 100644 (file)
@@ -27,12 +27,7 @@ import * as assert from 'assert.js';
 import * as BuildWebAssembly from 'Builder_WebAssemblyBinary.js';
 import * as LLB from 'LowLevelBinary.js';
 import * as WASM from 'WASM.js';
-
-const _toJavaScriptName = name => {
-    const camelCase = name.replace(/([^a-z0-9].)/g, c => c[1].toUpperCase());
-    const CamelCase = camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
-    return CamelCase;
-};
+import * as util from 'utilities.js';
 
 const _isValidValue = (value, type) => {
     switch (type) {
@@ -225,7 +220,7 @@ const _importGlobalContinuation = (builder, section, nextBuilder) => {
             End: () => nextBuilder
         };
         for (let op of WASM.description.value_type) {
-            globalBuilder[_toJavaScriptName(op)] = (module, field, mutability) => {
+            globalBuilder[util.toJavaScriptName(op)] = (module, field, mutability) => {
                 assert.isString(module, `Import global module should be a string, got "${module}"`);
                 assert.isString(field, `Import global field should be a string, got "${field}"`);
                 assert.isString(mutability, `Import global mutability should be a string, got "${mutability}"`);
@@ -318,7 +313,7 @@ const _checkImms = (op, imms, expectedImms, ret) => {
 const _createFunctionBuilder = (func, builder, previousBuilder) => {
     let functionBuilder = {};
     for (const op in WASM.description.opcode) {
-        const name = _toJavaScriptName(op);
+        const name = util.toJavaScriptName(op);
         const value = WASM.description.opcode[op].value;
         const ret = WASM.description.opcode[op]["return"];
         const param = WASM.description.opcode[op].parameter;
@@ -542,7 +537,7 @@ export default class Builder {
                         }
                     };
                     for (let op of WASM.description.value_type) {
-                        globalBuilder[_toJavaScriptName(op)] = (initValue, mutability) => {
+                        globalBuilder[util.toJavaScriptName(op)] = (initValue, mutability) => {
                             s.data.push({ type: op, op: op + ".const", mutability: _normalizeMutability(mutability), initValue });
                             return _errorHandlingProxyFor(globalBuilder);
                         };
index 1bb79b8..1e64cae 100644 (file)
@@ -44,3 +44,33 @@ export const isValidBlockType = v => _blockTypeSet.has(v);
 export const externalKindValue = _mapValues(description.external_kind);
 export const sections = Object.keys(description.section);
 export const sectionEncodingType = description.section[sections[0]].type;
+
+export function* opcodes(category = undefined) {
+    for (let op in description.opcode)
+        if (category !== undefined && description.opcode[op].category === category)
+            yield { name: op, opcode: description.opcode[op] };
+};
+export const memoryAccessInfo = op => {
+    //                <-----------valueType----------->  <-------type-------><---------width-------->  <--sign-->
+    const classify = /((?:i32)|(?:i64)|(?:f32)|(?:f64))\.((?:load)|(?:store))((?:8)?|(?:16)?|(?:32)?)_?((?:s|u)?)/;
+    const found = op.name.match(classify);
+    const valueType = found[1];
+    const type = found[2];
+    const width = parseInt(found[3] ? found[3] : valueType.slice(1));
+    const sign = (() => {
+        switch (found[4]) {
+        case "s": return "signed";
+        case "u": return "unsigned";
+        default: return "agnostic";
+        }
+    })();
+    return { valueType, type, width, sign };
+};
+
+export const constForValueType = valueType => {
+    for (let op in description.opcode)
+        if (op.endsWith(".const") && description.opcode[op]["return"] == valueType)
+            return op;
+    throw new Error(`Implementation problem: no const type for ${valueType}`);
+};
+
diff --git a/JSTests/wasm/function-tests/memory-access-past-4gib.js b/JSTests/wasm/function-tests/memory-access-past-4gib.js
new file mode 100644 (file)
index 0000000..d1a5f03
--- /dev/null
@@ -0,0 +1,130 @@
+import Builder from '../Builder.js';
+import * as assert from '../assert.js';
+import * as LLB from '../LowLevelBinary.js';
+import * as WASM from '../WASM.js';
+import * as util from '../utilities.js';
+
+const verbose = false;
+
+const pageSize = 64 * 1024;
+const fourGiB = pageSize * 65536;
+const initial = 64;
+const maximum = 128;
+
+// When using fast memories, we allocate a redzone after the 4GiB huge
+// allocation. This redzone is used to trap reg+imm accesses which exceed
+// 32-bits. Anything past 4GiB must trap, but we cannot know statically that it
+// will.
+
+const offsets = [
+    0,
+    1,
+    2,
+    1024,
+    pageSize,
+    pageSize + pageSize / 2,
+    pageSize * 8,
+    pageSize * 16,
+    pageSize * 128,
+    pageSize * 512,
+    fourGiB / 4 - 4,
+    fourGiB / 4 - 3,
+    fourGiB / 4 - 2,
+    fourGiB / 4 - 1,
+    fourGiB / 4,
+    fourGiB / 4 + 1,
+    fourGiB / 4 + 2,
+    fourGiB / 4 + 3,
+    fourGiB / 4 + 4,
+    fourGiB / 2 - 4,
+    fourGiB / 2 - 3,
+    fourGiB / 2 - 2,
+    fourGiB / 2 - 1,
+    fourGiB / 2,
+    fourGiB / 2 + 1,
+    fourGiB / 2 + 2,
+    fourGiB / 2 + 3,
+    fourGiB / 2 + 4,
+    (fourGiB / 4) * 3,
+    fourGiB - 4,
+    fourGiB - 3,
+    fourGiB - 2,
+    fourGiB - 1,
+];
+
+for (let memoryDeclaration of [{ initial: initial }, { initial: initial, maximum: maximum }]) {
+    fullGC();
+
+    // Re-use a single memory so tests are more likely to get a fast memory.
+    const memory = new WebAssembly.Memory(memoryDeclaration);
+    if (verbose)
+        print(WebAssemblyMemoryMode(memory));
+    const buf = new Uint8Array(memory.buffer);
+
+    // Enumerate all memory access types.
+    for (const op of WASM.opcodes("memory")) {
+        const info = WASM.memoryAccessInfo(op);
+
+        // The accesses should fault even if only the last byte is off the end.
+        let wiggles = [0];
+        for (let wiggle = 0; wiggle !== info.width / 8; ++wiggle)
+            wiggles.push(wiggle);
+
+        let builder = (new Builder())
+            .Type().End()
+            .Import().Memory("imp", "memory", memoryDeclaration).End()
+            .Function().End()
+            .Export();
+
+        for (let offset of offsets)
+            switch (info.type) {
+            case "load": builder = builder.Function("get_" + offset); break;
+            case "store": builder = builder.Function("set_" + offset); break;
+            default: throw new Error(`Implementation problem: unknown memory access type ${info.type}`);
+            }
+
+        builder = builder.End().Code();
+
+        const align = 3; // No need to be precise, it's just a hint.
+        const constInstr = util.toJavaScriptName(WASM.constForValueType(info.valueType));
+        const instr = util.toJavaScriptName(op.name);
+        for (let offset of offsets)
+            switch (info.type) {
+            case "load":
+                builder = builder.Function("get_" + offset, { params: ["i32"] }).GetLocal(0)[instr](align, offset).Drop().End();
+                break;
+            case "store":
+                builder = builder.Function("set_" + offset, { params: ["i32"] }).GetLocal(0)[constInstr](0xdead)[instr](align, offset).End();
+                break;
+            default:
+                throw new Error(`Implementation problem: unknown memory access type ${info.type}`);
+            }
+        
+        builder = builder.End();
+
+        const instance = new WebAssembly.Instance(new WebAssembly.Module(builder.WebAssembly().get()), { imp: { memory: memory } });
+
+        for (let offset of offsets) {
+            for (let wiggle of wiggles) {
+                const address = LLB.varuint32Max - offset - wiggle;
+                if (verbose)
+                    print(`${op.name.padStart(16, ' ')}: base address ${address > 0 ? '0x' : '  '}${address.toString(16).padStart(8, address > 0 ? '0' : ' ')} + offset 0x${offset.toString(16).padStart(8, '0')} - wiggle ${wiggle} = effective address 0x${(address + offset - wiggle).toString(16).padStart(16, '0')}`);
+                switch (info.type) {
+                case "load":
+                    assert.throws(() => instance.exports["get_" + offset](address), WebAssembly.RuntimeError, `Out of bounds memory access`);
+                    break;
+                case "store":
+                    assert.throws(() => instance.exports["set_" + offset](address), WebAssembly.RuntimeError, `Out of bounds memory access`);
+                    break;
+                default: throw new Error(`Implementation problem: unknown memory access type ${info.type}`);
+                }
+            }
+        }
+
+        fullGC();
+    }
+
+    // Only check that the memory was untouched at the very end, before throwing it away entirely.
+    for (let idx = 0; idx < buf.byteLength; ++idx)
+        assert.eq(buf[idx], 0);
+}
diff --git a/JSTests/wasm/function-tests/memory-many.js b/JSTests/wasm/function-tests/memory-many.js
new file mode 100644 (file)
index 0000000..ba43c62
--- /dev/null
@@ -0,0 +1,18 @@
+const memories = 128;
+const verbose = false;
+const initial = 1;
+
+let types = {};
+
+for (let m = 0; m < memories; ++m) {
+    let memory = new WebAssembly.Memory({ initial: initial });
+    let type = WebAssemblyMemoryMode(memory);
+    types[type] = types[type] ? types[type] + 1 : 1;
+}
+
+if (verbose) {
+    let got = "Got: ";
+    for (let p in types)
+        got += ` ${types[p]}: ${p}`;
+    print(got);
+}
diff --git a/JSTests/wasm/function-tests/memory-multiagent.js b/JSTests/wasm/function-tests/memory-multiagent.js
new file mode 100644 (file)
index 0000000..4eb2d34
--- /dev/null
@@ -0,0 +1,117 @@
+const pageSize = 64 * 1024;
+
+const verbose = false;
+
+// Start multiple agents and create WebAssembly.Memory from each of
+// them. Perform writes into each memory, and then check that the memory only
+// contains that agent's writes. This would find bugs where an implementation's
+// memory reuse is buggy.
+
+// Use the agent support from test262: https://github.com/tc39/test262/blob/master/INTERPRETING.md#host-defined-functions
+
+const testIterations = 2;
+const numAgents = 8;
+const initialPages = 64;
+const numWrites = 1024;
+
+const stateWait = 0;
+const stateReady = 1;
+const stateCheck = 2;
+
+const startAgents = numAgentsToStart => {
+    for (let a = 0; a < numAgentsToStart; ++a) {
+        const agentScript = `
+        let state = ${stateWait};
+        let u8;
+
+        if (${verbose})
+            print("Agent ${a} started");
+        $.agent.report("Agent ${a} started");
+
+        $.agent.receiveBroadcast((sab, newState) => {
+            if (${verbose})
+                print("Agent ${a} received broadcast");
+            u8 = new Uint8Array(sab);
+            state = newState;
+        });
+
+        const busyWaitForValue = value => {
+            // Busy-wait so that once the SAB write occurs all agents try to create a memory at the same time.
+            while (Atomics.load(u8, 0) !== value) ;
+        };
+
+        if (${verbose})
+            print("Agent ${a} waits");
+        $.agent.report("Agent ${a} waits");
+        while (state === ${stateWait}) ;
+        $.agent.report("Agent ${a} received SAB");
+        // Use it for faster state change so all agents are more likely to execute at the same time.
+        busyWaitForValue(${stateReady});
+
+        let wasmMemory = new WebAssembly.Memory({ initial: ${initialPages} });
+        let memory = new Uint8Array((wasmMemory).buffer);
+        if (${verbose})
+            print("Agent ${a} performing writes");
+        for (let write = 0; write < ${numWrites}; ++write) {
+            // Perform writes of our agent number at a random location. This memory should not be shared, if we see writes of other values then something went wrong.
+            const idx = (Math.random() * ${pageSize} * ${initialPages}) | 0;
+            memory[idx] = ${a};
+        }
+        if (${verbose})
+            print("Agent ${a} writes performed");
+        $.agent.report("Agent ${a} performed writes");
+        busyWaitForValue(${stateCheck});
+
+        if (${verbose})
+            print("Agent ${a} checking");
+        // Check that our memory only contains 0 and our agent number.
+        for (let idx = 0; idx < ${pageSize} * ${initialPages}; ++idx)
+            if (memory[idx] !== 0 && memory[idx] !== ${a})
+                throw new Error("Agent ${a} found unexpected value " + memory[idx] + " at location " + idx);
+        $.agent.report("Agent ${a} checks out OK");
+        $.agent.leaving();
+        `;
+
+        if (verbose)
+            print(`Starting agent ${a}`);
+        $.agent.start(agentScript);
+    }
+};
+
+const waitForAgents = numAgentsToWaitFor => {
+    for (let a = 0; a < numAgentsToWaitFor; ++a) {
+        while (true) {
+            const report = $.agent.getReport();
+            if (report === null) {
+                $.agent.sleep(1);
+                continue;
+            }
+            if (verbose)
+                print(`Received: ${report}`);
+            break;
+        }
+    }
+};
+
+const broadcastToAgents = (sab, newState) => {
+    $.agent.broadcast(sab, newState);
+};
+
+const sab = new SharedArrayBuffer(1024);
+const u8 = new Uint8Array(sab);
+
+for (let it = 0; it < testIterations; ++it) {
+    startAgents(numAgents);
+    waitForAgents(numAgents);
+    broadcastToAgents(sab, stateReady);
+    waitForAgents(numAgents);
+    $.agent.sleep(1);
+    Atomics.store(u8, 0, stateReady);
+    waitForAgents(numAgents);
+    $.agent.sleep(1);
+    Atomics.store(u8, 0, stateCheck);
+    waitForAgents(numAgents);
+    if (verbose)
+        print("Everyting was fine");
+    $.agent.sleep(1);
+}
index 9191cec..5608526 100644 (file)
@@ -2,6 +2,7 @@ import Builder from '../Builder.js';
 import * as assert from '../assert.js';
 
 const iterations = 32;
+const verbose = false;
 
 // This API isn't part of WebAssembly's official spec. It is use for testing within the shell.
 
@@ -21,6 +22,8 @@ assert.throws(() => WebAssemblyMemoryMode(new WebAssembly.Table({initial: 1, ele
 
 const validateMode = what => {
     const mode = WebAssemblyMemoryMode(what);
+    if (verbose)
+        print(`    ${mode}`);
     switch (mode) {
     case "Signaling":
         break;
@@ -39,6 +42,8 @@ const instantiate = builder => {
 };
 
 (function testMemoryNoMax() {
+    if (verbose)
+        print(`testMemoryNoMax`);
     let memories = [];
     for (let i = 0; i != iterations; ++i)
         memories.push(validateMode(new WebAssembly.Memory({ initial: i })));
@@ -48,6 +53,8 @@ const instantiate = builder => {
 fullGC();
 
 (function testMemory() {
+    if (verbose)
+        print(`testMemory`);
     let memories = [];
     for (let i = 0; i != iterations; ++i)
         memories.push(validateMode(new WebAssembly.Memory({ initial: i, maximum: i })));
@@ -57,6 +64,8 @@ fullGC();
 fullGC();
 
 (function testInstanceNoMemory() {
+    if (verbose)
+        print(`testInstanceNoMemory`);
     let instances = [];
     for (let i = 0; i != iterations; ++i) {
         const builder = (new Builder())
@@ -66,6 +75,8 @@ fullGC();
         const instance = instantiate(builder);
         // No-memory instances should never be Signaling: it would be wasteful.
         assert.eq(WebAssemblyMemoryMode(instance), "BoundsChecking");
+        if (verbose)
+            print(`    ${WebAssemblyMemoryMode(instance)}`);
         instances.push(instance);
     }
     return instances;
@@ -74,6 +85,8 @@ fullGC();
 fullGC();
 
 (function testInstanceNoMax() {
+    if (verbose)
+        print(`testInstanceNoMax`);
     let instances = [];
     for (let i = 0; i != iterations; ++i) {
         // Note: not exported! The internal API can still access.
@@ -89,6 +102,8 @@ fullGC();
 fullGC();
 
 (function testInstance() {
+    if (verbose)
+        print(`testInstance`);
     let instances = [];
     for (let i = 0; i != iterations; ++i) {
         // Note: not exported! The internal API can still access.
index 19f9b4e..bc69ad7 100644 (file)
@@ -79,6 +79,12 @@ const _dump = (what, name, pad = '    ') => {
     return s;
 };
 
+export const toJavaScriptName = name => {
+    const camelCase = name.replace(/([^a-z0-9].)/g, c => c[1].toUpperCase());
+    const CamelCase = camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
+    return CamelCase;
+};
+
 // Use underscore names to avoid clashing with builtin names.
 export {
     _dump as dump,
index 7077611..af15ddb 100644 (file)
@@ -1,3 +1,148 @@
+2017-04-13  JF Bastien  <jfbastien@apple.com>
+
+        WebAssembly: manage memory better
+        https://bugs.webkit.org/show_bug.cgi?id=170628
+
+        Reviewed by Keith Miller, Michael Saboff.
+
+        WebAssembly fast memories weren't managed very well. This patch
+        refactors it and puts us in a good position to further improve our
+        fast memory handling in the future.
+
+        We now cache fast memories at a process granularity, but make sure
+        that they don't consume dirty pages. We add a cap to the total
+        number of allocated fast memories to avoid ASLR degradation.
+
+        We teach the GC about memories as a kind of resource it should
+        care about because it didn't have visibility into the amount of
+        memory each represented. This allows benchmarks which allocate
+        memories back-to-back to reliably get fast memories 100% of the
+        time, even on a system under load, which wasn't the case
+        before. This reliability yields roughly 8% perf bump on x86-64
+        WasmBench.
+
+        The GC heuristic is as follows: each time we allocate a fast
+        memory we notify the GC, which then keeps track of the total
+        number of fast memories allocated since it last GC'd. We
+        separately keep track of the total number of fast memories which
+        have ever existed at any point in time (cached + allocated). This
+        is a monotonically-increasing high watermark. The GC will force a
+        full collection if, since it last ran, half or more of the high
+        watermark of fast memories was allocated.
+
+        At the same time, if we fail obtaining a fast memory from the
+        cache we do a GC to try to find one. If that fails we'll allocate
+        a new one (this can also fail, then we go to slow memory). This
+        can also be improved, but it's a good start.
+
+        This currently disables fast memories on iOS because getting fast
+        memories isn't a guaranteed thing. Rather, we get quite a few of
+        them and achieve significant speedups, but benchmarks which
+        allocate memories back-to-back end up falling behind because the
+        GC can conservatively hold onto memories, which then yields a perf
+        cliff. That cliff isn't reliable, WasmBench gets roughly 10 of 18
+        fast memories when in theory it should get all of them fast (as
+        MacOS does). The patch significantly improves the state of iOS
+        though, and in a follow-up we could re-enable fast memories.
+
+        Part of this good positioning is a facility to pre-allocate fast
+        memories very early at startup, before any fragmentation
+        occurs. This is currently disabled but worked extremely reliably
+        on iOS. Once we fix the above issues we'll want to re-visit and
+        turn on pre-allocation.
+
+        We also avoid locking for fast memory identification when
+        performing signal handling. I'm very nervous about acquiring locks
+        in a signal handler because in general signals can happen when
+        we've messed up. This isn't the case with fast memories: we're
+        raising a signal on purpose and handling it. However this doesn't
+        mean we won't mess up elsewhere! This will get more complicated
+        once we add support for multiple threads sharing memories and
+        being able to grow their memories. One example: the code calls
+        CRASH(), which executes the following code in release:
+
+            *(int *)(uintptr_t)0xbbadbeef = 0;
+
+        This is a segfault, which our fast memory signal handler tries to
+        handle. It does so by first figuring out whether 0xbbadbeef is in
+        a fast memory region, reqiring a lock. If we CRASH() while holding
+        the lock then our thread self-deadlocks, giving us no crash report
+        and a bad user experience.
+
+        Avoiding a lock therefore it's not about speed or reduced
+        contention. In fact, I'd use something else than a FIFO if these
+        were a concern. We're also doing syscalls, which dwarf any locking
+        cost.
+
+        We now only allocate 4GiB + redzone of 64k * 128 for fast memories
+        instead of 8GiB. This patch reuses the logic from
+        B3::WasmBoundsCheck to perform bounds checks when accesses could
+        exceed the redzone. We'll therefore benefit from CSE goodness when
+        it reaches WasmBoundsCheck. See bug #163469.
+
+        * b3/B3LowerToAir.cpp: fix a baaaaddd bug where unsigned->signed
+        conversion allowed out-of-bounds reads by -2GiB. I'll follow-up in
+        bug #170692 to prevent this type of bug once and for all.
+        (JSC::B3::Air::LowerToAir::lower):
+        * b3/B3Validate.cpp: update WasmBoundsCheck validation.
+        * b3/B3Value.cpp:
+        (JSC::B3::Value::effects): update WasmBoundsCheck effects.
+        * b3/B3WasmBoundsCheckValue.cpp:
+        (JSC::B3::WasmBoundsCheckValue::WasmBoundsCheckValue):
+        (JSC::B3::WasmBoundsCheckValue::redzoneLimit):
+        (JSC::B3::WasmBoundsCheckValue::dumpMeta):
+        * b3/B3WasmBoundsCheckValue.h:
+        (JSC::B3::WasmBoundsCheckValue::maximum):
+        * b3/air/AirCustom.cpp:
+        (JSC::B3::Air::WasmBoundsCheckCustom::isValidForm):
+        * b3/testb3.cpp:
+        (JSC::B3::testWasmBoundsCheck):
+        * heap/Heap.cpp:
+        (JSC::Heap::Heap):
+        (JSC::Heap::reportWebAssemblyFastMemoriesAllocated):
+        (JSC::Heap::webAssemblyFastMemoriesThisCycleAtThreshold):
+        (JSC::Heap::updateAllocationLimits):
+        (JSC::Heap::didAllocateWebAssemblyFastMemories):
+        (JSC::Heap::shouldDoFullCollection):
+        (JSC::Heap::collectIfNecessaryOrDefer):
+        * heap/Heap.h:
+        * runtime/InitializeThreading.cpp:
+        (JSC::initializeThreading):
+        * runtime/Options.cpp:
+        * runtime/Options.h:
+        * wasm/WasmB3IRGenerator.cpp:
+        (JSC::Wasm::B3IRGenerator::fixupPointerPlusOffset):
+        (JSC::Wasm::B3IRGenerator::B3IRGenerator):
+        (JSC::Wasm::B3IRGenerator::emitCheckAndPreparePointer):
+        (JSC::Wasm::B3IRGenerator::emitLoadOp):
+        (JSC::Wasm::B3IRGenerator::emitStoreOp):
+        (JSC::Wasm::createJSToWasmWrapper):
+        * wasm/WasmFaultSignalHandler.cpp:
+        (JSC::Wasm::trapHandler):
+        * wasm/WasmMemory.cpp: Rewrite.
+        (JSC::Wasm::makeString):
+        (JSC::Wasm::Memory::initializePreallocations):
+        (JSC::Wasm::Memory::createImpl):
+        (JSC::Wasm::Memory::create):
+        (JSC::Wasm::Memory::~Memory):
+        (JSC::Wasm::Memory::fastMappedRedzoneBytes):
+        (JSC::Wasm::Memory::fastMappedBytes):
+        (JSC::Wasm::Memory::maxFastMemoryCount):
+        (JSC::Wasm::Memory::addressIsInActiveFastMemory):
+        (JSC::Wasm::Memory::grow):
+        * wasm/WasmMemory.h:
+        (Memory::maxFastMemoryCount):
+        (Memory::addressIsInActiveFastMemory):
+        * wasm/js/JSWebAssemblyInstance.cpp:
+        (JSC::JSWebAssemblyInstance::finishCreation):
+        (JSC::JSWebAssemblyInstance::visitChildren):
+        (JSC::JSWebAssemblyInstance::globalMemoryByteSize):
+        * wasm/js/JSWebAssemblyInstance.h:
+        * wasm/js/JSWebAssemblyMemory.cpp:
+        (JSC::JSWebAssemblyMemory::grow):
+        (JSC::JSWebAssemblyMemory::finishCreation):
+        (JSC::JSWebAssemblyMemory::visitChildren):
+
 2017-04-13  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         [JSC] Use proper ifdef guard for code using MachineContext
index 820d4c1..5ba5afa 100644 (file)
@@ -3144,22 +3144,46 @@ private:
         }
 
         case B3::WasmBoundsCheck: {
+#if ENABLE(WEBASSEMBLY)
             WasmBoundsCheckValue* value = m_value->as<WasmBoundsCheckValue>();
 
             Value* ptr = value->child(0);
 
-            Arg temp = m_code.newTmp(GP);
-            append(Inst(Move32, value, tmp(ptr), temp));
+            Arg ptrPlusImm = m_code.newTmp(GP);
+            append(Inst(Move32, value, tmp(ptr), ptrPlusImm));
             if (value->offset()) {
                 if (imm(value->offset()))
-                    append(Add64, imm(value->offset()), temp);
+                    append(Add64, imm(value->offset()), ptrPlusImm);
                 else {
                     Arg bigImm = m_code.newTmp(GP);
                     append(Move, Arg::bigImm(value->offset()), bigImm);
-                    append(Add64, bigImm, temp);
+                    append(Add64, bigImm, ptrPlusImm);
                 }
             }
-            append(Inst(Air::WasmBoundsCheck, value, temp, Arg(value->pinnedGPR())));
+
+            Arg limit;
+            if (value->pinnedGPR() != InvalidGPRReg)
+                limit = Arg(value->pinnedGPR());
+            else {
+                // Signaling memories don't pin a register because only the accesses whose reg+imm could ever overflow 4GiB+redzone need to be checked,
+                // and we don't think these will be frequent. All other accesses will trap due to PROT_NONE pages.
+                //
+                // If we got here it's because a memory access had a very large offset. We could check that it doesn't exceed 4GiB+redzone since that's
+                // technically the limit we need to avoid overflowing, but it's better if we use a smaller immediate which codegens more easily.
+                // We know that anything above the declared 'maximum' will trap, so we can compare against that number. If there was no declared
+                // 'maximum' then we still know that any access above 4GiB will trap, no need to add the redzone.
+                limit = m_code.newTmp(GP);
+                size_t limitValue = value->maximum() ? value->maximum().bytes() : std::numeric_limits<uint32_t>::max();
+                ASSERT(limitValue <= value->redzoneLimit());
+                if (imm(limitValue))
+                    append(Move, imm(limitValue), limit);
+                else
+                    append(Move, Arg::bigImm(limitValue), limit);
+            }
+            append(Inst(Air::WasmBoundsCheck, value, ptrPlusImm, limit));
+#else
+            append(Air::Oops);
+#endif // ENABLE(WEBASSEMBLY)
             return;
         }
 
index 6e6a93d..cc3a56f 100644 (file)
@@ -470,7 +470,8 @@ public:
                 VALIDATE(!value->kind().hasExtraBits(), ("At ", *value));
                 VALIDATE(value->numChildren() == 1, ("At ", *value));
                 VALIDATE(value->child(0)->type() == Int32, ("At ", *value));
-                VALIDATE(m_procedure.code().isPinned(value->as<WasmBoundsCheckValue>()->pinnedGPR()), ("At ", *value));
+                if (value->as<WasmBoundsCheckValue>()->pinnedGPR() != InvalidGPRReg)
+                    VALIDATE(m_procedure.code().isPinned(value->as<WasmBoundsCheckValue>()->pinnedGPR()), ("At ", *value));
                 VALIDATE(m_procedure.code().wasmBoundsCheckGenerator(), ("At ", *value));
                 break;
             case Upsilon:
index 8c910aa..65b17e3 100644 (file)
@@ -43,6 +43,7 @@
 #include "B3ValueInlines.h"
 #include "B3ValueKeyInlines.h"
 #include "B3VariableValue.h"
+#include "B3WasmBoundsCheckValue.h"
 #include <wtf/CommaPrinter.h>
 #include <wtf/ListDump.h>
 #include <wtf/StringPrintStream.h>
@@ -664,7 +665,8 @@ Effects Value::effects() const
         result = Effects::forCheck();
         break;
     case WasmBoundsCheck:
-        result.readsPinned = true;
+        if (as<WasmBoundsCheckValue>()->pinnedGPR() != InvalidGPRReg)
+            result.readsPinned = true;
         result.exitsSideways = true;
         break;
     case Upsilon:
index b3a3290..8aecc1d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 Apple Inc. All rights reserved.
+ * Copyright (C) 2016-2017 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -25,6 +25,7 @@
 
 #include "config.h"
 #include "B3WasmBoundsCheckValue.h"
+#include "WasmMemory.h"
 
 #if ENABLE(B3_JIT)
 
@@ -34,10 +35,11 @@ WasmBoundsCheckValue::~WasmBoundsCheckValue()
 {
 }
 
-WasmBoundsCheckValue::WasmBoundsCheckValue(Origin origin, Value* ptr, GPRReg pinnedGPR, unsigned offset)
+WasmBoundsCheckValue::WasmBoundsCheckValue(Origin origin, Value* ptr, GPRReg pinnedGPR, unsigned offset, PageCount maximum)
     : Value(CheckedOpcode, WasmBoundsCheck, origin, ptr)
     , m_pinnedGPR(pinnedGPR)
     , m_offset(offset)
+    , m_maximum(maximum)
 {
 }
 
@@ -46,9 +48,22 @@ Value* WasmBoundsCheckValue::cloneImpl() const
     return new WasmBoundsCheckValue(*this);
 }
 
+size_t WasmBoundsCheckValue::redzoneLimit() const
+{
+    ASSERT(m_pinnedGPR == InvalidGPRReg);
+#if ENABLE(WEBASSEMBLY)
+    return static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) + Wasm::Memory::fastMappedRedzoneBytes();
+#else
+    RELEASE_ASSERT_NOT_REACHED();
+#endif
+}
+
 void WasmBoundsCheckValue::dumpMeta(CommaPrinter& comma, PrintStream& out) const
 {
-    out.print(comma, "sizeRegister = ", m_pinnedGPR, ", offset = ", m_offset);
+    if (m_pinnedGPR == InvalidGPRReg)
+        out.print(comma, "redzoneLimit = ", redzoneLimit(), ", offset = ", m_offset);
+    else
+        out.print(comma, "sizeRegister = ", m_pinnedGPR, ", offset = ", m_offset);
 }
 
 } } // namespace JSC::B3
index ccc54b8..110e718 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015-2016 Apple Inc. All rights reserved.
+ * Copyright (C) 2015-2017 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -29,6 +29,7 @@
 
 #include "B3Value.h"
 #include "CCallHelpers.h"
+#include "WasmPageCount.h"
 
 namespace JSC { namespace B3 {
 
@@ -46,8 +47,16 @@ public:
     
     ~WasmBoundsCheckValue();
 
+#if ENABLE(WEBASSEMBLY)
+    typedef Wasm::PageCount PageCount;
+#else
+    typedef char PageCount;
+#endif
+
     GPRReg pinnedGPR() const { return m_pinnedGPR; }
     unsigned offset() const { return m_offset; }
+    size_t redzoneLimit() const;
+    PageCount maximum() const { return m_maximum; }
 
 protected:
     void dumpMeta(CommaPrinter&, PrintStream&) const override;
@@ -57,10 +66,11 @@ protected:
 private:
     friend class Procedure;
 
-    JS_EXPORT_PRIVATE WasmBoundsCheckValue(Origin, Value* ptr, GPRReg pinnedGPR, unsigned offset);
+    JS_EXPORT_PRIVATE WasmBoundsCheckValue(Origin, Value* ptr, GPRReg pinnedGPR, unsigned offset, PageCount maximum);
 
     GPRReg m_pinnedGPR;
     unsigned m_offset;
+    PageCount m_maximum;
 };
 
 } } // namespace JSC::B3
index 7ee7f3c..49ca54c 100644 (file)
@@ -185,7 +185,7 @@ bool WasmBoundsCheckCustom::isValidForm(Inst& inst)
     if (!inst.args[0].isTmp() && !inst.args[0].isSomeImm())
         return false;
 
-    return inst.args[1].isReg();
+    return inst.args[1].isReg() || inst.args[1].isTmp() || inst.args[1].isSomeImm();
 }
 
 
index d78ae97..80e754c 100644 (file)
@@ -15185,7 +15185,8 @@ void testWasmBoundsCheck(unsigned offset)
     Value* left = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0);
     if (pointerType() != Int32)
         left = root->appendNew<Value>(proc, Trunc, Origin(), left);
-    root->appendNew<WasmBoundsCheckValue>(proc, Origin(), left, pinned, offset);
+    Wasm::PageCount maximum;
+    root->appendNew<WasmBoundsCheckValue>(proc, Origin(), left, pinned, offset, maximum);
     Value* result = root->appendNew<Const32Value>(proc, Origin(), 0x42);
     root->appendNewControlValue(proc, Return, Origin(), result);
 
index 7223b20..85d3ad5 100644 (file)
@@ -62,6 +62,7 @@
 #include "TypeProfilerLog.h"
 #include "UnlinkedCodeBlock.h"
 #include "VM.h"
+#include "WasmMemory.h"
 #include "WeakSetInlines.h"
 #include <algorithm>
 #include <wtf/CurrentTime.h>
@@ -250,6 +251,7 @@ Heap::Heap(VM* vm, HeapType heapType)
     , m_sizeAfterLastEdenCollect(0)
     , m_sizeBeforeLastEdenCollect(0)
     , m_bytesAllocatedThisCycle(0)
+    , m_webAssemblyFastMemoriesAllocatedThisCycle(0)
     , m_bytesAbandonedSinceLastFullCollect(0)
     , m_maxEdenSize(m_minBytesPerCycle)
     , m_maxHeapSize(m_minBytesPerCycle)
@@ -466,6 +468,23 @@ void Heap::deprecatedReportExtraMemorySlowCase(size_t size)
     reportExtraMemoryAllocatedSlowCase(size);
 }
 
+void Heap::reportWebAssemblyFastMemoriesAllocated(size_t count)
+{
+    didAllocateWebAssemblyFastMemories(count);
+    collectIfNecessaryOrDefer();
+}
+
+bool Heap::webAssemblyFastMemoriesThisCycleAtThreshold() const
+{
+    // WebAssembly fast memories use large amounts of virtual memory and we
+    // don't know how many can exist in this process. We keep track of the most
+    // fast memories that have existed at any point in time. The GC uses this
+    // top watermark as an indication of whether recent allocations should cause
+    // a collection: get too close and we may be close to the actual limit.
+    size_t fastMemoryThreshold = std::max<size_t>(1, Wasm::Memory::maxFastMemoryCount() / 2);
+    return m_webAssemblyFastMemoriesAllocatedThisCycle > fastMemoryThreshold;
+}
+
 void Heap::reportAbandonedObjectGraph()
 {
     // Our clients don't know exactly how much memory they
@@ -2103,6 +2122,7 @@ void Heap::updateAllocationLimits()
     if (verbose) {
         dataLog("\n");
         dataLog("bytesAllocatedThisCycle = ", m_bytesAllocatedThisCycle, "\n");
+        dataLog("webAssemblyFastMemoriesAllocatedThisCycle = ", m_webAssemblyFastMemoriesAllocatedThisCycle, "\n");
     }
     
     // Calculate our current heap size threshold for the purpose of figuring out when we should
@@ -2180,6 +2200,7 @@ void Heap::updateAllocationLimits()
     if (verbose)
         dataLog("sizeAfterLastCollect = ", m_sizeAfterLastCollect, "\n");
     m_bytesAllocatedThisCycle = 0;
+    m_webAssemblyFastMemoriesAllocatedThisCycle = 0;
 
     if (Options::logGC())
         dataLog("=> ", currentHeapSize / 1024, "kb, ");
@@ -2253,6 +2274,11 @@ void Heap::didAllocate(size_t bytes)
     performIncrement(bytes);
 }
 
+void Heap::didAllocateWebAssemblyFastMemories(size_t count)
+{
+    m_webAssemblyFastMemoriesAllocatedThisCycle += count;
+}
+
 bool Heap::isValidAllocation(size_t)
 {
     if (!isValidThreadState(m_vm))
@@ -2305,7 +2331,7 @@ bool Heap::shouldDoFullCollection(std::optional<CollectionScope> scope) const
         return true;
 
     if (!scope)
-        return m_shouldDoFullCollection;
+        return m_shouldDoFullCollection || webAssemblyFastMemoriesThisCycleAtThreshold();
     return *scope == CollectionScope::Full;
 }
 
@@ -2456,7 +2482,8 @@ void Heap::collectIfNecessaryOrDefer(GCDeferralContext* deferralContext)
         if (m_bytesAllocatedThisCycle <= Options::gcMaxHeapSize())
             return;
     } else {
-        if (m_bytesAllocatedThisCycle <= m_maxEdenSize)
+        if (!webAssemblyFastMemoriesThisCycleAtThreshold()
+            && m_bytesAllocatedThisCycle <= m_maxEdenSize)
             return;
     }
 
index d67837a..4b63fc4 100644 (file)
@@ -199,6 +199,17 @@ public:
     void reportExtraMemoryAllocated(size_t);
     JS_EXPORT_PRIVATE void reportExtraMemoryVisited(size_t);
 
+    // Same as above, but for uncommitted virtual memory allocations caused by
+    // WebAssembly fast memories. This is counted separately because virtual
+    // memory is logically a different type of resource than committed physical
+    // memory. We can often allocate huge amounts of virtual memory (think
+    // gigabytes) without adversely affecting regular GC'd memory. At some point
+    // though, too much virtual memory becomes prohibitive and we want to
+    // collect GC-able objects which keep this virtual memory alive.
+    // This is counted in number of fast memories, not bytes.
+    void reportWebAssemblyFastMemoriesAllocated(size_t);
+    bool webAssemblyFastMemoriesThisCycleAtThreshold() const;
+
 #if ENABLE(RESOURCE_USAGE)
     // Use this API to report the subset of extra memory that lives outside this process.
     JS_EXPORT_PRIVATE void reportExternalMemoryVisited(size_t);
@@ -248,6 +259,7 @@ public:
     void deleteAllUnlinkedCodeBlocks(DeleteAllCodeEffort);
 
     void didAllocate(size_t);
+    void didAllocateWebAssemblyFastMemories(size_t);
     bool isPagedOut(double deadline);
     
     const JITStubRoutineSet& jitStubRoutines() { return *m_jitStubRoutines; }
@@ -526,6 +538,7 @@ private:
     size_t m_sizeBeforeLastEdenCollect;
 
     size_t m_bytesAllocatedThisCycle;
+    size_t m_webAssemblyFastMemoriesAllocatedThisCycle;
     size_t m_bytesAbandonedSinceLastFullCollect;
     size_t m_maxEdenSize;
     size_t m_maxHeapSize;
index be6b6f1..7c85a10 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, 2015-2016 Apple Inc. All rights reserved.
+ * Copyright (C) 2008, 2015-2017 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -39,6 +39,7 @@
 #include "Options.h"
 #include "StructureIDTable.h"
 #include "SuperSampler.h"
+#include "WasmMemory.h"
 #include "WasmThunks.h"
 #include "WriteBarrier.h"
 #include <mutex>
@@ -58,6 +59,9 @@ void initializeThreading()
     std::call_once(initializeThreadingOnceFlag, []{
         WTF::initializeThreading();
         Options::initialize();
+#if ENABLE(WEBASSEMBLY)
+        Wasm::Memory::initializePreallocations();
+#endif
 #if ENABLE(WRITE_BARRIER_PROFILING)
         WriteBarrierCounters::initialize();
 #endif
index 3bf7213..0d74f59 100644 (file)
@@ -59,6 +59,15 @@ bool restrictedOptionsEnabled = false;
 #else
 bool restrictedOptionsEnabled = true;
 #endif
+
+ALWAYS_INLINE bool isIOS()
+{
+#if PLATFORM(IOS)
+    return true;
+#else
+    return false;
+#endif
+}
 }
 
 void Options::enableRestrictedOptions(bool enableOrNot)
index 6376b0e..06d0f0d 100644 (file)
@@ -436,9 +436,11 @@ typedef const char* optionString;
     v(size, webAssemblyPartialCompileLimit, 5000, Normal, "Limit on the number of bytes a Wasm::Plan::compile should attempt before checking for other work.") \
     v(unsigned, webAssemblyB3OptimizationLevel, Options::defaultB3OptLevel(), Normal, "B3 Optimization level for Web Assembly modules.") \
     \
-    v(bool, simulateWebAssemblyLowMemory, false, Normal, "If true, the Memory object won't mmap the full 'maximum' range and instead will allocate the minimum required amount.") \
-    v(bool, useWebAssemblyFastMemory, true, Normal, "If true, we will try to use a 32-bit address space with a signal handler to bounds check wasm memory.") \
+    /* FIXME: enable fast memories on iOS and pre-allocate them. https://bugs.webkit.org/show_bug.cgi?id=170774 */ \
+    v(bool, useWebAssemblyFastMemory, !isIOS(), Normal, "If true, we will try to use a 32-bit address space with a signal handler to bounds check wasm memory.") \
+    v(unsigned, webAssemblyFastMemoryRedzonePages, 128, Normal, "WebAssembly fast memories use 4GiB virtual allocations, plus a redzone (counted as multiple of 64KiB WebAssembly pages) at the end to catch reg+imm accesses which exceed 32-bit, anything beyond the redzone is explicitly bounds-checked") \
     v(bool, crashIfWebAssemblyCantFastMemory, false, Normal, "If true, we will crash if we can't obtain fast memory for wasm.") \
+    v(unsigned, webAssemblyFastMemoryPreallocateCount, 0, Normal, "WebAssembly fast memories can be pre-allocated at program startup and remain cached to avoid fragmentation leading to bounds-checked memory. This number is an upper bound on initial allocation as well as total count of fast memories. Zero means no pre-allocation, no caching, and no limit to the number of runtime allocations.") \
     v(bool, useWebAssemblyFastTLS, true, Normal, "If true, we will try to use fast thread-local storage if available on the current platform.")
 
 
index 5ac8639..c83d36e 100644 (file)
@@ -236,6 +236,8 @@ private:
 
     void emitChecksForModOrDiv(B3::Opcode, ExpressionType left, ExpressionType right);
 
+    void fixupPointerPlusOffset(ExpressionType&, uint32_t&);
+
     Value* materializeWasmContext(Procedure&, BasicBlock*);
     void restoreWasmContext(Procedure&, BasicBlock*, Value*);
     void restoreWebAssemblyGlobalState(const MemoryInformation&, Value* instance, Procedure&, BasicBlock*);
@@ -257,6 +259,15 @@ private:
     Value* m_instanceValue; // FIXME: make this lazy https://bugs.webkit.org/show_bug.cgi?id=169792
 };
 
+// Memory accesses in WebAssembly have unsigned 32-bit offsets, whereas they have signed 32-bit offsets in B3.
+void B3IRGenerator::fixupPointerPlusOffset(ExpressionType& ptr, uint32_t& offset)
+{
+    if (static_cast<uint64_t>(offset) > static_cast<uint64_t>(std::numeric_limits<int32_t>::max())) {
+        ptr = m_currentBlock->appendNew<Value>(m_proc, Add, origin(), ptr, m_currentBlock->appendNew<Const64Value>(m_proc, origin(), offset));
+        offset = 0;
+    }
+}
+
 Value* B3IRGenerator::materializeWasmContext(Procedure& proc, BasicBlock* block)
 {
     if (useFastTLSForContext()) {
@@ -340,7 +351,16 @@ B3IRGenerator::B3IRGenerator(const ModuleInformation& info, Procedure& procedure
     if (info.memory) {
         m_proc.setWasmBoundsCheckGenerator([=] (CCallHelpers& jit, GPRReg pinnedGPR, unsigned) {
             AllowMacroScratchRegisterUsage allowScratch(jit);
-            ASSERT_UNUSED(pinnedGPR, m_memorySizeGPR == pinnedGPR);
+            switch (m_mode) {
+            case MemoryMode::BoundsChecking:
+                ASSERT_UNUSED(pinnedGPR, m_memorySizeGPR == pinnedGPR);
+                break;
+            case MemoryMode::Signaling:
+                ASSERT_UNUSED(pinnedGPR, InvalidGPRReg == pinnedGPR);
+                break;
+            case MemoryMode::NumberOfMemoryModes:
+                ASSERT_NOT_REACHED();
+            }
             this->emitExceptionCheck(jit, ExceptionType::OutOfBoundsMemoryAccess);
         });
     }
@@ -526,10 +546,20 @@ auto B3IRGenerator::setGlobal(uint32_t index, ExpressionType value) -> PartialRe
 inline Value* B3IRGenerator::emitCheckAndPreparePointer(ExpressionType pointer, uint32_t offset, uint32_t sizeOfOperation)
 {
     ASSERT(m_memoryBaseGPR);
-    if (m_mode == MemoryMode::BoundsChecking) {
+    switch (m_mode) {
+    case MemoryMode::BoundsChecking:
+        // We're not using signal handling at all, we must therefore check that no memory access exceeds the current memory size.
         ASSERT(m_memorySizeGPR);
         ASSERT(sizeOfOperation + offset > offset);
-        m_currentBlock->appendNew<WasmBoundsCheckValue>(m_proc, origin(), pointer, m_memorySizeGPR, sizeOfOperation + offset - 1);
+        m_currentBlock->appendNew<WasmBoundsCheckValue>(m_proc, origin(), pointer, m_memorySizeGPR, sizeOfOperation + offset - 1, m_info.memory.maximum());
+        break;
+    case MemoryMode::Signaling:
+        // We've virtually mapped 4GiB+redzone fo this memory. Only the user-allocated pages are addressable, contiguously in range [0, current], and everything above is mapped PROT_NONE. We don't need to perform any explicit bounds check in the 4GiB range because WebAssembly register memory accesses are 32-bit. However WebAssembly register+immediate accesses perform the addition in 64-bit which can push an access above the 32-bit limit. The redzone will catch most small immediates, and we'll explicitly bounds check any register + large immediate access.
+        if (offset >= Memory::fastMappedRedzoneBytes())
+            m_currentBlock->appendNew<WasmBoundsCheckValue>(m_proc, origin(), pointer, InvalidGPRReg, sizeOfOperation + offset - 1, m_info.memory.maximum());
+        break;
+    case MemoryMode::NumberOfMemoryModes:
+        RELEASE_ASSERT_NOT_REACHED();
     }
     pointer = m_currentBlock->appendNew<Value>(m_proc, ZExt32, origin(), pointer);
     return m_currentBlock->appendNew<WasmAddressValue>(m_proc, origin(), pointer, m_memoryBaseGPR);
@@ -569,6 +599,8 @@ inline B3::Kind B3IRGenerator::memoryKind(B3::Opcode memoryOp)
 
 inline Value* B3IRGenerator::emitLoadOp(LoadOpType op, ExpressionType pointer, uint32_t offset)
 {
+    fixupPointerPlusOffset(pointer, offset);
+
     switch (op) {
     case LoadOpType::I32Load8S: {
         return m_currentBlock->appendNew<MemoryValue>(m_proc, memoryKind(Load8S), origin(), pointer, offset);
@@ -706,6 +738,8 @@ inline uint32_t sizeOfStoreOp(StoreOpType op)
 
 inline void B3IRGenerator::emitStoreOp(StoreOpType op, ExpressionType pointer, ExpressionType value, uint32_t offset)
 {
+    fixupPointerPlusOffset(pointer, offset);
+
     switch (op) {
     case StoreOpType::I64Store8:
         value = m_currentBlock->appendNew<Value>(m_proc, Trunc, origin(), value);
@@ -1261,6 +1295,12 @@ static void createJSToWasmWrapper(CompilationContext& compilationContext, WasmIn
 
     compilationContext.jsEntrypointToWasmEntrypointCall = jit.call();
 
+    if (!!info.memory) {
+        // Resetting the register prevents the GC from mistakenly thinking that the context is still live.
+        GPRReg baseMemory = pinnedRegs.baseMemoryPointer;
+        jit.move(CCallHelpers::TrustedImm32(0), baseMemory);
+    }
+
     for (const RegisterAtOffset& regAtOffset : registersToSpill) {
         GPRReg reg = regAtOffset.reg().gpr();
         ASSERT(reg != GPRInfo::returnValueGPR);
index 58bf8a2..898104b 100644 (file)
@@ -70,15 +70,7 @@ static void trapHandler(int signal, siginfo_t* sigInfo, void* ucontext)
         {
             void* faultingAddress = sigInfo->si_addr;
             dataLogLnIf(verbose, "checking faulting address: ", RawPointer(faultingAddress), " is in an active fast memory");
-            LockHolder locker(memoryLock);
-            auto& activeFastMemories = viewActiveFastMemories(locker);
-            for (void* activeMemory : activeFastMemories) {
-                dataLogLnIf(verbose, "checking fast memory at: ", RawPointer(activeMemory));
-                if (activeMemory <= faultingAddress && faultingAddress < static_cast<char*>(activeMemory) + fastMemoryMappedBytes) {
-                    faultedInActiveFastMemory = true;
-                    break;
-                }
-            }
+            faultedInActiveFastMemory = Wasm::Memory::addressIsInActiveFastMemory(faultingAddress);
         }
         if (faultedInActiveFastMemory) {
             dataLogLnIf(verbose, "found active fast memory for faulting address");
index 7ef3d4f..9e8d6e0 100644 (file)
 #if ENABLE(WEBASSEMBLY)
 
 #include "VM.h"
-#include "WasmFaultSignalHandler.h"
 #include "WasmThunks.h"
 
-#include <wtf/HexNumber.h>
+#include <atomic>
+#include <wtf/MonotonicTime.h>
 #include <wtf/NeverDestroyed.h>
+#include <wtf/Platform.h>
 #include <wtf/PrintStream.h>
-#include <wtf/text/WTFString.h>
+#include <wtf/VMTags.h>
 
 namespace JSC { namespace Wasm {
 
+// FIXME: We could be smarter about memset / mmap / madvise. https://bugs.webkit.org/show_bug.cgi?id=170343
+// FIXME: Give up some of the cached fast memories if the GC determines it's easy to get them back, and they haven't been used in a while. https://bugs.webkit.org/show_bug.cgi?id=170773
+// FIXME: Limit slow memory size. https://bugs.webkit.org/show_bug.cgi?id=170825
+
 namespace {
-const bool verbose = false;
+constexpr bool verbose = false;
+
+NEVER_INLINE NO_RETURN_DUE_TO_CRASH void webAssemblyCouldntGetFastMemory() { CRASH(); }
+NEVER_INLINE NO_RETURN_DUE_TO_CRASH void webAssemblyCouldntUnmapMemoryBytes() { CRASH(); }
+NEVER_INLINE NO_RETURN_DUE_TO_CRASH void webAssemblyCouldntUnprotectMemory() { CRASH(); }
+
+void* mmapBytes(size_t bytes)
+{
+#if OS(DARWIN)
+    int fd = VM_TAG_FOR_WEBASSEMBLY_MEMORY;
+#else
+    int fd = -1;
+#endif
+
+    void* location = mmap(nullptr, bytes, PROT_NONE, MAP_PRIVATE | MAP_ANON, fd, 0);
+    return location == MAP_FAILED ? nullptr : location;
 }
 
-static NEVER_INLINE NO_RETURN_DUE_TO_CRASH void webAssemblyCouldntGetFastMemory()
+void munmapBytes(void* memory, size_t size)
 {
-    CRASH();
+    if (UNLIKELY(munmap(memory, size)))
+        webAssemblyCouldntUnmapMemoryBytes();
 }
 
-inline bool mmapBytes(size_t bytes, void*& memory)
+void zeroAndUnprotectBytes(void* start, size_t bytes)
 {
-    dataLogIf(verbose, "Attempting to mmap ", bytes, " bytes: ");
-    // FIXME: It would be nice if we had a VM tag for wasm memory. https://bugs.webkit.org/show_bug.cgi?id=163600
-    void* result = mmap(nullptr, bytes, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
-    if (result == MAP_FAILED) {
-        dataLogLnIf(verbose, "failed");
-        return false;
+    if (bytes) {
+        dataLogLnIf(verbose, "Zeroing and unprotecting ", bytes, " from ", RawPointer(start));
+        // FIXME: We could be smarter about memset / mmap / madvise. Here, we may not need to act synchronously, or maybe we can memset+unprotect smaller ranges of memory (which would pay off if not all the writable memory was actually physically backed: memset forces physical backing only to unprotect it right after). https://bugs.webkit.org/show_bug.cgi?id=170343
+        memset(start, 0, bytes);
+        if (UNLIKELY(mprotect(start, bytes, PROT_NONE)))
+            webAssemblyCouldntUnprotectMemory();
     }
-    dataLogLnIf(verbose, "succeeded");
-    memory = result;
-    return true;
 }
 
-const char* makeString(MemoryMode mode)
+// Allocate fast memories very early at program startup and cache them. The fast memories use significant amounts of virtual uncommitted address space, reducing the likelihood that we'll obtain any if we wait to allocate them.
+// We still try to allocate fast memories at runtime, and will cache them when relinquished up to the preallocation limit.
+// Note that this state is per-process, not per-VM.
+// We use simple static globals which don't allocate to avoid early fragmentation and to keep management to the bare minimum. We avoid locking because fast memories use segfault signal handling to handle out-of-bounds accesses. This requires identifying if the faulting address is in a fast memory range, which should avoid acquiring a lock lest the actual signal was caused by this very code while it already held the lock.
+// Speed and contention don't really matter here, but simplicity does. We therefore use straightforward FIFOs for our cache, and linear traversal for the list of currently active fast memories.
+constexpr size_t fastMemoryCacheHardLimit { 16 };
+constexpr size_t fastMemoryAllocationSoftLimit { 32 }; // Prevents filling up the virtual address space.
+static_assert(fastMemoryAllocationSoftLimit >= fastMemoryCacheHardLimit, "The cache shouldn't be bigger than the total number we'll ever allocate");
+size_t fastMemoryPreallocateCount { 0 };
+std::atomic<void*> fastMemoryCache[fastMemoryCacheHardLimit] = { ATOMIC_VAR_INIT(nullptr) };
+std::atomic<void*> currentlyActiveFastMemories[fastMemoryAllocationSoftLimit] = { ATOMIC_VAR_INIT(nullptr) };
+std::atomic<size_t> currentlyAllocatedFastMemories = ATOMIC_VAR_INIT(0);
+std::atomic<size_t> observedMaximumFastMemory = ATOMIC_VAR_INIT(0);
+
+void* tryGetCachedFastMemory()
 {
-    switch (mode) {
-    case MemoryMode::BoundsChecking: return "BoundsChecking";
-    case MemoryMode::Signaling: return "Signaling";
-    case MemoryMode::NumberOfMemoryModes: break;
+    for (unsigned idx = 0; idx < fastMemoryPreallocateCount; ++idx) {
+        if (void* previous = fastMemoryCache[idx].exchange(nullptr, std::memory_order_acq_rel))
+            return previous;
     }
-    RELEASE_ASSERT_NOT_REACHED();
-    return "";
+    return nullptr;
 }
 
-static const unsigned maxFastMemories = 4;
-static unsigned allocatedFastMemories { 0 };
-StaticLock memoryLock;
-inline Deque<void*, maxFastMemories>& availableFastMemories(const AbstractLocker&)
+bool tryAddToCachedFastMemory(void* memory)
 {
-    static NeverDestroyed<Deque<void*, maxFastMemories>> availableFastMemories;
-    return availableFastMemories;
+    for (unsigned i = 0; i < fastMemoryPreallocateCount; ++i) {
+        void* expected = nullptr;
+        if (fastMemoryCache[i].compare_exchange_strong(expected, memory, std::memory_order_acq_rel)) {
+            dataLogLnIf(verbose, "Cached fast memory ", RawPointer(memory));
+            return true;
+        }
+    }
+    return false;
 }
 
-inline HashSet<void*>& activeFastMemories(const AbstractLocker&)
+bool tryAddToCurrentlyActiveFastMemories(void* memory)
 {
-    static NeverDestroyed<HashSet<void*>> activeFastMemories;
-    return activeFastMemories;
+    for (size_t idx = 0; idx < fastMemoryAllocationSoftLimit; ++idx) {
+        void* expected = nullptr;
+        if (currentlyActiveFastMemories[idx].compare_exchange_strong(expected, memory, std::memory_order_acq_rel))
+            return true;
+    }
+    return false;
 }
 
-const HashSet<void*>& viewActiveFastMemories(const AbstractLocker& locker)
+void removeFromCurrentlyActiveFastMemories(void* memory)
 {
-    return activeFastMemories(locker);
+    for (size_t idx = 0; idx < fastMemoryAllocationSoftLimit; ++idx) {
+        void* expected = memory;
+        if (currentlyActiveFastMemories[idx].compare_exchange_strong(expected, nullptr, std::memory_order_acq_rel))
+            return;
+    }
+    RELEASE_ASSERT_NOT_REACHED();
 }
 
-inline bool tryGetFastMemory(VM& vm, void*& memory, size_t& mappedCapacity, MemoryMode& mode)
+void* tryGetFastMemory(VM& vm)
 {
-    auto dequeFastMemory = [&] () -> bool {
-        // FIXME: We should eventually return these to the OS if we go some number of GCs
-        // without using them.
-        LockHolder locker(memoryLock);
-        if (!availableFastMemories(locker).isEmpty()) {
-            memory = availableFastMemories(locker).takeFirst();
-            auto result = activeFastMemories(locker).add(memory);
-            ASSERT_UNUSED(result, result.isNewEntry);
-            mappedCapacity = fastMemoryMappedBytes;
-            mode = MemoryMode::Signaling;
-            return true;
+    void* memory = nullptr;
+
+    if (LIKELY(Options::useWebAssemblyFastMemory())) {
+        memory = tryGetCachedFastMemory();
+        if (memory)
+            dataLogLnIf(verbose, "tryGetFastMemory re-using ", RawPointer(memory));
+        else {
+            // No memory was available in the cache. Maybe waiting on GC will find a free one.
+            // FIXME collectSync(Full) and custom eager destruction of wasm memories could be better. For now use collectAllGarbage. Also, nothing tells us the current VM is holding onto fast memories. https://bugs.webkit.org/show_bug.cgi?id=170748
+            dataLogLnIf(verbose, "tryGetFastMemory waiting on GC and retrying");
+            vm.heap.collectAllGarbage();
+            memory = tryGetCachedFastMemory();
+            dataLogLnIf(verbose, "tryGetFastMemory waited on GC and retried ", memory? "successfully" : "unseccessfully");
         }
-        return false;
-    };
 
-    auto fail = [] () -> bool {
+        // The soft limit is inherently racy because checking+allocation isn't atomic. Exceeding it slightly is fine.
+        bool atAllocationSoftLimit = currentlyAllocatedFastMemories.load(std::memory_order_acquire) >= fastMemoryAllocationSoftLimit;
+        dataLogLnIf(verbose && atAllocationSoftLimit, "tryGetFastMemory reached allocation soft limit of ", fastMemoryAllocationSoftLimit);
+
+        if (!memory && !atAllocationSoftLimit) {
+            memory = mmapBytes(Memory::fastMappedBytes());
+            if (memory) {
+                size_t currentlyAllocated = 1 + currentlyAllocatedFastMemories.fetch_add(1, std::memory_order_acq_rel);
+                size_t currentlyObservedMaximum = observedMaximumFastMemory.load(std::memory_order_acquire);
+                if (currentlyAllocated > currentlyObservedMaximum) {
+                    size_t expected = currentlyObservedMaximum;
+                    bool success = observedMaximumFastMemory.compare_exchange_strong(expected, currentlyAllocated, std::memory_order_acq_rel);
+                    if (success)
+                        dataLogLnIf(verbose, "tryGetFastMemory currently observed maximum is now ", currentlyAllocated);
+                    else
+                        // We lost the update race, but the counter is monotonic so the winner must have updated the value to what we were going to update it to, or multiple winners did so.
+                        ASSERT(expected >= currentlyAllocated);
+                }
+                dataLogLnIf(verbose, "tryGetFastMemory allocated ", RawPointer(memory), ", currently allocated is ", currentlyAllocated);
+            }
+        }
+    }
+
+    if (memory) {
+        if (UNLIKELY(!tryAddToCurrentlyActiveFastMemories(memory))) {
+            // We got a memory, but reached the allocation soft limit *and* all of the allocated memories are active, none are cached. That's a bummer, we have to get rid of our memory. We can't just hold on to it because the list of active fast memories must be precise.
+            dataLogLnIf(verbose, "tryGetFastMemory found a fast memory but had to give it up");
+            munmapBytes(memory, Memory::fastMappedBytes());
+            currentlyAllocatedFastMemories.fetch_sub(1, std::memory_order_acq_rel);
+            memory = nullptr;
+        }
+    }
+
+    if (!memory) {
+        dataLogLnIf(verbose, "tryGetFastMemory couldn't re-use or allocate a fast memory");
         if (UNLIKELY(Options::crashIfWebAssemblyCantFastMemory()))
             webAssemblyCouldntGetFastMemory();
-        return false;
-    };
+    }
 
-    // We might GC here so we should be holding the API lock.
-    // FIXME: We should be able to syncronously trigger the GC from another thread.
-    ASSERT(vm.currentThreadIsHoldingAPILock());
-    if (UNLIKELY(!fastMemoryEnabled()))
-        return fail();
+    return memory;
+}
 
-    // We need to be sure we have a stub prior to running code.
-    if (UNLIKELY(!Thunks::singleton().stub(throwExceptionFromWasmThunkGenerator)))
-        return fail();
+void* tryGetSlowMemory(size_t bytes)
+{
+    void* memory = mmapBytes(bytes);
+    dataLogLnIf(memory && verbose, "Obtained slow memory ", RawPointer(memory), " with capacity ", bytes);
+    dataLogLnIf(!memory && verbose, "Failed obtaining slow memory with capacity ", bytes);
+    return memory;
+}
 
-    ASSERT(allocatedFastMemories <= maxFastMemories);
-    if (dequeFastMemory())
-        return true;
+void relinquishMemory(void* memory, size_t writableSize, size_t mappedCapacity, MemoryMode mode)
+{
+    switch (mode) {
+    case MemoryMode::Signaling: {
+        RELEASE_ASSERT(Options::useWebAssemblyFastMemory());
+        RELEASE_ASSERT(mappedCapacity == Memory::fastMappedBytes());
 
-    // If we have allocated all the fast memories... too bad.
-    if (allocatedFastMemories == maxFastMemories) {
-        // There is a reasonable chance that another module has died but has not been collected yet. Don't lose hope yet!
-        vm.heap.collectAllGarbage();
-        if (dequeFastMemory())
-            return true;
-        return fail();
+        // This memory cannot cause a trap anymore.
+        removeFromCurrentlyActiveFastMemories(memory);
+
+        // We may cache fast memories. Assuming we will, we have to reset them before inserting them into the cache.
+        zeroAndUnprotectBytes(memory, writableSize);
+
+        if (tryAddToCachedFastMemory(memory))
+            return;
+
+        dataLogLnIf(verbose, "relinquishMemory unable to cache fast memory, freeing instead ", RawPointer(memory));
+        munmapBytes(memory, Memory::fastMappedBytes());
+        currentlyAllocatedFastMemories.fetch_sub(1, std::memory_order_acq_rel);
+
+        return;
     }
 
-    if (mmapBytes(fastMemoryMappedBytes, memory)) {
-        mappedCapacity = fastMemoryMappedBytes;
-        mode = MemoryMode::Signaling;
-        LockHolder locker(memoryLock);
-        allocatedFastMemories++;
-        auto result = activeFastMemories(locker).add(memory);
-        ASSERT_UNUSED(result, result.isNewEntry);
+    case MemoryMode::BoundsChecking:
+        dataLogLnIf(verbose, "relinquishFastMemory freeing slow memory ", RawPointer(memory));
+        munmapBytes(memory, mappedCapacity);
+        return;
+
+    case MemoryMode::NumberOfMemoryModes:
+        break;
     }
 
-    if (memory)
-        return true;
+    RELEASE_ASSERT_NOT_REACHED();
+}
 
-    return fail();
+bool makeNewMemoryReadWriteOrRelinquish(void* memory, size_t initialBytes, size_t mappedCapacityBytes, MemoryMode mode)
+{
+    ASSERT(memory && initialBytes <= mappedCapacityBytes);
+    if (initialBytes) {
+        dataLogLnIf(verbose, "Marking WebAssembly memory's ", RawPointer(memory), "'s initial ", initialBytes, " bytes as read+write");
+        if (mprotect(memory, initialBytes, PROT_READ | PROT_WRITE)) {
+            const char* why = strerror(errno);
+            dataLogLnIf(verbose, "Failed making memory ", RawPointer(memory), " readable and writable: ", why);
+            relinquishMemory(memory, 0, mappedCapacityBytes, mode);
+            return false;
+        }
+    }
+    return true;
+}
+
+} // anonymous namespace
+
+
+const char* makeString(MemoryMode mode)
+{
+    switch (mode) {
+    case MemoryMode::BoundsChecking: return "BoundsChecking";
+    case MemoryMode::Signaling: return "Signaling";
+    case MemoryMode::NumberOfMemoryModes: break;
+    }
+    RELEASE_ASSERT_NOT_REACHED();
+    return "";
 }
 
-inline void releaseFastMemory(void*& memory, size_t writableSize, size_t mappedCapacity, MemoryMode mode)
+void Memory::initializePreallocations()
 {
-    if (mode != MemoryMode::Signaling || !memory)
+    if (UNLIKELY(!Options::useWebAssemblyFastMemory()))
         return;
 
-    RELEASE_ASSERT(memory && mappedCapacity == fastMemoryMappedBytes);
-    ASSERT(fastMemoryEnabled());
+    // Races cannot occur in this function: it is only called at program initialization, before WebAssembly can be invoked.
+
+    const auto startTime = MonotonicTime::now();
+    const size_t desiredFastMemories = std::min<size_t>(Options::webAssemblyFastMemoryPreallocateCount(), fastMemoryCacheHardLimit);
+
+    // Start off trying to allocate fast memories contiguously so they don't fragment each other. This can fail if the address space is otherwise fragmented. In that case, go for smaller contiguous allocations. We'll eventually get individual non-contiguous fast memories allocated, or we'll just be unable to fit a single one at which point we give up.
+    auto allocateContiguousFastMemories = [&] (size_t numContiguous) -> bool {
+        if (void *memory = mmapBytes(Memory::fastMappedBytes() * numContiguous)) {
+            for (size_t subMemory = 0; subMemory < numContiguous; ++subMemory) {
+                void* startAddress = reinterpret_cast<char*>(memory) + Memory::fastMappedBytes() * subMemory + subMemory;
+                bool inserted = false;
+                for (size_t cacheEntry = 0; cacheEntry < fastMemoryCacheHardLimit; ++cacheEntry) {
+                    if (fastMemoryCache[cacheEntry].load(std::memory_order_relaxed) == nullptr) {
+                        fastMemoryCache[cacheEntry].store(startAddress, std::memory_order_relaxed);
+                        inserted = true;
+                        break;
+                    }
+                }
+                RELEASE_ASSERT(inserted);
+            }
+            return true;
+        }
+        return false;
+    };
+
+    size_t fragments = 0;
+    size_t numFastMemories = 0;
+    size_t contiguousMemoryAllocationAttempt = desiredFastMemories;
+    while (numFastMemories != desiredFastMemories && contiguousMemoryAllocationAttempt != 0) {
+        if (allocateContiguousFastMemories(contiguousMemoryAllocationAttempt)) {
+            numFastMemories += contiguousMemoryAllocationAttempt;
+            contiguousMemoryAllocationAttempt = std::min(contiguousMemoryAllocationAttempt - 1, desiredFastMemories - numFastMemories);
+        } else
+            --contiguousMemoryAllocationAttempt;
+        ++fragments;
+    }
+
+    fastMemoryPreallocateCount = numFastMemories;
+    currentlyAllocatedFastMemories.store(fastMemoryPreallocateCount, std::memory_order_relaxed);
+    observedMaximumFastMemory.store(fastMemoryPreallocateCount, std::memory_order_relaxed);
 
-    memset(memory, 0, writableSize);
-    if (mprotect(memory, writableSize, PROT_NONE))
-        CRASH();
+    const auto endTime = MonotonicTime::now();
 
-    LockHolder locker(memoryLock);
-    bool result = activeFastMemories(locker).remove(memory);
-    ASSERT_UNUSED(result, result);
-    ASSERT(availableFastMemories(locker).size() < allocatedFastMemories);
-    availableFastMemories(locker).append(memory);
-    memory = nullptr;
+    for (size_t cacheEntry = 0; cacheEntry < fastMemoryPreallocateCount; ++cacheEntry) {
+        void* startAddress = fastMemoryCache[cacheEntry].load(std::memory_order_relaxed);
+        ASSERT(startAddress);
+        dataLogLnIf(verbose, "Pre-allocation of WebAssembly fast memory at ", RawPointer(startAddress));
+    }
+    dataLogLnIf(verbose, "Pre-allocated ", fastMemoryPreallocateCount, " WebAssembly fast memories in ", fastMemoryPreallocateCount == 0 ? 0 : fragments, fragments == 1 ? " fragment, took " : " fragments, took ", endTime - startTime);
 }
 
 Memory::Memory(PageCount initial, PageCount maximum)
@@ -194,87 +342,96 @@ Memory::Memory(void* memory, PageCount initial, PageCount maximum, size_t mapped
     dataLogLnIf(verbose, "Memory::Memory allocating ", *this);
 }
 
-RefPtr<Memory> Memory::createImpl(VM& vm, PageCount initial, PageCount maximum, std::optional<MemoryMode> requiredMode)
+RefPtr<Memory> Memory::create(VM& vm, PageCount initial, PageCount maximum)
 {
+    ASSERT(initial);
     RELEASE_ASSERT(!maximum || maximum >= initial); // This should be guaranteed by our caller.
 
-    MemoryMode mode = requiredMode ? *requiredMode : MemoryMode::BoundsChecking;
-    const size_t size = initial.bytes();
-    size_t mappedCapacity = maximum ? maximum.bytes() : PageCount::max().bytes();
-    void* memory = nullptr;
+    const size_t initialBytes = initial.bytes();
+    const size_t maximumBytes = maximum ? maximum.bytes() : 0;
+    size_t mappedCapacityBytes = 0;
+    MemoryMode mode;
 
-    auto makeEmptyMemory = [&] () -> RefPtr<Memory> {
-        if (mode == MemoryMode::Signaling)
-            return nullptr;
+    // We need to be sure we have a stub prior to running code.
+    if (UNLIKELY(!Thunks::singleton().stub(throwExceptionFromWasmThunkGenerator)))
+        return nullptr;
 
+    if (maximum && !maximumBytes) {
+        // User specified a zero maximum, initial size must also be zero.
+        RELEASE_ASSERT(!initialBytes);
         return adoptRef(new Memory(initial, maximum));
-    };
-
-    if (!mappedCapacity) {
-        // This means we specified a zero as maximum (which means we also have zero as initial size).
-        RELEASE_ASSERT(!size);
-        dataLogLnIf(verbose, "Memory::create allocating nothing");
-        return makeEmptyMemory();
     }
 
-    bool canUseFastMemory = !requiredMode || requiredMode == MemoryMode::Signaling;
-    if (!canUseFastMemory || !tryGetFastMemory(vm, memory, mappedCapacity, mode)) {
-        if (mode == MemoryMode::Signaling)
-            return nullptr;
-
-        if (Options::simulateWebAssemblyLowMemory() ? true : !mmapBytes(mappedCapacity, memory)) {
-            // Try again with a different number.
-            dataLogLnIf(verbose, "Memory::create mmap failed once for capacity, trying again");
-            mappedCapacity = size;
-            if (!mappedCapacity) {
-                dataLogLnIf(verbose, "Memory::create mmap not trying again because size is zero");
-                return makeEmptyMemory();
-            }
+    void* memory = nullptr;
 
-            if (!mmapBytes(mappedCapacity, memory)) {
-                dataLogLnIf(verbose, "Memory::create mmap failed twice");
-                return nullptr;
-            }
+    // First try fast memory, because they're fast. Fast memory is suitable for any initial / maximum.
+    memory = tryGetFastMemory(vm);
+    if (memory) {
+        mappedCapacityBytes = Memory::fastMappedBytes();
+        mode = MemoryMode::Signaling;
+    }
+
+    // If we can't get a fast memory but the user expressed the intent to grow memory up to a certain maximum then we should try to honor that desire. It'll mean that grow is more likely to succeed, and won't require remapping.
+    if (!memory && maximum) {
+        memory = tryGetSlowMemory(maximumBytes);
+        if (memory) {
+            mappedCapacityBytes = maximumBytes;
+            mode = MemoryMode::BoundsChecking;
         }
     }
 
-    ASSERT(memory && size <= mappedCapacity);
-    if (mprotect(memory, size, PROT_READ | PROT_WRITE)) {
-        // FIXME: should this ever occur? https://bugs.webkit.org/show_bug.cgi?id=169890
-        dataLogLnIf(verbose, "Memory::create mprotect failed");
-        releaseFastMemory(memory, 0, mappedCapacity, mode);
+    // We're stuck with a slow memory which may be slower or impossible to grow.
+    if (!memory) {
+        memory = tryGetSlowMemory(initialBytes);
         if (memory) {
-            if (munmap(memory, mappedCapacity))
-                CRASH();
+            mappedCapacityBytes = initialBytes;
+            mode = MemoryMode::BoundsChecking;
         }
-        return nullptr;
     }
 
-    dataLogLnIf(verbose, "Memory::create mmap succeeded");
-    return adoptRef(new Memory(memory, initial, maximum, mappedCapacity, mode));
+    if (!memory)
+        return nullptr;
+
+    if (!makeNewMemoryReadWriteOrRelinquish(memory, initialBytes, mappedCapacityBytes, mode))
+        return nullptr;
+
+    return adoptRef(new Memory(memory, initial, maximum, mappedCapacityBytes, mode));
 }
 
-RefPtr<Memory> Memory::create(VM& vm, PageCount initial, PageCount maximum, std::optional<MemoryMode> mode)
+Memory::~Memory()
 {
-    RELEASE_ASSERT(!maximum || maximum >= initial); // This should be guaranteed by our caller.
-    RefPtr<Memory> result = createImpl(vm, initial, maximum, mode);
-    if (result) {
-        if (result->mode() == MemoryMode::Signaling)
-            RELEASE_ASSERT(result->m_mappedCapacity == fastMemoryMappedBytes);
-        if (mode)
-            ASSERT(*mode == result->mode());
+    if (m_memory) {
+        dataLogLnIf(verbose, "Memory::~Memory ", *this);
+        relinquishMemory(m_memory, m_size, m_mappedCapacity, m_mode);
     }
-    return result;
 }
 
-Memory::~Memory()
+size_t Memory::fastMappedRedzoneBytes()
 {
-    dataLogLnIf(verbose, "Memory::~Memory ", *this);
-    releaseFastMemory(m_memory, m_size, m_mappedCapacity, m_mode);
-    if (m_memory) {
-        if (munmap(m_memory, m_mappedCapacity))
-            CRASH();
+    return static_cast<size_t>(PageCount::pageSize) * Options::webAssemblyFastMemoryRedzonePages();
+}
+
+size_t Memory::fastMappedBytes()
+{
+    static_assert(sizeof(uint64_t) == sizeof(size_t), "We rely on allowing the maximum size of Memory we map to be 2^32 + redzone which is larger than fits in a 32-bit integer that we'd pass to mprotect if this didn't hold.");
+    return static_cast<size_t>(std::numeric_limits<uint32_t>::max()) + fastMappedRedzoneBytes();
+}
+
+size_t Memory::maxFastMemoryCount()
+{
+    // The order can be relaxed here because we provide a monotonically-increasing estimate. A concurrent observer could see a slightly out-of-date value but can't tell that they did.
+    return observedMaximumFastMemory.load(std::memory_order_relaxed);
+}
+
+bool Memory::addressIsInActiveFastMemory(void* address)
+{
+    // This cannot race in any meaningful way: the thread which calls this function wants to know if a fault it received at a particular address is in a fast memory. That fast memory must therefore be active in that thread. It cannot be added or removed from the list of currently active fast memories. Other memories being added / removed concurrently are inconsequential.
+    for (size_t idx = 0; idx < fastMemoryAllocationSoftLimit; ++idx) {
+        char* start = static_cast<char*>(currentlyActiveFastMemories[idx].load(std::memory_order_acquire));
+        if (start <= address && address <= start + fastMappedBytes())
+            return true;
     }
+    return false;
 }
 
 bool Memory::grow(PageCount newSize)
@@ -301,8 +458,11 @@ bool Memory::grow(PageCount newSize)
     }
 
     if (m_memory && desiredSize <= m_mappedCapacity) {
-        if (mprotect(static_cast<uint8_t*>(m_memory) + m_size, static_cast<size_t>(desiredSize - m_size), PROT_READ | PROT_WRITE)) {
-            // FIXME: should this ever occur? https://bugs.webkit.org/show_bug.cgi?id=169890
+        uint8_t* startAddress = static_cast<uint8_t*>(m_memory) + m_size;
+        size_t extraBytes = desiredSize - m_size;
+        RELEASE_ASSERT(extraBytes);
+        dataLogLnIf(verbose, "Marking WebAssembly memory's ", RawPointer(m_memory), " as read+write in range [", RawPointer(startAddress), ", ", RawPointer(startAddress + extraBytes), ")");
+        if (mprotect(startAddress, extraBytes, PROT_READ | PROT_WRITE)) {
             dataLogLnIf(verbose, "Memory::grow in-place failed ", *this);
             return false;
         }
@@ -316,16 +476,20 @@ bool Memory::grow(PageCount newSize)
     RELEASE_ASSERT(mode() != MemoryMode::Signaling);
 
     // Otherwise, let's try to make some new memory.
-    // FIXME: It would be nice if we had a VM tag for wasm memory. https://bugs.webkit.org/show_bug.cgi?id=163600
-    void* newMemory = mmap(nullptr, desiredSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
-    if (newMemory == MAP_FAILED)
+    // FIXME mremap would be nice https://bugs.webkit.org/show_bug.cgi?id=170557
+    // FIXME should we over-allocate here? https://bugs.webkit.org/show_bug.cgi?id=170826
+    void* newMemory = tryGetSlowMemory(desiredSize);
+    if (!newMemory)
+        return false;
+
+    if (!makeNewMemoryReadWriteOrRelinquish(newMemory, desiredSize, desiredSize, mode()))
         return false;
 
     if (m_memory) {
         memcpy(newMemory, m_memory, m_size);
-        bool success = !munmap(m_memory, m_mappedCapacity);
-        RELEASE_ASSERT(success);
+        relinquishMemory(m_memory, m_size, m_size, m_mode);
     }
+
     m_memory = newMemory;
     m_mappedCapacity = desiredSize;
     m_size = desiredSize;
index b02451d..55345cf 100644 (file)
@@ -29,8 +29,6 @@
 
 #include "WasmPageCount.h"
 
-#include <wtf/HashSet.h>
-#include <wtf/Optional.h>
 #include <wtf/RefCounted.h>
 #include <wtf/RefPtr.h>
 
@@ -61,11 +59,17 @@ public:
 
     explicit operator bool() const { return !!m_memory; }
 
-    static RefPtr<Memory> create(VM&, PageCount initial, PageCount maximum, std::optional<MemoryMode> requiredMode = std::nullopt);
+    static void initializePreallocations();
+    static RefPtr<Memory> create(VM&, PageCount initial, PageCount maximum);
 
     Memory() = default;
     ~Memory();
 
+    static size_t fastMappedRedzoneBytes();
+    static size_t fastMappedBytes(); // Includes redzone.
+    static size_t maxFastMemoryCount();
+    static bool addressIsInActiveFastMemory(void*);
+
     void* memory() const { return m_memory; }
     size_t size() const { return m_size; }
     PageCount sizeInPages() const { return PageCount::fromBytes(m_size); }
@@ -81,7 +85,6 @@ public:
 
     void check() {  ASSERT(!deletionHasBegun()); }
 private:
-    static RefPtr<Memory> createImpl(VM&, PageCount initial, PageCount maximum, std::optional<MemoryMode> requiredMode = std::nullopt);
     Memory(void* memory, PageCount initial, PageCount maximum, size_t mappedCapacity, MemoryMode);
     Memory(PageCount initial, PageCount maximum);
 
@@ -94,11 +97,17 @@ private:
     MemoryMode m_mode { MemoryMode::BoundsChecking };
 };
 
-static_assert(sizeof(uint64_t) == sizeof(size_t), "We rely on allowing the maximum size of Memory we map to be 2^33 which is larger than fits in a 32-bit integer that we'd pass to mprotect if this didn't hold.");
+} } // namespace JSC::Wasm
+
+#else
 
-const size_t fastMemoryMappedBytes = (static_cast<size_t>(std::numeric_limits<uint32_t>::max()) + 1) * 2; // pointer max + offset max. This is all we need since a load straddling readable memory will trap.
-extern StaticLock memoryLock;
-const HashSet<void*>& viewActiveFastMemories(const AbstractLocker&);
+namespace JSC { namespace Wasm {
+
+class Memory {
+public:
+    static size_t maxFastMemoryCount() { return 0; }
+    static bool addressIsInActiveFastMemory(void*) { return false; }
+};
 
 } } // namespace JSC::Wasm
 
index 6547d62..9cd47d8 100644 (file)
@@ -63,7 +63,7 @@ void JSWebAssemblyInstance::finishCreation(VM& vm, JSWebAssemblyModule* module,
     ASSERT(inherits(vm, info()));
 
     m_module.set(vm, this, module);
-    const size_t extraMemorySize = module->moduleInformation().globals.size() * sizeof(Register);
+    const size_t extraMemorySize = globalMemoryByteSize();
     m_globals = MallocPtr<uint64_t>::malloc(extraMemorySize);
     heap()->reportExtraMemoryAllocated(extraMemorySize);
 
@@ -89,7 +89,7 @@ void JSWebAssemblyInstance::visitChildren(JSCell* cell, SlotVisitor& visitor)
     visitor.append(thisObject->m_memory);
     visitor.append(thisObject->m_table);
     visitor.append(thisObject->m_callee);
-    visitor.reportExtraMemoryVisited(thisObject->module()->moduleInformation().globals.size());
+    visitor.reportExtraMemoryVisited(thisObject->globalMemoryByteSize());
     for (unsigned i = 0; i < thisObject->m_numImportFunctions; ++i)
         visitor.append(thisObject->importFunctions()[i]);
 }
@@ -350,6 +350,10 @@ JSWebAssemblyInstance* JSWebAssemblyInstance::create(VM& vm, ExecState* exec, JS
     return instance;
 }
 
+size_t JSWebAssemblyInstance::globalMemoryByteSize() const
+{
+    return m_module->moduleInformation().globals.size() * sizeof(Register);
+}
 
 } // namespace JSC
 
index 8551fe8..53ed99f 100644 (file)
@@ -92,6 +92,7 @@ protected:
 private:
     VM* m_vm;
     WriteBarrier<JSObject>* importFunctions() { return bitwise_cast<WriteBarrier<JSObject>*>(bitwise_cast<char*>(this) + offsetOfImportFunctions()); }
+    size_t globalMemoryByteSize() const;
 
     WriteBarrier<JSWebAssemblyModule> m_module;
     WriteBarrier<JSWebAssemblyCodeBlock> m_codeBlock;
index 3369c0e..c6d78cf 100644 (file)
@@ -117,6 +117,7 @@ Wasm::PageCount JSWebAssemblyMemory::grow(VM& vm, ExecState* exec, uint32_t delt
     }
 
     memory().check();
+    // FIXME Should we report extra memory to the GC on allocation / grow / visit? https://bugs.webkit.org/show_bug.cgi?id=170690
     return oldPageCount;
 }
 
@@ -124,6 +125,8 @@ void JSWebAssemblyMemory::finishCreation(VM& vm)
 {
     Base::finishCreation(vm);
     ASSERT(inherits(vm, info()));
+    // FIXME Should we report extra memory to the GC on allocation / grow / visit? https://bugs.webkit.org/show_bug.cgi?id=170690
+    vm.heap.reportWebAssemblyFastMemoriesAllocated(1);
 }
 
 void JSWebAssemblyMemory::destroy(JSCell* cell)
@@ -141,6 +144,7 @@ void JSWebAssemblyMemory::visitChildren(JSCell* cell, SlotVisitor& visitor)
 
     Base::visitChildren(thisObject, visitor);
     visitor.append(thisObject->m_bufferWrapper);
+    // FIXME Should we report extra memory to the GC on allocation / grow / visit? https://bugs.webkit.org/show_bug.cgi?id=170690
 }
 
 } // namespace JSC
index 5842c66..c353bc8 100644 (file)
@@ -1,3 +1,23 @@
+2017-04-13  JF Bastien  <jfbastien@apple.com>
+
+        WebAssembly: manage memory better
+        https://bugs.webkit.org/show_bug.cgi?id=170628
+
+        Reviewed by Keith Miller, Michael Saboff.
+
+        Re-use a VM tag which was intended for JavaScript core, was then
+        used by our GC, and is now unused. If I don't do this then
+        WebAssembly fast memories will make vmmap look super weird because
+        it'll look like multi-gigabyte of virtual memory are allocated as
+        part of our process' regular memory!
+
+        Separately I need to update vmmap and other tools to print the
+        right name. Right now this tag gets identified as "JS garbage
+        collector".
+
+        * wtf/OSAllocator.h:
+        * wtf/VMTags.h:
+
 2017-04-13  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         [JSC] Use proper ifdef guard for code using MachineContext
index c9cb155..d7678b6 100644 (file)
@@ -36,7 +36,6 @@ public:
     enum Usage {
         UnknownUsage = -1,
         FastMallocPages = VM_TAG_FOR_TCMALLOC_MEMORY,
-        JSGCHeapPages = VM_TAG_FOR_COLLECTOR_MEMORY,
         JSVMStackPages = VM_TAG_FOR_REGISTERFILE_MEMORY,
         JSJITCodePages = VM_TAG_FOR_EXECUTABLEALLOCATOR_MEMORY,
     };
index aef05d2..27fc0de 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 Apple Inc. All rights reserved.
+ * Copyright (C) 2009, 2017 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 #endif // defined(VM_MEMORY_JAVASCRIPT_JIT_REGISTER_FILE)
 
 #if defined(VM_MEMORY_JAVASCRIPT_CORE)
-#define VM_TAG_FOR_COLLECTOR_MEMORY VM_MAKE_TAG(VM_MEMORY_JAVASCRIPT_CORE)
+#define VM_TAG_FOR_WEBASSEMBLY_MEMORY VM_MAKE_TAG(VM_MEMORY_JAVASCRIPT_CORE)
 #else
-#define VM_TAG_FOR_COLLECTOR_MEMORY VM_MAKE_TAG(63)
+#define VM_TAG_FOR_WEBASSEMBLY_MEMORY VM_MAKE_TAG(63)
 #endif // defined(VM_MEMORY_JAVASCRIPT_CORE)
 
 #else // OS(DARWIN)
 
 #define VM_TAG_FOR_TCMALLOC_MEMORY -1
-#define VM_TAG_FOR_COLLECTOR_MEMORY -1
+#define VM_TAG_FOR_WEBASSEMBLY_MEMORY -1
 #define VM_TAG_FOR_EXECUTABLEALLOCATOR_MEMORY -1
 #define VM_TAG_FOR_REGISTERFILE_MEMORY -1
 
index 848e939..dcdd1f1 100644 (file)
@@ -1,3 +1,29 @@
+2017-04-13  JF Bastien  <jfbastien@apple.com>
+
+        WebAssembly: manage memory better
+        https://bugs.webkit.org/show_bug.cgi?id=170628
+
+        Reviewed by Keith Miller, Michael Saboff.
+
+        Re-use a VM tag which was intended for JavaScript core, was then
+        used by our GC, and is now unused. If I don't do this then
+        WebAssembly fast memories will make vmmap look super weird because
+        it'll look like multi-gigabyte of virtual memory are allocated as
+        part of our process' regular memory!
+
+        Separately I need to update vmmap and other tools to print the
+        right name. Right now this tag gets identified as "JS garbage
+        collector".
+
+        * page/ResourceUsageData.cpp:
+        (WebCore::ResourceUsageData::ResourceUsageData):
+        * page/ResourceUsageData.h:
+        * page/cocoa/ResourceUsageOverlayCocoa.mm:
+        (WebCore::HistoricResourceUsageData::HistoricResourceUsageData):
+        * page/cocoa/ResourceUsageThreadCocoa.mm:
+        (WebCore::displayNameForVMTag):
+        (WebCore::categoryForVMTag):
+
 2017-04-13  Ryosuke Niwa  <rniwa@webkit.org>
 
         Update the comments for the number of bits in RenderStyle::InheritedFlags.
index 5912b05..7a17002 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 Apple Inc. All rights reserved.
+ * Copyright (C) 2016-2017 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -34,6 +34,7 @@ ResourceUsageData::ResourceUsageData()
 {
     // VM tag categories.
     categories[MemoryCategory::JSJIT] = MemoryCategoryInfo(MemoryCategory::JSJIT);
+    categories[MemoryCategory::WebAssembly] = MemoryCategoryInfo(MemoryCategory::WebAssembly);
     categories[MemoryCategory::Images] = MemoryCategoryInfo(MemoryCategory::Images);
     categories[MemoryCategory::Layers] = MemoryCategoryInfo(MemoryCategory::Layers);
     categories[MemoryCategory::LibcMalloc] = MemoryCategoryInfo(MemoryCategory::LibcMalloc);
index 02c3ba1..c881538 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 Apple Inc. All rights reserved.
+ * Copyright (C) 2016-2017 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -36,12 +36,13 @@ namespace MemoryCategory {
 static const unsigned bmalloc = 0;
 static const unsigned LibcMalloc = 1;
 static const unsigned JSJIT = 2;
-static const unsigned Images = 3;
-static const unsigned GCHeap = 4;
-static const unsigned GCOwned = 5;
-static const unsigned Other = 6;
-static const unsigned Layers = 7;
-static const unsigned NumberOfCategories = 8;
+static const unsigned WebAssembly = 3;
+static const unsigned Images = 4;
+static const unsigned GCHeap = 5;
+static const unsigned GCOwned = 6;
+static const unsigned Other = 7;
+static const unsigned Layers = 8;
+static const unsigned NumberOfCategories = 9;
 }
 
 struct MemoryCategoryInfo {
index 4f614db..8c54156 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 Apple Inc. All rights reserved.
+ * Copyright (C) 2015, 2017 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -162,6 +162,7 @@ HistoricResourceUsageData::HistoricResourceUsageData()
 {
     // VM tag categories.
     categories[MemoryCategory::JSJIT] = HistoricMemoryCategoryInfo(MemoryCategory::JSJIT, 0xFFFF60FF, "JS JIT");
+    categories[MemoryCategory::WebAssembly] = HistoricMemoryCategoryInfo(MemoryCategory::WebAssembly, 0xFF654FF0, "WebAssembly");
     categories[MemoryCategory::Images] = HistoricMemoryCategoryInfo(MemoryCategory::Images, 0xFFFFFF00, "Images");
     categories[MemoryCategory::Layers] = HistoricMemoryCategoryInfo(MemoryCategory::Layers, 0xFF00FFFF, "Layers");
     categories[MemoryCategory::LibcMalloc] = HistoricMemoryCategoryInfo(MemoryCategory::LibcMalloc, 0xFF00FF00, "libc malloc");
index 064a61a..a30ef21 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 Apple Inc. All rights reserved.
+ * Copyright (C) 2015, 2017 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -80,6 +80,7 @@ const char* displayNameForVMTag(unsigned tag)
     case VM_MEMORY_IMAGEIO: return "ImageIO";
     case VM_MEMORY_CGIMAGE: return "CG image";
     case VM_MEMORY_JAVASCRIPT_JIT_EXECUTABLE_ALLOCATOR: return "JSC JIT";
+    case VM_MEMORY_JAVASCRIPT_CORE: return "WebAssembly";
     case VM_MEMORY_MALLOC: return "malloc";
     case VM_MEMORY_MALLOC_HUGE: return "malloc (huge)";
     case VM_MEMORY_MALLOC_LARGE: return "malloc (large)";
@@ -181,6 +182,8 @@ static unsigned categoryForVMTag(unsigned tag)
         return MemoryCategory::Images;
     case VM_MEMORY_JAVASCRIPT_JIT_EXECUTABLE_ALLOCATOR:
         return MemoryCategory::JSJIT;
+    case VM_MEMORY_JAVASCRIPT_CORE:
+        return MemoryCategory::WebAssembly;
     case VM_MEMORY_MALLOC:
     case VM_MEMORY_MALLOC_HUGE:
     case VM_MEMORY_MALLOC_LARGE:
index 6510b1d..d2181d4 100644 (file)
@@ -1,3 +1,12 @@
+2017-04-13  JF Bastien  <jfbastien@apple.com>
+
+        WebAssembly: manage memory better
+        https://bugs.webkit.org/show_bug.cgi?id=170628
+
+        Reviewed by Keith Miller, Michael Saboff.
+
+        * docs/b3/intermediate-representation.html: typos
+
 2017-04-08  Simon Fraser  <simon.fraser@apple.com>
 
         Update the css-status page to handle changes in CSSProperties.json.
index 20b59b5..047493e 100644 (file)
@@ -654,11 +654,13 @@ patchpoint->setGenerator(
       <dd>Special Wasm opcode. This branches on the first child. If the first child plus the offset
         produces a Int64 less than to the pinnedGPR this falls through. Otherwise, it branches to
         the exit path generated by the passed generator. Unlike the Patch/Check family, the
-        generator used by WasmBoundsCheck sould be set on the Procuder itself. The GRPReg passed in
-        pinnedGPR must also be marked as pinned by calling the Procedure's pinning API. B3 assumes
-        the WasmBoundsCheck will side-exit when the it branches, so the generator must do some kind
-        of termination. In Wasm this is used to trap and unwind back to JS. Must use the
-        WasmBoundsCheckValue class.</dd>
+        generator used by WasmBoundsCheck should be set on the Procedure itself. The GRPReg passed in
+        pinnedGPR must also be marked as pinned by calling the Procedure's pinning API, or it must be
+        InvalidGPR, in which case the out-of-bounds limit is 4GiB. In the later case a maximum parameter
+        can be provided, to further constrain the out-of-bounds limit and help generate smaller
+        immediates. B3 assumes the WasmBoundsCheck will side-exit when it branches, so the generator
+        must do some kind of termination. In Wasm this is used to trap and unwind back to JS. Must use
+        the WasmBoundsCheckValue class.</dd>
 
       <dt>Void Upsilon(T, ^phi)</dt>
       <dd>B3 uses SSA.  SSA requires that each variable gets assigned to only once anywhere in