/* * Copyright (c) 2021 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ECMASCRIPT_CLASS_LINKER_BYTECODE_CIRCUIT_IR_BUILDER_H #define ECMASCRIPT_CLASS_LINKER_BYTECODE_CIRCUIT_IR_BUILDER_H #include #include #include #include #include #include "ecmascript/compiler/argument_accessor.h" #include "ecmascript/compiler/circuit.h" #include "ecmascript/compiler/type_recorder.h" #include "ecmascript/interpreter/interpreter-inl.h" #include "ecmascript/js_method.h" #include "ecmascript/jspandafile/js_pandafile.h" namespace panda::ecmascript::kungfu { using VRegIDType = uint16_t; using ImmValueType = uint64_t; using StringIdType = uint32_t; using MethodIdType = uint16_t; class VirtualRegister { public: explicit VirtualRegister(VRegIDType id) : id_(id) { } ~VirtualRegister() = default; void SetId(VRegIDType id) { id_ = id; } VRegIDType GetId() const { return id_; } private: VRegIDType id_; }; class Immediate { public: explicit Immediate(ImmValueType value) : value_(value) { } ~Immediate() = default; void SetValue(ImmValueType value) { value_ = value; } ImmValueType ToJSTaggedValueInt() const { return value_ | JSTaggedValue::TAG_INT; } ImmValueType ToJSTaggedValueDouble() const { return JSTaggedValue(bit_cast(value_)).GetRawData(); } ImmValueType GetValue() const { return value_; } private: ImmValueType value_; }; class StringId { public: explicit StringId(StringIdType id) : id_(id) { } ~StringId() = default; void SetId(StringIdType id) { id_ = id; } StringIdType GetId() const { return id_; } private: StringIdType id_; }; class MethodId { public: explicit MethodId(MethodIdType id) : id_(id) { } ~MethodId() = default; void SetId(MethodIdType id) { id_ = id; } MethodIdType GetId() const { return id_; } private: MethodIdType id_; }; enum class SplitKind : uint8_t { DEFAULT, START, END }; enum class VisitState : uint8_t { UNVISITED, PENDING, VISITED }; struct CfgInfo { uint8_t *pc {nullptr}; SplitKind splitKind {SplitKind::DEFAULT}; std::vector succs {}; CfgInfo(uint8_t *startOrEndPc, SplitKind kind, std::vector successors) : pc(startOrEndPc), splitKind(kind), succs(successors) {} bool operator<(const CfgInfo &rhs) const { if (this->pc != rhs.pc) { return this->pc < rhs.pc; } else { return this->splitKind < rhs.splitKind; } } bool operator==(const CfgInfo &rhs) const { return this->pc == rhs.pc && this->splitKind == rhs.splitKind; } }; struct BytecodeRegion { size_t id {0}; uint8_t *start {nullptr}; uint8_t *end {nullptr}; std::vector preds {}; // List of predessesor blocks std::vector succs {}; // List of successors blocks std::vector trys {}; // List of trys blocks std::vector catchs {}; // List of catches blocks std::vector immDomBlocks {}; // List of dominated blocks BytecodeRegion *iDominator {nullptr}; // Block that dominates the current block std::vector domFrontiers {}; // List of dominace frontiers std::set loopbackBlocks {}; // List of loopback block ids bool isDead {false}; std::set phi {}; // phi node bool phiAcc {false}; size_t numOfStatePreds {0}; size_t numOfLoopBacks {0}; size_t statePredIndex {0}; size_t forwardIndex {0}; size_t loopBackIndex {0}; std::vector> expandedPreds {}; kungfu::GateRef stateStart {kungfu::Circuit::NullGate()}; kungfu::GateRef dependStart {kungfu::Circuit::NullGate()}; kungfu::GateRef mergeForwardEdges {kungfu::Circuit::NullGate()}; kungfu::GateRef mergeLoopBackEdges {kungfu::Circuit::NullGate()}; kungfu::GateRef depForward {kungfu::Circuit::NullGate()}; kungfu::GateRef depLoopBack {kungfu::Circuit::NullGate()}; std::map vregToValSelectorGate {}; // corresponding ValueSelector gates of vregs kungfu::GateRef valueSelectorAccGate {kungfu::Circuit::NullGate()}; bool operator <(const BytecodeRegion &target) const { return id < target.id; } void SortCatches() { if (catchs.size() > 1) { std::sort(catchs.begin(), catchs.end(), [](BytecodeRegion *first, BytecodeRegion *second) { return first->start < second->start; }); } } void UpdateTryCatchInfoForDeadBlock() { // Try-Catch infos of dead block should be cleared UpdateTryCatchInfo(); isDead = true; } void UpdateRedundantTryCatchInfo(bool noThrow) { // if block which can throw exception has serval catchs block, only the innermost catch block is useful if (!noThrow && catchs.size() > 1) { size_t innerMostIndex = 1; UpdateTryCatchInfo(innerMostIndex); } } void UpdateTryCatchInfoIfNoThrow(bool noThrow) { // if block has no general insts, try-catch infos of it should be cleared if (noThrow && !catchs.empty()) { UpdateTryCatchInfo(); } } private: void UpdateTryCatchInfo(size_t index = 0) { for (auto catchBlock = catchs.begin() + index; catchBlock != catchs.end(); catchBlock++) { auto tryBlock = std::find((*catchBlock)->trys.begin(), (*catchBlock)->trys.end(), this); if (tryBlock != (*catchBlock)->trys.end()) { (*catchBlock)->trys.erase(tryBlock); } if ((*catchBlock)->trys.size() == 0) { (*catchBlock)->isDead = true; } } catchs.erase(catchs.begin() + index, catchs.end()); } }; using BytecodeGraph = std::vector; struct BytecodeInfo { // set of id, immediate and read register std::vector> inputs {}; std::vector vregOut {}; // write register bool accIn {false}; // read acc bool accOut {false}; // write acc uint8_t opcode {0}; uint16_t offset {0}; bool IsOut(VRegIDType reg, uint32_t index) const { bool isDefined = (!vregOut.empty() && (reg == vregOut.at(index))); return isDefined; } bool IsMov() const { auto ecmaOpcode = static_cast(opcode); switch (ecmaOpcode) { case EcmaOpcode::MOV_V4_V4: case EcmaOpcode::MOV_DYN_V8_V8: case EcmaOpcode::MOV_DYN_V16_V16: case EcmaOpcode::LDA_DYN_V8: case EcmaOpcode::STA_DYN_V8: return true; default: return false; } } bool IsJump() const { auto ecmaOpcode = static_cast(opcode); switch (ecmaOpcode) { case EcmaOpcode::JMP_IMM8: case EcmaOpcode::JMP_IMM16: case EcmaOpcode::JMP_IMM32: case EcmaOpcode::JEQZ_IMM8: case EcmaOpcode::JEQZ_IMM16: case EcmaOpcode::JNEZ_IMM8: case EcmaOpcode::JNEZ_IMM16: return true; default: return false; } } bool IsCondJump() const { auto ecmaOpcode = static_cast(opcode); switch (ecmaOpcode) { case EcmaOpcode::JEQZ_IMM8: case EcmaOpcode::JEQZ_IMM16: case EcmaOpcode::JNEZ_IMM8: case EcmaOpcode::JNEZ_IMM16: return true; default: return false; } } bool IsReturn() const { auto ecmaOpcode = static_cast(opcode); switch (ecmaOpcode) { case EcmaOpcode::RETURN_DYN: case EcmaOpcode::RETURNUNDEFINED_PREF: return true; default: return false; } } bool IsThrow() const { auto ecmaOpcode = static_cast(opcode); switch (ecmaOpcode) { case EcmaOpcode::THROWDYN_PREF: case EcmaOpcode::THROWCONSTASSIGNMENT_PREF_V8: case EcmaOpcode::THROWTHROWNOTEXISTS_PREF: case EcmaOpcode::THROWPATTERNNONCOERCIBLE_PREF: case EcmaOpcode::THROWDELETESUPERPROPERTY_PREF: return true; default: return false; } } bool IsDiscarded() const { auto ecmaOpcode = static_cast(opcode); switch (ecmaOpcode) { case EcmaOpcode::COPYMODULE_PREF_V8: case EcmaOpcode::DEBUGGER_PREF: return true; default: return false; } } bool IsSetConstant() const { auto ecmaOpcode = static_cast(opcode); switch (ecmaOpcode) { case EcmaOpcode::LDNAN_PREF: case EcmaOpcode::LDINFINITY_PREF: case EcmaOpcode::LDUNDEFINED_PREF: case EcmaOpcode::LDNULL_PREF: case EcmaOpcode::LDTRUE_PREF: case EcmaOpcode::LDFALSE_PREF: case EcmaOpcode::LDHOLE_PREF: case EcmaOpcode::LDAI_DYN_IMM32: case EcmaOpcode::FLDAI_DYN_IMM64: case EcmaOpcode::LDFUNCTION_PREF: return true; default: return false; } } bool IsGeneral() const { return !IsMov() && !IsJump() && !IsReturn() && !IsSetConstant() && !IsDiscarded(); } bool IsCall() const { auto ecmaOpcode = static_cast(opcode); switch (ecmaOpcode) { case EcmaOpcode::CALLARG0DYN_PREF_V8: case EcmaOpcode::CALLARG1DYN_PREF_V8_V8: case EcmaOpcode::CALLARGS2DYN_PREF_V8_V8_V8: case EcmaOpcode::CALLARGS3DYN_PREF_V8_V8_V8_V8: case EcmaOpcode::CALLITHISRANGEDYN_PREF_IMM16_V8: case EcmaOpcode::CALLIRANGEDYN_PREF_IMM16_V8: return true; default: return false; } } size_t ComputeBCOffsetInputCount() const { return IsCall() ? 1 : 0; } size_t ComputeValueInputCount() const { return (accIn ? 1 : 0) + inputs.size(); } size_t ComputeOutCount() const { return accOut ? 1 : 0; } size_t ComputeTotalValueCount() const { return ComputeValueInputCount() + ComputeBCOffsetInputCount(); } bool IsGeneratorRelative() const { auto ecmaOpcode = static_cast(opcode); switch (ecmaOpcode) { case EcmaOpcode::SUSPENDGENERATOR_PREF_V8_V8: case EcmaOpcode::RESUMEGENERATOR_PREF_V8: return true; default: return false; } } bool IsBc(EcmaOpcode ecmaOpcode) const { return opcode == ecmaOpcode; } }; enum BytecodeOffset { ONE = 1, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN }; class BytecodeCircuitBuilder { public: explicit BytecodeCircuitBuilder(const BytecodeTranslationInfo &translationInfo, size_t index, TSManager *tsManager, bool enableLog) : tsManager_(tsManager), file_(translationInfo.jsPandaFile), pf_(translationInfo.jsPandaFile->GetPandaFile()), method_(translationInfo.methodPcInfos[index].method), pcArray_(translationInfo.methodPcInfos[index].pcArray), constantPool_(translationInfo.constantPool), gateAcc_(&circuit_), argAcc_(&circuit_, method_), typeRecorder_(method_, tsManager), hasTypes_(file_->HasTSTypes()), enableLog_(enableLog) { } ~BytecodeCircuitBuilder() = default; NO_COPY_SEMANTIC(BytecodeCircuitBuilder); NO_MOVE_SEMANTIC(BytecodeCircuitBuilder); void PUBLIC_API BytecodeToCircuit(); [[nodiscard]] kungfu::Circuit* GetCircuit() { return &circuit_; } [[nodiscard]] const std::map>& GetGateToBytecode() const { return jsgateToBytecode_; } [[nodiscard]] const std::map& GetBytecodeToGate() const { return byteCodeToJSGate_; } [[nodiscard]] std::string GetBytecodeStr(kungfu::GateRef gate) const { auto pc = jsgateToBytecode_.at(gate).second; return GetEcmaOpcodeStr(static_cast(*pc)); } [[nodiscard]] EcmaOpcode GetByteCodeOpcode(kungfu::GateRef gate) const { auto pc = jsgateToBytecode_.at(gate).second; return static_cast(*pc); } [[nodiscard]] const uint8_t* GetJSBytecode(GateRef gate) const { return jsgateToBytecode_.at(gate).second; } [[nodiscard]] const JSMethod* GetMethod() const { return method_; } BytecodeInfo GetBytecodeInfo(const uint8_t *pc); // for external users, circuit must be built BytecodeInfo GetByteCodeInfo(const GateRef gate) { auto pc = jsgateToBytecode_.at(gate).second; return GetBytecodeInfo(pc); } bool IsLogEnabled() const { return enableLog_; } [[nodiscard]] const std::vector& GetAsyncRelatedGates() const { return suspendAndResumeGates_; } inline bool HasTypes() const { return hasTypes_; } template void EnumerateBlock(BytecodeRegion &bb, const Callback &cb) { auto pc = bb.start; while (pc <= bb.end) { auto bytecodeInfo = GetBytecodeInfo(pc); bool ret = cb(pc, bytecodeInfo); if (!ret) { break; } pc += bytecodeInfo.offset; } } private: void PUBLIC_API CollectBytecodeBlockInfo(uint8_t* pc, std::vector &bytecodeBlockInfos); std::map, std::vector> CollectTryCatchBlockInfo( std::map &byteCodeCurPrePc, std::vector &bytecodeBlockInfos); void CompleteBytecodeBlockInfo(std::map &byteCodeCurPrePc, std::vector &bytecodeBlockInfos); void BuildBasicBlocks(std::map, std::vector> &exception, std::vector &bytecodeBlockInfo, std::map &byteCodeCurPrePc); void ComputeDominatorTree(); void BuildImmediateDominator(const std::vector &immDom); void ComputeDomFrontiers(const std::vector &immDom); void RemoveDeadRegions(const std::map &dfsTimestamp); void InsertPhi(); void InsertExceptionPhi(std::map> &defsitesInfo); void UpdateCFG(); bool ShouldBeDead(BytecodeRegion &curBlock); // build circuit void BuildCircuitArgs(); void CollectPredsInfo(); void NewMerge(GateRef &state, GateRef &depend, size_t numOfIns); void NewLoopBegin(BytecodeRegion &bb); void BuildBlockCircuitHead(); std::vector CreateGateInList(const BytecodeInfo &info); void SetBlockPred(BytecodeRegion &bbNext, const GateRef &state, const GateRef &depend, bool isLoopBack); GateRef NewConst(const BytecodeInfo &info); void NewJSGate(BytecodeRegion &bb, const uint8_t *pc, GateRef &state, GateRef &depend); void NewJump(BytecodeRegion &bb, const uint8_t *pc, GateRef &state, GateRef &depend); void NewReturn(BytecodeRegion &bb, const uint8_t *pc, GateRef &state, GateRef &depend); void NewByteCode(BytecodeRegion &bb, const uint8_t *pc, GateRef &state, GateRef &depend); void AddBytecodeOffsetInfo(GateRef &gate, const BytecodeInfo &info, size_t bcOffsetIndex, uint8_t *pc); void BuildSubCircuit(); void NewPhi(BytecodeRegion &bb, uint16_t reg, bool acc, GateRef ¤tPhi); GateRef RenameVariable(const size_t bbId, const uint8_t *end, const uint16_t reg, const bool acc); void BuildCircuit(); void PrintCollectBlockInfo(std::vector &bytecodeBlockInfos); void PrintGraph(); void PrintBytecodeInfo(); void PrintBBInfo(); void PrintDefsitesInfo(const std::map> &defsitesInfo); inline bool IsEntryBlock(const size_t bbId) const { return bbId == 0; } kungfu::Circuit circuit_; std::map> jsgateToBytecode_; std::map byteCodeToJSGate_; BytecodeGraph graph_; TSManager *tsManager_ {nullptr}; const JSPandaFile *file_ {nullptr}; const panda_file::File *pf_ {nullptr}; const JSMethod *method_ {nullptr}; const std::vector pcArray_; JSHandle constantPool_; GateAccessor gateAcc_; ArgumentAccessor argAcc_; TypeRecorder typeRecorder_; bool hasTypes_ {false}; bool enableLog_ {false}; std::map pcToBCOffset_; std::vector suspendAndResumeGates_ {}; }; } // namespace panda::ecmascript::kungfu #endif // ECMASCRIPT_CLASS_LINKER_BYTECODE_CIRCUIT_IR_BUILDER_H