1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "jit/shared/CodeGenerator-shared-inl.h"
8
9 #include "mozilla/DebugOnly.h"
10
11 #include "jit/CompactBuffer.h"
12 #include "jit/JitcodeMap.h"
13 #include "jit/JitSpewer.h"
14 #include "jit/MacroAssembler.h"
15 #include "jit/MIR.h"
16 #include "jit/MIRGenerator.h"
17 #include "jit/OptimizationTracking.h"
18 #include "js/Conversions.h"
19 #include "vm/TraceLogging.h"
20
21 #include "jit/JitFrames-inl.h"
22 #include "jit/MacroAssembler-inl.h"
23
24 using namespace js;
25 using namespace js::jit;
26
27 using mozilla::BitwiseCast;
28 using mozilla::DebugOnly;
29
30 namespace js {
31 namespace jit {
32
ensureMasm(MacroAssembler * masmArg)33 MacroAssembler& CodeGeneratorShared::ensureMasm(MacroAssembler* masmArg) {
34 if (masmArg) return *masmArg;
35 maybeMasm_.emplace();
36 return *maybeMasm_;
37 }
38
CodeGeneratorShared(MIRGenerator * gen,LIRGraph * graph,MacroAssembler * masmArg)39 CodeGeneratorShared::CodeGeneratorShared(MIRGenerator* gen, LIRGraph* graph,
40 MacroAssembler* masmArg)
41 : maybeMasm_(),
42 masm(ensureMasm(masmArg)),
43 gen(gen),
44 graph(*graph),
45 current(nullptr),
46 snapshots_(),
47 recovers_(),
48 deoptTable_(),
49 #ifdef DEBUG
50 pushedArgs_(0),
51 #endif
52 lastOsiPointOffset_(0),
53 safepoints_(graph->totalSlotCount(),
54 (gen->info().nargs() + 1) * sizeof(Value)),
55 returnLabel_(),
56 stubSpace_(),
57 nativeToBytecodeMap_(nullptr),
58 nativeToBytecodeMapSize_(0),
59 nativeToBytecodeTableOffset_(0),
60 nativeToBytecodeNumRegions_(0),
61 nativeToBytecodeScriptList_(nullptr),
62 nativeToBytecodeScriptListLength_(0),
63 trackedOptimizationsMap_(nullptr),
64 trackedOptimizationsMapSize_(0),
65 trackedOptimizationsRegionTableOffset_(0),
66 trackedOptimizationsTypesTableOffset_(0),
67 trackedOptimizationsAttemptsTableOffset_(0),
68 osrEntryOffset_(0),
69 skipArgCheckEntryOffset_(0),
70 #ifdef CHECK_OSIPOINT_REGISTERS
71 checkOsiPointRegisters(JitOptions.checkOsiPointRegisters),
72 #endif
73 frameDepth_(graph->paddedLocalSlotsSize() + graph->argumentsSize()),
74 frameInitialAdjustment_(0) {
75 if (gen->isProfilerInstrumentationEnabled())
76 masm.enableProfilingInstrumentation();
77
78 if (gen->compilingWasm()) {
79 // Since wasm uses the system ABI which does not necessarily use a
80 // regular array where all slots are sizeof(Value), it maintains the max
81 // argument stack depth separately.
82 MOZ_ASSERT(graph->argumentSlotCount() == 0);
83 frameDepth_ += gen->wasmMaxStackArgBytes();
84
85 if (gen->usesSimd()) {
86 // If the function uses any SIMD then we may need to insert padding
87 // so that local slots are aligned for SIMD.
88 frameInitialAdjustment_ =
89 ComputeByteAlignment(sizeof(wasm::Frame), WasmStackAlignment);
90 frameDepth_ += frameInitialAdjustment_;
91
92 // Keep the stack aligned. Some SIMD sequences build values on the
93 // stack and need the stack aligned.
94 frameDepth_ += ComputeByteAlignment(sizeof(wasm::Frame) + frameDepth_,
95 WasmStackAlignment);
96 } else if (gen->needsStaticStackAlignment()) {
97 // An MWasmCall does not align the stack pointer at calls sites but
98 // instead relies on the a priori stack adjustment. This must be the
99 // last adjustment of frameDepth_.
100 frameDepth_ += ComputeByteAlignment(sizeof(wasm::Frame) + frameDepth_,
101 WasmStackAlignment);
102 }
103
104 // FrameSizeClass is only used for bailing, which cannot happen in
105 // wasm code.
106 frameClass_ = FrameSizeClass::None();
107 } else {
108 frameClass_ = FrameSizeClass::FromDepth(frameDepth_);
109 }
110 }
111
generatePrologue()112 bool CodeGeneratorShared::generatePrologue() {
113 MOZ_ASSERT(masm.framePushed() == 0);
114 MOZ_ASSERT(!gen->compilingWasm());
115
116 #ifdef JS_USE_LINK_REGISTER
117 masm.pushReturnAddress();
118 #endif
119
120 // If profiling, save the current frame pointer to a per-thread global field.
121 if (isProfilerInstrumentationEnabled())
122 masm.profilerEnterFrame(masm.getStackPointer(), CallTempReg0);
123
124 // Ensure that the Ion frame is properly aligned.
125 masm.assertStackAlignment(JitStackAlignment, 0);
126
127 // Note that this automatically sets MacroAssembler::framePushed().
128 masm.reserveStack(frameSize());
129 masm.checkStackAlignment();
130
131 emitTracelogIonStart();
132 return true;
133 }
134
generateEpilogue()135 bool CodeGeneratorShared::generateEpilogue() {
136 MOZ_ASSERT(!gen->compilingWasm());
137 masm.bind(&returnLabel_);
138
139 emitTracelogIonStop();
140
141 masm.freeStack(frameSize());
142 MOZ_ASSERT(masm.framePushed() == 0);
143
144 // If profiling, reset the per-thread global lastJitFrame to point to
145 // the previous frame.
146 if (isProfilerInstrumentationEnabled()) masm.profilerExitFrame();
147
148 masm.ret();
149
150 // On systems that use a constant pool, this is a good time to emit.
151 masm.flushBuffer();
152 return true;
153 }
154
generateOutOfLineCode()155 bool CodeGeneratorShared::generateOutOfLineCode() {
156 // OOL paths should not attempt to use |current| as it's the last block
157 // instead of the block corresponding to the OOL path.
158 current = nullptr;
159
160 for (size_t i = 0; i < outOfLineCode_.length(); i++) {
161 // Add native => bytecode mapping entries for OOL sites.
162 // Not enabled on wasm yet since it doesn't contain bytecode mappings.
163 if (!gen->compilingWasm()) {
164 if (!addNativeToBytecodeEntry(outOfLineCode_[i]->bytecodeSite()))
165 return false;
166 }
167
168 if (!gen->alloc().ensureBallast()) return false;
169
170 JitSpew(JitSpew_Codegen, "# Emitting out of line code");
171
172 masm.setFramePushed(outOfLineCode_[i]->framePushed());
173 lastPC_ = outOfLineCode_[i]->pc();
174 outOfLineCode_[i]->bind(&masm);
175
176 outOfLineCode_[i]->generate(this);
177 }
178
179 return !masm.oom();
180 }
181
addOutOfLineCode(OutOfLineCode * code,const MInstruction * mir)182 void CodeGeneratorShared::addOutOfLineCode(OutOfLineCode* code,
183 const MInstruction* mir) {
184 MOZ_ASSERT(mir);
185 addOutOfLineCode(code, mir->trackedSite());
186 }
187
addOutOfLineCode(OutOfLineCode * code,const BytecodeSite * site)188 void CodeGeneratorShared::addOutOfLineCode(OutOfLineCode* code,
189 const BytecodeSite* site) {
190 code->setFramePushed(masm.framePushed());
191 code->setBytecodeSite(site);
192 MOZ_ASSERT_IF(!gen->compilingWasm(), code->script()->containsPC(code->pc()));
193 masm.propagateOOM(outOfLineCode_.append(code));
194 }
195
addNativeToBytecodeEntry(const BytecodeSite * site)196 bool CodeGeneratorShared::addNativeToBytecodeEntry(const BytecodeSite* site) {
197 // Skip the table entirely if profiling is not enabled.
198 if (!isProfilerInstrumentationEnabled()) return true;
199
200 // Fails early if the last added instruction caused the macro assembler to
201 // run out of memory as continuity assumption below do not hold.
202 if (masm.oom()) return false;
203
204 MOZ_ASSERT(site);
205 MOZ_ASSERT(site->tree());
206 MOZ_ASSERT(site->pc());
207
208 InlineScriptTree* tree = site->tree();
209 jsbytecode* pc = site->pc();
210 uint32_t nativeOffset = masm.currentOffset();
211
212 MOZ_ASSERT_IF(nativeToBytecodeList_.empty(), nativeOffset == 0);
213
214 if (!nativeToBytecodeList_.empty()) {
215 size_t lastIdx = nativeToBytecodeList_.length() - 1;
216 NativeToBytecode& lastEntry = nativeToBytecodeList_[lastIdx];
217
218 MOZ_ASSERT(nativeOffset >= lastEntry.nativeOffset.offset());
219
220 // If the new entry is for the same inlineScriptTree and same
221 // bytecodeOffset, but the nativeOffset has changed, do nothing.
222 // The same site just generated some more code.
223 if (lastEntry.tree == tree && lastEntry.pc == pc) {
224 JitSpew(JitSpew_Profiling, " => In-place update [%zu-%" PRIu32 "]",
225 lastEntry.nativeOffset.offset(), nativeOffset);
226 return true;
227 }
228
229 // If the new entry is for the same native offset, then update the
230 // previous entry with the new bytecode site, since the previous
231 // bytecode site did not generate any native code.
232 if (lastEntry.nativeOffset.offset() == nativeOffset) {
233 lastEntry.tree = tree;
234 lastEntry.pc = pc;
235 JitSpew(JitSpew_Profiling, " => Overwriting zero-length native region.");
236
237 // This overwrite might have made the entry merge-able with a
238 // previous one. If so, merge it.
239 if (lastIdx > 0) {
240 NativeToBytecode& nextToLastEntry = nativeToBytecodeList_[lastIdx - 1];
241 if (nextToLastEntry.tree == lastEntry.tree &&
242 nextToLastEntry.pc == lastEntry.pc) {
243 JitSpew(JitSpew_Profiling, " => Merging with previous region");
244 nativeToBytecodeList_.erase(&lastEntry);
245 }
246 }
247
248 dumpNativeToBytecodeEntry(nativeToBytecodeList_.length() - 1);
249 return true;
250 }
251 }
252
253 // Otherwise, some native code was generated for the previous bytecode site.
254 // Add a new entry for code that is about to be generated.
255 NativeToBytecode entry;
256 entry.nativeOffset = CodeOffset(nativeOffset);
257 entry.tree = tree;
258 entry.pc = pc;
259 if (!nativeToBytecodeList_.append(entry)) return false;
260
261 JitSpew(JitSpew_Profiling, " => Push new entry.");
262 dumpNativeToBytecodeEntry(nativeToBytecodeList_.length() - 1);
263 return true;
264 }
265
dumpNativeToBytecodeEntries()266 void CodeGeneratorShared::dumpNativeToBytecodeEntries() {
267 #ifdef JS_JITSPEW
268 InlineScriptTree* topTree = gen->info().inlineScriptTree();
269 JitSpewStart(JitSpew_Profiling, "Native To Bytecode Entries for %s:%zu\n",
270 topTree->script()->filename(), topTree->script()->lineno());
271 for (unsigned i = 0; i < nativeToBytecodeList_.length(); i++)
272 dumpNativeToBytecodeEntry(i);
273 #endif
274 }
275
dumpNativeToBytecodeEntry(uint32_t idx)276 void CodeGeneratorShared::dumpNativeToBytecodeEntry(uint32_t idx) {
277 #ifdef JS_JITSPEW
278 NativeToBytecode& ref = nativeToBytecodeList_[idx];
279 InlineScriptTree* tree = ref.tree;
280 JSScript* script = tree->script();
281 uint32_t nativeOffset = ref.nativeOffset.offset();
282 unsigned nativeDelta = 0;
283 unsigned pcDelta = 0;
284 if (idx + 1 < nativeToBytecodeList_.length()) {
285 NativeToBytecode* nextRef = &ref + 1;
286 nativeDelta = nextRef->nativeOffset.offset() - nativeOffset;
287 if (nextRef->tree == ref.tree) pcDelta = nextRef->pc - ref.pc;
288 }
289 JitSpewStart(
290 JitSpew_Profiling, " %08zx [+%-6d] => %-6ld [%-4d] {%-10s} (%s:%zu",
291 ref.nativeOffset.offset(), nativeDelta, (long)(ref.pc - script->code()),
292 pcDelta, CodeName[JSOp(*ref.pc)], script->filename(), script->lineno());
293
294 for (tree = tree->caller(); tree; tree = tree->caller()) {
295 JitSpewCont(JitSpew_Profiling, " <= %s:%zu", tree->script()->filename(),
296 tree->script()->lineno());
297 }
298 JitSpewCont(JitSpew_Profiling, ")");
299 JitSpewFin(JitSpew_Profiling);
300 #endif
301 }
302
addTrackedOptimizationsEntry(const TrackedOptimizations * optimizations)303 bool CodeGeneratorShared::addTrackedOptimizationsEntry(
304 const TrackedOptimizations* optimizations) {
305 if (!isOptimizationTrackingEnabled()) return true;
306
307 MOZ_ASSERT(optimizations);
308
309 uint32_t nativeOffset = masm.currentOffset();
310
311 if (!trackedOptimizations_.empty()) {
312 NativeToTrackedOptimizations& lastEntry = trackedOptimizations_.back();
313 MOZ_ASSERT_IF(!masm.oom(), nativeOffset >= lastEntry.endOffset.offset());
314
315 // If we're still generating code for the same set of optimizations,
316 // we are done.
317 if (lastEntry.optimizations == optimizations) return true;
318 }
319
320 // If we're generating code for a new set of optimizations, add a new
321 // entry.
322 NativeToTrackedOptimizations entry;
323 entry.startOffset = CodeOffset(nativeOffset);
324 entry.endOffset = CodeOffset(nativeOffset);
325 entry.optimizations = optimizations;
326 return trackedOptimizations_.append(entry);
327 }
328
extendTrackedOptimizationsEntry(const TrackedOptimizations * optimizations)329 void CodeGeneratorShared::extendTrackedOptimizationsEntry(
330 const TrackedOptimizations* optimizations) {
331 if (!isOptimizationTrackingEnabled()) return;
332
333 uint32_t nativeOffset = masm.currentOffset();
334 NativeToTrackedOptimizations& entry = trackedOptimizations_.back();
335 MOZ_ASSERT(entry.optimizations == optimizations);
336 MOZ_ASSERT_IF(!masm.oom(), nativeOffset >= entry.endOffset.offset());
337
338 entry.endOffset = CodeOffset(nativeOffset);
339
340 // If we generated no code, remove the last entry.
341 if (nativeOffset == entry.startOffset.offset())
342 trackedOptimizations_.popBack();
343 }
344
345 // see OffsetOfFrameSlot
ToStackIndex(LAllocation * a)346 static inline int32_t ToStackIndex(LAllocation* a) {
347 if (a->isStackSlot()) {
348 MOZ_ASSERT(a->toStackSlot()->slot() >= 1);
349 return a->toStackSlot()->slot();
350 }
351 return -int32_t(sizeof(JitFrameLayout) + a->toArgument()->index());
352 }
353
encodeAllocation(LSnapshot * snapshot,MDefinition * mir,uint32_t * allocIndex)354 void CodeGeneratorShared::encodeAllocation(LSnapshot* snapshot,
355 MDefinition* mir,
356 uint32_t* allocIndex) {
357 if (mir->isBox()) mir = mir->toBox()->getOperand(0);
358
359 MIRType type =
360 mir->isRecoveredOnBailout()
361 ? MIRType::None
362 : mir->isUnused() ? MIRType::MagicOptimizedOut : mir->type();
363
364 RValueAllocation alloc;
365
366 switch (type) {
367 case MIRType::None: {
368 MOZ_ASSERT(mir->isRecoveredOnBailout());
369 uint32_t index = 0;
370 LRecoverInfo* recoverInfo = snapshot->recoverInfo();
371 MNode** it = recoverInfo->begin();
372 MNode** end = recoverInfo->end();
373 while (it != end && mir != *it) {
374 ++it;
375 ++index;
376 }
377
378 // This MDefinition is recovered, thus it should be listed in the
379 // LRecoverInfo.
380 MOZ_ASSERT(it != end && mir == *it);
381
382 // Lambda should have a default value readable for iterating over the
383 // inner frames.
384 if (mir->isLambda() || mir->isLambdaArrow()) {
385 MConstant* constant = mir->isLambda()
386 ? mir->toLambda()->functionOperand()
387 : mir->toLambdaArrow()->functionOperand();
388 uint32_t cstIndex;
389 masm.propagateOOM(
390 graph.addConstantToPool(constant->toJSValue(), &cstIndex));
391 alloc = RValueAllocation::RecoverInstruction(index, cstIndex);
392 break;
393 }
394
395 alloc = RValueAllocation::RecoverInstruction(index);
396 break;
397 }
398 case MIRType::Undefined:
399 alloc = RValueAllocation::Undefined();
400 break;
401 case MIRType::Null:
402 alloc = RValueAllocation::Null();
403 break;
404 case MIRType::Int32:
405 case MIRType::String:
406 case MIRType::Symbol:
407 case MIRType::Object:
408 case MIRType::ObjectOrNull:
409 case MIRType::Boolean:
410 case MIRType::Double: {
411 LAllocation* payload = snapshot->payloadOfSlot(*allocIndex);
412 if (payload->isConstant()) {
413 MConstant* constant = mir->toConstant();
414 uint32_t index;
415 masm.propagateOOM(
416 graph.addConstantToPool(constant->toJSValue(), &index));
417 alloc = RValueAllocation::ConstantPool(index);
418 break;
419 }
420
421 JSValueType valueType = (type == MIRType::ObjectOrNull)
422 ? JSVAL_TYPE_OBJECT
423 : ValueTypeFromMIRType(type);
424
425 MOZ_DIAGNOSTIC_ASSERT(payload->isMemory() || payload->isRegister());
426 if (payload->isMemory())
427 alloc = RValueAllocation::Typed(valueType, ToStackIndex(payload));
428 else if (payload->isGeneralReg())
429 alloc = RValueAllocation::Typed(valueType, ToRegister(payload));
430 else if (payload->isFloatReg())
431 alloc = RValueAllocation::Double(ToFloatRegister(payload));
432 else
433 MOZ_CRASH("Unexpected payload type.");
434 break;
435 }
436 case MIRType::Float32:
437 case MIRType::Int8x16:
438 case MIRType::Int16x8:
439 case MIRType::Int32x4:
440 case MIRType::Float32x4:
441 case MIRType::Bool8x16:
442 case MIRType::Bool16x8:
443 case MIRType::Bool32x4: {
444 LAllocation* payload = snapshot->payloadOfSlot(*allocIndex);
445 if (payload->isConstant()) {
446 MConstant* constant = mir->toConstant();
447 uint32_t index;
448 masm.propagateOOM(
449 graph.addConstantToPool(constant->toJSValue(), &index));
450 alloc = RValueAllocation::ConstantPool(index);
451 break;
452 }
453
454 MOZ_ASSERT(payload->isMemory() || payload->isFloatReg());
455 if (payload->isFloatReg())
456 alloc = RValueAllocation::AnyFloat(ToFloatRegister(payload));
457 else
458 alloc = RValueAllocation::AnyFloat(ToStackIndex(payload));
459 break;
460 }
461 case MIRType::MagicOptimizedArguments:
462 case MIRType::MagicOptimizedOut:
463 case MIRType::MagicUninitializedLexical:
464 case MIRType::MagicIsConstructing: {
465 uint32_t index;
466 JSWhyMagic why = JS_GENERIC_MAGIC;
467 switch (type) {
468 case MIRType::MagicOptimizedArguments:
469 why = JS_OPTIMIZED_ARGUMENTS;
470 break;
471 case MIRType::MagicOptimizedOut:
472 why = JS_OPTIMIZED_OUT;
473 break;
474 case MIRType::MagicUninitializedLexical:
475 why = JS_UNINITIALIZED_LEXICAL;
476 break;
477 case MIRType::MagicIsConstructing:
478 why = JS_IS_CONSTRUCTING;
479 break;
480 default:
481 MOZ_CRASH("Invalid Magic MIRType");
482 }
483
484 Value v = MagicValue(why);
485 masm.propagateOOM(graph.addConstantToPool(v, &index));
486 alloc = RValueAllocation::ConstantPool(index);
487 break;
488 }
489 default: {
490 MOZ_ASSERT(mir->type() == MIRType::Value);
491 LAllocation* payload = snapshot->payloadOfSlot(*allocIndex);
492 #ifdef JS_NUNBOX32
493 LAllocation* type = snapshot->typeOfSlot(*allocIndex);
494 if (type->isRegister()) {
495 if (payload->isRegister())
496 alloc =
497 RValueAllocation::Untyped(ToRegister(type), ToRegister(payload));
498 else
499 alloc = RValueAllocation::Untyped(ToRegister(type),
500 ToStackIndex(payload));
501 } else {
502 if (payload->isRegister())
503 alloc = RValueAllocation::Untyped(ToStackIndex(type),
504 ToRegister(payload));
505 else
506 alloc = RValueAllocation::Untyped(ToStackIndex(type),
507 ToStackIndex(payload));
508 }
509 #elif JS_PUNBOX64
510 if (payload->isRegister())
511 alloc = RValueAllocation::Untyped(ToRegister(payload));
512 else
513 alloc = RValueAllocation::Untyped(ToStackIndex(payload));
514 #endif
515 break;
516 }
517 }
518 MOZ_DIAGNOSTIC_ASSERT(alloc.valid());
519
520 // This set an extra bit as part of the RValueAllocation, such that we know
521 // that recover instruction have to be executed without wrapping the
522 // instruction in a no-op recover instruction.
523 if (mir->isIncompleteObject()) alloc.setNeedSideEffect();
524
525 masm.propagateOOM(snapshots_.add(alloc));
526
527 *allocIndex += mir->isRecoveredOnBailout() ? 0 : 1;
528 }
529
encode(LRecoverInfo * recover)530 void CodeGeneratorShared::encode(LRecoverInfo* recover) {
531 if (recover->recoverOffset() != INVALID_RECOVER_OFFSET) return;
532
533 uint32_t numInstructions = recover->numInstructions();
534 JitSpew(JitSpew_IonSnapshots,
535 "Encoding LRecoverInfo %p (frameCount %u, instructions %u)",
536 (void*)recover, recover->mir()->frameCount(), numInstructions);
537
538 MResumePoint::Mode mode = recover->mir()->mode();
539 MOZ_ASSERT(mode != MResumePoint::Outer);
540 bool resumeAfter = (mode == MResumePoint::ResumeAfter);
541
542 RecoverOffset offset = recovers_.startRecover(numInstructions, resumeAfter);
543
544 for (MNode* insn : *recover) recovers_.writeInstruction(insn);
545
546 recovers_.endRecover();
547 recover->setRecoverOffset(offset);
548 masm.propagateOOM(!recovers_.oom());
549 }
550
encode(LSnapshot * snapshot)551 void CodeGeneratorShared::encode(LSnapshot* snapshot) {
552 if (snapshot->snapshotOffset() != INVALID_SNAPSHOT_OFFSET) return;
553
554 LRecoverInfo* recoverInfo = snapshot->recoverInfo();
555 encode(recoverInfo);
556
557 RecoverOffset recoverOffset = recoverInfo->recoverOffset();
558 MOZ_ASSERT(recoverOffset != INVALID_RECOVER_OFFSET);
559
560 JitSpew(JitSpew_IonSnapshots, "Encoding LSnapshot %p (LRecover %p)",
561 (void*)snapshot, (void*)recoverInfo);
562
563 SnapshotOffset offset =
564 snapshots_.startSnapshot(recoverOffset, snapshot->bailoutKind());
565
566 #ifdef TRACK_SNAPSHOTS
567 uint32_t pcOpcode = 0;
568 uint32_t lirOpcode = 0;
569 uint32_t lirId = 0;
570 uint32_t mirOpcode = 0;
571 uint32_t mirId = 0;
572
573 if (LNode* ins = instruction()) {
574 lirOpcode = ins->op();
575 lirId = ins->id();
576 if (ins->mirRaw()) {
577 mirOpcode = uint32_t(ins->mirRaw()->op());
578 mirId = ins->mirRaw()->id();
579 if (ins->mirRaw()->trackedPc()) pcOpcode = *ins->mirRaw()->trackedPc();
580 }
581 }
582 snapshots_.trackSnapshot(pcOpcode, mirOpcode, mirId, lirOpcode, lirId);
583 #endif
584
585 uint32_t allocIndex = 0;
586 for (LRecoverInfo::OperandIter it(recoverInfo); !it; ++it) {
587 DebugOnly<uint32_t> allocWritten = snapshots_.allocWritten();
588 encodeAllocation(snapshot, *it, &allocIndex);
589 MOZ_ASSERT_IF(!snapshots_.oom(),
590 allocWritten + 1 == snapshots_.allocWritten());
591 }
592
593 MOZ_ASSERT(allocIndex == snapshot->numSlots());
594 snapshots_.endSnapshot();
595 snapshot->setSnapshotOffset(offset);
596 masm.propagateOOM(!snapshots_.oom());
597 }
598
assignBailoutId(LSnapshot * snapshot)599 bool CodeGeneratorShared::assignBailoutId(LSnapshot* snapshot) {
600 MOZ_ASSERT(snapshot->snapshotOffset() != INVALID_SNAPSHOT_OFFSET);
601
602 // Can we not use bailout tables at all?
603 if (!deoptTable_) return false;
604
605 MOZ_ASSERT(frameClass_ != FrameSizeClass::None());
606
607 if (snapshot->bailoutId() != INVALID_BAILOUT_ID) return true;
608
609 // Is the bailout table full?
610 if (bailouts_.length() >= BAILOUT_TABLE_SIZE) return false;
611
612 unsigned bailoutId = bailouts_.length();
613 snapshot->setBailoutId(bailoutId);
614 JitSpew(JitSpew_IonSnapshots, "Assigned snapshot bailout id %u", bailoutId);
615 masm.propagateOOM(bailouts_.append(snapshot->snapshotOffset()));
616 return true;
617 }
618
encodeSafepoints()619 bool CodeGeneratorShared::encodeSafepoints() {
620 for (SafepointIndex& index : safepointIndices_) {
621 LSafepoint* safepoint = index.safepoint();
622
623 if (!safepoint->encoded()) safepoints_.encode(safepoint);
624
625 index.resolve();
626 }
627
628 return !safepoints_.oom();
629 }
630
createNativeToBytecodeScriptList(JSContext * cx)631 bool CodeGeneratorShared::createNativeToBytecodeScriptList(JSContext* cx) {
632 js::Vector<JSScript*, 0, SystemAllocPolicy> scriptList;
633 InlineScriptTree* tree = gen->info().inlineScriptTree();
634 for (;;) {
635 // Add script from current tree.
636 bool found = false;
637 for (uint32_t i = 0; i < scriptList.length(); i++) {
638 if (scriptList[i] == tree->script()) {
639 found = true;
640 break;
641 }
642 }
643 if (!found) {
644 if (!scriptList.append(tree->script())) return false;
645 }
646
647 // Process rest of tree
648
649 // If children exist, emit children.
650 if (tree->hasChildren()) {
651 tree = tree->firstChild();
652 continue;
653 }
654
655 // Otherwise, find the first tree up the chain (including this one)
656 // that contains a next sibling.
657 while (!tree->hasNextCallee() && tree->hasCaller()) tree = tree->caller();
658
659 // If we found a sibling, use it.
660 if (tree->hasNextCallee()) {
661 tree = tree->nextCallee();
662 continue;
663 }
664
665 // Otherwise, we must have reached the top without finding any siblings.
666 MOZ_ASSERT(tree->isOutermostCaller());
667 break;
668 }
669
670 // Allocate array for list.
671 JSScript** data = cx->zone()->pod_malloc<JSScript*>(scriptList.length());
672 if (!data) return false;
673
674 for (uint32_t i = 0; i < scriptList.length(); i++) data[i] = scriptList[i];
675
676 // Success.
677 nativeToBytecodeScriptListLength_ = scriptList.length();
678 nativeToBytecodeScriptList_ = data;
679 return true;
680 }
681
generateCompactNativeToBytecodeMap(JSContext * cx,JitCode * code)682 bool CodeGeneratorShared::generateCompactNativeToBytecodeMap(JSContext* cx,
683 JitCode* code) {
684 MOZ_ASSERT(nativeToBytecodeScriptListLength_ == 0);
685 MOZ_ASSERT(nativeToBytecodeScriptList_ == nullptr);
686 MOZ_ASSERT(nativeToBytecodeMap_ == nullptr);
687 MOZ_ASSERT(nativeToBytecodeMapSize_ == 0);
688 MOZ_ASSERT(nativeToBytecodeTableOffset_ == 0);
689 MOZ_ASSERT(nativeToBytecodeNumRegions_ == 0);
690
691 if (!createNativeToBytecodeScriptList(cx)) return false;
692
693 MOZ_ASSERT(nativeToBytecodeScriptListLength_ > 0);
694 MOZ_ASSERT(nativeToBytecodeScriptList_ != nullptr);
695
696 CompactBufferWriter writer;
697 uint32_t tableOffset = 0;
698 uint32_t numRegions = 0;
699
700 if (!JitcodeIonTable::WriteIonTable(
701 writer, nativeToBytecodeScriptList_,
702 nativeToBytecodeScriptListLength_, &nativeToBytecodeList_[0],
703 &nativeToBytecodeList_[0] + nativeToBytecodeList_.length(),
704 &tableOffset, &numRegions)) {
705 js_free(nativeToBytecodeScriptList_);
706 return false;
707 }
708
709 MOZ_ASSERT(tableOffset > 0);
710 MOZ_ASSERT(numRegions > 0);
711
712 // Writer is done, copy it to sized buffer.
713 uint8_t* data = cx->zone()->pod_malloc<uint8_t>(writer.length());
714 if (!data) {
715 js_free(nativeToBytecodeScriptList_);
716 return false;
717 }
718
719 memcpy(data, writer.buffer(), writer.length());
720 nativeToBytecodeMap_ = data;
721 nativeToBytecodeMapSize_ = writer.length();
722 nativeToBytecodeTableOffset_ = tableOffset;
723 nativeToBytecodeNumRegions_ = numRegions;
724
725 verifyCompactNativeToBytecodeMap(code);
726
727 JitSpew(JitSpew_Profiling, "Compact Native To Bytecode Map [%p-%p]", data,
728 data + nativeToBytecodeMapSize_);
729
730 return true;
731 }
732
verifyCompactNativeToBytecodeMap(JitCode * code)733 void CodeGeneratorShared::verifyCompactNativeToBytecodeMap(JitCode* code) {
734 #ifdef DEBUG
735 MOZ_ASSERT(nativeToBytecodeScriptListLength_ > 0);
736 MOZ_ASSERT(nativeToBytecodeScriptList_ != nullptr);
737 MOZ_ASSERT(nativeToBytecodeMap_ != nullptr);
738 MOZ_ASSERT(nativeToBytecodeMapSize_ > 0);
739 MOZ_ASSERT(nativeToBytecodeTableOffset_ > 0);
740 MOZ_ASSERT(nativeToBytecodeNumRegions_ > 0);
741
742 // The pointer to the table must be 4-byte aligned
743 const uint8_t* tablePtr = nativeToBytecodeMap_ + nativeToBytecodeTableOffset_;
744 MOZ_ASSERT(uintptr_t(tablePtr) % sizeof(uint32_t) == 0);
745
746 // Verify that numRegions was encoded correctly.
747 const JitcodeIonTable* ionTable =
748 reinterpret_cast<const JitcodeIonTable*>(tablePtr);
749 MOZ_ASSERT(ionTable->numRegions() == nativeToBytecodeNumRegions_);
750
751 // Region offset for first region should be at the start of the payload
752 // region. Since the offsets are backward from the start of the table, the
753 // first entry backoffset should be equal to the forward table offset from the
754 // start of the allocated data.
755 MOZ_ASSERT(ionTable->regionOffset(0) == nativeToBytecodeTableOffset_);
756
757 // Verify each region.
758 for (uint32_t i = 0; i < ionTable->numRegions(); i++) {
759 // Back-offset must point into the payload region preceding the table, not
760 // before it.
761 MOZ_ASSERT(ionTable->regionOffset(i) <= nativeToBytecodeTableOffset_);
762
763 // Back-offset must point to a later area in the payload region than
764 // previous back-offset. This means that back-offsets decrease
765 // monotonically.
766 MOZ_ASSERT_IF(i > 0,
767 ionTable->regionOffset(i) < ionTable->regionOffset(i - 1));
768
769 JitcodeRegionEntry entry = ionTable->regionEntry(i);
770
771 // Ensure native code offset for region falls within jitcode.
772 MOZ_ASSERT(entry.nativeOffset() <= code->instructionsSize());
773
774 // Read out script/pc stack and verify.
775 JitcodeRegionEntry::ScriptPcIterator scriptPcIter =
776 entry.scriptPcIterator();
777 while (scriptPcIter.hasMore()) {
778 uint32_t scriptIdx = 0, pcOffset = 0;
779 scriptPcIter.readNext(&scriptIdx, &pcOffset);
780
781 // Ensure scriptIdx refers to a valid script in the list.
782 MOZ_ASSERT(scriptIdx < nativeToBytecodeScriptListLength_);
783 JSScript* script = nativeToBytecodeScriptList_[scriptIdx];
784
785 // Ensure pcOffset falls within the script.
786 MOZ_ASSERT(pcOffset < script->length());
787 }
788
789 // Obtain the original nativeOffset and pcOffset and script.
790 uint32_t curNativeOffset = entry.nativeOffset();
791 JSScript* script = nullptr;
792 uint32_t curPcOffset = 0;
793 {
794 uint32_t scriptIdx = 0;
795 scriptPcIter.reset();
796 scriptPcIter.readNext(&scriptIdx, &curPcOffset);
797 script = nativeToBytecodeScriptList_[scriptIdx];
798 }
799
800 // Read out nativeDeltas and pcDeltas and verify.
801 JitcodeRegionEntry::DeltaIterator deltaIter = entry.deltaIterator();
802 while (deltaIter.hasMore()) {
803 uint32_t nativeDelta = 0;
804 int32_t pcDelta = 0;
805 deltaIter.readNext(&nativeDelta, &pcDelta);
806
807 curNativeOffset += nativeDelta;
808 curPcOffset = uint32_t(int32_t(curPcOffset) + pcDelta);
809
810 // Ensure that nativeOffset still falls within jitcode after delta.
811 MOZ_ASSERT(curNativeOffset <= code->instructionsSize());
812
813 // Ensure that pcOffset still falls within bytecode after delta.
814 MOZ_ASSERT(curPcOffset < script->length());
815 }
816 }
817 #endif // DEBUG
818 }
819
generateCompactTrackedOptimizationsMap(JSContext * cx,JitCode * code,IonTrackedTypeVector * allTypes)820 bool CodeGeneratorShared::generateCompactTrackedOptimizationsMap(
821 JSContext* cx, JitCode* code, IonTrackedTypeVector* allTypes) {
822 MOZ_ASSERT(trackedOptimizationsMap_ == nullptr);
823 MOZ_ASSERT(trackedOptimizationsMapSize_ == 0);
824 MOZ_ASSERT(trackedOptimizationsRegionTableOffset_ == 0);
825 MOZ_ASSERT(trackedOptimizationsTypesTableOffset_ == 0);
826 MOZ_ASSERT(trackedOptimizationsAttemptsTableOffset_ == 0);
827
828 if (trackedOptimizations_.empty()) return true;
829
830 UniqueTrackedOptimizations unique(cx);
831 if (!unique.init()) return false;
832
833 // Iterate through all entries to deduplicate their optimization attempts.
834 for (size_t i = 0; i < trackedOptimizations_.length(); i++) {
835 NativeToTrackedOptimizations& entry = trackedOptimizations_[i];
836 if (!unique.add(entry.optimizations)) return false;
837 }
838
839 // Sort the unique optimization attempts by frequency to stabilize the
840 // attempts' indices in the compact table we will write later.
841 if (!unique.sortByFrequency(cx)) return false;
842
843 // Write out the ranges and the table.
844 CompactBufferWriter writer;
845 uint32_t numRegions;
846 uint32_t regionTableOffset;
847 uint32_t typesTableOffset;
848 uint32_t attemptsTableOffset;
849 if (!WriteIonTrackedOptimizationsTable(
850 cx, writer, trackedOptimizations_.begin(),
851 trackedOptimizations_.end(), unique, &numRegions, ®ionTableOffset,
852 &typesTableOffset, &attemptsTableOffset, allTypes)) {
853 return false;
854 }
855
856 MOZ_ASSERT(regionTableOffset > 0);
857 MOZ_ASSERT(typesTableOffset > 0);
858 MOZ_ASSERT(attemptsTableOffset > 0);
859 MOZ_ASSERT(typesTableOffset > regionTableOffset);
860 MOZ_ASSERT(attemptsTableOffset > typesTableOffset);
861
862 // Copy over the table out of the writer's buffer.
863 uint8_t* data = cx->zone()->pod_malloc<uint8_t>(writer.length());
864 if (!data) return false;
865
866 memcpy(data, writer.buffer(), writer.length());
867 trackedOptimizationsMap_ = data;
868 trackedOptimizationsMapSize_ = writer.length();
869 trackedOptimizationsRegionTableOffset_ = regionTableOffset;
870 trackedOptimizationsTypesTableOffset_ = typesTableOffset;
871 trackedOptimizationsAttemptsTableOffset_ = attemptsTableOffset;
872
873 verifyCompactTrackedOptimizationsMap(code, numRegions, unique, allTypes);
874
875 JitSpew(JitSpew_OptimizationTrackingExtended,
876 "== Compact Native To Optimizations Map [%p-%p] size %u", data,
877 data + trackedOptimizationsMapSize_, trackedOptimizationsMapSize_);
878 JitSpew(JitSpew_OptimizationTrackingExtended,
879 " with type list of length %zu, size %zu", allTypes->length(),
880 allTypes->length() * sizeof(IonTrackedTypeWithAddendum));
881
882 return true;
883 }
884
885 #ifdef DEBUG
886 class ReadTempAttemptsVectorOp
887 : public JS::ForEachTrackedOptimizationAttemptOp {
888 TempOptimizationAttemptsVector* attempts_;
889 bool oom_;
890
891 public:
ReadTempAttemptsVectorOp(TempOptimizationAttemptsVector * attempts)892 explicit ReadTempAttemptsVectorOp(TempOptimizationAttemptsVector* attempts)
893 : attempts_(attempts), oom_(false) {}
894
oom()895 bool oom() { return oom_; }
896
operator ()(JS::TrackedStrategy strategy,JS::TrackedOutcome outcome)897 void operator()(JS::TrackedStrategy strategy,
898 JS::TrackedOutcome outcome) override {
899 if (!attempts_->append(OptimizationAttempt(strategy, outcome))) oom_ = true;
900 }
901 };
902
903 struct ReadTempTypeInfoVectorOp
904 : public IonTrackedOptimizationsTypeInfo::ForEachOp {
905 TempAllocator& alloc_;
906 TempOptimizationTypeInfoVector* types_;
907 TempTypeList accTypes_;
908 bool oom_;
909
910 public:
ReadTempTypeInfoVectorOpjs::jit::ReadTempTypeInfoVectorOp911 ReadTempTypeInfoVectorOp(TempAllocator& alloc,
912 TempOptimizationTypeInfoVector* types)
913 : alloc_(alloc), types_(types), accTypes_(alloc), oom_(false) {}
914
oomjs::jit::ReadTempTypeInfoVectorOp915 bool oom() { return oom_; }
916
readTypejs::jit::ReadTempTypeInfoVectorOp917 void readType(const IonTrackedTypeWithAddendum& tracked) override {
918 if (!accTypes_.append(tracked.type)) oom_ = true;
919 }
920
operator ()js::jit::ReadTempTypeInfoVectorOp921 void operator()(JS::TrackedTypeSite site, MIRType mirType) override {
922 OptimizationTypeInfo ty(alloc_, site, mirType);
923 for (uint32_t i = 0; i < accTypes_.length(); i++) {
924 if (!ty.trackType(accTypes_[i])) oom_ = true;
925 }
926 if (!types_->append(mozilla::Move(ty))) oom_ = true;
927 accTypes_.clear();
928 }
929 };
930 #endif // DEBUG
931
verifyCompactTrackedOptimizationsMap(JitCode * code,uint32_t numRegions,const UniqueTrackedOptimizations & unique,const IonTrackedTypeVector * allTypes)932 void CodeGeneratorShared::verifyCompactTrackedOptimizationsMap(
933 JitCode* code, uint32_t numRegions,
934 const UniqueTrackedOptimizations& unique,
935 const IonTrackedTypeVector* allTypes) {
936 #ifdef DEBUG
937 MOZ_ASSERT(trackedOptimizationsMap_ != nullptr);
938 MOZ_ASSERT(trackedOptimizationsMapSize_ > 0);
939 MOZ_ASSERT(trackedOptimizationsRegionTableOffset_ > 0);
940 MOZ_ASSERT(trackedOptimizationsTypesTableOffset_ > 0);
941 MOZ_ASSERT(trackedOptimizationsAttemptsTableOffset_ > 0);
942
943 // Table pointers must all be 4-byte aligned.
944 const uint8_t* regionTableAddr =
945 trackedOptimizationsMap_ + trackedOptimizationsRegionTableOffset_;
946 const uint8_t* typesTableAddr =
947 trackedOptimizationsMap_ + trackedOptimizationsTypesTableOffset_;
948 const uint8_t* attemptsTableAddr =
949 trackedOptimizationsMap_ + trackedOptimizationsAttemptsTableOffset_;
950 MOZ_ASSERT(uintptr_t(regionTableAddr) % sizeof(uint32_t) == 0);
951 MOZ_ASSERT(uintptr_t(typesTableAddr) % sizeof(uint32_t) == 0);
952 MOZ_ASSERT(uintptr_t(attemptsTableAddr) % sizeof(uint32_t) == 0);
953
954 // Assert that the number of entries matches up for the tables.
955 const IonTrackedOptimizationsRegionTable* regionTable =
956 (const IonTrackedOptimizationsRegionTable*)regionTableAddr;
957 MOZ_ASSERT(regionTable->numEntries() == numRegions);
958 const IonTrackedOptimizationsTypesTable* typesTable =
959 (const IonTrackedOptimizationsTypesTable*)typesTableAddr;
960 MOZ_ASSERT(typesTable->numEntries() == unique.count());
961 const IonTrackedOptimizationsAttemptsTable* attemptsTable =
962 (const IonTrackedOptimizationsAttemptsTable*)attemptsTableAddr;
963 MOZ_ASSERT(attemptsTable->numEntries() == unique.count());
964
965 // Verify each region.
966 uint32_t trackedIdx = 0;
967 for (uint32_t regionIdx = 0; regionIdx < regionTable->numEntries();
968 regionIdx++) {
969 // Check reverse offsets are within bounds.
970 MOZ_ASSERT(regionTable->entryOffset(regionIdx) <=
971 trackedOptimizationsRegionTableOffset_);
972 MOZ_ASSERT_IF(regionIdx > 0, regionTable->entryOffset(regionIdx) <
973 regionTable->entryOffset(regionIdx - 1));
974
975 IonTrackedOptimizationsRegion region = regionTable->entry(regionIdx);
976
977 // Check the region range is covered by jitcode.
978 MOZ_ASSERT(region.startOffset() <= code->instructionsSize());
979 MOZ_ASSERT(region.endOffset() <= code->instructionsSize());
980
981 IonTrackedOptimizationsRegion::RangeIterator iter = region.ranges();
982 while (iter.more()) {
983 // Assert that the offsets are correctly decoded from the delta.
984 uint32_t startOffset, endOffset;
985 uint8_t index;
986 iter.readNext(&startOffset, &endOffset, &index);
987 NativeToTrackedOptimizations& entry = trackedOptimizations_[trackedIdx++];
988 MOZ_ASSERT(startOffset == entry.startOffset.offset());
989 MOZ_ASSERT(endOffset == entry.endOffset.offset());
990 MOZ_ASSERT(index == unique.indexOf(entry.optimizations));
991
992 // Assert that the type info and attempts vectors are correctly
993 // decoded. This is disabled for now if the types table might
994 // contain nursery pointers, in which case the types might not
995 // match, see bug 1175761.
996 if (!code->zone()->group()->storeBuffer().cancelIonCompilations()) {
997 IonTrackedOptimizationsTypeInfo typeInfo = typesTable->entry(index);
998 TempOptimizationTypeInfoVector tvec(alloc());
999 ReadTempTypeInfoVectorOp top(alloc(), &tvec);
1000 typeInfo.forEach(top, allTypes);
1001 MOZ_ASSERT_IF(!top.oom(), entry.optimizations->matchTypes(tvec));
1002 }
1003
1004 IonTrackedOptimizationsAttempts attempts = attemptsTable->entry(index);
1005 TempOptimizationAttemptsVector avec(alloc());
1006 ReadTempAttemptsVectorOp aop(&avec);
1007 attempts.forEach(aop);
1008 MOZ_ASSERT_IF(!aop.oom(), entry.optimizations->matchAttempts(avec));
1009 }
1010 }
1011 #endif
1012 }
1013
markSafepoint(LInstruction * ins)1014 void CodeGeneratorShared::markSafepoint(LInstruction* ins) {
1015 markSafepointAt(masm.currentOffset(), ins);
1016 }
1017
markSafepointAt(uint32_t offset,LInstruction * ins)1018 void CodeGeneratorShared::markSafepointAt(uint32_t offset, LInstruction* ins) {
1019 MOZ_ASSERT_IF(
1020 !safepointIndices_.empty() && !masm.oom(),
1021 offset - safepointIndices_.back().displacement() >= sizeof(uint32_t));
1022 masm.propagateOOM(
1023 safepointIndices_.append(SafepointIndex(offset, ins->safepoint())));
1024 }
1025
ensureOsiSpace()1026 void CodeGeneratorShared::ensureOsiSpace() {
1027 // For a refresher, an invalidation point is of the form:
1028 // 1: call <target>
1029 // 2: ...
1030 // 3: <osipoint>
1031 //
1032 // The four bytes *before* instruction 2 are overwritten with an offset.
1033 // Callers must ensure that the instruction itself has enough bytes to
1034 // support this.
1035 //
1036 // The bytes *at* instruction 3 are overwritten with an invalidation jump.
1037 // jump. These bytes may be in a completely different IR sequence, but
1038 // represent the join point of the call out of the function.
1039 //
1040 // At points where we want to ensure that invalidation won't corrupt an
1041 // important instruction, we make sure to pad with nops.
1042 if (masm.currentOffset() - lastOsiPointOffset_ <
1043 Assembler::PatchWrite_NearCallSize()) {
1044 int32_t paddingSize = Assembler::PatchWrite_NearCallSize();
1045 paddingSize -= masm.currentOffset() - lastOsiPointOffset_;
1046 for (int32_t i = 0; i < paddingSize; ++i) masm.nop();
1047 }
1048 MOZ_ASSERT_IF(!masm.oom(), masm.currentOffset() - lastOsiPointOffset_ >=
1049 Assembler::PatchWrite_NearCallSize());
1050 lastOsiPointOffset_ = masm.currentOffset();
1051 }
1052
markOsiPoint(LOsiPoint * ins)1053 uint32_t CodeGeneratorShared::markOsiPoint(LOsiPoint* ins) {
1054 encode(ins->snapshot());
1055 ensureOsiSpace();
1056
1057 uint32_t offset = masm.currentOffset();
1058 SnapshotOffset so = ins->snapshot()->snapshotOffset();
1059 masm.propagateOOM(osiIndices_.append(OsiIndex(offset, so)));
1060
1061 return offset;
1062 }
1063
1064 #ifdef CHECK_OSIPOINT_REGISTERS
1065 template <class Op>
HandleRegisterDump(Op op,MacroAssembler & masm,LiveRegisterSet liveRegs,Register activation,Register scratch)1066 static void HandleRegisterDump(Op op, MacroAssembler& masm,
1067 LiveRegisterSet liveRegs, Register activation,
1068 Register scratch) {
1069 const size_t baseOffset = JitActivation::offsetOfRegs();
1070
1071 // Handle live GPRs.
1072 for (GeneralRegisterIterator iter(liveRegs.gprs()); iter.more(); ++iter) {
1073 Register reg = *iter;
1074 Address dump(activation, baseOffset + RegisterDump::offsetOfRegister(reg));
1075
1076 if (reg == activation) {
1077 // To use the original value of the activation register (that's
1078 // now on top of the stack), we need the scratch register.
1079 masm.push(scratch);
1080 masm.loadPtr(Address(masm.getStackPointer(), sizeof(uintptr_t)), scratch);
1081 op(scratch, dump);
1082 masm.pop(scratch);
1083 } else {
1084 op(reg, dump);
1085 }
1086 }
1087
1088 // Handle live FPRs.
1089 for (FloatRegisterIterator iter(liveRegs.fpus()); iter.more(); ++iter) {
1090 FloatRegister reg = *iter;
1091 Address dump(activation, baseOffset + RegisterDump::offsetOfRegister(reg));
1092 op(reg, dump);
1093 }
1094 }
1095
1096 class StoreOp {
1097 MacroAssembler& masm;
1098
1099 public:
StoreOp(MacroAssembler & masm)1100 explicit StoreOp(MacroAssembler& masm) : masm(masm) {}
1101
operator ()(Register reg,Address dump)1102 void operator()(Register reg, Address dump) { masm.storePtr(reg, dump); }
operator ()(FloatRegister reg,Address dump)1103 void operator()(FloatRegister reg, Address dump) {
1104 if (reg.isDouble())
1105 masm.storeDouble(reg, dump);
1106 else if (reg.isSingle())
1107 masm.storeFloat32(reg, dump);
1108 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
1109 else if (reg.isSimd128())
1110 masm.storeUnalignedSimd128Float(reg, dump);
1111 #endif
1112 else
1113 MOZ_CRASH("Unexpected register type.");
1114 }
1115 };
1116
StoreAllLiveRegs(MacroAssembler & masm,LiveRegisterSet liveRegs)1117 static void StoreAllLiveRegs(MacroAssembler& masm, LiveRegisterSet liveRegs) {
1118 // Store a copy of all live registers before performing the call.
1119 // When we reach the OsiPoint, we can use this to check nothing
1120 // modified them in the meantime.
1121
1122 // Load pointer to the JitActivation in a scratch register.
1123 AllocatableGeneralRegisterSet allRegs(GeneralRegisterSet::All());
1124 Register scratch = allRegs.takeAny();
1125 masm.push(scratch);
1126 masm.loadJitActivation(scratch);
1127
1128 Address checkRegs(scratch, JitActivation::offsetOfCheckRegs());
1129 masm.add32(Imm32(1), checkRegs);
1130
1131 StoreOp op(masm);
1132 HandleRegisterDump<StoreOp>(op, masm, liveRegs, scratch, allRegs.getAny());
1133
1134 masm.pop(scratch);
1135 }
1136
1137 class VerifyOp {
1138 MacroAssembler& masm;
1139 Label* failure_;
1140
1141 public:
VerifyOp(MacroAssembler & masm,Label * failure)1142 VerifyOp(MacroAssembler& masm, Label* failure)
1143 : masm(masm), failure_(failure) {}
1144
operator ()(Register reg,Address dump)1145 void operator()(Register reg, Address dump) {
1146 masm.branchPtr(Assembler::NotEqual, dump, reg, failure_);
1147 }
operator ()(FloatRegister reg,Address dump)1148 void operator()(FloatRegister reg, Address dump) {
1149 FloatRegister scratch;
1150 if (reg.isDouble()) {
1151 scratch = ScratchDoubleReg;
1152 masm.loadDouble(dump, scratch);
1153 masm.branchDouble(Assembler::DoubleNotEqual, scratch, reg, failure_);
1154 } else if (reg.isSingle()) {
1155 scratch = ScratchFloat32Reg;
1156 masm.loadFloat32(dump, scratch);
1157 masm.branchFloat(Assembler::DoubleNotEqual, scratch, reg, failure_);
1158 }
1159
1160 // :TODO: (Bug 1133745) Add support to verify SIMD registers.
1161 }
1162 };
1163
verifyOsiPointRegs(LSafepoint * safepoint)1164 void CodeGeneratorShared::verifyOsiPointRegs(LSafepoint* safepoint) {
1165 // Ensure the live registers stored by callVM did not change between
1166 // the call and this OsiPoint. Try-catch relies on this invariant.
1167
1168 // Load pointer to the JitActivation in a scratch register.
1169 AllocatableGeneralRegisterSet allRegs(GeneralRegisterSet::All());
1170 Register scratch = allRegs.takeAny();
1171 masm.push(scratch);
1172 masm.loadJitActivation(scratch);
1173
1174 // If we should not check registers (because the instruction did not call
1175 // into the VM, or a GC happened), we're done.
1176 Label failure, done;
1177 Address checkRegs(scratch, JitActivation::offsetOfCheckRegs());
1178 masm.branch32(Assembler::Equal, checkRegs, Imm32(0), &done);
1179
1180 // Having more than one VM function call made in one visit function at
1181 // runtime is a sec-ciritcal error, because if we conservatively assume that
1182 // one of the function call can re-enter Ion, then the invalidation process
1183 // will potentially add a call at a random location, by patching the code
1184 // before the return address.
1185 masm.branch32(Assembler::NotEqual, checkRegs, Imm32(1), &failure);
1186
1187 // Set checkRegs to 0, so that we don't try to verify registers after we
1188 // return from this script to the caller.
1189 masm.store32(Imm32(0), checkRegs);
1190
1191 // Ignore clobbered registers. Some instructions (like LValueToInt32) modify
1192 // temps after calling into the VM. This is fine because no other
1193 // instructions (including this OsiPoint) will depend on them. Also
1194 // backtracking can also use the same register for an input and an output.
1195 // These are marked as clobbered and shouldn't get checked.
1196 LiveRegisterSet liveRegs;
1197 liveRegs.set() = RegisterSet::Intersect(
1198 safepoint->liveRegs().set(),
1199 RegisterSet::Not(safepoint->clobberedRegs().set()));
1200
1201 VerifyOp op(masm, &failure);
1202 HandleRegisterDump<VerifyOp>(op, masm, liveRegs, scratch, allRegs.getAny());
1203
1204 masm.jump(&done);
1205
1206 // Do not profile the callWithABI that occurs below. This is to avoid a
1207 // rare corner case that occurs when profiling interacts with itself:
1208 //
1209 // When slow profiling assertions are turned on, FunctionBoundary ops
1210 // (which update the profiler pseudo-stack) may emit a callVM, which
1211 // forces them to have an osi point associated with them. The
1212 // FunctionBoundary for inline function entry is added to the caller's
1213 // graph with a PC from the caller's code, but during codegen it modifies
1214 // Gecko Profiler instrumentation to add the callee as the current top-most
1215 // script. When codegen gets to the OSIPoint, and the callWithABI below is
1216 // emitted, the codegen thinks that the current frame is the callee, but
1217 // the PC it's using from the OSIPoint refers to the caller. This causes
1218 // the profiler instrumentation of the callWithABI below to ASSERT, since
1219 // the script and pc are mismatched. To avoid this, we simply omit
1220 // instrumentation for these callWithABIs.
1221
1222 // Any live register captured by a safepoint (other than temp registers)
1223 // must remain unchanged between the call and the OsiPoint instruction.
1224 masm.bind(&failure);
1225 masm.assumeUnreachable("Modified registers between VM call and OsiPoint");
1226
1227 masm.bind(&done);
1228 masm.pop(scratch);
1229 }
1230
shouldVerifyOsiPointRegs(LSafepoint * safepoint)1231 bool CodeGeneratorShared::shouldVerifyOsiPointRegs(LSafepoint* safepoint) {
1232 if (!checkOsiPointRegisters) return false;
1233
1234 if (safepoint->liveRegs().emptyGeneral() &&
1235 safepoint->liveRegs().emptyFloat())
1236 return false; // No registers to check.
1237
1238 return true;
1239 }
1240
resetOsiPointRegs(LSafepoint * safepoint)1241 void CodeGeneratorShared::resetOsiPointRegs(LSafepoint* safepoint) {
1242 if (!shouldVerifyOsiPointRegs(safepoint)) return;
1243
1244 // Set checkRegs to 0. If we perform a VM call, the instruction
1245 // will set it to 1.
1246 AllocatableGeneralRegisterSet allRegs(GeneralRegisterSet::All());
1247 Register scratch = allRegs.takeAny();
1248 masm.push(scratch);
1249 masm.loadJitActivation(scratch);
1250 Address checkRegs(scratch, JitActivation::offsetOfCheckRegs());
1251 masm.store32(Imm32(0), checkRegs);
1252 masm.pop(scratch);
1253 }
1254 #endif
1255
1256 // Before doing any call to Cpp, you should ensure that volatile
1257 // registers are evicted by the register allocator.
callVM(const VMFunction & fun,LInstruction * ins,const Register * dynStack)1258 void CodeGeneratorShared::callVM(const VMFunction& fun, LInstruction* ins,
1259 const Register* dynStack) {
1260 // If we're calling a function with an out parameter type of double, make
1261 // sure we have an FPU.
1262 MOZ_ASSERT_IF(fun.outParam == Type_Double,
1263 gen->runtime->jitSupportsFloatingPoint());
1264
1265 #ifdef DEBUG
1266 if (ins->mirRaw()) {
1267 MOZ_ASSERT(ins->mirRaw()->isInstruction());
1268 MInstruction* mir = ins->mirRaw()->toInstruction();
1269 MOZ_ASSERT_IF(mir->needsResumePoint(), mir->resumePoint());
1270 }
1271 #endif
1272
1273 // Stack is:
1274 // ... frame ...
1275 // [args]
1276 #ifdef DEBUG
1277 MOZ_ASSERT(pushedArgs_ == fun.explicitArgs);
1278 pushedArgs_ = 0;
1279 #endif
1280
1281 // Get the wrapper of the VM function.
1282 TrampolinePtr wrapper = gen->jitRuntime()->getVMWrapper(fun);
1283
1284 #ifdef CHECK_OSIPOINT_REGISTERS
1285 if (shouldVerifyOsiPointRegs(ins->safepoint()))
1286 StoreAllLiveRegs(masm, ins->safepoint()->liveRegs());
1287 #endif
1288
1289 // Push an exit frame descriptor. If |dynStack| is a valid pointer to a
1290 // register, then its value is added to the value of the |framePushed()| to
1291 // fill the frame descriptor.
1292 if (dynStack) {
1293 masm.addPtr(Imm32(masm.framePushed()), *dynStack);
1294 masm.makeFrameDescriptor(*dynStack, JitFrame_IonJS,
1295 ExitFrameLayout::Size());
1296 masm.Push(*dynStack); // descriptor
1297 } else {
1298 masm.pushStaticFrameDescriptor(JitFrame_IonJS, ExitFrameLayout::Size());
1299 }
1300
1301 // Call the wrapper function. The wrapper is in charge to unwind the stack
1302 // when returning from the call. Failures are handled with exceptions based
1303 // on the return value of the C functions. To guard the outcome of the
1304 // returned value, use another LIR instruction.
1305 uint32_t callOffset = masm.callJit(wrapper);
1306 markSafepointAt(callOffset, ins);
1307
1308 // Remove rest of the frame left on the stack. We remove the return address
1309 // which is implicitly poped when returning.
1310 int framePop = sizeof(ExitFrameLayout) - sizeof(void*);
1311
1312 // Pop arguments from framePushed.
1313 masm.implicitPop(fun.explicitStackSlots() * sizeof(void*) + framePop);
1314 // Stack is:
1315 // ... frame ...
1316 }
1317
1318 class OutOfLineTruncateSlow : public OutOfLineCodeBase<CodeGeneratorShared> {
1319 FloatRegister src_;
1320 Register dest_;
1321 bool widenFloatToDouble_;
1322 wasm::BytecodeOffset bytecodeOffset_;
1323
1324 public:
OutOfLineTruncateSlow(FloatRegister src,Register dest,bool widenFloatToDouble=false,wasm::BytecodeOffset bytecodeOffset=wasm::BytecodeOffset ())1325 OutOfLineTruncateSlow(
1326 FloatRegister src, Register dest, bool widenFloatToDouble = false,
1327 wasm::BytecodeOffset bytecodeOffset = wasm::BytecodeOffset())
1328 : src_(src),
1329 dest_(dest),
1330 widenFloatToDouble_(widenFloatToDouble),
1331 bytecodeOffset_(bytecodeOffset) {}
1332
accept(CodeGeneratorShared * codegen)1333 void accept(CodeGeneratorShared* codegen) override {
1334 codegen->visitOutOfLineTruncateSlow(this);
1335 }
src() const1336 FloatRegister src() const { return src_; }
dest() const1337 Register dest() const { return dest_; }
widenFloatToDouble() const1338 bool widenFloatToDouble() const { return widenFloatToDouble_; }
bytecodeOffset() const1339 wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
1340 };
1341
oolTruncateDouble(FloatRegister src,Register dest,MInstruction * mir,wasm::BytecodeOffset bytecodeOffset)1342 OutOfLineCode* CodeGeneratorShared::oolTruncateDouble(
1343 FloatRegister src, Register dest, MInstruction* mir,
1344 wasm::BytecodeOffset bytecodeOffset) {
1345 MOZ_ASSERT_IF(IsCompilingWasm(), bytecodeOffset.isValid());
1346
1347 OutOfLineTruncateSlow* ool = new (alloc())
1348 OutOfLineTruncateSlow(src, dest, /* float32 */ false, bytecodeOffset);
1349 addOutOfLineCode(ool, mir);
1350 return ool;
1351 }
1352
emitTruncateDouble(FloatRegister src,Register dest,MTruncateToInt32 * mir)1353 void CodeGeneratorShared::emitTruncateDouble(FloatRegister src, Register dest,
1354 MTruncateToInt32* mir) {
1355 OutOfLineCode* ool = oolTruncateDouble(src, dest, mir, mir->bytecodeOffset());
1356
1357 masm.branchTruncateDoubleMaybeModUint32(src, dest, ool->entry());
1358 masm.bind(ool->rejoin());
1359 }
1360
emitTruncateFloat32(FloatRegister src,Register dest,MTruncateToInt32 * mir)1361 void CodeGeneratorShared::emitTruncateFloat32(FloatRegister src, Register dest,
1362 MTruncateToInt32* mir) {
1363 OutOfLineTruncateSlow* ool = new (alloc()) OutOfLineTruncateSlow(
1364 src, dest, /* float32 */ true, mir->bytecodeOffset());
1365 addOutOfLineCode(ool, mir);
1366
1367 masm.branchTruncateFloat32MaybeModUint32(src, dest, ool->entry());
1368 masm.bind(ool->rejoin());
1369 }
1370
visitOutOfLineTruncateSlow(OutOfLineTruncateSlow * ool)1371 void CodeGeneratorShared::visitOutOfLineTruncateSlow(
1372 OutOfLineTruncateSlow* ool) {
1373 FloatRegister src = ool->src();
1374 Register dest = ool->dest();
1375
1376 saveVolatile(dest);
1377 masm.outOfLineTruncateSlow(src, dest, ool->widenFloatToDouble(),
1378 gen->compilingWasm(), ool->bytecodeOffset());
1379 restoreVolatile(dest);
1380
1381 masm.jump(ool->rejoin());
1382 }
1383
omitOverRecursedCheck() const1384 bool CodeGeneratorShared::omitOverRecursedCheck() const {
1385 // If the current function makes no calls (which means it isn't recursive)
1386 // and it uses only a small amount of stack space, it doesn't need a
1387 // stack overflow check. Note that the actual number here is somewhat
1388 // arbitrary, and codegen actually uses small bounded amounts of
1389 // additional stack space in some cases too.
1390 return frameSize() < MAX_UNCHECKED_LEAF_FRAME_SIZE &&
1391 !gen->needsOverrecursedCheck();
1392 }
1393
emitWasmCallBase(MWasmCall * mir,bool needsBoundsCheck)1394 void CodeGeneratorShared::emitWasmCallBase(MWasmCall* mir,
1395 bool needsBoundsCheck) {
1396 if (mir->spIncrement()) masm.freeStack(mir->spIncrement());
1397
1398 MOZ_ASSERT((sizeof(wasm::Frame) + masm.framePushed()) % WasmStackAlignment ==
1399 0);
1400 static_assert(
1401 WasmStackAlignment >= ABIStackAlignment &&
1402 WasmStackAlignment % ABIStackAlignment == 0,
1403 "The wasm stack alignment should subsume the ABI-required alignment");
1404
1405 #ifdef DEBUG
1406 Label ok;
1407 masm.branchTestStackPtr(Assembler::Zero, Imm32(WasmStackAlignment - 1), &ok);
1408 masm.breakpoint();
1409 masm.bind(&ok);
1410 #endif
1411
1412 // LWasmCallBase::isCallPreserved() assumes that all MWasmCalls preserve the
1413 // TLS and pinned regs. The only case where where we don't have to reload
1414 // the TLS and pinned regs is when the callee preserves them.
1415 bool reloadRegs = true;
1416
1417 const wasm::CallSiteDesc& desc = mir->desc();
1418 const wasm::CalleeDesc& callee = mir->callee();
1419 switch (callee.which()) {
1420 case wasm::CalleeDesc::Func:
1421 masm.call(desc, callee.funcIndex());
1422 reloadRegs = false;
1423 break;
1424 case wasm::CalleeDesc::Import:
1425 masm.wasmCallImport(desc, callee);
1426 break;
1427 case wasm::CalleeDesc::AsmJSTable:
1428 case wasm::CalleeDesc::WasmTable:
1429 masm.wasmCallIndirect(desc, callee, needsBoundsCheck);
1430 reloadRegs = callee.which() == wasm::CalleeDesc::WasmTable &&
1431 callee.wasmTableIsExternal();
1432 break;
1433 case wasm::CalleeDesc::Builtin:
1434 masm.call(desc, callee.builtin());
1435 reloadRegs = false;
1436 break;
1437 case wasm::CalleeDesc::BuiltinInstanceMethod:
1438 masm.wasmCallBuiltinInstanceMethod(desc, mir->instanceArg(),
1439 callee.builtin());
1440 break;
1441 }
1442
1443 if (reloadRegs) {
1444 masm.loadWasmTlsRegFromFrame();
1445 masm.loadWasmPinnedRegsFromTls();
1446 }
1447
1448 if (mir->spIncrement()) masm.reserveStack(mir->spIncrement());
1449 }
1450
visitWasmLoadGlobalVar(LWasmLoadGlobalVar * ins)1451 void CodeGeneratorShared::visitWasmLoadGlobalVar(LWasmLoadGlobalVar* ins) {
1452 MWasmLoadGlobalVar* mir = ins->mir();
1453
1454 MIRType type = mir->type();
1455 MOZ_ASSERT(IsNumberType(type) || IsSimdType(type));
1456
1457 Register tls = ToRegister(ins->tlsPtr());
1458 Address addr(tls,
1459 offsetof(wasm::TlsData, globalArea) + mir->globalDataOffset());
1460 switch (type) {
1461 case MIRType::Int32:
1462 masm.load32(addr, ToRegister(ins->output()));
1463 break;
1464 case MIRType::Float32:
1465 masm.loadFloat32(addr, ToFloatRegister(ins->output()));
1466 break;
1467 case MIRType::Double:
1468 masm.loadDouble(addr, ToFloatRegister(ins->output()));
1469 break;
1470 // Aligned access: code is aligned on PageSize + there is padding
1471 // before the global data section.
1472 case MIRType::Int8x16:
1473 case MIRType::Int16x8:
1474 case MIRType::Int32x4:
1475 case MIRType::Bool8x16:
1476 case MIRType::Bool16x8:
1477 case MIRType::Bool32x4:
1478 masm.loadInt32x4(addr, ToFloatRegister(ins->output()));
1479 break;
1480 case MIRType::Float32x4:
1481 masm.loadFloat32x4(addr, ToFloatRegister(ins->output()));
1482 break;
1483 default:
1484 MOZ_CRASH("unexpected type in visitWasmLoadGlobalVar");
1485 }
1486 }
1487
visitWasmStoreGlobalVar(LWasmStoreGlobalVar * ins)1488 void CodeGeneratorShared::visitWasmStoreGlobalVar(LWasmStoreGlobalVar* ins) {
1489 MWasmStoreGlobalVar* mir = ins->mir();
1490
1491 MIRType type = mir->value()->type();
1492 MOZ_ASSERT(IsNumberType(type) || IsSimdType(type));
1493
1494 Register tls = ToRegister(ins->tlsPtr());
1495 Address addr(tls,
1496 offsetof(wasm::TlsData, globalArea) + mir->globalDataOffset());
1497 switch (type) {
1498 case MIRType::Int32:
1499 masm.store32(ToRegister(ins->value()), addr);
1500 break;
1501 case MIRType::Float32:
1502 masm.storeFloat32(ToFloatRegister(ins->value()), addr);
1503 break;
1504 case MIRType::Double:
1505 masm.storeDouble(ToFloatRegister(ins->value()), addr);
1506 break;
1507 // Aligned access: code is aligned on PageSize + there is padding
1508 // before the global data section.
1509 case MIRType::Int8x16:
1510 case MIRType::Int16x8:
1511 case MIRType::Int32x4:
1512 case MIRType::Bool8x16:
1513 case MIRType::Bool16x8:
1514 case MIRType::Bool32x4:
1515 masm.storeInt32x4(ToFloatRegister(ins->value()), addr);
1516 break;
1517 case MIRType::Float32x4:
1518 masm.storeFloat32x4(ToFloatRegister(ins->value()), addr);
1519 break;
1520 default:
1521 MOZ_CRASH("unexpected type in visitWasmStoreGlobalVar");
1522 }
1523 }
1524
visitWasmLoadGlobalVarI64(LWasmLoadGlobalVarI64 * ins)1525 void CodeGeneratorShared::visitWasmLoadGlobalVarI64(
1526 LWasmLoadGlobalVarI64* ins) {
1527 MWasmLoadGlobalVar* mir = ins->mir();
1528 MOZ_ASSERT(mir->type() == MIRType::Int64);
1529
1530 Register tls = ToRegister(ins->tlsPtr());
1531 Address addr(tls,
1532 offsetof(wasm::TlsData, globalArea) + mir->globalDataOffset());
1533
1534 Register64 output = ToOutRegister64(ins);
1535 masm.load64(addr, output);
1536 }
1537
visitWasmStoreGlobalVarI64(LWasmStoreGlobalVarI64 * ins)1538 void CodeGeneratorShared::visitWasmStoreGlobalVarI64(
1539 LWasmStoreGlobalVarI64* ins) {
1540 MWasmStoreGlobalVar* mir = ins->mir();
1541 MOZ_ASSERT(mir->value()->type() == MIRType::Int64);
1542
1543 Register tls = ToRegister(ins->tlsPtr());
1544 Address addr(tls,
1545 offsetof(wasm::TlsData, globalArea) + mir->globalDataOffset());
1546
1547 Register64 value = ToRegister64(ins->value());
1548 masm.store64(value, addr);
1549 }
1550
emitPreBarrier(Register base,const LAllocation * index,int32_t offsetAdjustment)1551 void CodeGeneratorShared::emitPreBarrier(Register base,
1552 const LAllocation* index,
1553 int32_t offsetAdjustment) {
1554 if (index->isConstant()) {
1555 Address address(base, ToInt32(index) * sizeof(Value) + offsetAdjustment);
1556 masm.guardedCallPreBarrier(address, MIRType::Value);
1557 } else {
1558 BaseIndex address(base, ToRegister(index), TimesEight, offsetAdjustment);
1559 masm.guardedCallPreBarrier(address, MIRType::Value);
1560 }
1561 }
1562
emitPreBarrier(Address address)1563 void CodeGeneratorShared::emitPreBarrier(Address address) {
1564 masm.guardedCallPreBarrier(address, MIRType::Value);
1565 }
1566
labelForBackedgeWithImplicitCheck(MBasicBlock * mir)1567 Label* CodeGeneratorShared::labelForBackedgeWithImplicitCheck(
1568 MBasicBlock* mir) {
1569 // If this is a loop backedge to a loop header with an implicit interrupt
1570 // check, use a patchable jump. Skip this search if compiling without a
1571 // script for wasm, as there will be no interrupt check instruction.
1572 // Due to critical edge unsplitting there may no longer be unique loop
1573 // backedges, so just look for any edge going to an earlier block in RPO.
1574 if (!gen->compilingWasm() && mir->isLoopHeader() &&
1575 mir->id() <= current->mir()->id()) {
1576 for (LInstructionIterator iter = mir->lir()->begin();
1577 iter != mir->lir()->end(); iter++) {
1578 if (iter->isMoveGroup()) {
1579 // Continue searching for an interrupt check.
1580 } else {
1581 // The interrupt check should be the first instruction in the
1582 // loop header other than move groups.
1583 MOZ_ASSERT(iter->isInterruptCheck());
1584 if (iter->toInterruptCheck()->implicit())
1585 return iter->toInterruptCheck()->oolEntry();
1586 return nullptr;
1587 }
1588 }
1589 }
1590
1591 return nullptr;
1592 }
1593
jumpToBlock(MBasicBlock * mir)1594 void CodeGeneratorShared::jumpToBlock(MBasicBlock* mir) {
1595 // Skip past trivial blocks.
1596 mir = skipTrivialBlocks(mir);
1597
1598 // No jump necessary if we can fall through to the next block.
1599 if (isNextBlock(mir->lir())) return;
1600
1601 if (Label* oolEntry = labelForBackedgeWithImplicitCheck(mir)) {
1602 // Note: the backedge is initially a jump to the next instruction.
1603 // It will be patched to the target block's label during link().
1604 RepatchLabel rejoin;
1605 CodeOffsetJump backedge = masm.backedgeJump(&rejoin, mir->lir()->label());
1606 masm.bind(&rejoin);
1607
1608 masm.propagateOOM(patchableBackedges_.append(
1609 PatchableBackedgeInfo(backedge, mir->lir()->label(), oolEntry)));
1610 } else {
1611 masm.jump(mir->lir()->label());
1612 }
1613 }
1614
getJumpLabelForBranch(MBasicBlock * block)1615 Label* CodeGeneratorShared::getJumpLabelForBranch(MBasicBlock* block) {
1616 // Skip past trivial blocks.
1617 block = skipTrivialBlocks(block);
1618
1619 if (!labelForBackedgeWithImplicitCheck(block)) return block->lir()->label();
1620
1621 // We need to use a patchable jump for this backedge, but want to treat
1622 // this as a normal label target to simplify codegen. Efficiency isn't so
1623 // important here as these tests are extremely unlikely to be used in loop
1624 // backedges, so emit inline code for the patchable jump. Heap allocating
1625 // the label allows it to be used by out of line blocks.
1626 Label* res = alloc().lifoAlloc()->newInfallible<Label>();
1627 Label after;
1628 masm.jump(&after);
1629 masm.bind(res);
1630 jumpToBlock(block);
1631 masm.bind(&after);
1632 return res;
1633 }
1634
1635 // This function is not used for MIPS/MIPS64. MIPS has branchToBlock.
1636 #if !defined(JS_CODEGEN_MIPS32) && !defined(JS_CODEGEN_MIPS64)
jumpToBlock(MBasicBlock * mir,Assembler::Condition cond)1637 void CodeGeneratorShared::jumpToBlock(MBasicBlock* mir,
1638 Assembler::Condition cond) {
1639 // Skip past trivial blocks.
1640 mir = skipTrivialBlocks(mir);
1641
1642 if (Label* oolEntry = labelForBackedgeWithImplicitCheck(mir)) {
1643 // Note: the backedge is initially a jump to the next instruction.
1644 // It will be patched to the target block's label during link().
1645 RepatchLabel rejoin;
1646 CodeOffsetJump backedge =
1647 masm.jumpWithPatch(&rejoin, cond, mir->lir()->label());
1648 masm.bind(&rejoin);
1649
1650 masm.propagateOOM(patchableBackedges_.append(
1651 PatchableBackedgeInfo(backedge, mir->lir()->label(), oolEntry)));
1652 } else {
1653 masm.j(cond, mir->lir()->label());
1654 }
1655 }
1656 #endif
1657
computeDivisionConstants(uint32_t d,int maxLog)1658 ReciprocalMulConstants CodeGeneratorShared::computeDivisionConstants(
1659 uint32_t d, int maxLog) {
1660 MOZ_ASSERT(maxLog >= 2 && maxLog <= 32);
1661 // In what follows, 0 < d < 2^maxLog and d is not a power of 2.
1662 MOZ_ASSERT(d < (uint64_t(1) << maxLog) && (d & (d - 1)) != 0);
1663
1664 // Speeding up division by non power-of-2 constants is possible by
1665 // calculating, during compilation, a value M such that high-order
1666 // bits of M*n correspond to the result of the division of n by d.
1667 // No value of M can serve this purpose for arbitrarily big values
1668 // of n but, for optimizing integer division, we're just concerned
1669 // with values of n whose absolute value is bounded (by fitting in
1670 // an integer type, say). With this in mind, we'll find a constant
1671 // M as above that works for -2^maxLog <= n < 2^maxLog; maxLog can
1672 // then be 31 for signed division or 32 for unsigned division.
1673 //
1674 // The original presentation of this technique appears in Hacker's
1675 // Delight, a book by Henry S. Warren, Jr.. A proof of correctness
1676 // for our version follows; we'll denote maxLog by L in the proof,
1677 // for conciseness.
1678 //
1679 // Formally, for |d| < 2^L, we'll compute two magic values M and s
1680 // in the ranges 0 <= M < 2^(L+1) and 0 <= s <= L such that
1681 // (M * n) >> (32 + s) = floor(n/d) if 0 <= n < 2^L
1682 // (M * n) >> (32 + s) = ceil(n/d) - 1 if -2^L <= n < 0.
1683 //
1684 // Define p = 32 + s, M = ceil(2^p/d), and assume that s satisfies
1685 // M - 2^p/d <= 2^(p-L)/d. (1)
1686 // (Observe that p = CeilLog32(d) + L satisfies this, as the right
1687 // side of (1) is at least one in this case). Then,
1688 //
1689 // a) If p <= CeilLog32(d) + L, then M < 2^(L+1) - 1.
1690 // Proof: Indeed, M is monotone in p and, for p equal to the above
1691 // value, the bounds 2^L > d >= 2^(p-L-1) + 1 readily imply that
1692 // 2^p / d < 2^p/(d - 1) * (d - 1)/d
1693 // <= 2^(L+1) * (1 - 1/d) < 2^(L+1) - 2.
1694 // The claim follows by applying the ceiling function.
1695 //
1696 // b) For any 0 <= n < 2^L, floor(Mn/2^p) = floor(n/d).
1697 // Proof: Put x = floor(Mn/2^p); it's the unique integer for which
1698 // Mn/2^p - 1 < x <= Mn/2^p. (2)
1699 // Using M >= 2^p/d on the LHS and (1) on the RHS, we get
1700 // n/d - 1 < x <= n/d + n/(2^L d) < n/d + 1/d.
1701 // Since x is an integer, it's not in the interval (n/d, (n+1)/d),
1702 // and so n/d - 1 < x <= n/d, which implies x = floor(n/d).
1703 //
1704 // c) For any -2^L <= n < 0, floor(Mn/2^p) + 1 = ceil(n/d).
1705 // Proof: The proof is similar. Equation (2) holds as above. Using
1706 // M > 2^p/d (d isn't a power of 2) on the RHS and (1) on the LHS,
1707 // n/d + n/(2^L d) - 1 < x < n/d.
1708 // Using n >= -2^L and summing 1,
1709 // n/d - 1/d < x + 1 < n/d + 1.
1710 // Since x + 1 is an integer, this implies n/d <= x + 1 < n/d + 1.
1711 // In other words, x + 1 = ceil(n/d).
1712 //
1713 // Condition (1) isn't necessary for the existence of M and s with
1714 // the properties above. Hacker's Delight provides a slightly less
1715 // restrictive condition when d >= 196611, at the cost of a 3-page
1716 // proof of correctness, for the case L = 31.
1717 //
1718 // Note that, since d*M - 2^p = d - (2^p)%d, (1) can be written as
1719 // 2^(p-L) >= d - (2^p)%d.
1720 // In order to avoid overflow in the (2^p) % d calculation, we can
1721 // compute it as (2^p-1) % d + 1, where 2^p-1 can then be computed
1722 // without overflow as UINT64_MAX >> (64-p).
1723
1724 // We now compute the least p >= 32 with the property above...
1725 int32_t p = 32;
1726 while ((uint64_t(1) << (p - maxLog)) + (UINT64_MAX >> (64 - p)) % d + 1 < d)
1727 p++;
1728
1729 // ...and the corresponding M. For either the signed (L=31) or the
1730 // unsigned (L=32) case, this value can be too large (cf. item a).
1731 // Codegen can still multiply by M by multiplying by (M - 2^L) and
1732 // adjusting the value afterwards, if this is the case.
1733 ReciprocalMulConstants rmc;
1734 rmc.multiplier = (UINT64_MAX >> (64 - p)) / d + 1;
1735 rmc.shiftAmount = p - 32;
1736
1737 return rmc;
1738 }
1739
1740 #ifdef JS_TRACE_LOGGING
1741
emitTracelogScript(bool isStart)1742 void CodeGeneratorShared::emitTracelogScript(bool isStart) {
1743 if (!TraceLogTextIdEnabled(TraceLogger_Scripts)) return;
1744
1745 Label done;
1746
1747 AllocatableRegisterSet regs(RegisterSet::Volatile());
1748 Register logger = regs.takeAnyGeneral();
1749 Register script = regs.takeAnyGeneral();
1750
1751 masm.Push(logger);
1752
1753 masm.loadTraceLogger(logger);
1754 masm.branchTestPtr(Assembler::Zero, logger, logger, &done);
1755
1756 Address enabledAddress(logger, TraceLoggerThread::offsetOfEnabled());
1757 masm.branch32(Assembler::Equal, enabledAddress, Imm32(0), &done);
1758
1759 masm.Push(script);
1760
1761 CodeOffset patchScript = masm.movWithPatch(ImmWord(0), script);
1762 masm.propagateOOM(patchableTLScripts_.append(patchScript));
1763
1764 if (isStart)
1765 masm.tracelogStartId(logger, script);
1766 else
1767 masm.tracelogStopId(logger, script);
1768
1769 masm.Pop(script);
1770
1771 masm.bind(&done);
1772
1773 masm.Pop(logger);
1774 }
1775
emitTracelogTree(bool isStart,uint32_t textId)1776 void CodeGeneratorShared::emitTracelogTree(bool isStart, uint32_t textId) {
1777 if (!TraceLogTextIdEnabled(textId)) return;
1778
1779 Label done;
1780 AllocatableRegisterSet regs(RegisterSet::Volatile());
1781 Register logger = regs.takeAnyGeneral();
1782
1783 masm.Push(logger);
1784
1785 masm.loadTraceLogger(logger);
1786 masm.branchTestPtr(Assembler::Zero, logger, logger, &done);
1787
1788 Address enabledAddress(logger, TraceLoggerThread::offsetOfEnabled());
1789 masm.branch32(Assembler::Equal, enabledAddress, Imm32(0), &done);
1790
1791 if (isStart)
1792 masm.tracelogStartId(logger, textId);
1793 else
1794 masm.tracelogStopId(logger, textId);
1795
1796 masm.bind(&done);
1797
1798 masm.Pop(logger);
1799 }
1800
emitTracelogTree(bool isStart,const char * text,TraceLoggerTextId enabledTextId)1801 void CodeGeneratorShared::emitTracelogTree(bool isStart, const char* text,
1802 TraceLoggerTextId enabledTextId) {
1803 if (!TraceLogTextIdEnabled(enabledTextId)) return;
1804
1805 Label done;
1806
1807 AllocatableRegisterSet regs(RegisterSet::Volatile());
1808 Register loggerReg = regs.takeAnyGeneral();
1809 Register eventReg = regs.takeAnyGeneral();
1810
1811 masm.Push(loggerReg);
1812
1813 masm.loadTraceLogger(loggerReg);
1814 masm.branchTestPtr(Assembler::Zero, loggerReg, loggerReg, &done);
1815
1816 Address enabledAddress(loggerReg, TraceLoggerThread::offsetOfEnabled());
1817 masm.branch32(Assembler::Equal, enabledAddress, Imm32(0), &done);
1818
1819 masm.Push(eventReg);
1820
1821 PatchableTLEvent patchEvent(masm.movWithPatch(ImmWord(0), eventReg), text);
1822 masm.propagateOOM(patchableTLEvents_.append(Move(patchEvent)));
1823
1824 if (isStart)
1825 masm.tracelogStartId(loggerReg, eventReg);
1826 else
1827 masm.tracelogStopId(loggerReg, eventReg);
1828
1829 masm.Pop(eventReg);
1830
1831 masm.bind(&done);
1832
1833 masm.Pop(loggerReg);
1834 }
1835 #endif
1836
1837 } // namespace jit
1838 } // namespace js
1839