// This file is part of AsmJit project // // See asmjit.h or LICENSE.md for license and copyright information // SPDX-License-Identifier: Zlib #ifndef ASMJIT_CORE_RABUILDERS_P_H_INCLUDED #define ASMJIT_CORE_RABUILDERS_P_H_INCLUDED #include "../core/api-config.h" #ifndef ASMJIT_NO_COMPILER #include "../core/formatter.h" #include "../core/rapass_p.h" ASMJIT_BEGIN_NAMESPACE //! \cond INTERNAL //! \addtogroup asmjit_ra //! \{ template class RACFGBuilderT { public: enum : uint32_t { kRootIndentation = 2, kCodeIndentation = 4, // NOTE: This is a bit hacky. There are some nodes which are processed twice (see `onBeforeInvoke()` and // `onBeforeRet()`) as they can insert some nodes around them. Since we don't have any flags to mark these // we just use their position that is [at that time] unassigned. kNodePositionDidOnBefore = 0xFFFFFFFFu }; //! \name Members //! \{ BaseRAPass* _pass = nullptr; BaseCompiler* _cc = nullptr; RABlock* _curBlock = nullptr; RABlock* _retBlock = nullptr; FuncNode* _funcNode = nullptr; RARegsStats _blockRegStats {}; uint32_t _exitLabelId = Globals::kInvalidId; ZoneVector _sharedAssignmentsMap {}; // Only used by logging, it's fine to be here to prevent more #ifdefs... bool _hasCode = false; RABlock* _lastLoggedBlock = nullptr; #ifndef ASMJIT_NO_LOGGING Logger* _logger = nullptr; FormatOptions _formatOptions {}; StringTmp<512> _sb; #endif //! \} inline RACFGBuilderT(BaseRAPass* pass) noexcept : _pass(pass), _cc(pass->cc()) { #ifndef ASMJIT_NO_LOGGING _logger = _pass->hasDiagnosticOption(DiagnosticOptions::kRADebugCFG) ? _pass->logger() : nullptr; if (_logger) _formatOptions = _logger->options(); #endif } inline BaseCompiler* cc() const noexcept { return _cc; } //! \name Run //! \{ //! Called per function by an architecture-specific CFG builder. Error run() noexcept { log("[BuildCFG]\n"); ASMJIT_PROPAGATE(prepare()); logNode(_funcNode, kRootIndentation); logBlock(_curBlock, kRootIndentation); RABlock* entryBlock = _curBlock; BaseNode* node = _funcNode->next(); if (ASMJIT_UNLIKELY(!node)) return DebugUtils::errored(kErrorInvalidState); _curBlock->setFirst(_funcNode); _curBlock->setLast(_funcNode); RAInstBuilder ib; ZoneVector blocksWithUnknownJumps; for (;;) { BaseNode* next = node->next(); ASMJIT_ASSERT(node->position() == 0 || node->position() == kNodePositionDidOnBefore); if (node->isInst()) { // Instruction | Jump | Invoke | Return // ------------------------------------ // Handle `InstNode`, `InvokeNode`, and `FuncRetNode`. All of them share the same interface that provides // operands that have read/write semantics. if (ASMJIT_UNLIKELY(!_curBlock)) { // Unreachable code has to be removed, we cannot allocate registers in such code as we cannot do proper // liveness analysis in such case. removeNode(node); node = next; continue; } _hasCode = true; if (node->isInvoke() || node->isFuncRet()) { if (node->position() != kNodePositionDidOnBefore) { // Call and Reg are complicated as they may insert some surrounding code around them. The simplest // approach is to get the previous node, call the `onBefore()` handlers and then check whether // anything changed and restart if so. By restart we mean that the current `node` would go back to // the first possible inserted node by `onBeforeInvoke()` or `onBeforeRet()`. BaseNode* prev = node->prev(); if (node->type() == NodeType::kInvoke) ASMJIT_PROPAGATE(static_cast(this)->onBeforeInvoke(node->as())); else ASMJIT_PROPAGATE(static_cast(this)->onBeforeRet(node->as())); if (prev != node->prev()) { // If this was the first node in the block and something was // inserted before it then we have to update the first block. if (_curBlock->first() == node) _curBlock->setFirst(prev->next()); node->setPosition(kNodePositionDidOnBefore); node = prev->next(); // `onBeforeInvoke()` and `onBeforeRet()` can only insert instructions. ASMJIT_ASSERT(node->isInst()); } // Necessary if something was inserted after `node`, but nothing before. next = node->next(); } else { // Change the position back to its original value. node->setPosition(0); } } InstNode* inst = node->as(); logNode(inst, kCodeIndentation); InstControlFlow cf = InstControlFlow::kRegular; ib.reset(); ASMJIT_PROPAGATE(static_cast(this)->onInst(inst, cf, ib)); if (node->isInvoke()) { ASMJIT_PROPAGATE(static_cast(this)->onInvoke(inst->as(), ib)); } if (node->isFuncRet()) { ASMJIT_PROPAGATE(static_cast(this)->onRet(inst->as(), ib)); cf = InstControlFlow::kReturn; } if (cf == InstControlFlow::kJump) { uint32_t fixedRegCount = 0; for (RATiedReg& tiedReg : ib) { RAWorkReg* workReg = _pass->workRegById(tiedReg.workId()); if (workReg->group() == RegGroup::kGp) { uint32_t useId = tiedReg.useId(); if (useId == BaseReg::kIdBad) { useId = _pass->_scratchRegIndexes[fixedRegCount++]; tiedReg.setUseId(useId); } _curBlock->addExitScratchGpRegs(Support::bitMask(useId)); } } } ASMJIT_PROPAGATE(_pass->assignRAInst(inst, _curBlock, ib)); _blockRegStats.combineWith(ib._stats); if (cf != InstControlFlow::kRegular) { // Support for conditional and unconditional jumps. if (cf == InstControlFlow::kJump || cf == InstControlFlow::kBranch) { _curBlock->setLast(node); _curBlock->addFlags(RABlockFlags::kHasTerminator); _curBlock->makeConstructed(_blockRegStats); if (!inst->hasOption(InstOptions::kUnfollow)) { // Jmp/Jcc/Call/Loop/etc... uint32_t opCount = inst->opCount(); const Operand* opArray = inst->operands(); // Cannot jump anywhere without operands. if (ASMJIT_UNLIKELY(!opCount)) return DebugUtils::errored(kErrorInvalidState); if (opArray[opCount - 1].isLabel()) { // Labels are easy for constructing the control flow. LabelNode* labelNode; ASMJIT_PROPAGATE(cc()->labelNodeOf(&labelNode, opArray[opCount - 1].as