WebAssembly: NFC s/goto/lambda/g
[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     auto fail = [] () -> bool {
122         if (UNLIKELY(Options::crashIfWebAssemblyCantFastMemory()))
123             webAssemblyCouldntGetFastMemory();
124         return false;
125     };
126
127     // We might GC here so we should be holding the API lock.
128     // FIXME: We should be able to syncronously trigger the GC from another thread.
129     ASSERT(vm.currentThreadIsHoldingAPILock());
130     if (UNLIKELY(!fastMemoryEnabled()))
131         return fail();
132
133     // We need to be sure we have a stub prior to running code.
134     if (UNLIKELY(!vm.getCTIStub(throwExceptionFromWasmThunkGenerator).size()))
135         return fail();
136
137     ASSERT(allocatedFastMemories <= maxFastMemories);
138     if (dequeFastMemory())
139         return true;
140
141     // If we have allocated all the fast memories... too bad.
142     if (allocatedFastMemories == maxFastMemories) {
143         // There is a reasonable chance that another module has died but has not been collected yet. Don't lose hope yet!
144         vm.heap.collectAllGarbage();
145         if (dequeFastMemory())
146             return true;
147         return fail();
148     }
149
150     if (mmapBytes(fastMemoryMappedBytes, memory)) {
151         mappedCapacity = fastMemoryMappedBytes;
152         mode = MemoryMode::Signaling;
153         LockHolder locker(memoryLock);
154         allocatedFastMemories++;
155         auto result = activeFastMemories(locker).add(memory);
156         ASSERT_UNUSED(result, result.isNewEntry);
157     }
158
159     if (memory)
160         return true;
161
162     return fail();
163 }
164
165 inline void releaseFastMemory(void*& memory, size_t writableSize, size_t mappedCapacity, MemoryMode mode)
166 {
167     if (mode != MemoryMode::Signaling || !memory)
168         return;
169
170     RELEASE_ASSERT(memory && mappedCapacity == fastMemoryMappedBytes);
171     ASSERT(fastMemoryEnabled());
172
173     memset(memory, 0, writableSize);
174     if (mprotect(memory, writableSize, PROT_NONE))
175         CRASH();
176
177     LockHolder locker(memoryLock);
178     bool result = activeFastMemories(locker).remove(memory);
179     ASSERT_UNUSED(result, result);
180     ASSERT(availableFastMemories(locker).size() < allocatedFastMemories);
181     availableFastMemories(locker).append(memory);
182     memory = nullptr;
183 }
184
185 Memory::Memory(PageCount initial, PageCount maximum)
186     : m_initial(initial)
187     , m_maximum(maximum)
188 {
189     ASSERT(!initial.bytes());
190     ASSERT(m_mode == MemoryMode::BoundsChecking);
191     dataLogLnIf(verbose, "Memory::Memory allocating ", *this);
192 }
193
194 Memory::Memory(void* memory, PageCount initial, PageCount maximum, size_t mappedCapacity, MemoryMode mode)
195     : m_memory(memory)
196     , m_size(initial.bytes())
197     , m_initial(initial)
198     , m_maximum(maximum)
199     , m_mappedCapacity(mappedCapacity)
200     , m_mode(mode)
201 {
202     dataLogLnIf(verbose, "Memory::Memory allocating ", *this);
203 }
204
205 RefPtr<Memory> Memory::createImpl(VM& vm, PageCount initial, PageCount maximum, std::optional<MemoryMode> requiredMode)
206 {
207     RELEASE_ASSERT(!maximum || maximum >= initial); // This should be guaranteed by our caller.
208
209     MemoryMode mode = requiredMode ? *requiredMode : MemoryMode::BoundsChecking;
210     const size_t size = initial.bytes();
211     size_t mappedCapacity = maximum ? maximum.bytes() : PageCount::max().bytes();
212     void* memory = nullptr;
213
214     auto makeEmptyMemory = [&] () -> RefPtr<Memory> {
215         if (mode == MemoryMode::Signaling)
216             return nullptr;
217
218         lastAllocatedMemoryMode = MemoryMode::BoundsChecking;
219         return adoptRef(new Memory(initial, maximum));
220     };
221
222     if (!mappedCapacity) {
223         // This means we specified a zero as maximum (which means we also have zero as initial size).
224         RELEASE_ASSERT(!size);
225         dataLogLnIf(verbose, "Memory::create allocating nothing");
226         return makeEmptyMemory();
227     }
228
229     bool canUseFastMemory = !requiredMode || requiredMode == MemoryMode::Signaling;
230     if (!canUseFastMemory || !tryGetFastMemory(vm, memory, mappedCapacity, mode)) {
231         if (mode == MemoryMode::Signaling)
232             return nullptr;
233
234         if (Options::simulateWebAssemblyLowMemory() ? true : !mmapBytes(mappedCapacity, memory)) {
235             // Try again with a different number.
236             dataLogLnIf(verbose, "Memory::create mmap failed once for capacity, trying again");
237             mappedCapacity = size;
238             if (!mappedCapacity) {
239                 dataLogLnIf(verbose, "Memory::create mmap not trying again because size is zero");
240                 return makeEmptyMemory();
241             }
242
243             if (!mmapBytes(mappedCapacity, memory)) {
244                 dataLogLnIf(verbose, "Memory::create mmap failed twice");
245                 return nullptr;
246             }
247         }
248     }
249
250     ASSERT(memory && size <= mappedCapacity);
251     if (mprotect(memory, size, PROT_READ | PROT_WRITE)) {
252         // FIXME: should this ever occur? https://bugs.webkit.org/show_bug.cgi?id=169890
253         dataLogLnIf(verbose, "Memory::create mprotect failed");
254         releaseFastMemory(memory, 0, mappedCapacity, mode);
255         if (memory) {
256             if (munmap(memory, mappedCapacity))
257                 CRASH();
258         }
259         return nullptr;
260     }
261
262     lastAllocatedMemoryMode = mode;
263     dataLogLnIf(verbose, "Memory::create mmap succeeded");
264     return adoptRef(new Memory(memory, initial, maximum, mappedCapacity, mode));
265 }
266
267 RefPtr<Memory> Memory::create(VM& vm, PageCount initial, PageCount maximum, std::optional<MemoryMode> mode)
268 {
269     RELEASE_ASSERT(!maximum || maximum >= initial); // This should be guaranteed by our caller.
270     RefPtr<Memory> result = createImpl(vm, initial, maximum, mode);
271     if (result) {
272         if (result->mode() == MemoryMode::Signaling)
273             RELEASE_ASSERT(result->m_mappedCapacity == fastMemoryMappedBytes);
274         if (mode)
275             ASSERT(*mode == result->mode());
276     }
277     return result;
278 }
279
280 Memory::~Memory()
281 {
282     dataLogLnIf(verbose, "Memory::~Memory ", *this);
283     releaseFastMemory(m_memory, m_size, m_mappedCapacity, m_mode);
284     if (m_memory) {
285         if (munmap(m_memory, m_mappedCapacity))
286             CRASH();
287     }
288 }
289
290 bool Memory::grow(PageCount newSize)
291 {
292     RELEASE_ASSERT(newSize > PageCount::fromBytes(m_size));
293
294     dataLogLnIf(verbose, "Memory::grow to ", newSize, " from ", *this);
295
296     if (maximum() && newSize > maximum())
297         return false;
298
299     size_t desiredSize = newSize.bytes();
300
301     switch (mode()) {
302     case MemoryMode::BoundsChecking:
303         RELEASE_ASSERT(maximum().bytes() != 0);
304         break;
305     case MemoryMode::Signaling:
306         // Signaling memory must have been pre-allocated virtually.
307         RELEASE_ASSERT(m_memory);
308         break;
309     case MemoryMode::NumberOfMemoryModes:
310         RELEASE_ASSERT_NOT_REACHED();
311     }
312
313     if (m_memory && desiredSize <= m_mappedCapacity) {
314         if (mprotect(static_cast<uint8_t*>(m_memory) + m_size, static_cast<size_t>(desiredSize - m_size), PROT_READ | PROT_WRITE)) {
315             // FIXME: should this ever occur? https://bugs.webkit.org/show_bug.cgi?id=169890
316             dataLogLnIf(verbose, "Memory::grow in-place failed ", *this);
317             return false;
318         }
319
320         m_size = desiredSize;
321         dataLogLnIf(verbose, "Memory::grow in-place ", *this);
322         return true;
323     }
324
325     // Signaling memory can't grow past its already-mapped size.
326     RELEASE_ASSERT(mode() != MemoryMode::Signaling);
327
328     // Otherwise, let's try to make some new memory.
329     // FIXME: It would be nice if we had a VM tag for wasm memory. https://bugs.webkit.org/show_bug.cgi?id=163600
330     void* newMemory = mmap(nullptr, desiredSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
331     if (newMemory == MAP_FAILED)
332         return false;
333
334     if (m_memory) {
335         memcpy(newMemory, m_memory, m_size);
336         bool success = !munmap(m_memory, m_mappedCapacity);
337         RELEASE_ASSERT(success);
338     }
339     m_memory = newMemory;
340     m_mappedCapacity = desiredSize;
341     m_size = desiredSize;
342
343     dataLogLnIf(verbose, "Memory::grow ", *this);
344     return true;
345 }
346
347 void Memory::dump(PrintStream& out) const
348 {
349     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));
350 }
351
352 } // namespace JSC
353
354 } // namespace Wasm
355
356 #endif // ENABLE(WEBASSEMBLY)