2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2016 Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/vm/jit/mc-generator.h"
18 #include "hphp/runtime/vm/jit/vtune-jit.h"
35 #include <unordered_set>
38 #include <folly/Format.h>
39 #include <folly/MapUtil.h>
40 #include <folly/Optional.h>
41 #include <folly/String.h>
42 #include <folly/portability/SysMman.h>
43 #include <folly/portability/Unistd.h>
45 #include "hphp/util/abi-cxx.h"
46 #include "hphp/util/asm-x64.h"
47 #include "hphp/util/bitops.h"
48 #include "hphp/util/cycles.h"
49 #include "hphp/util/debug.h"
50 #include "hphp/util/disasm.h"
51 #include "hphp/util/eh-frame.h"
52 #include "hphp/util/logger.h"
53 #include "hphp/util/maphuge.h"
54 #include "hphp/util/meta.h"
55 #include "hphp/util/process.h"
56 #include "hphp/util/rank.h"
57 #include "hphp/util/build-info.h"
58 #include "hphp/util/ringbuffer.h"
59 #include "hphp/util/safe-cast.h"
60 #include "hphp/util/service-data.h"
61 #include "hphp/util/struct-log.h"
62 #include "hphp/util/timer.h"
63 #include "hphp/util/trace.h"
65 #include "hphp/vixl/a64/constants-a64.h"
66 #include "hphp/vixl/a64/macro-assembler-a64.h"
68 #include "hphp/runtime/base/arch.h"
69 #include "hphp/runtime/base/execution-context.h"
70 #include "hphp/runtime/base/rds.h"
71 #include "hphp/runtime/base/runtime-option.h"
72 #include "hphp/runtime/base/stats.h"
73 #include "hphp/runtime/base/strings.h"
74 #include "hphp/runtime/base/zend-string.h"
75 #include "hphp/runtime/vm/blob-helper.h"
76 #include "hphp/runtime/vm/bytecode.h"
77 #include "hphp/runtime/vm/debug/debug.h"
78 #include "hphp/runtime/vm/func.h"
79 #include "hphp/runtime/vm/hhbc-codec.h"
80 #include "hphp/runtime/vm/member-operations.h"
81 #include "hphp/runtime/vm/php-debug.h"
82 #include "hphp/runtime/vm/repo.h"
83 #include "hphp/runtime/vm/runtime.h"
84 #include "hphp/runtime/vm/srckey.h"
85 #include "hphp/runtime/vm/treadmill.h"
86 #include "hphp/runtime/vm/type-profile.h"
87 #include "hphp/runtime/vm/unwind.h"
89 #include "hphp/runtime/vm/jit/align.h"
90 #include "hphp/runtime/vm/jit/cg-meta.h"
91 #include "hphp/runtime/vm/jit/check.h"
92 #include "hphp/runtime/vm/jit/code-cache.h"
93 #include "hphp/runtime/vm/jit/code-gen-helpers.h"
94 #include "hphp/runtime/vm/jit/debug-guards.h"
95 #include "hphp/runtime/vm/jit/func-guard.h"
96 #include "hphp/runtime/vm/jit/func-prologue.h"
97 #include "hphp/runtime/vm/jit/inlining-decider.h"
98 #include "hphp/runtime/vm/jit/irgen.h"
99 #include "hphp/runtime/vm/jit/irlower.h"
100 #include "hphp/runtime/vm/jit/location.h"
101 #include "hphp/runtime/vm/jit/normalized-instruction.h"
102 #include "hphp/runtime/vm/jit/perf-counters.h"
103 #include "hphp/runtime/vm/jit/print.h"
104 #include "hphp/runtime/vm/jit/prof-data.h"
105 #include "hphp/runtime/vm/jit/punt.h"
106 #include "hphp/runtime/vm/jit/recycle-tc.h"
107 #include "hphp/runtime/vm/jit/region-selection.h"
108 #include "hphp/runtime/vm/jit/relocation.h"
109 #include "hphp/runtime/vm/jit/service-requests.h"
110 #include "hphp/runtime/vm/jit/smashable-instr.h"
111 #include "hphp/runtime/vm/jit/srcdb.h"
112 #include "hphp/runtime/vm/jit/tc-info.h"
113 #include "hphp/runtime/vm/jit/timer.h"
114 #include "hphp/runtime/vm/jit/translate-region.h"
115 #include "hphp/runtime/vm/jit/translator-inline.h"
116 #include "hphp/runtime/vm/jit/unwind-itanium.h"
117 #include "hphp/runtime/vm/jit/vasm-emit.h"
118 #include "hphp/runtime/vm/jit/vasm-gen.h"
119 #include "hphp/runtime/vm/jit/vasm-instr.h"
121 #include "hphp/runtime/ext/generator/ext_generator.h"
122 #include "hphp/runtime/ext/std/ext_std_function.h"
123 #include "hphp/runtime/server/http-server.h"
124 #include "hphp/runtime/server/source-root-info.h"
126 namespace HPHP
{ namespace jit
{
128 ///////////////////////////////////////////////////////////////////////////////
132 using namespace Trace
;
134 // The global MCGenerator object.
137 static __thread
size_t s_initialTCSize
;
138 static ServiceData::ExportedCounter
* s_jitMaturityCounter
;
139 static std::atomic
<bool> s_loggedJitMature
{false};
141 ///////////////////////////////////////////////////////////////////////////////
145 * Convenience class for creating TransLocs and TransRecs for new translations.
147 * Records the beginning and end of a translation and stores the size of the
148 * cold and frozen regions in the first 4 bytes of their respective regions.
150 struct TransLocMaker
{
151 explicit TransLocMaker(CodeCache::View c
) : cache(c
) {}
154 * Record the start of a translation, and reserve space at the top of cold
155 * and frozen (if they aren't the same) to record sizes.
158 loc
.setMainStart(cache
.main().frontier());
159 loc
.setColdStart(cache
.cold().frontier());
160 loc
.setFrozenStart(cache
.frozen().frontier());
161 dataStart
= cache
.data().frontier();
163 cache
.cold().dword(0);
164 if (&cache
.cold() != &cache
.frozen()) cache
.frozen().dword(0);
168 * If loc contains a valid location, reset the frontiers of all code and data
169 * blocks to the positions recorded by the last call to markStart().
172 if (loc
.empty()) return;
174 cache
.main().setFrontier(loc
.mainStart());
175 cache
.cold().setFrontier(loc
.coldStart());
176 cache
.frozen().setFrontier(loc
.frozenStart());
177 cache
.data().setFrontier(dataStart
);
181 * Record the end of a translation, storing the size of cold and frozen,
182 * returns a TransLoc representing the translation.
185 uint32_t* coldSize
= (uint32_t*)loc
.coldStart();
186 uint32_t* frozenSize
= (uint32_t*)loc
.frozenStart();
187 *coldSize
= cache
.cold().frontier() - loc
.coldStart();
188 *frozenSize
= cache
.frozen().frontier() - loc
.frozenStart();
189 loc
.setMainSize(cache
.main().frontier() - loc
.mainStart());
195 * Create a TransRec for the translation, markEnd() should be called prior to
202 RegionDescPtr region
= RegionDescPtr(),
203 std::vector
<TransBCMapping
> bcmap
= std::vector
<TransBCMapping
>(),
204 Annotations
&& annot
= Annotations(),
205 bool hasLoop
= false) const {
206 return TransRec(sk
, transID
, kind
,
207 loc
.mainStart(), loc
.mainSize(),
208 loc
.coldCodeStart(), loc
.coldCodeSize(),
209 loc
.frozenCodeStart(), loc
.frozenCodeSize(),
210 std::move(region
), std::move(bcmap
),
211 std::move(annot
), hasLoop
);
215 CodeCache::View cache
;
221 ///////////////////////////////////////////////////////////////////////////////
223 bool shouldPGOFunc(const Func
& func
) {
224 if (profData() == nullptr) return false;
226 // JITing pseudo-mains requires extra checks that blow the IR. PGO
227 // can significantly increase the size of the regions, so disable it for
228 // pseudo-mains (so regions will be just tracelets).
229 if (func
.isPseudoMain()) return false;
231 if (!RuntimeOption::EvalJitPGOHotOnly
) return true;
232 return func
.attrs() & AttrHot
;
235 bool dumpTCAnnotation(const Func
& func
, TransKind transKind
) {
236 return RuntimeOption::EvalDumpTCAnnotationsForAllTrans
||
237 (transKind
== TransKind::Optimize
&& (func
.attrs() & AttrHot
));
240 ///////////////////////////////////////////////////////////////////////////////
242 bool MCGenerator::profileSrcKey(SrcKey sk
) const {
243 if (!shouldPGOFunc(*sk
.func())) return false;
244 if (profData()->optimized(sk
.funcID())) return false;
245 if (profData()->profiling(sk
.funcID())) return true;
247 // Don't start profiling new functions if the size of either main or
248 // prof is already above Eval.JitAMaxUsage and we already filled hot.
249 auto tcUsage
= std::max(m_code
.main().used(), m_code
.prof().used());
250 if (tcUsage
>= CodeCache::AMaxUsage
&& !m_code
.hotEnabled()) {
254 return requestCount() <= RuntimeOption::EvalJitProfileRequests
;
258 * Invalidate the SrcDB entries for func's SrcKeys that have any
259 * Profile translation.
261 void MCGenerator::invalidateFuncProfSrcKeys(const Func
* func
) {
263 FuncId funcId
= func
->getFuncId();
264 for (auto tid
: profData()->funcProfTransIDs(funcId
)) {
265 invalidateSrcKey(profData()->transRec(tid
)->srcKey());
269 TransResult
MCGenerator::retranslate(const TransArgs
& args
) {
270 auto sr
= m_srcDB
.find(args
.sk
);
272 bool locked
= sr
->tryLock();
274 if (locked
) sr
->freeLock();
276 if (isDebuggerAttachedProcess() && m_tx
.isSrcKeyInBL(args
.sk
)) {
277 // We are about to translate something known to be blacklisted by
278 // debugger, exit early
279 SKTRACE(1, args
.sk
, "retranslate abort due to debugger\n");
283 LeaseHolder
writer(Translator::WriteLease(), args
.sk
.func());
284 if (!writer
.canTranslate() ||
285 !shouldTranslate(args
.sk
.func(), args
.kind
)) {
289 // Even though we knew above that we were going to skip doing another
290 // translation, we wait until we get the write lease, to avoid spinning
291 // through the tracelet guards again and again while another thread is
293 return sr
->getTopTranslation();
295 if (sr
->translations().size() > RuntimeOption::EvalJitMaxTranslations
) {
296 always_assert(sr
->translations().size() ==
297 RuntimeOption::EvalJitMaxTranslations
+ 1);
298 return sr
->getTopTranslation();
300 SKTRACE(1, args
.sk
, "retranslate\n");
303 newArgs
.kind
= profileSrcKey(args
.sk
) ? TransKind::Profile
: TransKind::Live
;
305 // We only allow concurrent jitting of non-Profile translations if
306 // EvalJitConcurrently is >= 2.
307 if (RuntimeOption::EvalJitConcurrently
< 2 &&
308 newArgs
.kind
!= TransKind::Profile
&&
313 auto result
= translate(newArgs
);
319 TCA
MCGenerator::retranslateOpt(TransID transId
, bool align
) {
320 if (profData() == nullptr) return nullptr;
321 if (isDebuggerAttachedProcess()) return nullptr;
323 // Until the rest of this function is cleaned up enough to support concurrent
324 // retranslation of different functions, we grab both the global write lease
325 // and a Func-specific lease for the current Func.
326 LeaseHolder
writer(Translator::WriteLease());
327 if (!writer
.canWrite()) return nullptr;
329 auto rec
= profData()->transRec(transId
);
331 always_assert(rec
->region() != nullptr);
332 LeaseHolder
funcWriter(Translator::WriteLease(), rec
->func());
333 if (!funcWriter
.canTranslate()) return nullptr;
335 TRACE(1, "retranslateOpt: transId = %u\n", transId
);
337 auto func
= rec
->func();
338 auto funcId
= func
->getFuncId();
339 auto sk
= rec
->srcKey();
341 if (profData()->optimized(funcId
)) return nullptr;
342 profData()->setOptimized(funcId
);
344 func
->setFuncBody(ustubs().funcBodyHelperThunk
);
346 // Invalidate SrcDB's entries for all func's SrcKeys.
347 invalidateFuncProfSrcKeys(func
);
349 // Regenerate the prologues and DV funclets before the actual function body.
350 bool includedBody
{false};
351 TCA start
= regeneratePrologues(func
, sk
, includedBody
);
353 // Regionize func and translate all its regions.
354 std::string transCFGAnnot
;
355 auto const regions
= includedBody
? std::vector
<RegionDescPtr
>{}
356 : regionizeFunc(func
, this, transCFGAnnot
);
358 for (auto region
: regions
) {
359 always_assert(!region
->empty());
360 auto regionSk
= region
->start();
361 auto transArgs
= TransArgs
{regionSk
};
362 if (transCFGAnnot
.size() > 0) {
363 transArgs
.annotations
.emplace_back("TransCFG", transCFGAnnot
);
365 transArgs
.region
= region
;
366 transArgs
.kind
= TransKind::Optimize
;
368 auto const regionStart
= translate(transArgs
).tca();
369 if (regionStart
!= nullptr &&
370 regionSk
.offset() == func
->base() &&
371 func
->getDVFunclets().size() == 0 &&
372 func
->getFuncBody() == ustubs().funcBodyHelperThunk
) {
373 func
->setFuncBody(regionStart
);
375 if (start
== nullptr && regionSk
== sk
) {
378 transCFGAnnot
= ""; // so we don't annotate it again
385 void MCGenerator::checkFreeProfData() {
386 // In PGO mode, we free all the profiling data once the main area code reaches
387 // its maximum usage and either the hot area is also full or all the functions
388 // that were profiled have already been optimized.
390 !RuntimeOption::EvalEnableReusableTC
&&
391 m_code
.main().used() >= CodeCache::AMaxUsage
&&
392 (!m_code
.hotEnabled() || profData()->optimizedAllProfiledFuncs())) {
397 static bool liveFrameIsPseudoMain() {
398 auto const ar
= vmfp();
399 if (!(ar
->func()->attrs() & AttrMayUseVV
)) return false;
400 return ar
->hasVarEnv() && ar
->getVarEnv()->isGlobalScope();
404 * Return an existing translation for `args', or nullptr if one can't be found.
406 TCA
MCGenerator::findTranslation(const TransArgs
& args
) const {
408 sk
.func()->validate();
410 if (liveFrameIsPseudoMain() && !RuntimeOption::EvalJitPseudomain
) {
411 SKTRACE(2, sk
, "punting on pseudoMain\n");
415 if (auto const sr
= m_srcDB
.find(sk
)) {
416 if (auto const tca
= sr
->getTopTranslation()) {
417 SKTRACE(2, sk
, "getTranslation: found %p\n", tca
);
426 * Find or create a translation for `args'. Returns TCA of "best" current
427 * translation. May return nullptr if it is currently impossible to create a
430 TransResult
MCGenerator::getTranslation(const TransArgs
& args
) {
431 if (auto const tca
= findTranslation(args
)) return tca
;
433 return createTranslation(args
);
436 int MCGenerator::numTranslations(SrcKey sk
) const {
437 if (const SrcRec
* sr
= m_srcDB
.find(sk
)) {
438 return sr
->translations().size();
444 s_php_errormsg("php_errormsg"),
445 s_http_response_header("http_response_header");
447 bool MCGenerator::shouldTranslateNoSizeLimit(const Func
* func
) const {
448 // If we've hit Eval.JitGlobalTranslationLimit, then we stop translating.
449 if (m_numTrans
>= RuntimeOption::EvalJitGlobalTranslationLimit
) {
453 // Do not translate functions from units marked as interpret-only.
454 if (func
->unit()->isInterpretOnly()) {
459 * We don't support JIT compiling functions that use some super-dynamic php
462 if (func
->lookupVarId(s_php_errormsg
.get()) != -1 ||
463 func
->lookupVarId(s_http_response_header
.get()) != -1) {
470 bool MCGenerator::shouldTranslate(const Func
* func
, TransKind kind
) const {
471 if (!shouldTranslateNoSizeLimit(func
)) return false;
473 // Otherwise, follow the Eval.JitAMaxUsage limit. However, we do allow PGO
474 // translations past that limit if there's still space in code.hot.
475 if (m_code
.main().used() < CodeCache::AMaxUsage
) return true;
478 case TransKind::ProfPrologue
:
479 case TransKind::Profile
:
480 case TransKind::OptPrologue
:
481 case TransKind::Optimize
:
482 return m_code
.hotEnabled();
490 static void populateLiveContext(RegionContext
& ctx
) {
491 auto const fp
= vmfp();
492 auto const sp
= vmsp();
494 always_assert(ctx
.func
== fp
->m_func
);
496 // Track local types.
497 for (uint32_t i
= 0; i
< fp
->m_func
->numLocals(); ++i
) {
498 ctx
.liveTypes
.push_back(
499 { Location::Local
{i
}, typeFromTV(frame_local(fp
, i
)) }
501 FTRACE(2, "added live type {}\n", show(ctx
.liveTypes
.back()));
504 // Track stack types and pre-live ActRecs.
505 int32_t stackOff
= 0;
507 fp
, sp
, ctx
.bcOffset
,
508 [&] (const ActRec
* ar
, Offset
) {
509 auto const objOrCls
=
510 ar
->hasThis() ? Type::SubObj(ar
->getThis()->getVMClass()) :
511 ar
->hasClass() ? Type::SubCls(ar
->getClass())
514 ctx
.preLiveARs
.push_back({ stackOff
, ar
->m_func
, objOrCls
});
515 FTRACE(2, "added prelive ActRec {}\n", show(ctx
.preLiveARs
.back()));
516 stackOff
+= kNumActRecCells
;
518 [&] (const TypedValue
* tv
) {
519 ctx
.liveTypes
.push_back(
520 { Location::Stack
{ctx
.spOffset
- stackOff
}, typeFromTV(tv
) }
523 FTRACE(2, "added live type {}\n", show(ctx
.liveTypes
.back()));
528 TransResult
MCGenerator::createTranslation(const TransArgs
& args
) {
529 if (!shouldTranslate(args
.sk
.func(), args
.kind
)) return nullptr;
532 * Try to become the writer. We delay this until we *know* we will have a
533 * need to create new translations, instead of just trying to win the lottery
534 * at the dawn of time. Hopefully lots of requests won't require any new
538 LeaseHolder
writer(Translator::WriteLease(), sk
.func());
539 if (!writer
.canTranslate() ||
540 !shouldTranslate(args
.sk
.func(), args
.kind
)) {
544 if (RuntimeOption::EvalFailJitPrologs
&& sk
.op() == Op::FCallAwait
) {
548 if (!createSrcRec(sk
)) return nullptr;
550 auto sr
= m_srcDB
.find(sk
);
553 if (auto const tca
= sr
->getTopTranslation()) {
554 // Handle extremely unlikely race; someone may have just added the first
555 // translation for this SrcRec while we did a non-blocking wait on the
556 // write lease in createSrcRec().
560 return retranslate(args
);
563 bool MCGenerator::createSrcRec(SrcKey sk
) {
564 if (m_srcDB
.find(sk
)) return true;
566 auto const srcRecSPOff
= sk
.resumed() ? folly::none
567 : folly::make_optional(liveSpOff());
569 // We put retranslate requests at the end of our slab to more frequently
570 // allow conditional jump fall-throughs
571 auto code
= m_code
.view();
572 TCA astart
= code
.main().frontier();
573 TCA coldStart
= code
.cold().frontier();
574 TCA frozenStart
= code
.frozen().frontier();
576 if (!RuntimeOption::EvalEnableReusableTC
) {
577 req
= svcreq::emit_persistent(code
.cold(),
582 TransFlags().packed
);
584 auto const stubsize
= svcreq::stub_size();
585 auto newStart
= code
.cold().allocInner(stubsize
);
587 newStart
= code
.cold().frontier();
589 // Ensure that the anchor translation is a known size so that it can be
590 // reclaimed when the function is freed
591 req
= svcreq::emit_ephemeral(code
.cold(),
597 TransFlags().packed
);
599 SKTRACE(1, sk
, "inserting anchor translation for (%p,%d) at %p\n",
600 sk
.unit(), sk
.offset(), req
);
602 always_assert(m_srcDB
.find(sk
) == nullptr);
603 auto const sr
= m_srcDB
.insert(sk
);
604 if (RuntimeOption::EvalEnableReusableTC
) {
605 recordFuncSrcRec(sk
.func(), sr
);
607 sr
->setFuncInfo(sk
.func());
608 sr
->setAnchorTranslation(req
);
610 if (srcRecSPOff
) always_assert(sr
->nonResumedSPOff() == *srcRecSPOff
);
612 size_t asize
= code
.main().frontier() - astart
;
613 size_t coldSize
= code
.cold().frontier() - coldStart
;
614 size_t frozenSize
= code
.frozen().frontier() - frozenStart
;
616 if (coldSize
&& RuntimeOption::EvalDumpTCAnchors
) {
618 profData() && Translator::isTransDBEnabled() ? profData()->allocTransID()
620 TransRec
tr(sk
, transID
, TransKind::Anchor
,
621 astart
, asize
, coldStart
, coldSize
,
622 frozenStart
, frozenSize
);
623 m_tx
.addTranslation(tr
);
624 if (RuntimeOption::EvalJitUseVtuneAPI
) {
625 reportTraceletToVtune(sk
.unit(), sk
.func(), tr
);
628 assertx(!m_tx
.isTransDBEnabled() ||
629 m_tx
.getTransRec(coldStart
)->kind
== TransKind::Anchor
);
636 MCGenerator::lookupTranslation(SrcKey sk
) const {
637 if (auto const sr
= m_srcDB
.find(sk
)) {
638 return sr
->getTopTranslation();
645 * Returns true iff we already have Eval.JitMaxTranslations translations
646 * recorded in srcRec.
648 bool reachedTranslationLimit(SrcKey sk
, const SrcRec
& srcRec
) {
649 if (srcRec
.translations().size() != RuntimeOption::EvalJitMaxTranslations
) {
654 if (debug
&& Trace::moduleEnabled(Trace::mcg
, 2)) {
655 const auto& tns
= srcRec
.translations();
656 TRACE(1, "Too many (%zd) translations: %s, BC offset %d\n",
657 tns
.size(), sk
.unit()->filepath()->data(),
659 SKTRACE(2, sk
, "{\n");
660 TCA topTrans
= srcRec
.getTopTranslation();
661 for (size_t i
= 0; i
< tns
.size(); ++i
) {
662 auto const rec
= mcg
->tx().getTransRec(tns
[i
].mainStart());
664 SKTRACE(2, sk
, "%zd %p\n", i
, tns
[i
].mainStart());
665 if (tns
[i
].mainStart() == topTrans
) {
666 SKTRACE(2, sk
, "%zd: *Top*\n", i
);
668 if (rec
->kind
== TransKind::Anchor
) {
669 SKTRACE(2, sk
, "%zd: Anchor\n", i
);
671 SKTRACE(2, sk
, "%zd: guards {\n", i
);
672 for (unsigned j
= 0; j
< rec
->guards
.size(); ++j
) {
673 FTRACE(2, "{}\n", rec
->guards
[j
]);
675 SKTRACE(2, sk
, "%zd } guards\n", i
);
678 SKTRACE(2, sk
, "} /* Too many translations */\n");
684 * Analyze the given TransArgs and return the region to translate, or nullptr
685 * if one could not be selected.
687 RegionDescPtr
prepareRegion(const TransArgs
& args
) {
688 if (args
.kind
== TransKind::Optimize
) {
689 assertx(RuntimeOption::EvalJitPGO
);
690 if (args
.region
) return args
.region
;
692 assertx(isValidTransID(args
.transId
));
693 return selectHotRegion(args
.transId
, mcg
);
696 // Attempt to create a region at this SrcKey
697 assertx(args
.kind
== TransKind::Profile
|| args
.kind
== TransKind::Live
);
698 auto const sk
= args
.sk
;
699 RegionContext rContext
{ sk
.func(), sk
.offset(), liveSpOff(),
701 FTRACE(2, "populating live context for region\n");
702 populateLiveContext(rContext
);
703 return selectRegion(rContext
, args
.kind
);
707 TransResult
MCGenerator::translate(TransArgs args
) {
710 assert(args
.kind
!= TransKind::Invalid
);
711 assertx(((uintptr_t)vmsp() & (sizeof(Cell
) - 1)) == 0);
712 assertx(((uintptr_t)vmfp() & (sizeof(Cell
) - 1)) == 0);
714 if (!shouldTranslate(args
.sk
.func(), args
.kind
)) return nullptr;
716 Timer
timer(Timer::mcg_translate
);
718 auto const srcRec
= m_srcDB
.find(args
.sk
);
719 always_assert(srcRec
);
721 args
.region
= reachedTranslationLimit(args
.sk
, *srcRec
) ? nullptr
722 : prepareRegion(args
);
724 env
.initSpOffset
= args
.region
? args
.region
->entry()->initialSpOffset()
726 env
.annotations
.insert(env
.annotations
.end(),
727 args
.annotations
.begin(), args
.annotations
.end());
729 // Lower the RegionDesc to an IRUnit, then lower that to a Vunit.
731 if (args
.kind
== TransKind::Profile
||
732 (profData() && Translator::isTransDBEnabled())) {
733 env
.transID
= profData()->allocTransID();
735 auto const transContext
=
736 TransContext
{env
.transID
, args
.kind
, args
.flags
, args
.sk
,
739 env
.unit
= irGenRegion(*args
.region
, transContext
,
740 env
.pconds
, env
.annotations
);
742 env
.vunit
= irlower::lowerUnit(*env
.unit
);
746 // If our caller requested partial translation and we don't already have the
747 // write lease, we're done here. Otherwise, finish the translation now.
748 if (args
.allowPartial
&& !Translator::WriteLease().amOwner()) {
749 return std::move(env
);
753 return finishTranslation(std::move(env
));
756 TCA
MCGenerator::getFuncBody(Func
* func
) {
757 TCA tca
= func
->getFuncBody();
758 if (tca
!= ustubs().funcBodyHelperThunk
) return tca
;
760 DVFuncletsVec dvs
= func
->getDVFunclets();
763 LeaseHolder
writer(Translator::WriteLease());
764 if (!writer
.canWrite()) return nullptr;
765 tca
= func
->getFuncBody();
766 if (tca
!= ustubs().funcBodyHelperThunk
) return tca
;
767 tca
= genFuncBodyDispatch(func
, dvs
, m_code
.view());
768 func
->setFuncBody(tca
);
770 SrcKey
sk(func
, func
->base(), false);
771 tca
= mcg
->getTranslation(TransArgs
{sk
}).tca();
772 if (tca
) func
->setFuncBody(tca
);
781 * Given a callee and a number of args, match up to the callee's
782 * argument expectations and dispatch.
784 * Call/return hand-shaking is a bit funny initially. At translation time,
785 * we don't necessarily know what function we're calling. For instance,
789 * Will lead to a set of basic blocks like:
796 * The fcall labelled "b2" above may not be statically bindable in our
799 * We decouple the call work into a per-callsite portion, responsible
800 * for recording the return address, and a per-(callee, numArgs) portion,
801 * responsible for fixing up arguments and dispatching to remaining
802 * code. We call the per-callee portion a "prologue."
804 * Also, we are called from two distinct environments. From REQ_BIND_CALL,
805 * we're running "between" basic blocks, with all VM registers sync'ed.
806 * However, we're also called in the middle of basic blocks, when dropping
807 * entries into func->m_prologues. So don't go around using the
808 * translation-time values of vmfp()/vmsp(), since they have an
809 * unpredictable relationship to the source.
812 MCGenerator::checkCachedPrologue(const Func
* func
, int paramIdx
,
813 TCA
& prologue
) const {
814 prologue
= (TCA
)func
->getPrologue(paramIdx
);
815 if (prologue
!= ustubs().fcallHelperThunk
) {
816 TRACE(1, "cached prologue %s(%d) -> cached %p\n",
817 func
->fullName()->data(), paramIdx
, prologue
);
818 assertx(m_code
.isValidCodeAddress(prologue
));
824 TCA
MCGenerator::emitFuncPrologue(Func
* func
, int argc
,
825 bool forRegeneratePrologue
) {
826 const int nparams
= func
->numNonVariadicParams();
827 const int paramIndex
= argc
<= nparams
? argc
: nparams
+ 1;
829 auto const funcBody
= SrcKey
{func
, func
->getEntryForNumArgs(argc
), false};
830 auto const kind
= profileSrcKey(funcBody
) ? TransKind::ProfPrologue
:
831 forRegeneratePrologue
? TransKind::OptPrologue
:
832 TransKind::LivePrologue
;
834 profileSetHotFuncAttr();
835 auto code
= m_code
.view(kind
);
836 TCA mainOrig
= code
.main().frontier();
839 // If we're close to a cache line boundary, just burn some space to
840 // try to keep the func and its body on fewer total lines.
841 align(code
.main(), &fixups
, Alignment::CacheLineRoundUp
, AlignContext::Dead
);
843 TransLocMaker
maker(code
);
846 // Careful: this isn't necessarily the real entry point. For funcIsMagic
847 // prologues, this is just a possible prologue.
848 TCA aStart
= code
.main().frontier();
850 // Give the prologue a TransID if we have profiling data.
851 auto const transID
= [&]{
852 if (kind
== TransKind::ProfPrologue
) {
853 auto const profData
= jit::profData();
854 auto const id
= profData
->allocTransID();
855 profData
->addTransProfPrologue(id
, funcBody
, paramIndex
);
858 if (profData() && Translator::isTransDBEnabled()) {
859 return profData()->allocTransID();
861 return kInvalidTransID
;
864 TCA start
= genFuncPrologue(transID
, kind
, func
, argc
, code
, fixups
);
866 auto loc
= maker
.markEnd();
867 if (RuntimeOption::EvalEnableReusableTC
) {
868 TCA UNUSED ms
= loc
.mainStart(), me
= loc
.mainEnd(),
869 cs
= loc
.coldStart(), ce
= loc
.coldEnd(),
870 fs
= loc
.frozenStart(), fe
= loc
.frozenEnd(),
872 bool did_relocate
= relocateNewTranslation(loc
, code
, fixups
, &start
);
875 FTRACE_MOD(reusetc
, 1,
876 "Relocated prologue for func {} (id = {}) "
877 "from M[{}, {}], C[{}, {}], F[{}, {}] to M[{}, {}] "
878 "C[{}, {}] F[{}, {}] orig start @ {} new start @ {}\n",
879 func
->fullName()->data(), func
->getFuncId(),
880 ms
, me
, cs
, ce
, fs
, fe
, loc
.mainStart(), loc
.mainEnd(),
881 loc
.coldStart(), loc
.coldEnd(), loc
.frozenStart(),
882 loc
.frozenEnd(), oldStart
, start
);
884 FTRACE_MOD(reusetc
, 1,
885 "Created prologue for func {} (id = {}) at "
886 "M[{}, {}], C[{}, {}], F[{}, {}] start @ {}\n",
887 func
->fullName()->data(), func
->getFuncId(),
888 ms
, me
, cs
, ce
, fs
, fe
, oldStart
);
891 recordFuncPrologue(func
, loc
);
892 if (loc
.mainStart() != aStart
) {
893 code
.main().setFrontier(mainOrig
); // we may have shifted to align
896 if (RuntimeOption::EvalPerfRelocate
) {
897 GrowableVector
<IncomingBranch
> incomingBranches
;
898 recordPerfRelocMap(loc
.mainStart(), loc
.mainEnd(),
899 loc
.coldCodeStart(), loc
.coldEnd(),
900 funcBody
, paramIndex
,
904 fixups
.process(nullptr);
906 assertx(funcGuardMatches(funcGuardFromPrologue(start
, func
), func
));
907 assertx(m_code
.isValidCodeAddress(start
));
909 TRACE(2, "funcPrologue mcg %p %s(%d) setting prologue %p\n",
910 this, func
->fullName()->data(), argc
, start
);
911 func
->setPrologue(paramIndex
, start
);
913 assertx(kind
== TransKind::LivePrologue
||
914 kind
== TransKind::ProfPrologue
||
915 kind
== TransKind::OptPrologue
);
917 auto tr
= maker
.rec(funcBody
, transID
, kind
);
918 m_tx
.addTranslation(tr
);
919 if (RuntimeOption::EvalJitUseVtuneAPI
) {
920 reportTraceletToVtune(func
->unit(), func
, tr
);
924 recordGdbTranslation(funcBody
, func
, code
.main(), aStart
, false, true);
925 recordBCInstr(OpFuncPrologue
, loc
.mainStart(), loc
.mainEnd(), false);
928 assertx(m_numTrans
<= RuntimeOption::EvalJitGlobalTranslationLimit
);
933 TCA
MCGenerator::getFuncPrologue(Func
* func
, int nPassed
, ActRec
* ar
,
934 bool forRegeneratePrologue
) {
936 TRACE(1, "funcPrologue %s(%d)\n", func
->fullName()->data(), nPassed
);
937 int const numParams
= func
->numNonVariadicParams();
938 int paramIndex
= nPassed
<= numParams
? nPassed
: numParams
+ 1;
940 // Do a quick test before grabbing the write lease
942 if (checkCachedPrologue(func
, paramIndex
, prologue
)) return prologue
;
944 LeaseHolder
writer(Translator::WriteLease());
945 if (!writer
.canWrite()) return nullptr;
947 // If we're regenerating a prologue, and we want to check shouldTranslate()
948 // but ignore the code size limits. We still want to respect the global
949 // translation limit and other restrictions, though.
950 if (forRegeneratePrologue
) {
951 if (!shouldTranslateNoSizeLimit(func
)) return nullptr;
953 if (!shouldTranslate(func
, TransKind::LivePrologue
)) return nullptr;
956 // Double check the prologue array now that we have the write lease
957 // in case another thread snuck in and set the prologue already.
958 if (checkCachedPrologue(func
, paramIndex
, prologue
)) return prologue
;
961 return emitFuncPrologue(func
, nPassed
, forRegeneratePrologue
);
962 } catch (const DataBlockFull
& dbFull
) {
964 // Fail hard if the block isn't code.hot.
965 always_assert_flog(dbFull
.name
== "hot",
966 "data block = {}\nmessage: {}\n",
967 dbFull
.name
, dbFull
.what());
969 // Otherwise, fall back to code.main and retry.
972 return emitFuncPrologue(func
, nPassed
, forRegeneratePrologue
);
973 } catch (const DataBlockFull
& dbFull
) {
974 always_assert_flog(0, "data block = {}\nmessage: {}\n",
975 dbFull
.name
, dbFull
.what());
981 * Given the proflogueTransId for a TransProflogue translation, regenerate the
982 * prologue (as a TransPrologue). Returns the starting address for the
983 * translation corresponding to triggerSk, if such translation is generated;
984 * otherwise returns nullptr.
986 TCA
MCGenerator::regeneratePrologue(TransID prologueTransId
, SrcKey triggerSk
,
987 bool& emittedDVInit
) {
988 assertx(Translator::WriteLease().amOwner());
989 auto rec
= profData()->transRec(prologueTransId
);
990 auto func
= rec
->func();
991 auto nArgs
= rec
->prologueArgs();
992 emittedDVInit
= false;
994 // Regenerate the prologue.
995 func
->resetPrologue(nArgs
);
996 auto const start
= getFuncPrologue(
999 nullptr /* ActRec */,
1000 true /* regeneratePrologue */
1002 if (!start
) return nullptr;
1004 func
->setPrologue(nArgs
, start
);
1006 // Smash callers of the old prologue with the address of the new one.
1007 for (auto toSmash
: rec
->mainCallers()) {
1008 smashCall(toSmash
, start
);
1011 // If the prologue has a matching guard, then smash its guard-callers as
1013 auto const guard
= funcGuardFromPrologue(start
, func
);
1014 if (funcGuardMatches(guard
, func
)) {
1015 for (auto toSmash
: rec
->guardCallers()) {
1016 smashCall(toSmash
, guard
);
1019 rec
->clearAllCallers();
1021 // If this prologue has a DV funclet, then generate a translation for the DV
1022 // funclet right after the prologue.
1023 TCA triggerSkStart
= nullptr;
1024 if (nArgs
< func
->numNonVariadicParams()) {
1025 auto paramInfo
= func
->params()[nArgs
];
1026 if (paramInfo
.hasDefaultValue()) {
1027 SrcKey
funcletSK(func
, paramInfo
.funcletOff
, false);
1028 auto funcletTransId
= profData()->dvFuncletTransId(func
, nArgs
);
1029 if (funcletTransId
!= kInvalidTransID
) {
1030 invalidateSrcKey(funcletSK
);
1031 auto args
= TransArgs
{funcletSK
};
1032 args
.transId
= funcletTransId
;
1033 args
.kind
= TransKind::Optimize
;
1034 auto dvStart
= translate(args
).tca();
1035 emittedDVInit
|= dvStart
!= nullptr;
1036 if (dvStart
&& !triggerSkStart
&& funcletSK
== triggerSk
) {
1037 triggerSkStart
= dvStart
;
1039 // Flag that this translation has been retranslated, so that
1040 // it's not retranslated again along with the function body.
1041 profData()->setOptimized(funcletSK
);
1046 return triggerSkStart
;
1050 * Regenerate all prologues of func that were previously generated.
1051 * The prologues are sorted in ascending order of profile counters.
1052 * For prologues with corresponding DV funclets, their corresponding
1053 * DV funclet will be regenerated right after them. The idea is to
1054 * generate the function body right after calling this function, so
1055 * that all prologues are placed right before it, and with the hottest
1056 * prologues closer to it.
1058 * Returns the starting address for the translation corresponding to
1059 * triggerSk, if such translation is generated; otherwise returns
1062 TCA
MCGenerator::regeneratePrologues(Func
* func
, SrcKey triggerSk
,
1063 bool& includedBody
) {
1064 TCA triggerStart
= nullptr;
1065 std::vector
<TransID
> prologTransIDs
;
1067 for (int nArgs
= 0; nArgs
< func
->numPrologues(); nArgs
++) {
1068 TransID tid
= profData()->proflogueTransId(func
, nArgs
);
1069 if (tid
!= kInvalidTransID
) {
1070 prologTransIDs
.push_back(tid
);
1074 std::sort(prologTransIDs
.begin(), prologTransIDs
.end(),
1075 [&](TransID t1
, TransID t2
) -> bool {
1076 // This will sort in ascending order.
1077 return profData()->transCounter(t2
) >
1078 profData()->transCounter(t1
);
1081 // Next, we're going to regenerate each prologue along with its DV funclet.
1082 // We consider the option of either including the DV funclets in the same
1083 // region as the function body or not. Including them in the same region
1084 // enables some type information to flow and thus can eliminate some stores
1085 // and type checks, but it can also increase the code size by duplicating the
1086 // whole function body. Therefore, we only include the function body along
1087 // with the DV init if both (a) the function has a single proflogue, and (b)
1088 // the size of the function is within a certain threshold.
1090 // The mechanism used to keep the function body separate from the DV init is
1091 // to temporarily mark the SrcKey for the function body as already optimized.
1092 // (The region selectors break a region whenever they hit a SrcKey that has
1093 // already been optimized.)
1094 SrcKey
funcBodySk(func
, func
->base(), false);
1095 includedBody
= prologTransIDs
.size() <= 1 &&
1096 func
->past() - func
->base() <= RuntimeOption::EvalJitPGOMaxFuncSizeDupBody
;
1098 if (!includedBody
) profData()->setOptimized(funcBodySk
);
1099 SCOPE_EXIT
{ profData()->clearOptimized(funcBodySk
); };
1101 bool emittedAnyDVInit
= false;
1102 for (TransID tid
: prologTransIDs
) {
1103 bool emittedDVInit
= false;
1104 TCA start
= regeneratePrologue(tid
, triggerSk
, emittedDVInit
);
1105 if (triggerStart
== nullptr && start
!= nullptr) {
1106 triggerStart
= start
;
1108 emittedAnyDVInit
|= emittedDVInit
;
1111 // If we tried to include the function body along with a DV init, but didn't
1112 // end up generating any DV init, then flag that the function body was not
1114 if (!emittedAnyDVInit
) includedBody
= false;
1116 return triggerStart
;
1122 * Runtime service handler that patches a jmp to the translation of
1123 * u:dest from toSmash.
1126 MCGenerator::bindJmp(TCA toSmash
, SrcKey destSk
, ServiceRequest req
,
1127 TransFlags trflags
, bool& smashed
) {
1128 auto args
= TransArgs
{destSk
};
1129 args
.flags
= trflags
;
1130 auto tDest
= getTranslation(args
).tca();
1131 if (!tDest
) return nullptr;
1133 LeaseHolder
writer(Translator::WriteLease());
1134 if (!writer
.canWrite()) return tDest
;
1136 auto const sr
= m_srcDB
.find(destSk
);
1138 // The top translation may have changed while we waited for the write lease,
1139 // so read it again. If it was replaced with a new translation, then bind to
1140 // the new one. If it was invalidated, then don't bind the jump.
1141 tDest
= sr
->getTopTranslation();
1142 if (tDest
== nullptr) return nullptr;
1144 if (req
== REQ_BIND_ADDR
) {
1145 auto addr
= reinterpret_cast<TCA
*>(toSmash
);
1146 if (*addr
== tDest
) {
1150 sr
->chainFrom(IncomingBranch::addr(addr
));
1155 auto const isJcc
= [&] {
1158 x64::DecodedInstruction
di(toSmash
);
1159 return (di
.isBranch() && !di
.isJmp());
1163 using namespace vixl
;
1164 struct JccDecoder
: public Decoder
{
1165 void VisitConditionalBranch(Instruction
* inst
) override
{
1171 decoder
.Decode(Instruction::Cast(toSmash
));
1176 always_assert(false);
1182 auto const target
= smashableJccTarget(toSmash
);
1185 // Return if already smashed.
1186 if (target
== tDest
) return tDest
;
1187 sr
->chainFrom(IncomingBranch::jccFrom(toSmash
));
1189 auto const target
= smashableJmpTarget(toSmash
);
1192 // Return if already smashed.
1193 if (!target
|| target
== tDest
) return tDest
;
1194 sr
->chainFrom(IncomingBranch::jmpFrom(toSmash
));
1203 struct FreeRequestStubTrigger
{
1204 explicit FreeRequestStubTrigger(TCA stub
) : m_stub(stub
) {
1205 TRACE(3, "FreeStubTrigger @ %p, stub %p\n", this, m_stub
);
1208 TRACE(3, "FreeStubTrigger: Firing @ %p , stub %p\n", this, m_stub
);
1209 if (mcg
->freeRequestStub(m_stub
) != true) {
1210 // If we can't free the stub, enqueue again to retry.
1211 TRACE(3, "FreeStubTrigger: write lease failed, requeueing %p\n", m_stub
);
1212 Treadmill::enqueue(FreeRequestStubTrigger(m_stub
));
1222 MCGenerator::enterTC(TCA start
, ActRec
* stashedAR
) {
1228 assertx(m_code
.isValidCodeAddress(start
));
1229 assertx(((uintptr_t)vmsp() & (sizeof(Cell
) - 1)) == 0);
1230 assertx(((uintptr_t)vmfp() & (sizeof(Cell
) - 1)) == 0);
1232 assertx(!Translator::WriteLease().amOwner());
1235 if (Trace::moduleEnabled(Trace::ringbuffer
, 1)) {
1236 auto skData
= SrcKey
{liveFunc(), vmpc(), liveResumed()}.toAtomicInt();
1237 Trace::ringbufferEntry(RBTypeEnterTC
, skData
, (uint64_t)start
);
1240 tl_regState
= VMRegState::DIRTY
;
1241 enterTCImpl(start
, stashedAR
);
1242 tl_regState
= VMRegState::CLEAN
;
1243 assertx(isValidVMStackAddress(vmsp()));
1248 TCA
MCGenerator::handleServiceRequest(svcreq::ReqInfo
& info
) noexcept
{
1249 FTRACE(1, "handleServiceRequest {}\n", svcreq::to_name(info
.req
));
1251 assert_native_stack_aligned();
1252 tl_regState
= VMRegState::CLEAN
; // partially a lie: vmpc() isn't synced
1254 if (Trace::moduleEnabled(Trace::ringbuffer
, 1)) {
1255 Trace::ringbufferEntry(
1256 RBTypeServiceReq
, (uint64_t)info
.req
, (uint64_t)info
.args
[0].tca
1260 TCA start
= nullptr;
1262 auto smashed
= false;
1266 case REQ_BIND_ADDR
: {
1267 auto const toSmash
= info
.args
[0].tca
;
1268 sk
= SrcKey::fromAtomicInt(info
.args
[1].sk
);
1269 auto const trflags
= info
.args
[2].trflags
;
1270 start
= bindJmp(toSmash
, sk
, info
.req
, trflags
, smashed
);
1274 case REQ_RETRANSLATE
: {
1275 INC_TPC(retranslate
);
1276 sk
= SrcKey
{liveFunc(), info
.args
[0].offset
, liveResumed()};
1277 auto trflags
= info
.args
[1].trflags
;
1278 auto args
= TransArgs
{sk
};
1279 args
.flags
= trflags
;
1280 start
= retranslate(args
).tca();
1281 SKTRACE(2, sk
, "retranslated @%p\n", start
);
1285 case REQ_RETRANSLATE_OPT
: {
1286 sk
= SrcKey::fromAtomicInt(info
.args
[0].sk
);
1287 auto transID
= info
.args
[1].transID
;
1288 start
= retranslateOpt(transID
, false);
1289 SKTRACE(2, sk
, "retranslated-OPT: transId = %d start: @%p\n", transID
,
1294 case REQ_POST_INTERP_RET
: {
1295 // This is only responsible for the control-flow aspect of the Ret:
1296 // getting to the destination's translation, if any.
1297 auto ar
= info
.args
[0].ar
;
1298 auto caller
= info
.args
[1].ar
;
1299 assertx(caller
== vmfp());
1300 // If caller is a resumable (aka a generator) then whats likely happened
1301 // here is that we're resuming a yield from. That expression happens to
1302 // cause an assumption that we made earlier to be violated (that `ar` is
1303 // on the stack), so if we detect this situation we need to fix up the
1305 if (UNLIKELY(caller
->resumed() &&
1306 caller
->func()->isNonAsyncGenerator())) {
1307 auto gen
= frame_generator(caller
);
1308 if (gen
->m_delegate
.m_type
== KindOfObject
) {
1309 auto delegate
= gen
->m_delegate
.m_data
.pobj
;
1310 // We only checked that our delegate is an object, but we can't get
1311 // into this situation if the object itself isn't a Generator
1312 assert(delegate
->getVMClass() == Generator::getClass());
1313 // Ok so we're in a `yield from` situation, we know our ar is garbage.
1314 // The ar that we're looking for is the ar of the delegate generator,
1315 // so grab that here.
1316 ar
= Generator::fromObject(delegate
)->actRec();
1319 Unit
* destUnit
= caller
->func()->unit();
1320 // Set PC so logging code in getTranslation doesn't get confused.
1321 vmpc() = destUnit
->at(caller
->m_func
->base() + ar
->m_soff
);
1322 if (ar
->isFCallAwait()) {
1323 // If there was an interped FCallAwait, and we return via the
1324 // jit, we need to deal with the suspend case here.
1325 assert(ar
->m_r
.m_aux
.u_fcallAwaitFlag
< 2);
1326 if (ar
->m_r
.m_aux
.u_fcallAwaitFlag
) {
1327 start
= ustubs().fcallAwaitSuspendHelper
;
1331 sk
= SrcKey
{caller
->func(), vmpc(), caller
->resumed()};
1332 start
= getTranslation(TransArgs
{sk
}).tca();
1333 TRACE(3, "REQ_POST_INTERP_RET: from %s to %s\n",
1334 ar
->m_func
->fullName()->data(),
1335 caller
->m_func
->fullName()->data());
1339 case REQ_POST_DEBUGGER_RET
: {
1341 auto caller
= fp
->func();
1342 vmpc() = caller
->unit()->at(caller
->base() +
1343 g_unwind_rds
->debuggerReturnOff
);
1344 FTRACE(3, "REQ_DEBUGGER_RET: pc {} in {}\n",
1345 vmpc(), fp
->func()->fullName()->data());
1346 sk
= SrcKey
{fp
->func(), vmpc(), fp
->resumed()};
1347 start
= getTranslation(TransArgs
{sk
}).tca();
1352 if (smashed
&& info
.stub
) {
1353 Treadmill::enqueue(FreeRequestStubTrigger(info
.stub
));
1356 if (start
== nullptr) {
1357 vmpc() = sk
.unit()->at(sk
.offset());
1358 start
= ustubs().interpHelperSyncedPC
;
1361 if (Trace::moduleEnabled(Trace::ringbuffer
, 1)) {
1362 auto skData
= sk
.valid() ? sk
.toAtomicInt() : uint64_t(-1LL);
1363 Trace::ringbufferEntry(RBTypeResumeTC
, skData
, (uint64_t)start
);
1366 tl_regState
= VMRegState::DIRTY
;
1370 TCA
MCGenerator::handleBindCall(TCA toSmash
,
1371 ActRec
* calleeFrame
,
1373 Func
* func
= const_cast<Func
*>(calleeFrame
->m_func
);
1374 int nArgs
= calleeFrame
->numArgs();
1375 TRACE(2, "bindCall %s, ActRec %p\n", func
->fullName()->data(), calleeFrame
);
1376 TCA start
= getFuncPrologue(func
, nArgs
);
1377 TRACE(2, "bindCall -> %p\n", start
);
1378 if (start
&& !isImmutable
) {
1379 // We dont know we're calling the right function, so adjust start to point
1380 // to the dynamic check of ar->m_func.
1381 start
= funcGuardFromPrologue(start
, func
);
1383 TRACE(2, "bindCall immutably %s -> %p\n", func
->fullName()->data(), start
);
1386 if (start
&& !RuntimeOption::EvalFailJitPrologs
) {
1387 LeaseHolder
writer(Translator::WriteLease());
1388 if (writer
.canWrite()) {
1389 // Someone else may have changed the func prologue while we waited for
1390 // the write lease, so read it again.
1391 start
= getFuncPrologue(func
, nArgs
);
1392 if (start
&& !isImmutable
) start
= funcGuardFromPrologue(start
, func
);
1394 if (start
&& smashableCallTarget(toSmash
) != start
) {
1395 assertx(smashableCallTarget(toSmash
));
1396 TRACE(2, "bindCall smash %p -> %p\n", toSmash
, start
);
1397 smashCall(toSmash
, start
);
1399 bool is_profiled
= false;
1400 // For functions to be PGO'ed, if their current prologues are still
1401 // profiling ones (living in code.prof()), then save toSmash as a
1402 // caller to the prologue, so that it can later be smashed to call a
1403 // new prologue when it's generated.
1404 int calleeNumParams
= func
->numNonVariadicParams();
1405 int calledPrologNumArgs
= (nArgs
<= calleeNumParams
?
1406 nArgs
: calleeNumParams
+ 1);
1407 auto const profData
= jit::profData();
1408 if (profData
!= nullptr && m_code
.prof().contains(start
)) {
1409 auto rec
= profData
->prologueTransRec(
1414 rec
->addMainCaller(toSmash
);
1416 rec
->addGuardCaller(toSmash
);
1421 // We need to be able to reclaim the function prologues once the unit
1422 // associated with this function is treadmilled-- so record all of the
1423 // callers that will need to be re-smashed
1424 if (RuntimeOption::EvalEnableReusableTC
) {
1425 if (debug
|| !isImmutable
) {
1426 recordFuncCaller(func
, toSmash
, isImmutable
,
1427 is_profiled
, calledPrologNumArgs
);
1433 // We couldn't get a prologue address. Return a stub that will finish
1434 // entering the callee frame in C++, then call handleResume at the callee's
1436 start
= ustubs().fcallHelperThunk
;
1442 TCA
MCGenerator::handleFCallAwaitSuspend() {
1443 assert_native_stack_aligned();
1444 FTRACE(1, "handleFCallAwaitSuspend\n");
1446 tl_regState
= VMRegState::CLEAN
;
1448 vmJitCalledFrame() = vmfp();
1449 SCOPE_EXIT
{ vmJitCalledFrame() = nullptr; };
1451 auto start
= suspendStack(vmpc());
1452 tl_regState
= VMRegState::DIRTY
;
1453 return start
? start
: ustubs().resumeHelper
;
1456 TCA
MCGenerator::handleResume(bool interpFirst
) {
1457 assert_native_stack_aligned();
1458 FTRACE(1, "handleResume({})\n", interpFirst
);
1460 if (!vmRegsUnsafe().pc
) return ustubs().callToExit
;
1462 tl_regState
= VMRegState::CLEAN
;
1464 auto sk
= SrcKey
{liveFunc(), vmpc(), liveResumed()};
1468 INC_TPC(interp_bb_force
);
1470 start
= getTranslation(TransArgs(sk
)).tca();
1473 vmJitCalledFrame() = vmfp();
1474 SCOPE_EXIT
{ vmJitCalledFrame() = nullptr; };
1476 // If we can't get a translation at the current SrcKey, interpret basic
1477 // blocks until we end up somewhere with a translation (which we may have
1478 // created, if the lease holder dropped it).
1481 if (auto retAddr
= HPHP::dispatchBB()) {
1487 sk
= SrcKey
{liveFunc(), vmpc(), liveResumed()};
1488 start
= getTranslation(TransArgs
{sk
}).tca();
1491 if (Trace::moduleEnabled(Trace::ringbuffer
, 1)) {
1492 auto skData
= sk
.valid() ? sk
.toAtomicInt() : uint64_t(-1LL);
1493 Trace::ringbufferEntry(RBTypeResumeTC
, skData
, (uint64_t)start
);
1496 tl_regState
= VMRegState::DIRTY
;
1500 ///////////////////////////////////////////////////////////////////////////////
1503 * Support for the stub freelist.
1505 TCA
FreeStubList::maybePop() {
1506 StubNode
* ret
= m_list
;
1508 TRACE(1, "alloc stub %p\n", ret
);
1509 m_list
= ret
->m_next
;
1510 ret
->m_freed
= ~kStubFree
;
1515 void FreeStubList::push(TCA stub
) {
1517 * A freed stub may be released by Treadmill more than once if multiple
1518 * threads execute the service request before it is freed. We detect
1519 * duplicates by marking freed stubs
1521 StubNode
* n
= reinterpret_cast<StubNode
*>(stub
);
1522 if (n
->m_freed
== kStubFree
) {
1523 TRACE(1, "already freed stub %p\n", stub
);
1526 n
->m_freed
= kStubFree
;
1528 TRACE(1, "free stub %p (-> %p)\n", stub
, m_list
);
1533 MCGenerator::freeRequestStub(TCA stub
) {
1534 LeaseHolder
writer(Translator::WriteLease());
1536 * If we can't acquire the write lock, the caller
1537 * (FreeRequestStubTrigger) retries
1539 if (!writer
.canWrite()) return false;
1540 assertx(m_code
.frozen().contains(stub
));
1541 m_debugInfo
.recordRelocMap(stub
, 0, "FreeStub");
1542 m_freeStubs
.push(stub
);
1546 TCA
MCGenerator::getFreeStub(CodeBlock
& frozen
, CGMeta
* fixups
,
1548 TCA ret
= m_freeStubs
.maybePop();
1549 if (isReused
) *isReused
= ret
;
1552 Stats::inc(Stats::Astub_Reused
);
1553 always_assert(m_freeStubs
.peek() == nullptr ||
1554 m_code
.isValidCodeAddress(m_freeStubs
.peek()));
1555 TRACE(1, "recycle stub %p\n", ret
);
1557 ret
= frozen
.frontier();
1558 Stats::inc(Stats::Astub_New
);
1559 TRACE(1, "alloc new stub %p\n", ret
);
1563 fixups
->reusedStubs
.emplace_back(ret
);
1568 void MCGenerator::syncWork() {
1569 assertx(tl_regState
!= VMRegState::CLEAN
);
1570 m_fixupMap
.fixup(g_context
.getNoCheck());
1571 tl_regState
= VMRegState::CLEAN
;
1572 Stats::inc(Stats::TC_Sync
);
1577 * Attempt to emit code for the given IRUnit to `code'. Returns true on
1578 * success, false if codegen failed.
1580 bool mcGenUnit(TransEnv
& env
, CodeCache::View code
, CGMeta
& fixups
) {
1581 auto const& unit
= *env
.unit
;
1583 emitVunit(*env
.vunit
, unit
, code
, fixups
,
1584 dumpTCAnnotation(*env
.args
.sk
.func(), env
.args
.kind
)
1587 } catch (const DataBlockFull
& dbFull
) {
1588 if (dbFull
.name
== "hot") {
1589 mcg
->code().disableHot();
1592 always_assert_flog(0, "data block = {}\nmessage: {}\n",
1593 dbFull
.name
, dbFull
.what());
1597 auto const startSk
= unit
.context().srcKey();
1598 if (unit
.context().kind
== TransKind::Profile
) {
1599 profData()->setProfiling(startSk
.func()->getFuncId());
1606 * If TC reuse is enabled, attempt to relocate the newly-emitted translation to
1607 * a hole reclaimed from dead code. Returns true if the translation was
1608 * relocated and false otherwise.
1610 bool tryRelocateNewTranslation(SrcKey sk
, TransLoc
& loc
,
1611 CodeCache::View code
, CGMeta
& fixups
) {
1612 if (!RuntimeOption::EvalEnableReusableTC
) return false;
1614 TCA UNUSED ms
= loc
.mainStart(), me
= loc
.mainEnd(),
1615 cs
= loc
.coldStart(), ce
= loc
.coldEnd(),
1616 fs
= loc
.frozenStart(), fe
= loc
.frozenEnd();
1617 bool did_relocate
= relocateNewTranslation(loc
, code
, fixups
);
1620 FTRACE_MOD(reusetc
, 1,
1621 "Relocated translation for func {} (id = {}) @ sk({}) "
1622 "from M[{}, {}], C[{}, {}], F[{}, {}] to M[{}, {}] "
1623 "C[{}, {}] F[{}, {}]\n",
1624 sk
.func()->fullName()->data(), sk
.func()->getFuncId(),
1625 sk
.offset(), ms
, me
, cs
, ce
, fs
, fe
, loc
.mainStart(),
1626 loc
.mainEnd(), loc
.coldStart(), loc
.coldEnd(),
1627 loc
.frozenStart(), loc
.frozenEnd());
1629 FTRACE_MOD(reusetc
, 1,
1630 "Created translation for func {} (id = {}) "
1631 " @ sk({}) at M[{}, {}], C[{}, {}], F[{}, {}]\n",
1632 sk
.func()->fullName()->data(), sk
.func()->getFuncId(),
1633 sk
.offset(), ms
, me
, cs
, ce
, fs
, fe
);
1636 assertx(did_relocate
== (loc
.mainStart() != ms
));
1637 return did_relocate
;
1641 * If live code relocation is enabled, record metadata for the current
1644 void recordRelocationMetaData(SrcKey sk
, SrcRec
& srcRec
,
1645 const TransLoc
& loc
, CGMeta
& fixups
) {
1646 if (!RuntimeOption::EvalPerfRelocate
) return;
1648 recordPerfRelocMap(loc
.mainStart(), loc
.mainEnd(),
1649 loc
.coldCodeStart(), loc
.coldEnd(),
1651 srcRec
.tailFallbackJumps(),
1656 * If the jit maturity counter is enabled, update it with the current amount of
1659 void reportJitMaturity(const CodeCache
& code
) {
1660 // Optimized translations are faster than profiling translations, which are
1661 // faster than the interpreter. But when optimized translations are
1662 // generated, some profiling translations will become dead. We assume the
1663 // incremental value of an optimized translation over the corresponding
1664 // profiling translations is comparable to the incremental value of a
1665 // profiling translation of similar size; thus we don't have to apply
1666 // different weights to code in different regions.
1667 auto const codeSize
=
1668 code
.hot().used() + code
.main().used() + code
.prof().used();
1669 if (s_jitMaturityCounter
) {
1670 // EvalJitMatureSize is supposed to to be set to approximately 20% of the
1671 // code that will give us full performance, so recover the "fully mature"
1672 // size with some math.
1673 auto const fullSize
= RuntimeOption::EvalJitMatureSize
* 5;
1674 auto const after
= codeSize
>= fullSize
? 100
1675 : (codeSize
* 100 / fullSize
);
1676 auto const before
= s_jitMaturityCounter
->getValue();
1677 if (after
> before
) s_jitMaturityCounter
->setValue(after
);
1680 if (!s_loggedJitMature
.load(std::memory_order_relaxed
) &&
1681 StructuredLog::enabled() &&
1682 codeSize
>= RuntimeOption::EvalJitMatureSize
&&
1683 !s_loggedJitMature
.exchange(true, std::memory_order_relaxed
)) {
1684 StructuredLogEntry cols
;
1685 cols
.setInt("jit_mature_sec", time(nullptr) - HttpServer::StartTime
);
1686 StructuredLog::log("hhvm_warmup", cols
);
1691 TCA
MCGenerator::finishTranslation(TransEnv env
) {
1692 Timer
timer(Timer::mcg_finishTranslation
);
1694 auto& args
= env
.args
;
1695 auto const sk
= args
.sk
;
1697 profileSetHotFuncAttr();
1699 // Grab the write lease before touching the TC. We use a blocking aqcuisition
1700 // because either a) we already have the lease and this will return right
1701 // away, or b) a caller above our pay grade decided to do the first part of
1702 // the translation process without the write lease, and wants us to make sure
1703 // we emit code here rather than throwing away the work already done.
1704 BlockingLeaseHolder write
{Translator::WriteLease()};
1705 auto code
= m_code
.view(args
.kind
);
1708 TransLocMaker maker
{code
};
1711 if (env
.vunit
&& !mcGenUnit(env
, code
, fixups
)) {
1712 // mcGenUnit() failed. Roll back, drop the unit and region, and clear
1718 args
.region
.reset();
1724 assertx(m_numTrans
<= RuntimeOption::EvalJitGlobalTranslationLimit
);
1726 args
.kind
= TransKind::Interp
;
1727 FTRACE(1, "emitting dispatchBB interp request for failed "
1728 "translation (spOff = {})\n", env
.initSpOffset
.offset
);
1729 vwrap(code
.main(), code
.data(), fixups
,
1730 [&] (Vout
& v
) { emitInterpReq(v
, sk
, env
.initSpOffset
); },
1734 Timer
metaTimer(Timer::mcg_finishTranslation_metadata
);
1735 auto loc
= maker
.markEnd();
1737 tryRelocateNewTranslation(sk
, loc
, code
, fixups
);
1739 // Finally, record various metadata about the translation and add it to the
1741 if (RuntimeOption::EvalProfileBC
) {
1742 auto const vmUnit
= sk
.unit();
1743 TransBCMapping prev
{};
1744 for (auto& cur
: fixups
.bcMap
) {
1745 if (!cur
.aStart
) continue;
1747 if (prev
.bcStart
< vmUnit
->bclen()) {
1748 recordBCInstr(uint32_t(vmUnit
->getOp(prev
.bcStart
)),
1749 prev
.aStart
, cur
.aStart
, false);
1752 recordBCInstr(OpTraceletGuard
, loc
.mainStart(), cur
.aStart
, false);
1758 auto const srcRec
= m_srcDB
.find(args
.sk
);
1759 always_assert(srcRec
);
1760 recordRelocationMetaData(sk
, *srcRec
, loc
, fixups
);
1761 recordGdbTranslation(sk
, sk
.func(), code
.main(), loc
.mainStart(),
1763 recordGdbTranslation(sk
, sk
.func(), code
.cold(), loc
.coldStart(),
1765 if (args
.kind
== TransKind::Profile
) {
1766 always_assert(args
.region
);
1767 profData()->addTransProfile(env
.transID
, args
.region
, env
.pconds
);
1770 auto tr
= maker
.rec(sk
, env
.transID
, args
.kind
, args
.region
, fixups
.bcMap
,
1771 std::move(env
.annotations
),
1772 env
.unit
&& cfgHasLoop(*env
.unit
));
1773 m_tx
.addTranslation(tr
);
1774 if (RuntimeOption::EvalJitUseVtuneAPI
) {
1775 reportTraceletToVtune(sk
.unit(), sk
.func(), tr
);
1778 GrowableVector
<IncomingBranch
> inProgressTailBranches
;
1779 fixups
.process(&inProgressTailBranches
);
1781 // SrcRec::newTranslation() makes this code reachable. Do this last;
1782 // otherwise there's some chance of hitting in the reader threads whose
1783 // metadata is not yet visible.
1784 TRACE(1, "newTranslation: %p sk: (func %d, bcOff %d)\n",
1785 loc
.mainStart(), sk
.funcID(), sk
.offset());
1786 srcRec
->newTranslation(loc
, inProgressTailBranches
);
1788 TRACE(1, "mcg: %zd-byte tracelet\n", (ssize_t
)loc
.mainSize());
1789 if (Trace::moduleEnabledRelease(Trace::tcspace
, 1)) {
1790 Trace::traceRelease("%s", getTCSpace().c_str());
1793 reportJitMaturity(m_code
);
1795 return loc
.mainStart();
1798 MCGenerator::MCGenerator()
1800 , m_catchTraceMap(128)
1802 TRACE(1, "MCGenerator@%p startup\n", this);
1805 g_unwind_rds
.bind();
1807 static bool profileUp
= false;
1813 if (Trace::moduleEnabledRelease(Trace::printir
) &&
1814 !RuntimeOption::EvalJit
) {
1815 Trace::traceRelease("TRACE=printir is set but the jit isn't on. "
1816 "Did you mean to run with -vEval.Jit=1?\n");
1819 s_jitMaturityCounter
= ServiceData::createCounter("jit.maturity");
1821 // Do not initialize JIT stubs for PPC64 - port under development
1822 #if !defined(__powerpc64__)
1823 m_ustubs
.emitAll(m_code
, m_debugInfo
);
1826 // Write an .eh_frame section that covers the whole TC.
1829 ehfw
.begin_fde(m_code
.base());
1830 ehfw
.end_fde(m_code
.codeSize());
1833 m_ehFrames
.push_back(ehfw
.register_and_release());
1836 folly::Optional
<TCA
> MCGenerator::getCatchTrace(CTCA ip
) const {
1837 TCA
* found
= m_catchTraceMap
.find(ip
);
1838 if (found
&& *found
!= kInvalidCatchTrace
) return *found
;
1842 void codeEmittedThisRequest(size_t& requestEntry
, size_t& now
) {
1843 requestEntry
= s_initialTCSize
;
1844 now
= mcg
->code().totalUsed();
1848 __thread
std::unordered_map
<const ActRec
*, TCA
>* tl_debuggerCatches
{nullptr};
1851 void stashDebuggerCatch(const ActRec
* fp
) {
1852 if (!tl_debuggerCatches
) {
1853 tl_debuggerCatches
= new std::unordered_map
<const ActRec
*, TCA
>();
1856 auto optCatchBlock
= mcg
->getCatchTrace(TCA(fp
->m_savedRip
));
1857 always_assert(optCatchBlock
&& *optCatchBlock
);
1858 auto catchBlock
= *optCatchBlock
;
1859 FTRACE(1, "Pushing debugger catch {} with fp {}\n", catchBlock
, fp
);
1860 tl_debuggerCatches
->emplace(fp
, catchBlock
);
1863 TCA
unstashDebuggerCatch(const ActRec
* fp
) {
1864 always_assert(tl_debuggerCatches
);
1865 auto const it
= tl_debuggerCatches
->find(fp
);
1866 always_assert(it
!= tl_debuggerCatches
->end());
1867 auto const catchBlock
= it
->second
;
1868 tl_debuggerCatches
->erase(it
);
1869 FTRACE(1, "Popped debugger catch {} for fp {}\n", catchBlock
, fp
);
1873 void MCGenerator::requestInit() {
1874 tl_regState
= VMRegState::CLEAN
;
1875 Timer::RequestInit();
1876 memset(&tl_perf_counters
, 0, sizeof(tl_perf_counters
));
1878 requestInitProfData();
1879 s_initialTCSize
= m_code
.totalUsed();
1882 void MCGenerator::requestExit() {
1883 always_assert(!Translator::WriteLease().amOwner());
1884 TRACE_MOD(txlease
, 2, "%" PRIx64
" write lease stats: %15" PRId64
1885 " kept, %15" PRId64
" grabbed\n",
1886 Process::GetThreadIdForTrace(), Translator::WriteLease().hintKept(),
1887 Translator::WriteLease().hintGrabbed());
1890 Timer::RequestExit();
1891 if (profData()) profData()->maybeResetCounters();
1892 requestExitProfData();
1894 if (Trace::moduleEnabledRelease(Trace::mcgstats
, 1)) {
1895 Trace::traceRelease("MCGenerator perf counters for %s:\n",
1896 g_context
->getRequestUrl(50).c_str());
1897 for (int i
= 0; i
< tpc_num_counters
; i
++) {
1898 Trace::traceRelease("%-20s %10" PRId64
"\n",
1899 kPerfCounterNames
[i
], tl_perf_counters
[i
]);
1901 Trace::traceRelease("\n");
1904 delete tl_debuggerCatches
;
1905 tl_debuggerCatches
= nullptr;
1908 MCGenerator::~MCGenerator() {
1911 static Debug::TCRange
rangeFrom(const CodeBlock
& cb
, const TCA addr
,
1913 assertx(cb
.contains(addr
));
1914 return Debug::TCRange(addr
, cb
.frontier(), isAcold
);
1917 void MCGenerator::recordBCInstr(uint32_t op
,
1922 m_debugInfo
.recordBCInstr(
1923 Debug::TCRange(addr
, end
, cold
), op
);
1927 void MCGenerator::recordGdbTranslation(SrcKey sk
,
1928 const Func
* srcFunc
,
1929 const CodeBlock
& cb
,
1933 if (start
!= cb
.frontier()) {
1934 assertx(Translator::WriteLease().amOwner());
1935 if (!RuntimeOption::EvalJitNoGdb
) {
1936 m_debugInfo
.recordTracelet(rangeFrom(cb
, start
, &cb
== &m_code
.cold()),
1939 srcFunc
->unit()->at(sk
.offset()) : nullptr,
1942 if (RuntimeOption::EvalPerfPidMap
) {
1943 m_debugInfo
.recordPerfMap(rangeFrom(cb
, start
, &cb
== &m_code
.cold()),
1944 srcFunc
, exit
, inPrologue
);
1949 bool MCGenerator::addDbgGuards(const Unit
* unit
) {
1951 // It grabs the write lease and iterates through whole SrcDB...
1952 struct timespec tsBegin
, tsEnd
;
1954 BlockingLeaseHolder
writer(Translator::WriteLease());
1955 auto code
= m_code
.view();
1956 auto& main
= code
.main();
1957 auto& data
= code
.data();
1959 HPHP::Timer::GetMonotonicTime(tsBegin
);
1960 // Doc says even find _could_ invalidate iterator, in pactice it should
1961 // be very rare, so go with it now.
1963 for (auto& pair
: m_srcDB
) {
1964 SrcKey
const sk
= SrcKey::fromAtomicInt(pair
.first
);
1965 // We may have a SrcKey to a deleted function. NB: this may miss a
1966 // race with deleting a Func. See task #2826313.
1967 if (!Func::isFuncIdValid(sk
.funcID())) continue;
1968 SrcRec
* sr
= pair
.second
;
1969 if (sr
->unitMd5() == unit
->md5() &&
1970 !sr
->hasDebuggerGuard() &&
1971 m_tx
.isSrcKeyInBL(sk
)) {
1972 addDbgGuardImpl(sk
, sr
, main
, data
, fixups
);
1975 fixups
.process(nullptr);
1977 HPHP::Timer::GetMonotonicTime(tsEnd
);
1978 int64_t elapsed
= gettime_diff_us(tsBegin
, tsEnd
);
1979 if (Trace::moduleEnabledRelease(Trace::mcg
, 5)) {
1980 Trace::traceRelease("addDbgGuards got lease for %" PRId64
" us\n", elapsed
);
1985 bool MCGenerator::addDbgGuard(const Func
* func
, Offset offset
, bool resumed
) {
1986 SrcKey
sk(func
, offset
, resumed
);
1988 if (SrcRec
* sr
= m_srcDB
.find(sk
)) {
1989 if (sr
->hasDebuggerGuard()) {
1993 // no translation yet
1998 if (!m_tx
.isSrcKeyInBL(sk
)) {
1999 TRACE(5, "calling addDbgGuard on PC that is not in blacklist");
2003 BlockingLeaseHolder
writer(Translator::WriteLease());
2006 if (SrcRec
* sr
= m_srcDB
.find(sk
)) {
2007 auto code
= m_code
.view();
2008 addDbgGuardImpl(sk
, sr
, code
.main(), code
.data(), fixups
);
2010 fixups
.process(nullptr);
2014 bool MCGenerator::dumpTCCode(const char* filename
) {
2015 #define OPEN_FILE(F, SUFFIX) \
2016 std::string F ## name = std::string(filename).append(SUFFIX); \
2017 FILE* F = fopen(F ## name .c_str(),"wb"); \
2018 if (F == nullptr) return false; \
2019 SCOPE_EXIT{ fclose(F); };
2021 OPEN_FILE(ahotFile
, "_ahot");
2022 OPEN_FILE(aFile
, "_a");
2023 OPEN_FILE(aprofFile
, "_aprof");
2024 OPEN_FILE(acoldFile
, "_acold");
2025 OPEN_FILE(afrozenFile
, "_afrozen");
2026 OPEN_FILE(helperAddrFile
, "_helpers_addrs.txt");
2030 // dump starting from the hot region
2032 auto writeBlock
= [&](const CodeBlock
& cb
, FILE* file
) {
2034 auto const count
= cb
.used();
2035 result
= fwrite(cb
.base(), 1, count
, file
) == count
;
2039 writeBlock(m_code
.hot(), ahotFile
);
2040 writeBlock(m_code
.main(), aFile
);
2041 writeBlock(m_code
.prof(), aprofFile
);
2042 writeBlock(m_code
.cold(), acoldFile
);
2043 writeBlock(m_code
.frozen(), afrozenFile
);
2047 bool MCGenerator::dumpTC(bool ignoreLease
/* = false */) {
2048 folly::Optional
<BlockingLeaseHolder
> writer
;
2050 writer
.emplace(Translator::WriteLease());
2052 return dumpTCData() && dumpTCCode("/tmp/tc_dump");
2055 // Returns true on success
2056 bool MCGenerator::dumpTCData() {
2057 gzFile tcDataFile
= gzopen("/tmp/tc_data.txt.gz", "w");
2058 if (!tcDataFile
) return false;
2060 if (!gzprintf(tcDataFile
,
2061 "repo_schema = %s\n"
2063 "ahot.frontier = %p\n"
2067 "aprof.frontier = %p\n"
2069 "acold.frontier = %p\n"
2070 "afrozen.base = %p\n"
2071 "afrozen.frontier = %p\n\n",
2072 repoSchemaId().begin(),
2073 m_code
.hot().base(), m_code
.hot().frontier(),
2074 m_code
.main().base(), m_code
.main().frontier(),
2075 m_code
.prof().base(), m_code
.prof().frontier(),
2076 m_code
.cold().base(), m_code
.cold().frontier(),
2077 m_code
.frozen().base(), m_code
.frozen().frontier())) {
2081 if (!gzprintf(tcDataFile
, "total_translations = %zu\n\n",
2082 m_tx
.getCurrentTransID())) {
2086 for (TransID t
= 0; t
< m_tx
.getCurrentTransID(); t
++) {
2087 if (gzputs(tcDataFile
,
2088 m_tx
.getTransRec(t
)->print(m_tx
.getTransCounter(t
)).c_str()) ==
2094 gzclose(tcDataFile
);
2098 void MCGenerator::invalidateSrcKey(SrcKey sk
) {
2099 assertx(!RuntimeOption::RepoAuthoritative
|| RuntimeOption::EvalJitPGO
);
2100 assertx(Translator::WriteLease().amOwner());
2102 * Reroute existing translations for SrcKey to an as-yet indeterminate
2105 auto const sr
= m_srcDB
.find(sk
);
2108 * Since previous translations aren't reachable from here, we know we
2109 * just created some garbage in the TC. We currently have no mechanism
2112 FTRACE_MOD(reusetc
, 1, "Replacing translations from func {} (id = {}) "
2113 "@ sk({}) in SrcRec addr={}\n",
2114 sk
.func()->fullName()->data(), sk
.func()->getFuncId(), sk
.offset(),
2117 sr
->replaceOldTranslations();
2120 ///////////////////////////////////////////////////////////////////////////////