1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qv4executableallocator_p.h" |
5 | #include <QtQml/private/qv4functiontable_p.h> |
6 | |
7 | #include <wtf/StdLibExtras.h> |
8 | #include <wtf/PageAllocation.h> |
9 | |
10 | using namespace QV4; |
11 | |
12 | void *ExecutableAllocator::Allocation::exceptionHandlerStart() const |
13 | { |
14 | return reinterpret_cast<void*>(addr); |
15 | } |
16 | |
17 | size_t ExecutableAllocator::Allocation::exceptionHandlerSize() const |
18 | { |
19 | return QV4::exceptionHandlerSize(); |
20 | } |
21 | |
22 | void *ExecutableAllocator::Allocation::memoryStart() const |
23 | { |
24 | return reinterpret_cast<void*>(addr); |
25 | } |
26 | |
27 | void *ExecutableAllocator::Allocation::codeStart() const |
28 | { |
29 | return reinterpret_cast<void*>(addr + exceptionHandlerSize()); |
30 | } |
31 | |
32 | void ExecutableAllocator::Allocation::deallocate(ExecutableAllocator *allocator) |
33 | { |
34 | if (isValid()) |
35 | allocator->free(allocation: this); |
36 | else |
37 | delete this; |
38 | } |
39 | |
40 | ExecutableAllocator::Allocation *ExecutableAllocator::Allocation::split(size_t dividingSize) |
41 | { |
42 | Allocation *remainder = new Allocation; |
43 | if (next) |
44 | next->prev = remainder; |
45 | |
46 | remainder->next = next; |
47 | next = remainder; |
48 | |
49 | remainder->prev = this; |
50 | |
51 | remainder->size = size - dividingSize; |
52 | remainder->free = free; |
53 | remainder->addr = addr + dividingSize; |
54 | size = dividingSize; |
55 | |
56 | return remainder; |
57 | } |
58 | |
59 | bool ExecutableAllocator::Allocation::mergeNext(ExecutableAllocator *allocator) |
60 | { |
61 | Q_ASSERT(free); |
62 | if (!next || !next->free) |
63 | return false; |
64 | |
65 | allocator->freeAllocations.remove(key: size, value: this); |
66 | allocator->freeAllocations.remove(key: next->size, value: next); |
67 | |
68 | size += next->size; |
69 | Allocation *newNext = next->next; |
70 | delete next; |
71 | next = newNext; |
72 | if (next) |
73 | next->prev = this; |
74 | |
75 | allocator->freeAllocations.insert(key: size, value: this); |
76 | return true; |
77 | } |
78 | |
79 | bool ExecutableAllocator::Allocation::mergePrevious(ExecutableAllocator *allocator) |
80 | { |
81 | Q_ASSERT(free); |
82 | if (!prev || !prev->free) |
83 | return false; |
84 | |
85 | allocator->freeAllocations.remove(key: size, value: this); |
86 | allocator->freeAllocations.remove(key: prev->size, value: prev); |
87 | |
88 | prev->size += size; |
89 | if (next) |
90 | next->prev = prev; |
91 | prev->next = next; |
92 | |
93 | allocator->freeAllocations.insert(key: prev->size, value: prev); |
94 | |
95 | delete this; |
96 | return true; |
97 | } |
98 | |
99 | ExecutableAllocator::ChunkOfPages::~ChunkOfPages() |
100 | { |
101 | Allocation *alloc = firstAllocation; |
102 | while (alloc) { |
103 | Allocation *next = alloc->next; |
104 | if (alloc->isValid()) |
105 | delete alloc; |
106 | alloc = next; |
107 | } |
108 | pages->deallocate(); |
109 | delete pages; |
110 | } |
111 | |
112 | bool ExecutableAllocator::ChunkOfPages::contains(Allocation *alloc) const |
113 | { |
114 | Allocation *it = firstAllocation; |
115 | while (it) { |
116 | if (it == alloc) |
117 | return true; |
118 | it = it->next; |
119 | } |
120 | return false; |
121 | } |
122 | |
123 | ExecutableAllocator::ExecutableAllocator() |
124 | = default; |
125 | |
126 | ExecutableAllocator::~ExecutableAllocator() |
127 | { |
128 | for (ChunkOfPages *chunk : std::as_const(t&: chunks)) { |
129 | for (Allocation *allocation = chunk->firstAllocation; allocation; allocation = allocation->next) |
130 | if (!allocation->free) |
131 | allocation->invalidate(); |
132 | } |
133 | |
134 | qDeleteAll(c: chunks); |
135 | } |
136 | |
137 | ExecutableAllocator::Allocation *ExecutableAllocator::allocate(size_t size) |
138 | { |
139 | QMutexLocker locker(&mutex); |
140 | Allocation *allocation = nullptr; |
141 | |
142 | // Code is best aligned to 16-byte boundaries. |
143 | size = WTF::roundUpToMultipleOf(divisor: 16, x: size + exceptionHandlerSize()); |
144 | |
145 | QMultiMap<size_t, Allocation*>::Iterator it = freeAllocations.lowerBound(key: size); |
146 | if (it != freeAllocations.end()) { |
147 | allocation = *it; |
148 | freeAllocations.erase(it); |
149 | } |
150 | |
151 | if (!allocation) { |
152 | ChunkOfPages *chunk = new ChunkOfPages; |
153 | size_t allocSize = WTF::roundUpToMultipleOf(divisor: WTF::pageSize(), x: size); |
154 | chunk->pages = new WTF::PageAllocation(WTF::PageAllocation::allocate(size: allocSize, usage: OSAllocator::JSJITCodePages)); |
155 | chunks.insert(key: reinterpret_cast<quintptr>(chunk->pages->base()) - 1, value: chunk); |
156 | allocation = new Allocation; |
157 | allocation->addr = reinterpret_cast<quintptr>(chunk->pages->base()); |
158 | allocation->size = allocSize; |
159 | allocation->free = true; |
160 | chunk->firstAllocation = allocation; |
161 | } |
162 | |
163 | Q_ASSERT(allocation); |
164 | Q_ASSERT(allocation->free); |
165 | |
166 | allocation->free = false; |
167 | |
168 | if (allocation->size > size) { |
169 | Allocation *remainder = allocation->split(dividingSize: size); |
170 | remainder->free = true; |
171 | if (!remainder->mergeNext(allocator: this)) |
172 | freeAllocations.insert(key: remainder->size, value: remainder); |
173 | } |
174 | |
175 | return allocation; |
176 | } |
177 | |
178 | void ExecutableAllocator::free(Allocation *allocation) |
179 | { |
180 | QMutexLocker locker(&mutex); |
181 | |
182 | Q_ASSERT(allocation); |
183 | |
184 | allocation->free = true; |
185 | |
186 | QMap<quintptr, ChunkOfPages*>::Iterator it = chunks.lowerBound(key: allocation->addr); |
187 | if (it != chunks.begin()) |
188 | --it; |
189 | Q_ASSERT(it != chunks.end()); |
190 | ChunkOfPages *chunk = *it; |
191 | Q_ASSERT(chunk->contains(allocation)); |
192 | |
193 | bool merged = allocation->mergeNext(allocator: this); |
194 | merged |= allocation->mergePrevious(allocator: this); |
195 | if (!merged) |
196 | freeAllocations.insert(key: allocation->size, value: allocation); |
197 | |
198 | allocation = nullptr; |
199 | |
200 | if (!chunk->firstAllocation->next) { |
201 | freeAllocations.remove(key: chunk->firstAllocation->size, value: chunk->firstAllocation); |
202 | chunks.erase(it); |
203 | delete chunk; |
204 | return; |
205 | } |
206 | } |
207 | |
208 | ExecutableAllocator::ChunkOfPages *ExecutableAllocator::chunkForAllocation(Allocation *allocation) const |
209 | { |
210 | QMutexLocker locker(&mutex); |
211 | |
212 | QMap<quintptr, ChunkOfPages*>::ConstIterator it = chunks.lowerBound(key: allocation->addr); |
213 | if (it != chunks.begin()) |
214 | --it; |
215 | if (it == chunks.end()) |
216 | return nullptr; |
217 | return *it; |
218 | } |
219 | |
220 | |