Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2
* vim: set ts=8 sts=2 et sw=2 tw=80:
3
*
4
* Copyright 2019 Mozilla Foundation
5
*
6
* Licensed under the Apache License, Version 2.0 (the "License");
7
* you may not use this file except in compliance with the License.
8
* You may obtain a copy of the License at
9
*
11
*
12
* Unless required by applicable law or agreed to in writing, software
13
* distributed under the License is distributed on an "AS IS" BASIS,
14
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
* See the License for the specific language governing permissions and
16
* limitations under the License.
17
*/
18
19
#ifndef wasm_gc_h
20
#define wasm_gc_h
21
22
#include "jit/MacroAssembler.h"
23
#include "util/Memory.h"
24
25
namespace js {
26
namespace wasm {
27
28
using namespace js::jit;
29
30
// Definitions for stack maps.
31
32
typedef Vector<bool, 32, SystemAllocPolicy> ExitStubMapVector;
33
34
struct StackMap final {
35
// A StackMap is a bit-array containing numMappedWords bits, one bit per
36
// word of stack. Bit index zero is for the lowest addressed word in the
37
// range.
38
//
39
// This is a variable-length structure whose size must be known at creation
40
// time.
41
//
42
// Users of the map will know the address of the wasm::Frame that is covered
43
// by this map. In order that they can calculate the exact address range
44
// covered by the map, the map also stores the offset, from the highest
45
// addressed word of the map, of the embedded wasm::Frame. This is an
46
// offset down from the highest address, rather than up from the lowest, so
47
// as to limit its range to 11 bits, where
48
// 11 == ceil(log2(MaxParams * sizeof-biggest-param-type-in-words))
49
//
50
// The map may also cover a ref-typed DebugFrame. If so that can be noted,
51
// since users of the map need to trace pointers in such a DebugFrame.
52
//
53
// Finally, for sanity checking only, for stack maps associated with a wasm
54
// trap exit stub, the number of words used by the trap exit stub save area
55
// is also noted. This is used in Instance::traceFrame to check that the
56
// TrapExitDummyValue is in the expected place in the frame.
57
58
// The total number of stack words covered by the map ..
59
uint32_t numMappedWords : 30;
60
61
// .. of which this many are "exit stub" extras
62
uint32_t numExitStubWords : 6;
63
64
// Where is Frame* relative to the top? This is an offset in words.
65
uint32_t frameOffsetFromTop : 11;
66
67
// Notes the presence of a ref-typed DebugFrame.
68
uint32_t hasRefTypedDebugFrame : 1;
69
70
private:
71
static constexpr uint32_t maxMappedWords = (1 << 30) - 1;
72
static constexpr uint32_t maxExitStubWords = (1 << 6) - 1;
73
static constexpr uint32_t maxFrameOffsetFromTop = (1 << 11) - 1;
74
75
uint32_t bitmap[1];
76
77
explicit StackMap(uint32_t numMappedWords)
78
: numMappedWords(numMappedWords),
79
numExitStubWords(0),
80
frameOffsetFromTop(0),
81
hasRefTypedDebugFrame(0) {
82
const uint32_t nBitmap = calcNBitmap(numMappedWords);
83
memset(bitmap, 0, nBitmap * sizeof(bitmap[0]));
84
}
85
86
public:
87
static StackMap* create(uint32_t numMappedWords) {
88
uint32_t nBitmap = calcNBitmap(numMappedWords);
89
char* buf =
90
(char*)js_malloc(sizeof(StackMap) + (nBitmap - 1) * sizeof(bitmap[0]));
91
if (!buf) {
92
return nullptr;
93
}
94
return ::new (buf) StackMap(numMappedWords);
95
}
96
97
void destroy() { js_free((char*)this); }
98
99
// Record the number of words in the map used as a wasm trap exit stub
100
// save area. See comment above.
101
void setExitStubWords(uint32_t nWords) {
102
MOZ_ASSERT(numExitStubWords == 0);
103
MOZ_RELEASE_ASSERT(nWords <= maxExitStubWords);
104
MOZ_ASSERT(nWords <= numMappedWords);
105
numExitStubWords = nWords;
106
}
107
108
// Record the offset from the highest-addressed word of the map, that the
109
// wasm::Frame lives at. See comment above.
110
void setFrameOffsetFromTop(uint32_t nWords) {
111
MOZ_ASSERT(frameOffsetFromTop == 0);
112
MOZ_RELEASE_ASSERT(nWords <= maxFrameOffsetFromTop);
113
MOZ_ASSERT(frameOffsetFromTop < numMappedWords);
114
frameOffsetFromTop = nWords;
115
}
116
117
// If the frame described by this StackMap includes a DebugFrame for a
118
// ref-typed return value, call here to record that fact.
119
void setHasRefTypedDebugFrame() {
120
MOZ_ASSERT(hasRefTypedDebugFrame == 0);
121
hasRefTypedDebugFrame = 1;
122
}
123
124
inline void setBit(uint32_t bitIndex) {
125
MOZ_ASSERT(bitIndex < numMappedWords);
126
uint32_t wordIndex = bitIndex / wordsPerBitmapElem;
127
uint32_t wordOffset = bitIndex % wordsPerBitmapElem;
128
bitmap[wordIndex] |= (1 << wordOffset);
129
}
130
131
inline uint32_t getBit(uint32_t bitIndex) const {
132
MOZ_ASSERT(bitIndex < numMappedWords);
133
uint32_t wordIndex = bitIndex / wordsPerBitmapElem;
134
uint32_t wordOffset = bitIndex % wordsPerBitmapElem;
135
return (bitmap[wordIndex] >> wordOffset) & 1;
136
}
137
138
private:
139
static constexpr uint32_t wordsPerBitmapElem = sizeof(bitmap[0]) * 8;
140
141
static uint32_t calcNBitmap(uint32_t numMappedWords) {
142
MOZ_RELEASE_ASSERT(numMappedWords <= maxMappedWords);
143
uint32_t nBitmap =
144
(numMappedWords + wordsPerBitmapElem - 1) / wordsPerBitmapElem;
145
return nBitmap == 0 ? 1 : nBitmap;
146
}
147
};
148
149
// This is the expected size for a map that covers 32 or fewer words.
150
static_assert(sizeof(StackMap) == 12, "wasm::StackMap has unexpected size");
151
152
class StackMaps {
153
public:
154
// A Maplet holds a single code-address-to-map binding. Note that the
155
// code address is the lowest address of the instruction immediately
156
// following the instruction of interest, not of the instruction of
157
// interest itself. In practice (at least for the Wasm Baseline compiler)
158
// this means that |nextInsnAddr| points either immediately after a call
159
// instruction, after a trap instruction or after a no-op.
160
struct Maplet {
161
uint8_t* nextInsnAddr;
162
StackMap* map;
163
Maplet(uint8_t* nextInsnAddr, StackMap* map)
164
: nextInsnAddr(nextInsnAddr), map(map) {}
165
void offsetBy(uintptr_t delta) { nextInsnAddr += delta; }
166
bool operator<(const Maplet& other) const {
167
return uintptr_t(nextInsnAddr) < uintptr_t(other.nextInsnAddr);
168
}
169
};
170
171
private:
172
bool sorted_;
173
Vector<Maplet, 0, SystemAllocPolicy> mapping_;
174
175
public:
176
StackMaps() : sorted_(false) {}
177
~StackMaps() {
178
for (size_t i = 0; i < mapping_.length(); i++) {
179
mapping_[i].map->destroy();
180
mapping_[i].map = nullptr;
181
}
182
}
183
MOZ_MUST_USE bool add(uint8_t* nextInsnAddr, StackMap* map) {
184
MOZ_ASSERT(!sorted_);
185
return mapping_.append(Maplet(nextInsnAddr, map));
186
}
187
MOZ_MUST_USE bool add(const Maplet& maplet) {
188
return add(maplet.nextInsnAddr, maplet.map);
189
}
190
void clear() {
191
for (size_t i = 0; i < mapping_.length(); i++) {
192
mapping_[i].nextInsnAddr = nullptr;
193
mapping_[i].map = nullptr;
194
}
195
mapping_.clear();
196
}
197
bool empty() const { return mapping_.empty(); }
198
size_t length() const { return mapping_.length(); }
199
Maplet get(size_t i) const { return mapping_[i]; }
200
Maplet move(size_t i) {
201
Maplet m = mapping_[i];
202
mapping_[i].map = nullptr;
203
return m;
204
}
205
void offsetBy(uintptr_t delta) {
206
for (size_t i = 0; i < mapping_.length(); i++) mapping_[i].offsetBy(delta);
207
}
208
void sort() {
209
MOZ_ASSERT(!sorted_);
210
std::sort(mapping_.begin(), mapping_.end());
211
sorted_ = true;
212
}
213
const StackMap* findMap(uint8_t* nextInsnAddr) const {
214
struct Comparator {
215
int operator()(Maplet aVal) const {
216
if (uintptr_t(mTarget) < uintptr_t(aVal.nextInsnAddr)) {
217
return -1;
218
}
219
if (uintptr_t(mTarget) > uintptr_t(aVal.nextInsnAddr)) {
220
return 1;
221
}
222
return 0;
223
}
224
explicit Comparator(uint8_t* aTarget) : mTarget(aTarget) {}
225
const uint8_t* mTarget;
226
};
227
228
size_t result;
229
if (BinarySearchIf(mapping_, 0, mapping_.length(), Comparator(nextInsnAddr),
230
&result)) {
231
return mapping_[result].map;
232
}
233
234
return nullptr;
235
}
236
};
237
238
// Supporting code for creation of stackmaps.
239
240
// StackArgAreaSizeUnaligned returns the size, in bytes, of the stack arg area
241
// size needed to pass |argTypes|, excluding any alignment padding beyond the
242
// size of the area as a whole. The size is as determined by the platforms
243
// native ABI.
244
//
245
// StackArgAreaSizeAligned returns the same, but rounded up to the nearest 16
246
// byte boundary.
247
//
248
// Note, StackArgAreaSize{Unaligned,Aligned}() must process all the arguments
249
// in order to take into account all necessary alignment constraints. The
250
// signature must include any receiver argument -- in other words, it must be
251
// the complete native-ABI-level call signature.
252
template <class T>
253
static inline size_t StackArgAreaSizeUnaligned(const T& argTypes) {
254
ABIArgIter<const T> i(argTypes);
255
while (!i.done()) {
256
i++;
257
}
258
return i.stackBytesConsumedSoFar();
259
}
260
261
static inline size_t StackArgAreaSizeUnaligned(
262
const SymbolicAddressSignature& saSig) {
263
// ABIArgIter::ABIArgIter wants the items to be iterated over to be
264
// presented in some type that has methods length() and operator[]. So we
265
// have to wrap up |saSig|'s array of types in this API-matching class.
266
class MOZ_STACK_CLASS ItemsAndLength {
267
const MIRType* items_;
268
size_t length_;
269
270
public:
271
ItemsAndLength(const MIRType* items, size_t length)
272
: items_(items), length_(length) {}
273
size_t length() const { return length_; }
274
MIRType operator[](size_t i) const { return items_[i]; }
275
};
276
277
// Assert, at least crudely, that we're not accidentally going to run off
278
// the end of the array of types, nor into undefined parts of it, while
279
// iterating.
280
MOZ_ASSERT(saSig.numArgs <
281
sizeof(saSig.argTypes) / sizeof(saSig.argTypes[0]));
282
MOZ_ASSERT(saSig.argTypes[saSig.numArgs] == MIRType::None /*the end marker*/);
283
284
ItemsAndLength itemsAndLength(saSig.argTypes, saSig.numArgs);
285
286
ABIArgIter<ItemsAndLength> i(itemsAndLength);
287
while (!i.done()) {
288
i++;
289
}
290
return i.stackBytesConsumedSoFar();
291
}
292
293
static inline size_t AlignStackArgAreaSize(size_t unalignedSize) {
294
return AlignBytes(unalignedSize, 16u);
295
}
296
297
template <class T>
298
static inline size_t StackArgAreaSizeAligned(const T& argTypes) {
299
return AlignStackArgAreaSize(StackArgAreaSizeUnaligned(argTypes));
300
}
301
302
// At a resumable wasm trap, the machine's registers are saved on the stack by
303
// (code generated by) GenerateTrapExit(). This function writes into |args| a
304
// vector of booleans describing the ref-ness of the saved integer registers.
305
// |args[0]| corresponds to the low addressed end of the described section of
306
// the save area.
307
MOZ_MUST_USE bool GenerateStackmapEntriesForTrapExit(
308
const ValTypeVector& args, const MachineState& trapExitLayout,
309
const size_t trapExitLayoutNumWords, ExitStubMapVector* extras);
310
311
// Shared write barrier code.
312
//
313
// A barriered store looks like this:
314
//
315
// Label skipPreBarrier;
316
// EmitWasmPreBarrierGuard(..., &skipPreBarrier);
317
// <COMPILER-SPECIFIC ACTIONS HERE>
318
// EmitWasmPreBarrierCall(...);
319
// bind(&skipPreBarrier);
320
//
321
// <STORE THE VALUE IN MEMORY HERE>
322
//
323
// Label skipPostBarrier;
324
// <COMPILER-SPECIFIC ACTIONS HERE>
325
// EmitWasmPostBarrierGuard(..., &skipPostBarrier);
326
// <CALL POST-BARRIER HERE IN A COMPILER-SPECIFIC WAY>
327
// bind(&skipPostBarrier);
328
//
329
// The actions are divided up to allow other actions to be placed between them,
330
// such as saving and restoring live registers. The postbarrier call invokes
331
// C++ and will kill all live registers.
332
333
// Before storing a GC pointer value in memory, skip to `skipBarrier` if the
334
// prebarrier is not needed. Will clobber `scratch`.
335
//
336
// It is OK for `tls` and `scratch` to be the same register.
337
338
void EmitWasmPreBarrierGuard(MacroAssembler& masm, Register tls,
339
Register scratch, Register valueAddr,
340
Label* skipBarrier);
341
342
// Before storing a GC pointer value in memory, call out-of-line prebarrier
343
// code. This assumes `PreBarrierReg` contains the address that will be updated.
344
// On ARM64 it also assums that x28 (the PseudoStackPointer) has the same value
345
// as SP. `PreBarrierReg` is preserved by the barrier function. Will clobber
346
// `scratch`.
347
//
348
// It is OK for `tls` and `scratch` to be the same register.
349
350
void EmitWasmPreBarrierCall(MacroAssembler& masm, Register tls,
351
Register scratch, Register valueAddr);
352
353
// After storing a GC pointer value in memory, skip to `skipBarrier` if a
354
// postbarrier is not needed. If the location being set is in an heap-allocated
355
// object then `object` must reference that object; otherwise it should be None.
356
// The value that was stored is `setValue`. Will clobber `otherScratch` and
357
// will use other available scratch registers.
358
//
359
// `otherScratch` cannot be a designated scratch register.
360
361
void EmitWasmPostBarrierGuard(MacroAssembler& masm,
362
const Maybe<Register>& object,
363
Register otherScratch, Register setValue,
364
Label* skipBarrier);
365
366
} // namespace wasm
367
} // namespace js
368
369
#endif // wasm_gc_h