2b6521070f290a45affad84de82af80708759fa3
[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 "WasmFaultSignalHandler.h"
33
34 #include <wtf/HexNumber.h>
35 #include <wtf/NeverDestroyed.h>
36 #include <wtf/PrintStream.h>
37 #include <wtf/text/WTFString.h>
38
39 namespace JSC { namespace Wasm {
40
41 namespace {
42 const bool verbose = false;
43 }
44
45 static NEVER_INLINE NO_RETURN_DUE_TO_CRASH void webAssemblyCouldntGetFastMemory()
46 {
47     CRASH();
48 }
49
50 inline bool mmapBytes(size_t bytes, void*& memory)
51 {
52     dataLogIf(verbose, "Attempting to mmap ", bytes, " bytes: ");
53     // FIXME: It would be nice if we had a VM tag for wasm memory. https://bugs.webkit.org/show_bug.cgi?id=163600
54     void* result = mmap(nullptr, bytes, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
55     if (result == MAP_FAILED) {
56         dataLogLnIf(verbose, "failed");
57         return false;
58     }
59     dataLogLnIf(verbose, "succeeded");
60     memory = result;
61     return true;
62 }
63
64 const char* makeString(MemoryMode mode)
65 {
66     switch (mode) {
67     case MemoryMode::BoundsChecking: return "BoundsChecking";
68     case MemoryMode::Signaling: return "Signaling";
69     case MemoryMode::NumberOfMemoryModes: break;
70     }
71     RELEASE_ASSERT_NOT_REACHED();
72     return "";
73 }
74
75 // We use this as a heuristic to guess what mode a memory import will be. Most of the time we expect users to
76 // allocate the memory they are going to pass to all their modules right before compilation.
77 static MemoryMode lastAllocatedMemoryMode { MemoryMode::Signaling };
78
79 MemoryMode Memory::lastAllocatedMode()
80 {
81     return lastAllocatedMemoryMode;
82 }
83
84 static const unsigned maxFastMemories = 4;
85 static unsigned allocatedFastMemories { 0 };
86 StaticLock memoryLock;
87 inline Deque<void*, maxFastMemories>& availableFastMemories(const AbstractLocker&)
88 {
89     static NeverDestroyed<Deque<void*, maxFastMemories>> availableFastMemories;
90     return availableFastMemories;
91 }
92
93 inline HashSet<void*>& activeFastMemories(const AbstractLocker&)
94 {
95     static NeverDestroyed<HashSet<void*>> activeFastMemories;
96     return activeFastMemories;
97 }
98
99 const HashSet<void*>& viewActiveFastMemories(const AbstractLocker& locker)
100 {
101     return activeFastMemories(locker);
102 }
103
104 inline bool tryGetFastMemory(VM& vm, void*& memory, size_t& mappedCapacity, MemoryMode& mode)
105 {
106     auto dequeFastMemory = [&] () -> bool {
107         // FIXME: We should eventually return these to the OS if we go some number of GCs
108         // without using them.
109         LockHolder locker(memoryLock);
110         if (!availableFastMemories(locker).isEmpty()) {
111             memory = availableFastMemories(locker).takeFirst();
112             auto result = activeFastMemories(locker).add(memory);
113             ASSERT_UNUSED(result, result.isNewEntry);
114             mappedCapacity = fastMemoryMappedBytes;
115             mode = MemoryMode::Signaling;
116             return true;
117         }
118         return false;
119     };
120
121     // We might GC here so we should be holding the API lock.
122     // FIXME: We should be able to syncronously trigger the GC from another thread.
123     ASSERT(vm.currentThreadIsHoldingAPILock());
124     if (UNLIKELY(!fastMemoryEnabled()))
125         goto fail;
126
127     // We need to be sure we have a stub prior to running code.
128     if (UNLIKELY(!vm.getCTIStub(throwExceptionFromWasmThunkGenerator).size()))
129         goto fail;
130
131     ASSERT(allocatedFastMemories <= maxFastMemories);
132     if (dequeFastMemory())
133         return true;
134
135     // If we have allocated all the fast memories... too bad.
136     if (allocatedFastMemories == maxFastMemories) {
137         // There is a reasonable chance that another module has died but has not been collected yet. Don't lose hope yet!
138         vm.heap.collectAllGarbage();
139         if (dequeFastMemory())
140             return true;
141         goto fail;
142     }
143
144     if (mmapBytes(fastMemoryMappedBytes, memory)) {
145         mappedCapacity = fastMemoryMappedBytes;
146         mode = MemoryMode::Signaling;
147         LockHolder locker(memoryLock);
148         allocatedFastMemories++;
149         auto result = activeFastMemories(locker).add(memory);
150         ASSERT_UNUSED(result, result.isNewEntry);
151     }
152
153     if (memory)
154         return true;
155     goto fail;
156
157 fail:
158     if (UNLIKELY(Options::crashIfWebAssemblyCantFastMemory()))
159         webAssemblyCouldntGetFastMemory();
160
161     return false;
162 }
163
164 inline void releaseFastMemory(void*& memory, size_t writableSize, size_t mappedCapacity, MemoryMode mode)
165 {
166     if (mode != MemoryMode::Signaling || !memory)
167         return;
168
169     RELEASE_ASSERT(memory && mappedCapacity == fastMemoryMappedBytes);
170     ASSERT(fastMemoryEnabled());
171
172     memset(memory, 0, writableSize);
173     if (mprotect(memory, writableSize, PROT_NONE))
174         CRASH();
175
176     LockHolder locker(memoryLock);
177     bool result = activeFastMemories(locker).remove(memory);
178     ASSERT_UNUSED(result, result);
179     ASSERT(availableFastMemories(locker).size() < allocatedFastMemories);
180     availableFastMemories(locker).append(memory);
181     memory = nullptr;
182 }
183
184 Memory::Memory(PageCount initial, PageCount maximum)
185     : m_initial(initial)
186     , m_maximum(maximum)
187 {
188     ASSERT(!initial.bytes());
189     ASSERT(m_mode == MemoryMode::BoundsChecking);
190     dataLogLnIf(verbose, "Memory::Memory allocating ", *this);
191 }
192
193 Memory::Memory(void* memory, PageCount initial, PageCount maximum, size_t mappedCapacity, MemoryMode mode)
194     : m_memory(memory)
195     , m_size(initial.bytes())
196     , m_initial(initial)
197     , m_maximum(maximum)
198     , m_mappedCapacity(mappedCapacity)
199     , m_mode(mode)
200 {
201     dataLogLnIf(verbose, "Memory::Memory allocating ", *this);
202 }
203
204 RefPtr<Memory> Memory::createImpl(VM& vm, PageCount initial, PageCount maximum, std::optional<MemoryMode> requiredMode)
205 {
206     RELEASE_ASSERT(!maximum || maximum >= initial); // This should be guaranteed by our caller.
207
208     MemoryMode mode = requiredMode ? *requiredMode : MemoryMode::BoundsChecking;
209     const size_t size = initial.bytes();
210     size_t mappedCapacity = maximum ? maximum.bytes() : PageCount::max().bytes();
211     void* memory = nullptr;
212
213     auto makeEmptyMemory = [&] () -> RefPtr<Memory> {
214         if (mode == MemoryMode::Signaling)
215             return nullptr;
216
217         lastAllocatedMemoryMode = MemoryMode::BoundsChecking;
218         return adoptRef(new Memory(initial, maximum));
219     };
220
221     if (!mappedCapacity) {
222         // This means we specified a zero as maximum (which means we also have zero as initial size).
223         RELEASE_ASSERT(!size);
224         dataLogLnIf(verbose, "Memory::create allocating nothing");
225         return makeEmptyMemory();
226     }
227
228     bool canUseFastMemory = !requiredMode || requiredMode == MemoryMode::Signaling;
229     if (!canUseFastMemory || !tryGetFastMemory(vm, memory, mappedCapacity, mode)) {
230         if (mode == MemoryMode::Signaling)
231             return nullptr;
232
233         if (Options::simulateWebAssemblyLowMemory() ? true : !mmapBytes(mappedCapacity, memory)) {
234             // Try again with a different number.
235             dataLogLnIf(verbose, "Memory::create mmap failed once for capacity, trying again");
236             mappedCapacity = size;
237             if (!mappedCapacity) {
238                 dataLogLnIf(verbose, "Memory::create mmap not trying again because size is zero");
239                 return makeEmptyMemory();
240             }
241
242             if (!mmapBytes(mappedCapacity, memory)) {
243                 dataLogLnIf(verbose, "Memory::create mmap failed twice");
244                 return nullptr;
245             }
246         }
247     }
248
249     ASSERT(memory && size <= mappedCapacity);
250     if (mprotect(memory, size, PROT_READ | PROT_WRITE)) {
251         // FIXME: should this ever occur? https://bugs.webkit.org/show_bug.cgi?id=169890
252         dataLogLnIf(verbose, "Memory::create mprotect failed");
253         releaseFastMemory(memory, 0, mappedCapacity, mode);
254         if (memory) {
255             if (munmap(memory, mappedCapacity))
256                 CRASH();
257         }
258         return nullptr;
259     }
260
261     lastAllocatedMemoryMode = mode;
262     dataLogLnIf(verbose, "Memory::create mmap succeeded");
263     return adoptRef(new Memory(memory, initial, maximum, mappedCapacity, mode));
264 }
265
266 RefPtr<Memory> Memory::create(VM& vm, PageCount initial, PageCount maximum, std::optional<MemoryMode> mode)
267 {
268     RELEASE_ASSERT(!maximum || maximum >= initial); // This should be guaranteed by our caller.
269     RefPtr<Memory> result = createImpl(vm, initial, maximum, mode);
270     if (result) {
271         if (result->mode() == MemoryMode::Signaling)
272             RELEASE_ASSERT(result->m_mappedCapacity == fastMemoryMappedBytes);
273         if (mode)
274             ASSERT(*mode == result->mode());
275     }
276     return result;
277 }
278
279 Memory::~Memory()
280 {
281     dataLogLnIf(verbose, "Memory::~Memory ", *this);
282     releaseFastMemory(m_memory, m_size, m_mappedCapacity, m_mode);
283     if (m_memory) {
284         if (munmap(m_memory, m_mappedCapacity))
285             CRASH();
286     }
287 }
288
289 bool Memory::grow(PageCount newSize)
290 {
291     RELEASE_ASSERT(newSize > PageCount::fromBytes(m_size));
292
293     dataLogLnIf(verbose, "Memory::grow to ", newSize, " from ", *this);
294
295     if (maximum() && newSize > maximum())
296         return false;
297
298     size_t desiredSize = newSize.bytes();
299
300     switch (mode()) {
301     case MemoryMode::BoundsChecking:
302         RELEASE_ASSERT(maximum().bytes() != 0);
303         break;
304     case MemoryMode::Signaling:
305         // Signaling memory must have been pre-allocated virtually.
306         RELEASE_ASSERT(m_memory);
307         break;
308     case MemoryMode::NumberOfMemoryModes:
309         RELEASE_ASSERT_NOT_REACHED();
310     }
311
312     if (m_memory && desiredSize <= m_mappedCapacity) {
313         if (mprotect(static_cast<uint8_t*>(m_memory) + m_size, static_cast<size_t>(desiredSize - m_size), PROT_READ | PROT_WRITE)) {
314             // FIXME: should this ever occur? https://bugs.webkit.org/show_bug.cgi?id=169890
315             dataLogLnIf(verbose, "Memory::grow in-place failed ", *this);
316             return false;
317         }
318
319         m_size = desiredSize;
320         dataLogLnIf(verbose, "Memory::grow in-place ", *this);
321         return true;
322     }
323
324     // Signaling memory can't grow past its already-mapped size.
325     RELEASE_ASSERT(mode() != MemoryMode::Signaling);
326
327     // Otherwise, let's try to make some new memory.
328     // FIXME: It would be nice if we had a VM tag for wasm memory. https://bugs.webkit.org/show_bug.cgi?id=163600
329     void* newMemory = mmap(nullptr, desiredSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
330     if (newMemory == MAP_FAILED)
331         return false;
332
333     if (m_memory) {
334         memcpy(newMemory, m_memory, m_size);
335         bool success = !munmap(m_memory, m_mappedCapacity);
336         RELEASE_ASSERT(success);
337     }
338     m_memory = newMemory;
339     m_mappedCapacity = desiredSize;
340     m_size = desiredSize;
341
342     dataLogLnIf(verbose, "Memory::grow ", *this);
343     return true;
344 }
345
346 void Memory::dump(PrintStream& out) const
347 {
348     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));
349 }
350
351 } // namespace JSC
352
353 } // namespace Wasm
354
355 #endif // ENABLE(WEBASSEMBLY)