94d064c36d3e7c4a7adc838b74a11625d00eb440
[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 #include <wtf/Gigacage.h>
34 #include <wtf/Lock.h>
35 #include <wtf/Platform.h>
36 #include <wtf/PrintStream.h>
37 #include <wtf/RAMSize.h>
38
39 namespace JSC { namespace Wasm {
40
41 // FIXME: We could be smarter about memset / mmap / madvise. https://bugs.webkit.org/show_bug.cgi?id=170343
42 // 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
43 // FIXME: Limit slow memory size. https://bugs.webkit.org/show_bug.cgi?id=170825
44
45 namespace {
46
47 constexpr bool verbose = false;
48
49 NEVER_INLINE NO_RETURN_DUE_TO_CRASH void webAssemblyCouldntGetFastMemory() { CRASH(); }
50
51 struct MemoryResult {
52     enum Kind {
53         Success,
54         SuccessAndAsyncGC,
55         SyncGCAndRetry
56     };
57     
58     static const char* toString(Kind kind)
59     {
60         switch (kind) {
61         case Success:
62             return "Success";
63         case SuccessAndAsyncGC:
64             return "SuccessAndAsyncGC";
65         case SyncGCAndRetry:
66             return "SyncGCAndRetry";
67         }
68         RELEASE_ASSERT_NOT_REACHED();
69         return nullptr;
70     }
71     
72     MemoryResult() { }
73     
74     MemoryResult(void* basePtr, Kind kind)
75         : basePtr(basePtr)
76         , kind(kind)
77     {
78     }
79     
80     void dump(PrintStream& out) const
81     {
82         out.print("{basePtr = ", RawPointer(basePtr), ", kind = ", toString(kind), "}");
83     }
84     
85     void* basePtr;
86     Kind kind;
87 };
88
89 class MemoryManager {
90 public:
91     MemoryManager()
92         : m_maxCount(Options::maxNumWebAssemblyFastMemories())
93     {
94     }
95     
96     MemoryResult tryAllocateVirtualPages()
97     {
98         MemoryResult result = [&] {
99             auto holder = holdLock(m_lock);
100             if (m_memories.size() >= m_maxCount)
101                 return MemoryResult(nullptr, MemoryResult::SyncGCAndRetry);
102             
103             void* result = Gigacage::tryAllocateVirtualPages(Gigacage::Primitive, Memory::fastMappedBytes());
104             if (!result)
105                 return MemoryResult(nullptr, MemoryResult::SyncGCAndRetry);
106             
107             m_memories.append(result);
108             
109             return MemoryResult(
110                 result,
111                 m_memories.size() >= m_maxCount / 2 ? MemoryResult::SuccessAndAsyncGC : MemoryResult::Success);
112         }();
113         
114         if (Options::logWebAssemblyMemory())
115             dataLog("Allocated virtual: ", result, "; state: ", *this, "\n");
116         
117         return result;
118     }
119     
120     void freeVirtualPages(void* basePtr)
121     {
122         {
123             auto holder = holdLock(m_lock);
124             Gigacage::freeVirtualPages(Gigacage::Primitive, basePtr, Memory::fastMappedBytes());
125             m_memories.removeFirst(basePtr);
126         }
127         
128         if (Options::logWebAssemblyMemory())
129             dataLog("Freed virtual; state: ", *this, "\n");
130     }
131     
132     bool containsAddress(void* address)
133     {
134         // NOTE: This can be called from a signal handler, but only after we proved that we're in JIT code.
135         auto holder = holdLock(m_lock);
136         for (void* memory : m_memories) {
137             char* start = static_cast<char*>(memory);
138             if (start <= address && address <= start + Memory::fastMappedBytes())
139                 return true;
140         }
141         return false;
142     }
143     
144     // FIXME: Ideally, bmalloc would have this kind of mechanism. Then, we would just forward to that
145     // mechanism here.
146     MemoryResult::Kind tryAllocatePhysicalBytes(size_t bytes)
147     {
148         MemoryResult::Kind result = [&] {
149             auto holder = holdLock(m_lock);
150             if (m_physicalBytes + bytes > ramSize())
151                 return MemoryResult::SyncGCAndRetry;
152             
153             m_physicalBytes += bytes;
154             
155             if (m_physicalBytes >= ramSize() / 2)
156                 return MemoryResult::SuccessAndAsyncGC;
157             
158             return MemoryResult::Success;
159         }();
160         
161         if (Options::logWebAssemblyMemory())
162             dataLog("Allocated physical: ", bytes, ", ", MemoryResult::toString(result), "; state: ", *this, "\n");
163         
164         return result;
165     }
166     
167     void freePhysicalBytes(size_t bytes)
168     {
169         {
170             auto holder = holdLock(m_lock);
171             m_physicalBytes -= bytes;
172         }
173         
174         if (Options::logWebAssemblyMemory())
175             dataLog("Freed physical: ", bytes, "; state: ", *this, "\n");
176     }
177     
178     void dump(PrintStream& out) const
179     {
180         out.print("memories =  ", m_memories.size(), "/", m_maxCount, ", bytes = ", m_physicalBytes, "/", ramSize());
181     }
182     
183 private:
184     Lock m_lock;
185     unsigned m_maxCount { 0 };
186     Vector<void*> m_memories;
187     size_t m_physicalBytes { 0 };
188 };
189
190 static MemoryManager& memoryManager()
191 {
192     static std::once_flag onceFlag;
193     static MemoryManager* manager;
194     std::call_once(
195         onceFlag,
196         [] {
197             manager = new MemoryManager();
198         });
199     return *manager;
200 }
201
202 template<typename Func>
203 bool tryAndGC(VM& vm, const Func& allocate)
204 {
205     unsigned numTries = 2;
206     bool done = false;
207     for (unsigned i = 0; i < numTries && !done; ++i) {
208         switch (allocate()) {
209         case MemoryResult::Success:
210             done = true;
211             break;
212         case MemoryResult::SuccessAndAsyncGC:
213             vm.heap.collectAsync(CollectionScope::Full);
214             done = true;
215             break;
216         case MemoryResult::SyncGCAndRetry:
217             if (i + 1 == numTries)
218                 break;
219             vm.heap.collectSync(CollectionScope::Full);
220             break;
221         }
222     }
223     return done;
224 }
225
226 } // anonymous namespace
227
228 const char* makeString(MemoryMode mode)
229 {
230     switch (mode) {
231     case MemoryMode::BoundsChecking: return "BoundsChecking";
232     case MemoryMode::Signaling: return "Signaling";
233     }
234     RELEASE_ASSERT_NOT_REACHED();
235     return "";
236 }
237
238 Memory::Memory(PageCount initial, PageCount maximum)
239     : m_initial(initial)
240     , m_maximum(maximum)
241 {
242     ASSERT(!initial.bytes());
243     ASSERT(m_mode == MemoryMode::BoundsChecking);
244     dataLogLnIf(verbose, "Memory::Memory allocating ", *this);
245 }
246
247 Memory::Memory(void* memory, PageCount initial, PageCount maximum, size_t mappedCapacity, MemoryMode mode)
248     : m_memory(memory)
249     , m_size(initial.bytes())
250     , m_initial(initial)
251     , m_maximum(maximum)
252     , m_mappedCapacity(mappedCapacity)
253     , m_mode(mode)
254 {
255     dataLogLnIf(verbose, "Memory::Memory allocating ", *this);
256 }
257
258 RefPtr<Memory> Memory::create(VM& vm, PageCount initial, PageCount maximum)
259 {
260     ASSERT(initial);
261     RELEASE_ASSERT(!maximum || maximum >= initial); // This should be guaranteed by our caller.
262
263     const size_t initialBytes = initial.bytes();
264     const size_t maximumBytes = maximum ? maximum.bytes() : 0;
265
266     // We need to be sure we have a stub prior to running code.
267     if (UNLIKELY(!Thunks::singleton().stub(throwExceptionFromWasmThunkGenerator)))
268         return nullptr;
269
270     if (maximum && !maximumBytes) {
271         // User specified a zero maximum, initial size must also be zero.
272         RELEASE_ASSERT(!initialBytes);
273         return adoptRef(new Memory(initial, maximum));
274     }
275     
276     bool done = tryAndGC(
277         vm,
278         [&] () -> MemoryResult::Kind {
279             return memoryManager().tryAllocatePhysicalBytes(initialBytes);
280         });
281     if (!done)
282         return nullptr;
283         
284     char* fastMemory = nullptr;
285     if (Options::useWebAssemblyFastMemory()) {
286         tryAndGC(
287             vm,
288             [&] () -> MemoryResult::Kind {
289                 auto result = memoryManager().tryAllocateVirtualPages();
290                 fastMemory = bitwise_cast<char*>(result.basePtr);
291                 return result.kind;
292             });
293     }
294     
295     if (fastMemory) {
296         bool writable = true;
297         bool executable = false;
298         OSAllocator::commit(fastMemory, initialBytes, writable, executable);
299         
300         if (mprotect(fastMemory + initialBytes, Memory::fastMappedBytes() - initialBytes, PROT_NONE)) {
301             dataLog("mprotect failed: ", strerror(errno), "\n");
302             RELEASE_ASSERT_NOT_REACHED();
303         }
304         
305         memset(fastMemory, 0, initialBytes);
306         return adoptRef(new Memory(fastMemory, initial, maximum, Memory::fastMappedBytes(), MemoryMode::Signaling));
307     }
308     
309     if (UNLIKELY(Options::crashIfWebAssemblyCantFastMemory()))
310         webAssemblyCouldntGetFastMemory();
311
312     if (!initialBytes)
313         return adoptRef(new Memory(initial, maximum));
314     
315     void* slowMemory = Gigacage::tryAlignedMalloc(Gigacage::Primitive, WTF::pageSize(), initialBytes);
316     if (!slowMemory) {
317         memoryManager().freePhysicalBytes(initialBytes);
318         return nullptr;
319     }
320     memset(slowMemory, 0, initialBytes);
321     return adoptRef(new Memory(slowMemory, initial, maximum, initialBytes, MemoryMode::BoundsChecking));
322 }
323
324 Memory::~Memory()
325 {
326     if (m_memory) {
327         memoryManager().freePhysicalBytes(m_size);
328         switch (m_mode) {
329         case MemoryMode::Signaling:
330             mprotect(m_memory, Memory::fastMappedBytes(), PROT_READ | PROT_WRITE);
331             memoryManager().freeVirtualPages(m_memory);
332             break;
333         case MemoryMode::BoundsChecking:
334             Gigacage::alignedFree(Gigacage::Primitive, m_memory);
335             break;
336         }
337     }
338 }
339
340 size_t Memory::fastMappedRedzoneBytes()
341 {
342     return static_cast<size_t>(PageCount::pageSize) * Options::webAssemblyFastMemoryRedzonePages();
343 }
344
345 size_t Memory::fastMappedBytes()
346 {
347     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.");
348     return static_cast<size_t>(std::numeric_limits<uint32_t>::max()) + fastMappedRedzoneBytes();
349 }
350
351 bool Memory::addressIsInActiveFastMemory(void* address)
352 {
353     return memoryManager().containsAddress(address);
354 }
355
356 bool Memory::grow(VM& vm, PageCount newSize)
357 {
358     RELEASE_ASSERT(newSize > PageCount::fromBytes(m_size));
359
360     dataLogLnIf(verbose, "Memory::grow to ", newSize, " from ", *this);
361
362     if (maximum() && newSize > maximum())
363         return false;
364
365     size_t desiredSize = newSize.bytes();
366     RELEASE_ASSERT(desiredSize > m_size);
367     size_t extraBytes = desiredSize - m_size;
368     RELEASE_ASSERT(extraBytes);
369     bool success = tryAndGC(
370         vm,
371         [&] () -> MemoryResult::Kind {
372             return memoryManager().tryAllocatePhysicalBytes(extraBytes);
373         });
374     if (!success)
375         return false;
376         
377     switch (mode()) {
378     case MemoryMode::BoundsChecking: {
379         RELEASE_ASSERT(maximum().bytes() != 0);
380         
381         void* newMemory = Gigacage::tryAlignedMalloc(Gigacage::Primitive, WTF::pageSize(), desiredSize);
382         if (!newMemory)
383             return false;
384         memcpy(newMemory, m_memory, m_size);
385         memset(static_cast<char*>(newMemory) + m_size, 0, desiredSize - m_size);
386         if (m_memory)
387             Gigacage::alignedFree(Gigacage::Primitive, m_memory);
388         m_memory = newMemory;
389         m_mappedCapacity = desiredSize;
390         m_size = desiredSize;
391         return true;
392     }
393     case MemoryMode::Signaling: {
394         RELEASE_ASSERT(m_memory);
395         // Signaling memory must have been pre-allocated virtually.
396         uint8_t* startAddress = static_cast<uint8_t*>(m_memory) + m_size;
397         
398         dataLogLnIf(verbose, "Marking WebAssembly memory's ", RawPointer(m_memory), " as read+write in range [", RawPointer(startAddress), ", ", RawPointer(startAddress + extraBytes), ")");
399         if (mprotect(startAddress, extraBytes, PROT_READ | PROT_WRITE)) {
400             dataLogLnIf(verbose, "Memory::grow in-place failed ", *this);
401             return false;
402         }
403         memset(startAddress, 0, extraBytes);
404         m_size = desiredSize;
405         return true;
406     } }
407     
408     RELEASE_ASSERT_NOT_REACHED();
409     return false;
410 }
411
412 void Memory::dump(PrintStream& out) const
413 {
414     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));
415 }
416
417 } // namespace JSC
418
419 } // namespace Wasm
420
421 #endif // ENABLE(WEBASSEMBLY)