WebAssembly: fast memory cleanups
authorjfbastien@apple.com <jfbastien@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 19 Apr 2017 22:05:51 +0000 (22:05 +0000)
committerjfbastien@apple.com <jfbastien@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 19 Apr 2017 22:05:51 +0000 (22:05 +0000)
https://bugs.webkit.org/show_bug.cgi?id=170909

Reviewed by Saam Barati.

* b3/B3LowerToAir.cpp: correct comment, and make wasm-independent
(JSC::B3::Air::LowerToAir::lower):
* b3/B3Procedure.h:
* b3/B3Validate.cpp:
* b3/B3Value.cpp:
(JSC::B3::Value::effects):
* b3/B3WasmBoundsCheckValue.cpp: have the creator pass in a
maximum, so we don't have to know so much about wasm here
(JSC::B3::WasmBoundsCheckValue::WasmBoundsCheckValue):
(JSC::B3::WasmBoundsCheckValue::cloneImpl):
(JSC::B3::WasmBoundsCheckValue::dumpMeta):
* b3/B3WasmBoundsCheckValue.h:
(JSC::B3::WasmBoundsCheckValue::boundsType):
(JSC::B3::WasmBoundsCheckValue::bounds):
* b3/air/AirCode.h:
* b3/air/AirCustom.h:
(JSC::B3::Air::WasmBoundsCheckCustom::generate):
* b3/testb3.cpp:
(JSC::B3::testWasmBoundsCheck):
* wasm/WasmB3IRGenerator.cpp:
(JSC::Wasm::B3IRGenerator::B3IRGenerator):
(JSC::Wasm::B3IRGenerator::emitCheckAndPreparePointer):
(JSC::Wasm::createJSToWasmWrapper): remove dead code
* wasm/WasmMemory.cpp: don't GC if no memory could possibly be free'd
(JSC::Wasm::Memory::initializePreallocations): verbose-only code,
and copy-pasta bug

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

12 files changed:
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/b3/B3LowerToAir.cpp
Source/JavaScriptCore/b3/B3Procedure.h
Source/JavaScriptCore/b3/B3Validate.cpp
Source/JavaScriptCore/b3/B3Value.cpp
Source/JavaScriptCore/b3/B3WasmBoundsCheckValue.cpp
Source/JavaScriptCore/b3/B3WasmBoundsCheckValue.h
Source/JavaScriptCore/b3/air/AirCode.h
Source/JavaScriptCore/b3/air/AirCustom.h
Source/JavaScriptCore/b3/testb3.cpp
Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp
Source/JavaScriptCore/wasm/WasmMemory.cpp

index a703e50..74cb537 100644 (file)
@@ -1,3 +1,37 @@
+2017-04-19  JF Bastien  <jfbastien@apple.com>
+
+        WebAssembly: fast memory cleanups
+        https://bugs.webkit.org/show_bug.cgi?id=170909
+
+        Reviewed by Saam Barati.
+
+        * b3/B3LowerToAir.cpp: correct comment, and make wasm-independent
+        (JSC::B3::Air::LowerToAir::lower):
+        * b3/B3Procedure.h:
+        * b3/B3Validate.cpp:
+        * b3/B3Value.cpp:
+        (JSC::B3::Value::effects):
+        * b3/B3WasmBoundsCheckValue.cpp: have the creator pass in a
+        maximum, so we don't have to know so much about wasm here
+        (JSC::B3::WasmBoundsCheckValue::WasmBoundsCheckValue):
+        (JSC::B3::WasmBoundsCheckValue::cloneImpl):
+        (JSC::B3::WasmBoundsCheckValue::dumpMeta):
+        * b3/B3WasmBoundsCheckValue.h:
+        (JSC::B3::WasmBoundsCheckValue::boundsType):
+        (JSC::B3::WasmBoundsCheckValue::bounds):
+        * b3/air/AirCode.h:
+        * b3/air/AirCustom.h:
+        (JSC::B3::Air::WasmBoundsCheckCustom::generate):
+        * b3/testb3.cpp:
+        (JSC::B3::testWasmBoundsCheck):
+        * wasm/WasmB3IRGenerator.cpp:
+        (JSC::Wasm::B3IRGenerator::B3IRGenerator):
+        (JSC::Wasm::B3IRGenerator::emitCheckAndPreparePointer):
+        (JSC::Wasm::createJSToWasmWrapper): remove dead code
+        * wasm/WasmMemory.cpp: don't GC if no memory could possibly be free'd
+        (JSC::Wasm::Memory::initializePreallocations): verbose-only code,
+        and copy-pasta bug
+
 2017-04-19  Mark Lam  <mark.lam@apple.com>
 
         B3StackmapSpecial should handle when stackmap values are not recoverable from a Def'ed arg.
index 506707d..4a7e0af 100644 (file)
@@ -3146,7 +3146,6 @@ private:
         }
 
         case B3::WasmBoundsCheck: {
-#if ENABLE(WEBASSEMBLY)
             WasmBoundsCheckValue* value = m_value->as<WasmBoundsCheckValue>();
 
             Value* ptr = value->child(0);
@@ -3164,28 +3163,21 @@ private:
             }
 
             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.
+            switch (value->boundsType()) {
+            case WasmBoundsCheckValue::Type::Pinned:
+                limit = Arg(value->bounds().pinned);
+                break;
+
+            case WasmBoundsCheckValue::Type::Maximum:
                 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);
+                if (imm(value->bounds().maximum))
+                    append(Move, imm(value->bounds().maximum), limit);
                 else
-                    append(Move, Arg::bigImm(limitValue), limit);
+                    append(Move, Arg::bigImm(value->bounds().maximum), limit);
+                break;
             }
+
             append(Inst(Air::WasmBoundsCheck, value, ptrPlusImm, limit));
-#else
-            append(Air::Oops);
-#endif // ENABLE(WEBASSEMBLY)
             return;
         }
 
index 48ede1b..8b6a3b3 100644 (file)
@@ -58,7 +58,7 @@ class Variable;
 
 namespace Air { class Code; }
 
-typedef void WasmBoundsCheckGeneratorFunction(CCallHelpers&, GPRReg, unsigned);
+typedef void WasmBoundsCheckGeneratorFunction(CCallHelpers&, GPRReg);
 typedef SharedTask<WasmBoundsCheckGeneratorFunction> WasmBoundsCheckGenerator;
 
 // This represents B3's view of a piece of code. Note that this object must exist in a 1:1
index cc3a56f..60b0b3b 100644 (file)
@@ -470,8 +470,13 @@ public:
                 VALIDATE(!value->kind().hasExtraBits(), ("At ", *value));
                 VALIDATE(value->numChildren() == 1, ("At ", *value));
                 VALIDATE(value->child(0)->type() == Int32, ("At ", *value));
-                if (value->as<WasmBoundsCheckValue>()->pinnedGPR() != InvalidGPRReg)
-                    VALIDATE(m_procedure.code().isPinned(value->as<WasmBoundsCheckValue>()->pinnedGPR()), ("At ", *value));
+                switch (value->as<WasmBoundsCheckValue>()->boundsType()) {
+                case WasmBoundsCheckValue::Type::Pinned:
+                    VALIDATE(m_procedure.code().isPinned(value->as<WasmBoundsCheckValue>()->bounds().pinned), ("At ", *value));
+                    break;
+                case WasmBoundsCheckValue::Type::Maximum:
+                    break;
+                }
                 VALIDATE(m_procedure.code().wasmBoundsCheckGenerator(), ("At ", *value));
                 break;
             case Upsilon:
index 65b17e3..5a90169 100644 (file)
@@ -665,8 +665,13 @@ Effects Value::effects() const
         result = Effects::forCheck();
         break;
     case WasmBoundsCheck:
-        if (as<WasmBoundsCheckValue>()->pinnedGPR() != InvalidGPRReg)
+        switch (as<WasmBoundsCheckValue>()->boundsType()) {
+        case WasmBoundsCheckValue::Type::Pinned:
             result.readsPinned = true;
+            break;
+        case WasmBoundsCheckValue::Type::Maximum:
+            break;
+        }
         result.exitsSideways = true;
         break;
     case Upsilon:
index 8aecc1d..c348479 100644 (file)
@@ -35,35 +35,41 @@ WasmBoundsCheckValue::~WasmBoundsCheckValue()
 {
 }
 
-WasmBoundsCheckValue::WasmBoundsCheckValue(Origin origin, Value* ptr, GPRReg pinnedGPR, unsigned offset, PageCount maximum)
+WasmBoundsCheckValue::WasmBoundsCheckValue(Origin origin, Value* ptr, unsigned offset, GPRReg pinnedGPR)
     : Value(CheckedOpcode, WasmBoundsCheck, origin, ptr)
-    , m_pinnedGPR(pinnedGPR)
     , m_offset(offset)
-    , m_maximum(maximum)
+    , m_boundsType(Type::Pinned)
 {
+    m_bounds.pinned = pinnedGPR;
 }
 
-Value* WasmBoundsCheckValue::cloneImpl() const
+WasmBoundsCheckValue::WasmBoundsCheckValue(Origin origin, Value* ptr, unsigned offset, size_t maximum)
+    : Value(CheckedOpcode, WasmBoundsCheck, origin, ptr)
+    , m_offset(offset)
+    , m_boundsType(Type::Maximum)
 {
-    return new WasmBoundsCheckValue(*this);
+#if ENABLE(WEBASSEMBLY)
+    size_t redzoneLimit = static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) + Wasm::Memory::fastMappedRedzoneBytes();
+    ASSERT_UNUSED(redzoneLimit, maximum <= redzoneLimit);
+#endif
+    m_bounds.maximum = maximum;
 }
 
-size_t WasmBoundsCheckValue::redzoneLimit() const
+Value* WasmBoundsCheckValue::cloneImpl() 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
+    return new WasmBoundsCheckValue(*this);
 }
 
 void WasmBoundsCheckValue::dumpMeta(CommaPrinter& comma, PrintStream& out) const
 {
-    if (m_pinnedGPR == InvalidGPRReg)
-        out.print(comma, "redzoneLimit = ", redzoneLimit(), ", offset = ", m_offset);
-    else
-        out.print(comma, "sizeRegister = ", m_pinnedGPR, ", offset = ", m_offset);
+    switch (m_boundsType) {
+    case Type::Pinned:
+        out.print(comma, "offset = ", m_offset, ", pinned = ", m_bounds.pinned);
+        break;
+    case Type::Maximum:
+        out.print(comma, "offset = ", m_offset, ", maximum = ", m_bounds.maximum);
+        break;
+    }
 }
 
 } } // namespace JSC::B3
index 110e718..37b12e2 100644 (file)
@@ -29,7 +29,6 @@
 
 #include "B3Value.h"
 #include "CCallHelpers.h"
-#include "WasmPageCount.h"
 
 namespace JSC { namespace B3 {
 
@@ -47,16 +46,19 @@ public:
     
     ~WasmBoundsCheckValue();
 
-#if ENABLE(WEBASSEMBLY)
-    typedef Wasm::PageCount PageCount;
-#else
-    typedef char PageCount;
-#endif
+    enum class Type {
+        Pinned,
+        Maximum,
+    };
+
+    union Bounds {
+        GPRReg pinned;
+        size_t maximum;
+    };
 
-    GPRReg pinnedGPR() const { return m_pinnedGPR; }
     unsigned offset() const { return m_offset; }
-    size_t redzoneLimit() const;
-    PageCount maximum() const { return m_maximum; }
+    Type boundsType() const { return m_boundsType; }
+    Bounds bounds() const { return m_bounds; }
 
 protected:
     void dumpMeta(CommaPrinter&, PrintStream&) const override;
@@ -66,11 +68,12 @@ protected:
 private:
     friend class Procedure;
 
-    JS_EXPORT_PRIVATE WasmBoundsCheckValue(Origin, Value* ptr, GPRReg pinnedGPR, unsigned offset, PageCount maximum);
+    JS_EXPORT_PRIVATE WasmBoundsCheckValue(Origin, Value* ptr, unsigned offset, GPRReg pinnedGPR);
+    JS_EXPORT_PRIVATE WasmBoundsCheckValue(Origin, Value* ptr, unsigned offset, size_t maximum);
 
-    GPRReg m_pinnedGPR;
     unsigned m_offset;
-    PageCount m_maximum;
+    Type m_boundsType;
+    Bounds m_bounds;
 };
 
 } } // namespace JSC::B3
index 066195c..97506bd 100644 (file)
@@ -55,7 +55,7 @@ class CCallSpecial;
 class CFG;
 class Disassembler;
 
-typedef void WasmBoundsCheckGeneratorFunction(CCallHelpers&, GPRReg, unsigned);
+typedef void WasmBoundsCheckGeneratorFunction(CCallHelpers&, GPRReg);
 typedef SharedTask<WasmBoundsCheckGeneratorFunction> WasmBoundsCheckGenerator;
 
 // This is an IR that is very close to the bare metal. It requires about 40x more bytes than the
index a37b2db..f8f3dff 100644 (file)
@@ -314,7 +314,15 @@ struct WasmBoundsCheckCustom : public CommonCustomBase<WasmBoundsCheckCustom> {
         context.latePaths.append(createSharedTask<GenerationContext::LatePathFunction>(
             [outOfBounds, value] (CCallHelpers& jit, Air::GenerationContext& context) {
                 outOfBounds.link(&jit);
-                context.code->wasmBoundsCheckGenerator()->run(jit, value->pinnedGPR(), value->offset());
+                switch (value->boundsType()) {
+                case WasmBoundsCheckValue::Type::Pinned:
+                    context.code->wasmBoundsCheckGenerator()->run(jit, value->bounds().pinned);
+                    break;
+
+                case WasmBoundsCheckValue::Type::Maximum:
+                    context.code->wasmBoundsCheckGenerator()->run(jit, InvalidGPRReg);
+                    break;
+                }
             }));
 
         // We said we were not a terminal.
index 661a995..21f1d66 100644 (file)
@@ -15170,9 +15170,8 @@ void testWasmBoundsCheck(unsigned offset)
     GPRReg pinned = GPRInfo::argumentGPR1;
     proc.pinRegister(pinned);
 
-    proc.setWasmBoundsCheckGenerator([=] (CCallHelpers& jit, GPRReg pinnedGPR, unsigned actualOffset) {
+    proc.setWasmBoundsCheckGenerator([=] (CCallHelpers& jit, GPRReg pinnedGPR) {
         CHECK_EQ(pinnedGPR, pinned);
-        CHECK_EQ(actualOffset, offset);
 
         // This should always work because a function this simple should never have callee
         // saves.
@@ -15185,8 +15184,7 @@ void testWasmBoundsCheck(unsigned offset)
     Value* left = root->appendNew<ArgumentRegValue>(proc, Origin(), GPRInfo::argumentGPR0);
     if (pointerType() != Int32)
         left = root->appendNew<Value>(proc, Trunc, Origin(), left);
-    Wasm::PageCount maximum;
-    root->appendNew<WasmBoundsCheckValue>(proc, Origin(), left, pinned, offset, maximum);
+    root->appendNew<WasmBoundsCheckValue>(proc, Origin(), left, pinned, offset);
     Value* result = root->appendNew<Const32Value>(proc, Origin(), 0x42);
     root->appendNewControlValue(proc, Return, Origin(), result);
 
index 1f17ca3..87eef29 100644 (file)
@@ -59,6 +59,7 @@
 #include "WasmMemory.h"
 #include "WasmOpcodeOrigin.h"
 #include "WasmThunks.h"
+#include <limits>
 #include <wtf/Optional.h>
 #include <wtf/StdLibExtras.h>
 
@@ -352,7 +353,7 @@ B3IRGenerator::B3IRGenerator(const ModuleInformation& info, Procedure& procedure
     }
 
     if (info.memory) {
-        m_proc.setWasmBoundsCheckGenerator([=] (CCallHelpers& jit, GPRReg pinnedGPR, unsigned) {
+        m_proc.setWasmBoundsCheckGenerator([=] (CCallHelpers& jit, GPRReg pinnedGPR) {
             AllowMacroScratchRegisterUsage allowScratch(jit);
             switch (m_mode) {
             case MemoryMode::BoundsChecking:
@@ -549,18 +550,32 @@ auto B3IRGenerator::setGlobal(uint32_t index, ExpressionType value) -> PartialRe
 inline Value* B3IRGenerator::emitCheckAndPreparePointer(ExpressionType pointer, uint32_t offset, uint32_t sizeOfOperation)
 {
     ASSERT(m_memoryBaseGPR);
+
     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_info.memory.maximum());
+        m_currentBlock->appendNew<WasmBoundsCheckValue>(m_proc, origin(), pointer, sizeOfOperation + offset - 1, m_memorySizeGPR);
         break;
+
     case MemoryMode::Signaling:
-        // We've virtually mapped 4GiB+redzone for 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());
+        // We've virtually mapped 4GiB+redzone for 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 + offset accesses perform the addition in 64-bit which can push an access above
+        // the 32-bit limit (the offset is unsigned 32-bit). The redzone will catch most small offsets, and we'll explicitly bounds check any
+        // register + large offset access. We don't think this will be generated frequently.
+        //
+        // We could check that register + large offset doesn't exceed 4GiB+redzone since that's technically the limit we need to avoid overflowing the
+        // PROT_NONE region, but it's better if we use a smaller immediate because it can codegens better. We know that anything equal to or greater
+        // than 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 equal to or greater than 4GiB will trap, no need to add the redzone.
+        if (offset >= Memory::fastMappedRedzoneBytes()) {
+            size_t maximum = m_info.memory.maximum() ? m_info.memory.maximum().bytes() : std::numeric_limits<uint32_t>::max();
+            m_currentBlock->appendNew<WasmBoundsCheckValue>(m_proc, origin(), pointer, sizeOfOperation + offset - 1, maximum);
+        }
         break;
+
     case MemoryMode::NumberOfMemoryModes:
         RELEASE_ASSERT_NOT_REACHED();
     }
@@ -1300,12 +1315,6 @@ 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 610743d..7e6f5e6 100644 (file)
@@ -48,7 +48,7 @@ namespace {
 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 webAssemblyCouldntUnmapMemory() { CRASH(); }
 NEVER_INLINE NO_RETURN_DUE_TO_CRASH void webAssemblyCouldntUnprotectMemory() { CRASH(); }
 
 void* mmapBytes(size_t bytes)
@@ -66,7 +66,7 @@ void* mmapBytes(size_t bytes)
 void munmapBytes(void* memory, size_t size)
 {
     if (UNLIKELY(munmap(memory, size)))
-        webAssemblyCouldntUnmapMemoryBytes();
+        webAssemblyCouldntUnmapMemory();
 }
 
 void zeroAndUnprotectBytes(void* start, size_t bytes)
@@ -149,8 +149,8 @@ void* tryGetFastMemory(VM& vm)
         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.
+        else if (currentlyAllocatedFastMemories.load(std::memory_order_acquire) >= 1) {
+            // No memory was available in the cache, but we know there's at least one currently live. Maybe 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();
@@ -298,14 +298,17 @@ void Memory::initializePreallocations()
 
     // Races cannot occur in this function: it is only called at program initialization, before WebAssembly can be invoked.
 
-    const auto startTime = MonotonicTime::now();
+    MonotonicTime startTime;
+    if (verbose)
+        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;
+                void* startAddress = reinterpret_cast<char*>(memory) + Memory::fastMappedBytes() * subMemory;
                 bool inserted = false;
                 for (size_t cacheEntry = 0; cacheEntry < fastMemoryCacheHardLimit; ++cacheEntry) {
                     if (fastMemoryCache[cacheEntry].load(std::memory_order_relaxed) == nullptr) {
@@ -337,14 +340,17 @@ void Memory::initializePreallocations()
     currentlyAllocatedFastMemories.store(fastMemoryPreallocateCount, std::memory_order_relaxed);
     observedMaximumFastMemory.store(fastMemoryPreallocateCount, std::memory_order_relaxed);
 
-    const auto endTime = MonotonicTime::now();
+    if (verbose) {
+        MonotonicTime endTime = MonotonicTime::now();
+
+        for (size_t cacheEntry = 0; cacheEntry < fastMemoryPreallocateCount; ++cacheEntry) {
+            void* startAddress = fastMemoryCache[cacheEntry].load(std::memory_order_relaxed);
+            ASSERT(startAddress);
+            dataLogLn("Pre-allocation of WebAssembly fast memory at ", RawPointer(startAddress));
+        }
 
-    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));
+        dataLogLn("Pre-allocated ", fastMemoryPreallocateCount, " WebAssembly fast memories in ", fastMemoryPreallocateCount == 0 ? 0 : fragments, fragments == 1 ? " fragment, took " : " fragments, took ", endTime - startTime);
     }
-    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)