Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / js / src / wasm / WasmBCFrame.cpp
blobbe8192f34cbb17c8af379c2d21c13e64fc301fb4
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:
4 * Copyright 2016 Mozilla Foundation
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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include "wasm/WasmBCFrame.h"
21 #include "wasm/WasmBaselineCompile.h" // For BaseLocalIter
22 #include "wasm/WasmBCClass.h"
24 #include "jit/MacroAssembler-inl.h"
25 #include "wasm/WasmBCClass-inl.h"
26 #include "wasm/WasmBCCodegen-inl.h"
27 #include "wasm/WasmBCRegDefs-inl.h"
28 #include "wasm/WasmBCRegMgmt-inl.h"
29 #include "wasm/WasmBCStkMgmt-inl.h"
31 namespace js {
32 namespace wasm {
34 //////////////////////////////////////////////////////////////////////////////
36 // BaseLocalIter methods.
38 BaseLocalIter::BaseLocalIter(const ValTypeVector& locals,
39 const ArgTypeVector& args, bool debugEnabled)
40 : locals_(locals),
41 args_(args),
42 argsIter_(args_),
43 index_(0),
44 frameSize_(0),
45 nextFrameSize_(debugEnabled ? DebugFrame::offsetOfFrame() : 0),
46 frameOffset_(INT32_MAX),
47 stackResultPointerOffset_(INT32_MAX),
48 mirType_(MIRType::Undefined),
49 done_(false) {
50 MOZ_ASSERT(args.lengthWithoutStackResults() <= locals.length());
51 settle();
54 int32_t BaseLocalIter::pushLocal(size_t nbytes) {
55 MOZ_ASSERT(nbytes % 4 == 0 && nbytes <= 16);
56 nextFrameSize_ = AlignBytes(frameSize_, nbytes) + nbytes;
57 return nextFrameSize_; // Locals grow down so capture base address.
60 void BaseLocalIter::settle() {
61 MOZ_ASSERT(!done_);
62 frameSize_ = nextFrameSize_;
64 if (!argsIter_.done()) {
65 mirType_ = argsIter_.mirType();
66 MIRType concreteType = mirType_;
67 switch (mirType_) {
68 case MIRType::StackResults:
69 // The pointer to stack results is handled like any other argument:
70 // either addressed in place if it is passed on the stack, or we spill
71 // it in the frame if it's in a register.
72 MOZ_ASSERT(args_.isSyntheticStackResultPointerArg(index_));
73 concreteType = MIRType::Pointer;
74 [[fallthrough]];
75 case MIRType::Int32:
76 case MIRType::Int64:
77 case MIRType::Double:
78 case MIRType::Float32:
79 case MIRType::WasmAnyRef:
80 #ifdef ENABLE_WASM_SIMD
81 case MIRType::Simd128:
82 #endif
83 if (argsIter_->argInRegister()) {
84 frameOffset_ = pushLocal(MIRTypeToSize(concreteType));
85 } else {
86 frameOffset_ = -(argsIter_->offsetFromArgBase() + sizeof(Frame));
88 break;
89 default:
90 MOZ_CRASH("Argument type");
92 if (mirType_ == MIRType::StackResults) {
93 stackResultPointerOffset_ = frameOffset();
94 // Advance past the synthetic stack result pointer argument and fall
95 // through to the next case.
96 argsIter_++;
97 frameSize_ = nextFrameSize_;
98 MOZ_ASSERT(argsIter_.done());
99 } else {
100 return;
104 if (index_ < locals_.length()) {
105 switch (locals_[index_].kind()) {
106 case ValType::I32:
107 case ValType::I64:
108 case ValType::F32:
109 case ValType::F64:
110 #ifdef ENABLE_WASM_SIMD
111 case ValType::V128:
112 #endif
113 case ValType::Ref:
114 mirType_ = locals_[index_].toMIRType();
115 frameOffset_ = pushLocal(MIRTypeToSize(mirType_));
116 break;
117 default:
118 MOZ_CRASH("Compiler bug: Unexpected local type");
120 return;
123 done_ = true;
126 void BaseLocalIter::operator++(int) {
127 MOZ_ASSERT(!done_);
128 index_++;
129 if (!argsIter_.done()) {
130 argsIter_++;
132 settle();
135 //////////////////////////////////////////////////////////////////////////////
137 // Stack map methods.
139 bool BaseCompiler::createStackMap(const char* who) {
140 const ExitStubMapVector noExtras;
141 return stackMapGenerator_.createStackMap(who, noExtras, masm.currentOffset(),
142 HasDebugFrameWithLiveRefs::No, stk_);
145 bool BaseCompiler::createStackMap(const char* who, CodeOffset assemblerOffset) {
146 const ExitStubMapVector noExtras;
147 return stackMapGenerator_.createStackMap(who, noExtras,
148 assemblerOffset.offset(),
149 HasDebugFrameWithLiveRefs::No, stk_);
152 bool BaseCompiler::createStackMap(
153 const char* who, HasDebugFrameWithLiveRefs debugFrameWithLiveRefs) {
154 const ExitStubMapVector noExtras;
155 return stackMapGenerator_.createStackMap(who, noExtras, masm.currentOffset(),
156 debugFrameWithLiveRefs, stk_);
159 bool BaseCompiler::createStackMap(
160 const char* who, const ExitStubMapVector& extras, uint32_t assemblerOffset,
161 HasDebugFrameWithLiveRefs debugFrameWithLiveRefs) {
162 return stackMapGenerator_.createStackMap(who, extras, assemblerOffset,
163 debugFrameWithLiveRefs, stk_);
166 bool MachineStackTracker::cloneTo(MachineStackTracker* dst) {
167 MOZ_ASSERT(dst->vec_.empty());
168 if (!dst->vec_.appendAll(vec_)) {
169 return false;
171 dst->numPtrs_ = numPtrs_;
172 return true;
175 bool StackMapGenerator::generateStackmapEntriesForTrapExit(
176 const ArgTypeVector& args, ExitStubMapVector* extras) {
177 return GenerateStackmapEntriesForTrapExit(args, trapExitLayout_,
178 trapExitLayoutNumWords_, extras);
181 bool StackMapGenerator::createStackMap(
182 const char* who, const ExitStubMapVector& extras, uint32_t assemblerOffset,
183 HasDebugFrameWithLiveRefs debugFrameWithLiveRefs, const StkVector& stk) {
184 size_t countedPointers = machineStackTracker.numPtrs() + memRefsOnStk;
185 #ifndef DEBUG
186 // An important optimization. If there are obviously no pointers, as
187 // we expect in the majority of cases, exit quickly.
188 if (countedPointers == 0 &&
189 debugFrameWithLiveRefs == HasDebugFrameWithLiveRefs::No) {
190 // We can skip creating the map if there are no |true| elements in
191 // |extras|.
192 bool extrasHasRef = false;
193 for (bool b : extras) {
194 if (b) {
195 extrasHasRef = true;
196 break;
199 if (!extrasHasRef) {
200 return true;
203 #else
204 // In the debug case, create the stackmap regardless, and cross-check
205 // the pointer-counting below. We expect the final map to have
206 // |countedPointers| in total. This doesn't include those in the
207 // DebugFrame, but they do not appear in the map's bitmap. Note that
208 // |countedPointers| is debug-only from this point onwards.
209 for (bool b : extras) {
210 countedPointers += (b ? 1 : 0);
212 #endif
214 // Start with the frame-setup map, and add operand-stack information to
215 // that. augmentedMst holds live data only within individual calls to
216 // createStackMap.
217 augmentedMst.clear();
218 if (!machineStackTracker.cloneTo(&augmentedMst)) {
219 return false;
222 // At this point, augmentedMst only contains entries covering the
223 // incoming argument area (if any) and for the area allocated by this
224 // function's prologue. We now need to calculate how far the machine's
225 // stack pointer is below where it was at the start of the body. But we
226 // must take care not to include any words pushed as arguments to an
227 // upcoming function call, since those words "belong" to the stackmap of
228 // the callee, not to the stackmap of this function. Note however that
229 // any alignment padding pushed prior to pushing the args *does* belong to
230 // this function.
232 // That padding is taken into account at the point where
233 // framePushedExcludingOutboundCallArgs is set, viz, in startCallArgs(),
234 // and comprises two components:
236 // * call->frameAlignAdjustment
237 // * the padding applied to the stack arg area itself. That is:
238 // StackArgAreaSize(argTys) - StackArgAreaSizeUnpadded(argTys)
239 Maybe<uint32_t> framePushedExcludingArgs;
240 if (framePushedAtEntryToBody.isNothing()) {
241 // Still in the prologue. framePushedExcludingArgs remains Nothing.
242 MOZ_ASSERT(framePushedExcludingOutboundCallArgs.isNothing());
243 } else {
244 // In the body.
245 MOZ_ASSERT(masm_.framePushed() >= framePushedAtEntryToBody.value());
246 if (framePushedExcludingOutboundCallArgs.isSome()) {
247 // In the body, and we've potentially pushed some args onto the stack.
248 // We must ignore them when sizing the stackmap.
249 MOZ_ASSERT(masm_.framePushed() >=
250 framePushedExcludingOutboundCallArgs.value());
251 MOZ_ASSERT(framePushedExcludingOutboundCallArgs.value() >=
252 framePushedAtEntryToBody.value());
253 framePushedExcludingArgs =
254 Some(framePushedExcludingOutboundCallArgs.value());
255 } else {
256 // In the body, but not with call args on the stack. The stackmap
257 // must be sized so as to extend all the way "down" to
258 // masm_.framePushed().
259 framePushedExcludingArgs = Some(masm_.framePushed());
263 if (framePushedExcludingArgs.isSome()) {
264 uint32_t bodyPushedBytes =
265 framePushedExcludingArgs.value() - framePushedAtEntryToBody.value();
266 MOZ_ASSERT(0 == bodyPushedBytes % sizeof(void*));
267 if (!augmentedMst.pushNonGCPointers(bodyPushedBytes / sizeof(void*))) {
268 return false;
272 // Scan the operand stack, marking pointers in the just-added new
273 // section.
274 MOZ_ASSERT_IF(framePushedAtEntryToBody.isNothing(), stk.empty());
275 MOZ_ASSERT_IF(framePushedExcludingArgs.isNothing(), stk.empty());
277 for (const Stk& v : stk) {
278 #ifndef DEBUG
279 // We don't track roots in registers, per rationale below, so if this
280 // doesn't hold, something is seriously wrong, and we're likely to get a
281 // GC-related crash.
282 MOZ_RELEASE_ASSERT(v.kind() != Stk::RegisterRef);
283 if (v.kind() != Stk::MemRef) {
284 continue;
286 #else
287 // Take the opportunity to check everything we reasonably can about
288 // operand stack elements.
289 switch (v.kind()) {
290 case Stk::MemI32:
291 case Stk::MemI64:
292 case Stk::MemF32:
293 case Stk::MemF64:
294 case Stk::ConstI32:
295 case Stk::ConstI64:
296 case Stk::ConstF32:
297 case Stk::ConstF64:
298 # ifdef ENABLE_WASM_SIMD
299 case Stk::MemV128:
300 case Stk::ConstV128:
301 # endif
302 // All of these have uninteresting type.
303 continue;
304 case Stk::LocalI32:
305 case Stk::LocalI64:
306 case Stk::LocalF32:
307 case Stk::LocalF64:
308 # ifdef ENABLE_WASM_SIMD
309 case Stk::LocalV128:
310 # endif
311 // These also have uninteresting type. Check that they live in the
312 // section of stack set up by beginFunction(). The unguarded use of
313 // |value()| here is safe due to the assertion above this loop.
314 MOZ_ASSERT(v.offs() <= framePushedAtEntryToBody.value());
315 continue;
316 case Stk::RegisterI32:
317 case Stk::RegisterI64:
318 case Stk::RegisterF32:
319 case Stk::RegisterF64:
320 # ifdef ENABLE_WASM_SIMD
321 case Stk::RegisterV128:
322 # endif
323 // These also have uninteresting type, but more to the point: all
324 // registers holding live values should have been flushed to the
325 // machine stack immediately prior to the instruction to which this
326 // stackmap pertains. So these can't happen.
327 MOZ_CRASH("createStackMap: operand stack has Register-non-Ref");
328 case Stk::MemRef:
329 // This is the only case we care about. We'll handle it after the
330 // switch.
331 break;
332 case Stk::LocalRef:
333 // We need the stackmap to mention this pointer, but it should
334 // already be in the machineStackTracker section created by
335 // beginFunction().
336 MOZ_ASSERT(v.offs() <= framePushedAtEntryToBody.value());
337 continue;
338 case Stk::ConstRef:
339 // This can currently only be a null pointer.
340 MOZ_ASSERT(v.refval() == 0);
341 continue;
342 case Stk::RegisterRef:
343 // This can't happen, per rationale above.
344 MOZ_CRASH("createStackMap: operand stack contains RegisterRef");
345 default:
346 MOZ_CRASH("createStackMap: unknown operand stack element");
348 #endif
349 // v.offs() holds masm.framePushed() at the point immediately after it
350 // was pushed on the stack. Since it's still on the stack,
351 // masm.framePushed() can't be less.
352 MOZ_ASSERT(v.offs() <= framePushedExcludingArgs.value());
353 uint32_t offsFromMapLowest = framePushedExcludingArgs.value() - v.offs();
354 MOZ_ASSERT(0 == offsFromMapLowest % sizeof(void*));
355 augmentedMst.setGCPointer(offsFromMapLowest / sizeof(void*));
358 // Create the final StackMap. The initial map is zeroed out, so there's
359 // no need to write zero bits in it.
360 const uint32_t extraWords = extras.length();
361 const uint32_t augmentedMstWords = augmentedMst.length();
362 const uint32_t numMappedWords = extraWords + augmentedMstWords;
363 StackMap* stackMap = StackMap::create(numMappedWords);
364 if (!stackMap) {
365 return false;
369 // First the exit stub extra words, if any.
370 uint32_t i = 0;
371 for (bool b : extras) {
372 if (b) {
373 stackMap->setBit(i);
375 i++;
379 // Followed by the "main" part of the map.
381 // This is really just a bit-array copy, so it is reasonable to ask
382 // whether the representation of MachineStackTracker could be made more
383 // similar to that of StackMap, so that the copy could be done with
384 // `memcpy`. Unfortunately it's not so simple; see comment on `class
385 // MachineStackTracker` for details.
386 MachineStackTracker::Iter iter(augmentedMst);
387 while (true) {
388 size_t i = iter.get();
389 if (i == MachineStackTracker::Iter::FINISHED) {
390 break;
392 stackMap->setBit(extraWords + i);
396 stackMap->setExitStubWords(extraWords);
398 // Record in the map, how far down from the highest address the Frame* is.
399 // Take the opportunity to check that we haven't marked any part of the
400 // Frame itself as a pointer.
401 stackMap->setFrameOffsetFromTop(numStackArgWords +
402 sizeof(Frame) / sizeof(void*));
403 #ifdef DEBUG
404 for (uint32_t i = 0; i < sizeof(Frame) / sizeof(void*); i++) {
405 MOZ_ASSERT(stackMap->getBit(stackMap->header.numMappedWords -
406 stackMap->header.frameOffsetFromTop + i) == 0);
408 #endif
410 // Note the presence of a DebugFrame with live pointers, if any.
411 if (debugFrameWithLiveRefs != HasDebugFrameWithLiveRefs::No) {
412 stackMap->setHasDebugFrameWithLiveRefs();
415 // Add the completed map to the running collection thereof.
416 if (!stackMaps_->add((uint8_t*)(uintptr_t)assemblerOffset, stackMap)) {
417 stackMap->destroy();
418 return false;
421 #ifdef DEBUG
423 // Crosscheck the map pointer counting.
424 uint32_t nw = stackMap->header.numMappedWords;
425 uint32_t np = 0;
426 for (uint32_t i = 0; i < nw; i++) {
427 np += stackMap->getBit(i);
429 MOZ_ASSERT(size_t(np) == countedPointers);
431 #endif
433 return true;
436 //////////////////////////////////////////////////////////////////////////////
438 // Stack frame methods.
440 void BaseStackFrame::zeroLocals(BaseRegAlloc* ra) {
441 MOZ_ASSERT(varLow_ != UINT32_MAX);
443 if (varLow_ == varHigh_) {
444 return;
447 static const uint32_t wordSize = sizeof(void*);
449 // The adjustments to 'low' by the size of the item being stored compensates
450 // for the fact that locals offsets are the offsets from Frame to the bytes
451 // directly "above" the locals in the locals area. See comment at Local.
453 // On 64-bit systems we may have 32-bit alignment for the local area as it
454 // may be preceded by parameters and prologue/debug data.
456 uint32_t low = varLow_;
457 if (low % wordSize) {
458 masm.store32(Imm32(0), Address(sp_, localOffset(low + 4)));
459 low += 4;
461 MOZ_ASSERT(low % wordSize == 0);
463 const uint32_t high = AlignBytes(varHigh_, wordSize);
465 // An UNROLL_LIMIT of 16 is chosen so that we only need an 8-bit signed
466 // immediate to represent the offset in the store instructions in the loop
467 // on x64.
469 const uint32_t UNROLL_LIMIT = 16;
470 const uint32_t initWords = (high - low) / wordSize;
471 const uint32_t tailWords = initWords % UNROLL_LIMIT;
472 const uint32_t loopHigh = high - (tailWords * wordSize);
474 // With only one word to initialize, just store an immediate zero.
476 if (initWords == 1) {
477 masm.storePtr(ImmWord(0), Address(sp_, localOffset(low + wordSize)));
478 return;
481 // For other cases, it's best to have a zero in a register.
483 // One can do more here with SIMD registers (store 16 bytes at a time) or
484 // with instructions like STRD on ARM (store 8 bytes at a time), but that's
485 // for another day.
487 RegI32 zero = ra->needI32();
488 masm.mov(ImmWord(0), zero);
490 // For the general case we want to have a loop body of UNROLL_LIMIT stores
491 // and then a tail of less than UNROLL_LIMIT stores. When initWords is less
492 // than 2*UNROLL_LIMIT the loop trip count is at most 1 and there is no
493 // benefit to having the pointer calculations and the compare-and-branch.
494 // So we completely unroll when we have initWords < 2 * UNROLL_LIMIT. (In
495 // this case we'll end up using 32-bit offsets on x64 for up to half of the
496 // stores, though.)
498 // Fully-unrolled case.
500 if (initWords < 2 * UNROLL_LIMIT) {
501 for (uint32_t i = low; i < high; i += wordSize) {
502 masm.storePtr(zero, Address(sp_, localOffset(i + wordSize)));
504 ra->freeI32(zero);
505 return;
508 // Unrolled loop with a tail. Stores will use negative offsets. That's OK
509 // for x86 and ARM, at least.
511 // Compute pointer to the highest-addressed slot on the frame.
512 RegI32 p = ra->needI32();
513 masm.computeEffectiveAddress(Address(sp_, localOffset(low + wordSize)), p);
515 // Compute pointer to the lowest-addressed slot on the frame that will be
516 // initialized by the loop body.
517 RegI32 lim = ra->needI32();
518 masm.computeEffectiveAddress(Address(sp_, localOffset(loopHigh + wordSize)),
519 lim);
521 // The loop body. Eventually we'll have p == lim and exit the loop.
522 Label again;
523 masm.bind(&again);
524 for (uint32_t i = 0; i < UNROLL_LIMIT; ++i) {
525 masm.storePtr(zero, Address(p, -(wordSize * i)));
527 masm.subPtr(Imm32(UNROLL_LIMIT * wordSize), p);
528 masm.branchPtr(Assembler::LessThan, lim, p, &again);
530 // The tail.
531 for (uint32_t i = 0; i < tailWords; ++i) {
532 masm.storePtr(zero, Address(p, -(wordSize * i)));
535 ra->freeI32(p);
536 ra->freeI32(lim);
537 ra->freeI32(zero);
540 } // namespace wasm
541 } // namespace js