Improve use of NeverDestroyed
[WebKit-https.git] / Source / JavaScriptCore / wasm / WasmMemory.cpp
1 /*
2  * Copyright (C) 2016-2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "WasmMemory.h"
28
29 #if ENABLE(WEBASSEMBLY)
30
31 #include "VM.h"
32 #include "WasmThunks.h"
33
34 #include <atomic>
35 #include <wtf/MonotonicTime.h>
36 #include <wtf/Platform.h>
37 #include <wtf/PrintStream.h>
38 #include <wtf/VMTags.h>
39
40 namespace JSC { namespace Wasm {
41
42 // FIXME: We could be smarter about memset / mmap / madvise. https://bugs.webkit.org/show_bug.cgi?id=170343
43 // 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
44 // FIXME: Limit slow memory size. https://bugs.webkit.org/show_bug.cgi?id=170825
45
46 namespace {
47 constexpr bool verbose = false;
48
49 NEVER_INLINE NO_RETURN_DUE_TO_CRASH void webAssemblyCouldntGetFastMemory() { CRASH(); }
50 NEVER_INLINE NO_RETURN_DUE_TO_CRASH void webAssemblyCouldntUnmapMemory() { CRASH(); }
51 NEVER_INLINE NO_RETURN_DUE_TO_CRASH void webAssemblyCouldntUnprotectMemory() { CRASH(); }
52
53 void* mmapBytes(size_t bytes)
54 {
55     void* location = mmap(nullptr, bytes, PROT_NONE, MAP_PRIVATE | MAP_ANON, VM_TAG_FOR_WEBASSEMBLY_MEMORY, 0);
56     return location == MAP_FAILED ? nullptr : location;
57 }
58
59 void munmapBytes(void* memory, size_t size)
60 {
61     if (UNLIKELY(munmap(memory, size)))
62         webAssemblyCouldntUnmapMemory();
63 }
64
65 void zeroAndUnprotectBytes(void* start, size_t bytes)
66 {
67     if (bytes) {
68         dataLogLnIf(verbose, "Zeroing and unprotecting ", bytes, " from ", RawPointer(start));
69         // 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
70         memset(start, 0, bytes);
71         if (UNLIKELY(mprotect(start, bytes, PROT_NONE)))
72             webAssemblyCouldntUnprotectMemory();
73     }
74 }
75
76 // 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.
77 // We still try to allocate fast memories at runtime, and will cache them when relinquished up to the preallocation limit.
78 // Note that this state is per-process, not per-VM.
79 // 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.
80 // 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.
81 constexpr size_t fastMemoryCacheHardLimit { 16 };
82 constexpr size_t fastMemoryAllocationSoftLimit { 32 }; // Prevents filling up the virtual address space.
83 static_assert(fastMemoryAllocationSoftLimit >= fastMemoryCacheHardLimit, "The cache shouldn't be bigger than the total number we'll ever allocate");
84 size_t fastMemoryPreallocateCount { 0 };
85 std::atomic<void*> fastMemoryCache[fastMemoryCacheHardLimit] = { ATOMIC_VAR_INIT(nullptr) };
86 std::atomic<void*> currentlyActiveFastMemories[fastMemoryAllocationSoftLimit] = { ATOMIC_VAR_INIT(nullptr) };
87 std::atomic<size_t> currentlyAllocatedFastMemories = ATOMIC_VAR_INIT(0);
88 std::atomic<size_t> observedMaximumFastMemory = ATOMIC_VAR_INIT(0);
89 std::atomic<size_t> currentSlowMemoryCapacity = ATOMIC_VAR_INIT(0);
90
91 size_t fastMemoryAllocatedBytesSoftLimit()
92 {
93     return fastMemoryAllocationSoftLimit * Memory::fastMappedBytes();
94 }
95
96 void* tryGetCachedFastMemory()
97 {
98     for (unsigned idx = 0; idx < fastMemoryPreallocateCount; ++idx) {
99         if (void* previous = fastMemoryCache[idx].exchange(nullptr, std::memory_order_acq_rel))
100             return previous;
101     }
102     return nullptr;
103 }
104
105 bool tryAddToCachedFastMemory(void* memory)
106 {
107     for (unsigned i = 0; i < fastMemoryPreallocateCount; ++i) {
108         void* expected = nullptr;
109         if (fastMemoryCache[i].compare_exchange_strong(expected, memory, std::memory_order_acq_rel)) {
110             dataLogLnIf(verbose, "Cached fast memory ", RawPointer(memory));
111             return true;
112         }
113     }
114     return false;
115 }
116
117 bool tryAddToCurrentlyActiveFastMemories(void* memory)
118 {
119     for (size_t idx = 0; idx < fastMemoryAllocationSoftLimit; ++idx) {
120         void* expected = nullptr;
121         if (currentlyActiveFastMemories[idx].compare_exchange_strong(expected, memory, std::memory_order_acq_rel))
122             return true;
123     }
124     return false;
125 }
126
127 void removeFromCurrentlyActiveFastMemories(void* memory)
128 {
129     for (size_t idx = 0; idx < fastMemoryAllocationSoftLimit; ++idx) {
130         void* expected = memory;
131         if (currentlyActiveFastMemories[idx].compare_exchange_strong(expected, nullptr, std::memory_order_acq_rel))
132             return;
133     }
134     RELEASE_ASSERT_NOT_REACHED();
135 }
136
137 void* tryGetFastMemory(VM& vm)
138 {
139     void* memory = nullptr;
140
141     if (LIKELY(Options::useWebAssemblyFastMemory())) {
142         memory = tryGetCachedFastMemory();
143         if (memory)
144             dataLogLnIf(verbose, "tryGetFastMemory re-using ", RawPointer(memory));
145         else if (currentlyAllocatedFastMemories.load(std::memory_order_acquire) >= 1) {
146             // No memory was available in the cache, but we know there's at least one currently live. Maybe GC will find a free one.
147             // FIXME collectSync(Full) and custom eager destruction of wasm memories could be better. For now use collectNow. Also, nothing tells us the current VM is holding onto fast memories. https://bugs.webkit.org/show_bug.cgi?id=170748
148             dataLogLnIf(verbose, "tryGetFastMemory waiting on GC and retrying");
149             vm.heap.collectNow(Sync, CollectionScope::Full);
150             memory = tryGetCachedFastMemory();
151             dataLogLnIf(verbose, "tryGetFastMemory waited on GC and retried ", memory? "successfully" : "unseccessfully");
152         }
153
154         // The soft limit is inherently racy because checking+allocation isn't atomic. Exceeding it slightly is fine.
155         bool atAllocationSoftLimit = currentlyAllocatedFastMemories.load(std::memory_order_acquire) >= fastMemoryAllocationSoftLimit;
156         dataLogLnIf(verbose && atAllocationSoftLimit, "tryGetFastMemory reached allocation soft limit of ", fastMemoryAllocationSoftLimit);
157
158         if (!memory && !atAllocationSoftLimit) {
159             memory = mmapBytes(Memory::fastMappedBytes());
160             if (memory) {
161                 size_t currentlyAllocated = 1 + currentlyAllocatedFastMemories.fetch_add(1, std::memory_order_acq_rel);
162                 size_t currentlyObservedMaximum = observedMaximumFastMemory.load(std::memory_order_acquire);
163                 if (currentlyAllocated > currentlyObservedMaximum) {
164                     size_t expected = currentlyObservedMaximum;
165                     bool success = observedMaximumFastMemory.compare_exchange_strong(expected, currentlyAllocated, std::memory_order_acq_rel);
166                     if (success)
167                         dataLogLnIf(verbose, "tryGetFastMemory currently observed maximum is now ", currentlyAllocated);
168                     else
169                         // 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.
170                         ASSERT(expected >= currentlyAllocated);
171                 }
172                 dataLogLnIf(verbose, "tryGetFastMemory allocated ", RawPointer(memory), ", currently allocated is ", currentlyAllocated);
173             }
174         }
175     }
176
177     if (memory) {
178         if (UNLIKELY(!tryAddToCurrentlyActiveFastMemories(memory))) {
179             // 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.
180             dataLogLnIf(verbose, "tryGetFastMemory found a fast memory but had to give it up");
181             munmapBytes(memory, Memory::fastMappedBytes());
182             currentlyAllocatedFastMemories.fetch_sub(1, std::memory_order_acq_rel);
183             memory = nullptr;
184         }
185     }
186
187     if (!memory) {
188         dataLogLnIf(verbose, "tryGetFastMemory couldn't re-use or allocate a fast memory");
189         if (UNLIKELY(Options::crashIfWebAssemblyCantFastMemory()))
190             webAssemblyCouldntGetFastMemory();
191     }
192
193     return memory;
194 }
195
196 bool slowMemoryCapacitySoftMaximumExceeded()
197 {
198     // The limit on slow memory capacity is arbitrary. Its purpose is to limit
199     // virtual memory allocation. We choose to set the limit at the same virtual
200     // memory limit imposed on fast memories.
201     size_t maximum = fastMemoryAllocatedBytesSoftLimit();
202     size_t currentCapacity = currentSlowMemoryCapacity.load(std::memory_order_acquire);
203     if (UNLIKELY(currentCapacity > maximum)) {
204         dataLogLnIf(verbose, "Slow memory capacity limit reached");
205         return true;
206     }
207     return false;
208 }
209
210 void* tryGetSlowMemory(size_t bytes)
211 {
212     if (slowMemoryCapacitySoftMaximumExceeded())
213         return nullptr;
214     void* memory = mmapBytes(bytes);
215     if (memory)
216         currentSlowMemoryCapacity.fetch_add(bytes, std::memory_order_acq_rel);
217     dataLogLnIf(memory && verbose, "Obtained slow memory ", RawPointer(memory), " with capacity ", bytes);
218     dataLogLnIf(!memory && verbose, "Failed obtaining slow memory with capacity ", bytes);
219     return memory;
220 }
221
222 void relinquishMemory(void* memory, size_t writableSize, size_t mappedCapacity, MemoryMode mode)
223 {
224     switch (mode) {
225     case MemoryMode::Signaling: {
226         RELEASE_ASSERT(Options::useWebAssemblyFastMemory());
227         RELEASE_ASSERT(mappedCapacity == Memory::fastMappedBytes());
228
229         // This memory cannot cause a trap anymore.
230         removeFromCurrentlyActiveFastMemories(memory);
231
232         // We may cache fast memories. Assuming we will, we have to reset them before inserting them into the cache.
233         zeroAndUnprotectBytes(memory, writableSize);
234
235         if (tryAddToCachedFastMemory(memory))
236             return;
237
238         dataLogLnIf(verbose, "relinquishMemory unable to cache fast memory, freeing instead ", RawPointer(memory));
239         munmapBytes(memory, Memory::fastMappedBytes());
240         currentlyAllocatedFastMemories.fetch_sub(1, std::memory_order_acq_rel);
241
242         return;
243     }
244
245     case MemoryMode::BoundsChecking:
246         dataLogLnIf(verbose, "relinquishFastMemory freeing slow memory ", RawPointer(memory));
247         munmapBytes(memory, mappedCapacity);
248         currentSlowMemoryCapacity.fetch_sub(mappedCapacity, std::memory_order_acq_rel);
249         return;
250
251     case MemoryMode::NumberOfMemoryModes:
252         break;
253     }
254
255     RELEASE_ASSERT_NOT_REACHED();
256 }
257
258 bool makeNewMemoryReadWriteOrRelinquish(void* memory, size_t initialBytes, size_t mappedCapacityBytes, MemoryMode mode)
259 {
260     ASSERT(memory && initialBytes <= mappedCapacityBytes);
261     if (initialBytes) {
262         dataLogLnIf(verbose, "Marking WebAssembly memory's ", RawPointer(memory), "'s initial ", initialBytes, " bytes as read+write");
263         if (mprotect(memory, initialBytes, PROT_READ | PROT_WRITE)) {
264             const char* why = strerror(errno);
265             dataLogLnIf(verbose, "Failed making memory ", RawPointer(memory), " readable and writable: ", why);
266             relinquishMemory(memory, 0, mappedCapacityBytes, mode);
267             return false;
268         }
269     }
270     return true;
271 }
272
273 } // anonymous namespace
274
275
276 const char* makeString(MemoryMode mode)
277 {
278     switch (mode) {
279     case MemoryMode::BoundsChecking: return "BoundsChecking";
280     case MemoryMode::Signaling: return "Signaling";
281     case MemoryMode::NumberOfMemoryModes: break;
282     }
283     RELEASE_ASSERT_NOT_REACHED();
284     return "";
285 }
286
287 void Memory::initializePreallocations()
288 {
289     if (UNLIKELY(!Options::useWebAssemblyFastMemory()))
290         return;
291
292     // Races cannot occur in this function: it is only called at program initialization, before WebAssembly can be invoked.
293
294     MonotonicTime startTime;
295     if (verbose)
296         startTime = MonotonicTime::now();
297
298     const size_t desiredFastMemories = std::min<size_t>(Options::webAssemblyFastMemoryPreallocateCount(), fastMemoryCacheHardLimit);
299
300     // 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.
301     auto allocateContiguousFastMemories = [&] (size_t numContiguous) -> bool {
302         if (void *memory = mmapBytes(Memory::fastMappedBytes() * numContiguous)) {
303             for (size_t subMemory = 0; subMemory < numContiguous; ++subMemory) {
304                 void* startAddress = reinterpret_cast<char*>(memory) + Memory::fastMappedBytes() * subMemory;
305                 bool inserted = false;
306                 for (size_t cacheEntry = 0; cacheEntry < fastMemoryCacheHardLimit; ++cacheEntry) {
307                     if (fastMemoryCache[cacheEntry].load(std::memory_order_relaxed) == nullptr) {
308                         fastMemoryCache[cacheEntry].store(startAddress, std::memory_order_relaxed);
309                         inserted = true;
310                         break;
311                     }
312                 }
313                 RELEASE_ASSERT(inserted);
314             }
315             return true;
316         }
317         return false;
318     };
319
320     size_t fragments = 0;
321     size_t numFastMemories = 0;
322     size_t contiguousMemoryAllocationAttempt = desiredFastMemories;
323     while (numFastMemories != desiredFastMemories && contiguousMemoryAllocationAttempt != 0) {
324         if (allocateContiguousFastMemories(contiguousMemoryAllocationAttempt)) {
325             numFastMemories += contiguousMemoryAllocationAttempt;
326             contiguousMemoryAllocationAttempt = std::min(contiguousMemoryAllocationAttempt - 1, desiredFastMemories - numFastMemories);
327         } else
328             --contiguousMemoryAllocationAttempt;
329         ++fragments;
330     }
331
332     fastMemoryPreallocateCount = numFastMemories;
333     currentlyAllocatedFastMemories.store(fastMemoryPreallocateCount, std::memory_order_relaxed);
334     observedMaximumFastMemory.store(fastMemoryPreallocateCount, std::memory_order_relaxed);
335
336     if (verbose) {
337         MonotonicTime endTime = MonotonicTime::now();
338
339         for (size_t cacheEntry = 0; cacheEntry < fastMemoryPreallocateCount; ++cacheEntry) {
340             void* startAddress = fastMemoryCache[cacheEntry].load(std::memory_order_relaxed);
341             ASSERT(startAddress);
342             dataLogLn("Pre-allocation of WebAssembly fast memory at ", RawPointer(startAddress));
343         }
344
345         dataLogLn("Pre-allocated ", fastMemoryPreallocateCount, " WebAssembly fast memories in ", fastMemoryPreallocateCount == 0 ? 0 : fragments, fragments == 1 ? " fragment, took " : " fragments, took ", endTime - startTime);
346     }
347 }
348
349 Memory::Memory(PageCount initial, PageCount maximum)
350     : m_initial(initial)
351     , m_maximum(maximum)
352 {
353     ASSERT(!initial.bytes());
354     ASSERT(m_mode == MemoryMode::BoundsChecking);
355     dataLogLnIf(verbose, "Memory::Memory allocating ", *this);
356 }
357
358 Memory::Memory(void* memory, PageCount initial, PageCount maximum, size_t mappedCapacity, MemoryMode mode)
359     : m_memory(memory)
360     , m_size(initial.bytes())
361     , m_initial(initial)
362     , m_maximum(maximum)
363     , m_mappedCapacity(mappedCapacity)
364     , m_mode(mode)
365 {
366     dataLogLnIf(verbose, "Memory::Memory allocating ", *this);
367 }
368
369 RefPtr<Memory> Memory::create(VM& vm, PageCount initial, PageCount maximum)
370 {
371     ASSERT(initial);
372     RELEASE_ASSERT(!maximum || maximum >= initial); // This should be guaranteed by our caller.
373
374     const size_t initialBytes = initial.bytes();
375     const size_t maximumBytes = maximum ? maximum.bytes() : 0;
376     size_t mappedCapacityBytes = 0;
377     MemoryMode mode;
378
379     // We need to be sure we have a stub prior to running code.
380     if (UNLIKELY(!Thunks::singleton().stub(throwExceptionFromWasmThunkGenerator)))
381         return nullptr;
382
383     if (maximum && !maximumBytes) {
384         // User specified a zero maximum, initial size must also be zero.
385         RELEASE_ASSERT(!initialBytes);
386         return adoptRef(new Memory(initial, maximum));
387     }
388
389     void* memory = nullptr;
390
391     // First try fast memory, because they're fast. Fast memory is suitable for any initial / maximum.
392     memory = tryGetFastMemory(vm);
393     if (memory) {
394         mappedCapacityBytes = Memory::fastMappedBytes();
395         mode = MemoryMode::Signaling;
396     }
397
398     // 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.
399     if (!memory && maximum) {
400         memory = tryGetSlowMemory(maximumBytes);
401         if (memory) {
402             mappedCapacityBytes = maximumBytes;
403             mode = MemoryMode::BoundsChecking;
404         }
405     }
406
407     // We're stuck with a slow memory which may be slower or impossible to grow.
408     if (!memory) {
409         if (!initialBytes)
410             return adoptRef(new Memory(initial, maximum));
411         memory = tryGetSlowMemory(initialBytes);
412         if (memory) {
413             mappedCapacityBytes = initialBytes;
414             mode = MemoryMode::BoundsChecking;
415         }
416     }
417
418     if (!memory)
419         return nullptr;
420
421     if (!makeNewMemoryReadWriteOrRelinquish(memory, initialBytes, mappedCapacityBytes, mode))
422         return nullptr;
423
424     return adoptRef(new Memory(memory, initial, maximum, mappedCapacityBytes, mode));
425 }
426
427 Memory::~Memory()
428 {
429     if (m_memory) {
430         dataLogLnIf(verbose, "Memory::~Memory ", *this);
431         relinquishMemory(m_memory, m_size, m_mappedCapacity, m_mode);
432     }
433 }
434
435 size_t Memory::fastMappedRedzoneBytes()
436 {
437     return static_cast<size_t>(PageCount::pageSize) * Options::webAssemblyFastMemoryRedzonePages();
438 }
439
440 size_t Memory::fastMappedBytes()
441 {
442     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.");
443     return static_cast<size_t>(std::numeric_limits<uint32_t>::max()) + fastMappedRedzoneBytes();
444 }
445
446 size_t Memory::maxFastMemoryCount()
447 {
448     // 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.
449     return observedMaximumFastMemory.load(std::memory_order_relaxed);
450 }
451
452 bool Memory::addressIsInActiveFastMemory(void* address)
453 {
454     // 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.
455     for (size_t idx = 0; idx < fastMemoryAllocationSoftLimit; ++idx) {
456         char* start = static_cast<char*>(currentlyActiveFastMemories[idx].load(std::memory_order_acquire));
457         if (start <= address && address <= start + fastMappedBytes())
458             return true;
459     }
460     return false;
461 }
462
463 bool Memory::grow(PageCount newSize)
464 {
465     RELEASE_ASSERT(newSize > PageCount::fromBytes(m_size));
466
467     dataLogLnIf(verbose, "Memory::grow to ", newSize, " from ", *this);
468
469     if (maximum() && newSize > maximum())
470         return false;
471
472     size_t desiredSize = newSize.bytes();
473
474     switch (mode()) {
475     case MemoryMode::BoundsChecking:
476         RELEASE_ASSERT(maximum().bytes() != 0);
477         break;
478     case MemoryMode::Signaling:
479         // Signaling memory must have been pre-allocated virtually.
480         RELEASE_ASSERT(m_memory);
481         break;
482     case MemoryMode::NumberOfMemoryModes:
483         RELEASE_ASSERT_NOT_REACHED();
484     }
485
486     if (m_memory && desiredSize <= m_mappedCapacity) {
487         uint8_t* startAddress = static_cast<uint8_t*>(m_memory) + m_size;
488         size_t extraBytes = desiredSize - m_size;
489         RELEASE_ASSERT(extraBytes);
490         dataLogLnIf(verbose, "Marking WebAssembly memory's ", RawPointer(m_memory), " as read+write in range [", RawPointer(startAddress), ", ", RawPointer(startAddress + extraBytes), ")");
491         if (mprotect(startAddress, extraBytes, PROT_READ | PROT_WRITE)) {
492             dataLogLnIf(verbose, "Memory::grow in-place failed ", *this);
493             return false;
494         }
495
496         m_size = desiredSize;
497         dataLogLnIf(verbose, "Memory::grow in-place ", *this);
498         return true;
499     }
500
501     // Signaling memory can't grow past its already-mapped size.
502     RELEASE_ASSERT(mode() != MemoryMode::Signaling);
503
504     // Otherwise, let's try to make some new memory.
505     // FIXME mremap would be nice https://bugs.webkit.org/show_bug.cgi?id=170557
506     // FIXME should we over-allocate here? https://bugs.webkit.org/show_bug.cgi?id=170826
507     void* newMemory = tryGetSlowMemory(desiredSize);
508     if (!newMemory)
509         return false;
510
511     if (!makeNewMemoryReadWriteOrRelinquish(newMemory, desiredSize, desiredSize, mode()))
512         return false;
513
514     if (m_memory) {
515         memcpy(newMemory, m_memory, m_size);
516         relinquishMemory(m_memory, m_size, m_size, m_mode);
517     }
518
519     m_memory = newMemory;
520     m_mappedCapacity = desiredSize;
521     m_size = desiredSize;
522
523     dataLogLnIf(verbose, "Memory::grow ", *this);
524     return true;
525 }
526
527 void Memory::dump(PrintStream& out) const
528 {
529     out.print("Memory at ", RawPointer(m_memory), ", size ", m_size, "B capacity ", m_mappedCapacity, "B, initial ", m_initial, " maximum ", m_maximum, " mode ", makeString(m_mode));
530 }
531
532 } // namespace JSC
533
534 } // namespace Wasm
535
536 #endif // ENABLE(WEBASSEMBLY)