c6be975a28001177c85f5b50c363a9b3622d3079
[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 static void commitZeroPages(void* startAddress, size_t sizeInBytes)
259 {
260     bool writable = true;
261     bool executable = false;
262 #if OS(LINUX)
263     // In Linux, MADV_DONTNEED clears backing pages with zero. Be Careful that MADV_DONTNEED shows different semantics in different OSes.
264     // For example, FreeBSD does not clear backing pages immediately.
265     while (madvise(startAddress, sizeInBytes, MADV_DONTNEED) == -1 && errno == EAGAIN) { }
266     OSAllocator::commit(startAddress, sizeInBytes, writable, executable);
267 #else
268     OSAllocator::commit(startAddress, sizeInBytes, writable, executable);
269     memset(startAddress, 0, sizeInBytes);
270 #endif
271 }
272
273 RefPtr<Memory> Memory::create(VM& vm, PageCount initial, PageCount maximum)
274 {
275     ASSERT(initial);
276     RELEASE_ASSERT(!maximum || maximum >= initial); // This should be guaranteed by our caller.
277
278     const size_t initialBytes = initial.bytes();
279     const size_t maximumBytes = maximum ? maximum.bytes() : 0;
280
281     // We need to be sure we have a stub prior to running code.
282     if (UNLIKELY(!Thunks::singleton().stub(throwExceptionFromWasmThunkGenerator)))
283         return nullptr;
284
285     if (maximum && !maximumBytes) {
286         // User specified a zero maximum, initial size must also be zero.
287         RELEASE_ASSERT(!initialBytes);
288         return adoptRef(new Memory(initial, maximum));
289     }
290     
291     bool done = tryAndGC(
292         vm,
293         [&] () -> MemoryResult::Kind {
294             return memoryManager().tryAllocatePhysicalBytes(initialBytes);
295         });
296     if (!done)
297         return nullptr;
298         
299     char* fastMemory = nullptr;
300     if (Options::useWebAssemblyFastMemory()) {
301         tryAndGC(
302             vm,
303             [&] () -> MemoryResult::Kind {
304                 auto result = memoryManager().tryAllocateVirtualPages();
305                 fastMemory = bitwise_cast<char*>(result.basePtr);
306                 return result.kind;
307             });
308     }
309     
310     if (fastMemory) {
311         
312         if (mprotect(fastMemory + initialBytes, Memory::fastMappedBytes() - initialBytes, PROT_NONE)) {
313             dataLog("mprotect failed: ", strerror(errno), "\n");
314             RELEASE_ASSERT_NOT_REACHED();
315         }
316
317         commitZeroPages(fastMemory, initialBytes);
318         
319         return adoptRef(new Memory(fastMemory, initial, maximum, Memory::fastMappedBytes(), MemoryMode::Signaling));
320     }
321     
322     if (UNLIKELY(Options::crashIfWebAssemblyCantFastMemory()))
323         webAssemblyCouldntGetFastMemory();
324
325     if (!initialBytes)
326         return adoptRef(new Memory(initial, maximum));
327     
328     void* slowMemory = Gigacage::tryAlignedMalloc(Gigacage::Primitive, WTF::pageSize(), initialBytes);
329     if (!slowMemory) {
330         memoryManager().freePhysicalBytes(initialBytes);
331         return nullptr;
332     }
333     memset(slowMemory, 0, initialBytes);
334     return adoptRef(new Memory(slowMemory, initial, maximum, initialBytes, MemoryMode::BoundsChecking));
335 }
336
337 Memory::~Memory()
338 {
339     if (m_memory) {
340         memoryManager().freePhysicalBytes(m_size);
341         switch (m_mode) {
342         case MemoryMode::Signaling:
343             mprotect(m_memory, Memory::fastMappedBytes(), PROT_READ | PROT_WRITE);
344             memoryManager().freeVirtualPages(m_memory);
345             break;
346         case MemoryMode::BoundsChecking:
347             Gigacage::alignedFree(Gigacage::Primitive, m_memory);
348             break;
349         }
350     }
351 }
352
353 size_t Memory::fastMappedRedzoneBytes()
354 {
355     return static_cast<size_t>(PageCount::pageSize) * Options::webAssemblyFastMemoryRedzonePages();
356 }
357
358 size_t Memory::fastMappedBytes()
359 {
360     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.");
361     return static_cast<size_t>(std::numeric_limits<uint32_t>::max()) + fastMappedRedzoneBytes();
362 }
363
364 bool Memory::addressIsInActiveFastMemory(void* address)
365 {
366     return memoryManager().containsAddress(address);
367 }
368
369 bool Memory::grow(VM& vm, PageCount newSize)
370 {
371     RELEASE_ASSERT(newSize > PageCount::fromBytes(m_size));
372
373     dataLogLnIf(verbose, "Memory::grow to ", newSize, " from ", *this);
374
375     if (maximum() && newSize > maximum())
376         return false;
377
378     size_t desiredSize = newSize.bytes();
379     RELEASE_ASSERT(desiredSize > m_size);
380     size_t extraBytes = desiredSize - m_size;
381     RELEASE_ASSERT(extraBytes);
382     bool success = tryAndGC(
383         vm,
384         [&] () -> MemoryResult::Kind {
385             return memoryManager().tryAllocatePhysicalBytes(extraBytes);
386         });
387     if (!success)
388         return false;
389         
390     switch (mode()) {
391     case MemoryMode::BoundsChecking: {
392         RELEASE_ASSERT(maximum().bytes() != 0);
393         
394         void* newMemory = Gigacage::tryAlignedMalloc(Gigacage::Primitive, WTF::pageSize(), desiredSize);
395         if (!newMemory)
396             return false;
397         memcpy(newMemory, m_memory, m_size);
398         memset(static_cast<char*>(newMemory) + m_size, 0, desiredSize - m_size);
399         if (m_memory)
400             Gigacage::alignedFree(Gigacage::Primitive, m_memory);
401         m_memory = newMemory;
402         m_mappedCapacity = desiredSize;
403         m_size = desiredSize;
404         return true;
405     }
406     case MemoryMode::Signaling: {
407         RELEASE_ASSERT(m_memory);
408         // Signaling memory must have been pre-allocated virtually.
409         uint8_t* startAddress = static_cast<uint8_t*>(m_memory) + m_size;
410         
411         dataLogLnIf(verbose, "Marking WebAssembly memory's ", RawPointer(m_memory), " as read+write in range [", RawPointer(startAddress), ", ", RawPointer(startAddress + extraBytes), ")");
412         if (mprotect(startAddress, extraBytes, PROT_READ | PROT_WRITE)) {
413             dataLogLnIf(verbose, "Memory::grow in-place failed ", *this);
414             return false;
415         }
416         commitZeroPages(startAddress, extraBytes);
417         m_size = desiredSize;
418         return true;
419     } }
420     
421     RELEASE_ASSERT_NOT_REACHED();
422     return false;
423 }
424
425 void Memory::dump(PrintStream& out) const
426 {
427     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));
428 }
429
430 } // namespace JSC
431
432 } // namespace Wasm
433
434 #endif // ENABLE(WEBASSEMBLY)