/* * Copyright (c) 2022 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_COMPILER_BYTECODES_H #define ECMASCRIPT_COMPILER_BYTECODES_H #include <cstddef> #include <array> #include "libpandabase/macros.h" #include "libpandabase/utils/bit_field.h" #include "libpandafile/bytecode_instruction-inl.h" #include "ecmascript/common.h" #include "ecmascript/js_tagged_value.h" namespace panda::ecmascript::kungfu { using VRegIDType = uint32_t; using ICSlotIdType = uint16_t; using ImmValueType = uint64_t; using EcmaOpcode = BytecodeInstruction::Opcode; class BytecodeCircuitBuilder; class Bytecodes; class BytecodeInfo; class BytecodeIterator; enum BytecodeFlags : uint32_t { READ_ACC = 1 << 0, // 1: flag bit WRITE_ACC = 1 << 1, // 1: flag 1 SUPPORT_DEOPT = 1 << 2, // 2: flag 2 GENERAL_BC = 1 << 3, READ_THIS_OBJECT = 1 << 4, NO_SIDE_EFFECTS = 1 << 5, NO_THROW = 1 << 6, READ_ENV = 1 << 7, WRITE_ENV = 1 << 8, READ_FUNC = 1 << 9, READ_NEWTARGET = 1 << 10, READ_ARGC = 1 << 11, NO_GC = 1 << 12, DEBUGGER_STMT = 1 << 13, }; enum BytecodeKind : uint32_t { GENERAL = 0, THROW_BC, RETURN_BC, JUMP_IMM, CONDITIONAL_JUMP, MOV, SET_CONSTANT, SUSPEND, RESUME, GENERATOR_RESOLVE, DISCARDED, CALL_BC, ACCESSOR_BC, }; class BytecodeMetaData { public: static constexpr uint32_t MAX_OPCODE_SIZE = 16; static constexpr uint32_t MAX_SIZE_BITS = 4; static constexpr uint32_t BYTECODE_FLAGS_SIZE = 14; static constexpr uint32_t BYTECODE_KIND_SIZE = 4; using OpcodeField = panda::BitField<EcmaOpcode, 0, MAX_OPCODE_SIZE>; using SizeField = OpcodeField::NextField<size_t, MAX_SIZE_BITS>; using KindField = SizeField::NextField<BytecodeKind, BYTECODE_KIND_SIZE>; using FlagsField = KindField::NextField<BytecodeFlags, BYTECODE_FLAGS_SIZE>; bool HasAccIn() const { return HasFlag(BytecodeFlags::READ_ACC); } bool IsNoSideEffects() const { return HasFlag(BytecodeFlags::NO_SIDE_EFFECTS); } bool IsNoThrow() const { return HasFlag(BytecodeFlags::NO_THROW); } bool HasThisIn() const { return HasFlag(BytecodeFlags::READ_THIS_OBJECT); } bool HasAccOut() const { return HasFlag(BytecodeFlags::WRITE_ACC); } bool HasEnvIn() const { return HasFlag(BytecodeFlags::READ_ENV); } bool HasEnvOut() const { return HasFlag(BytecodeFlags::WRITE_ENV); } bool IsNoGC() const { return HasFlag(BytecodeFlags::NO_GC); } bool IsMov() const { return GetKind() == BytecodeKind::MOV; } bool IsReturn() const { return GetKind() == BytecodeKind::RETURN_BC; } bool IsThrow() const { return GetKind() == BytecodeKind::THROW_BC; } bool IsJump() const { return IsJumpImm() || IsCondJump(); } bool IsCondJump() const { return GetKind() == BytecodeKind::CONDITIONAL_JUMP; } bool IsJumpImm() const { return GetKind() == BytecodeKind::JUMP_IMM; } bool IsSuspend() const { return GetKind() == BytecodeKind::SUSPEND; } bool IsSetConstant() const { return GetKind() == BytecodeKind::SET_CONSTANT; } bool SupportDeopt() const { return HasFlag(BytecodeFlags::SUPPORT_DEOPT); } size_t GetSize() const { return SizeField::Get(value_); } bool IsGeneral() const { return HasFlag(BytecodeFlags::GENERAL_BC); } bool IsGeneratorResolve() const { return GetKind() == BytecodeKind::GENERATOR_RESOLVE; } bool IsGeneratorRelative() const { BytecodeKind kind = GetKind(); return (kind == BytecodeKind::RESUME) || (kind == BytecodeKind::SUSPEND) || (kind == BytecodeKind::GENERATOR_RESOLVE); } bool IsDiscarded() const { return GetKind() == BytecodeKind::DISCARDED; } bool HasFuncIn() const { return HasFlag(BytecodeFlags::READ_FUNC); } bool HasNewTargetIn() const { return HasFlag(BytecodeFlags::READ_NEWTARGET); } bool HasArgcIn() const { return HasFlag(BytecodeFlags::READ_ARGC); } inline EcmaOpcode GetOpcode() const { return OpcodeField::Get(value_); } bool IsInvalid() const { return value_ == 0; } bool IsCall() const { return GetKind() == BytecodeKind::CALL_BC; } bool IsAccessorBC() const { return GetKind() == BytecodeKind::ACCESSOR_BC; } bool HasDebuggerStmt() const { return HasFlag(BytecodeFlags::DEBUGGER_STMT); } private: BytecodeMetaData() = default; DEFAULT_NOEXCEPT_MOVE_SEMANTIC(BytecodeMetaData); DEFAULT_COPY_SEMANTIC(BytecodeMetaData); explicit BytecodeMetaData(uint64_t value) : value_(value) {} static BytecodeMetaData InitBytecodeMetaData(const uint8_t *pc); inline bool HasFlag(BytecodeFlags flag) const { return (GetFlags() & flag) == flag; } inline BytecodeFlags GetFlags() const { return FlagsField::Get(value_); } inline BytecodeKind GetKind() const { return KindField::Get(value_); } uint64_t value_ {0}; friend class Bytecodes; friend class BytecodeInfo; friend class BytecodeCircuitBuilder; }; class Bytecodes { public: static constexpr uint32_t NUM_BYTECODES = 0xFF; static constexpr uint32_t OPCODE_MASK = 0xFF00; static constexpr uint32_t BYTE_SIZE = 8; static constexpr uint32_t CALLRUNTIME_PREFIX_OPCODE_INDEX = 251; static constexpr uint32_t DEPRECATED_PREFIX_OPCODE_INDEX = 252; static constexpr uint32_t WIDE_PREFIX_OPCODE_INDEX = 253; static constexpr uint32_t THROW_PREFIX_OPCODE_INDEX = 254; static constexpr uint32_t MIN_PREFIX_OPCODE_INDEX = CALLRUNTIME_PREFIX_OPCODE_INDEX; static constexpr uint32_t LAST_OPCODE = static_cast<uint32_t>(EcmaOpcode::DEFINEFIELDBYNAME_IMM8_ID16_V8); static constexpr uint32_t LAST_DEPRECATED_OPCODE = static_cast<uint32_t>(EcmaOpcode::DEPRECATED_DYNAMICIMPORT_PREF_V8); static constexpr uint32_t LAST_WIDE_OPCODE = static_cast<uint32_t>(EcmaOpcode::WIDE_STPATCHVAR_PREF_IMM16); static constexpr uint32_t LAST_THROW_OPCODE = static_cast<uint32_t>(EcmaOpcode::THROW_UNDEFINEDIFHOLEWITHNAME_PREF_ID16); static constexpr uint32_t LAST_CALLRUNTIME_OPCODE = static_cast<uint32_t>(EcmaOpcode::CALLRUNTIME_LDSENDABLECLASS_PREF_IMM16); static_assert(CALLRUNTIME_PREFIX_OPCODE_INDEX == static_cast<uint32_t>(EcmaOpcode::CALLRUNTIME_NOTIFYCONCURRENTRESULT_PREF_NONE)); static_assert(DEPRECATED_PREFIX_OPCODE_INDEX == static_cast<uint32_t>(EcmaOpcode::DEPRECATED_LDLEXENV_PREF_NONE)); static_assert(WIDE_PREFIX_OPCODE_INDEX == static_cast<uint32_t>(EcmaOpcode::WIDE_CREATEOBJECTWITHEXCLUDEDKEYS_PREF_IMM16_V8_V8)); static_assert(THROW_PREFIX_OPCODE_INDEX == static_cast<uint32_t>(EcmaOpcode::THROW_PREF_NONE)); Bytecodes(); Bytecodes(const Bytecodes&) = delete; void operator=(const Bytecodes&) = delete; static EcmaOpcode GetOpcode(const uint8_t *pc) { uint8_t primary = ReadByte(pc); if (primary >= MIN_PREFIX_OPCODE_INDEX) { uint8_t secondary = ReadByte1(pc); return static_cast<EcmaOpcode>((secondary << 8U) | primary); // 8: byte size } return static_cast<EcmaOpcode>(primary); } BytecodeMetaData GetBytecodeMetaData(const uint8_t *pc) const { uint8_t primary = ReadByte(pc); if (primary >= MIN_PREFIX_OPCODE_INDEX) { uint8_t secondary = ReadByte1(pc); if (primary == CALLRUNTIME_PREFIX_OPCODE_INDEX) { return callRuntimeBytecodes_[secondary]; } else if (primary == DEPRECATED_PREFIX_OPCODE_INDEX) { return deprecatedBytecodes_[secondary]; } else if (primary == WIDE_PREFIX_OPCODE_INDEX) { return wideBytecodes_[secondary]; } else { ASSERT(primary == THROW_PREFIX_OPCODE_INDEX); return throwBytecodes_[secondary]; } } return bytecodes_[primary]; } static bool IsCallOp(EcmaOpcode opcode) { switch (opcode) { case EcmaOpcode::CALLARG0_IMM8: case EcmaOpcode::CALLARG1_IMM8_V8: case EcmaOpcode::CALLARGS2_IMM8_V8_V8: case EcmaOpcode::CALLARGS3_IMM8_V8_V8_V8: case EcmaOpcode::CALLRANGE_IMM8_IMM8_V8: case EcmaOpcode::WIDE_CALLRANGE_PREF_IMM16_V8: case EcmaOpcode::CALLTHIS0_IMM8_V8: case EcmaOpcode::CALLTHIS1_IMM8_V8_V8: case EcmaOpcode::CALLTHIS2_IMM8_V8_V8_V8: case EcmaOpcode::CALLTHIS3_IMM8_V8_V8_V8_V8: case EcmaOpcode::CALLTHISRANGE_IMM8_IMM8_V8: case EcmaOpcode::WIDE_CALLTHISRANGE_PREF_IMM16_V8: case EcmaOpcode::CALLRUNTIME_CALLINIT_PREF_IMM8_V8: return true; default: return false; } } static bool IsCreateObjectWithBufferOp(EcmaOpcode opcode) { switch (opcode) { case EcmaOpcode::CREATEOBJECTWITHBUFFER_IMM8_ID16: case EcmaOpcode::CREATEOBJECTWITHBUFFER_IMM16_ID16: return true; default: return false; } } static bool IsCreateEmptyArrayOp(EcmaOpcode opcode) { switch (opcode) { case EcmaOpcode::CREATEEMPTYARRAY_IMM8: case EcmaOpcode::CREATEEMPTYARRAY_IMM16: return true; default: return false; } } static bool IsCreateArrayWithBufferOp(EcmaOpcode opcode) { switch (opcode) { case EcmaOpcode::CREATEARRAYWITHBUFFER_IMM8_ID16: case EcmaOpcode::CREATEARRAYWITHBUFFER_IMM16_ID16: return true; default: return false; } } static bool IsDefineClassWithBufferOp(EcmaOpcode opcode) { switch (opcode) { case EcmaOpcode::DEFINECLASSWITHBUFFER_IMM8_ID16_ID16_IMM16_V8: case EcmaOpcode::DEFINECLASSWITHBUFFER_IMM16_ID16_ID16_IMM16_V8: return true; default: return false; } } static bool IsLdLexVarOp(EcmaOpcode opcode) { switch (opcode) { case EcmaOpcode::LDLEXVAR_IMM4_IMM4: case EcmaOpcode::LDLEXVAR_IMM8_IMM8: case EcmaOpcode::WIDE_LDLEXVAR_PREF_IMM16_IMM16: return true; default: return false; } } static bool IsStLexVarOp(EcmaOpcode opcode) { switch (opcode) { case EcmaOpcode::STLEXVAR_IMM4_IMM4: case EcmaOpcode::STLEXVAR_IMM8_IMM8: case EcmaOpcode::WIDE_STLEXVAR_PREF_IMM16_IMM16: return true; default: return false; } } static bool IsCallOrAccessorOp(EcmaOpcode opcode) { switch (opcode) { case EcmaOpcode::CALLARG0_IMM8: case EcmaOpcode::CALLARG1_IMM8_V8: case EcmaOpcode::CALLARGS2_IMM8_V8_V8: case EcmaOpcode::CALLARGS3_IMM8_V8_V8_V8: case EcmaOpcode::CALLRANGE_IMM8_IMM8_V8: case EcmaOpcode::WIDE_CALLRANGE_PREF_IMM16_V8: case EcmaOpcode::CALLTHIS0_IMM8_V8: case EcmaOpcode::CALLTHIS1_IMM8_V8_V8: case EcmaOpcode::CALLTHIS2_IMM8_V8_V8_V8: case EcmaOpcode::CALLTHIS3_IMM8_V8_V8_V8_V8: case EcmaOpcode::CALLTHISRANGE_IMM8_IMM8_V8: case EcmaOpcode::WIDE_CALLTHISRANGE_PREF_IMM16_V8: case EcmaOpcode::LDOBJBYNAME_IMM8_ID16: case EcmaOpcode::LDOBJBYNAME_IMM16_ID16: case EcmaOpcode::LDTHISBYNAME_IMM8_ID16: case EcmaOpcode::LDTHISBYNAME_IMM16_ID16: case EcmaOpcode::STOBJBYNAME_IMM8_ID16_V8: case EcmaOpcode::STOBJBYNAME_IMM16_ID16_V8: case EcmaOpcode::STTHISBYNAME_IMM8_ID16: case EcmaOpcode::STTHISBYNAME_IMM16_ID16: case EcmaOpcode::CALLRUNTIME_CALLINIT_PREF_IMM8_V8: return true; default: return false; } } static bool IsDefineFunc(EcmaOpcode opcode) { switch (opcode) { case EcmaOpcode::DEFINEFUNC_IMM8_ID16_IMM8: case EcmaOpcode::DEFINEFUNC_IMM16_ID16_IMM8: return true; default: return false; } } private: static uint8_t ReadByte(const uint8_t *pc) { return *pc; } static uint8_t ReadByte1(const uint8_t *pc) { return *(pc + 1); // 1: byte1 } BytecodeMetaData InitBytecodeMetaData(const uint8_t *pc); std::array<BytecodeMetaData, NUM_BYTECODES> bytecodes_{}; std::array<BytecodeMetaData, NUM_BYTECODES> callRuntimeBytecodes_{}; std::array<BytecodeMetaData, NUM_BYTECODES> deprecatedBytecodes_{}; std::array<BytecodeMetaData, NUM_BYTECODES> wideBytecodes_{}; std::array<BytecodeMetaData, NUM_BYTECODES> throwBytecodes_{}; }; enum class ConstDataIDType : uint8_t { StringIDType, MethodIDType, ArrayLiteralIDType, ObjectLiteralIDType, ClassLiteralIDType, }; 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(base::bit_cast<double>(value_)).GetRawData(); } ImmValueType GetValue() const { return value_; } private: ImmValueType value_; }; class ICSlotId { public: explicit ICSlotId(ICSlotIdType id) : id_(id) { } ~ICSlotId() = default; void SetId(ICSlotIdType id) { id_ = id; } ICSlotIdType GetId() const { return id_; } private: ICSlotIdType id_; }; class ConstDataId { public: ConstDataId(ConstDataIDType type, uint16_t id) :type_(type), id_(id) { } explicit ConstDataId(uint64_t bitfield) { type_ = ConstDataIDType(bitfield >> TYPE_SHIFT); id_ = bitfield & ((1 << TYPE_SHIFT) - 1); } ~ConstDataId() = default; void SetId(uint16_t id) { id_ = id; } uint16_t GetId() const { return id_; } void SetType(ConstDataIDType type) { type_ = type; } ConstDataIDType GetType() const { return type_; } bool IsStringId() const { return type_ == ConstDataIDType::StringIDType; } bool IsMethodId() const { return type_ == ConstDataIDType::MethodIDType; } bool IsClassLiteraId() const { return type_ == ConstDataIDType::ClassLiteralIDType; } bool IsObjectLiteralID() const { return type_ == ConstDataIDType::ObjectLiteralIDType; } bool IsArrayLiteralID() const { return type_ == ConstDataIDType::ArrayLiteralIDType; } uint64_t CaculateBitField() const { return (static_cast<uint8_t>(type_) << TYPE_SHIFT) | id_; } private: static constexpr int TYPE_SHIFT = 16; ConstDataIDType type_; uint16_t id_; }; class BytecodeInfo { public: // set of id, immediate and read register std::vector<std::variant<ConstDataId, ICSlotId, Immediate, VirtualRegister>> inputs {}; std::vector<VRegIDType> vregOut {}; // write register bool Deopt() const { return metaData_.SupportDeopt(); } bool AccOut() const { return metaData_.HasAccOut(); } bool AccIn() const { return metaData_.HasAccIn(); } bool EnvIn() const { return metaData_.HasEnvIn(); } bool EnvOut() const { return metaData_.HasEnvOut(); } bool NoSideEffects() const { return metaData_.IsNoSideEffects(); } bool NoThrow() const { return metaData_.IsNoThrow(); } bool ThisObjectIn() const { return metaData_.HasThisIn(); } size_t GetSize() const { return metaData_.GetSize(); } bool IsDef() const { return (!vregOut.empty()) || AccOut(); } bool IsOut(VRegIDType reg, uint32_t index) const { bool isDefined = (!vregOut.empty() && (reg == vregOut.at(index))); return isDefined; } bool IsMov() const { return metaData_.IsMov(); } bool IsJump() const { return metaData_.IsJump(); } bool IsCondJump() const { return metaData_.IsCondJump(); } bool IsReturn() const { return metaData_.IsReturn(); } bool IsThrow() const { return metaData_.IsThrow(); } bool IsSuspend() const { return metaData_.IsSuspend(); } bool IsGeneratorResolve() const { return metaData_.IsGeneratorResolve(); } bool IsDiscarded() const { return metaData_.IsDiscarded(); } bool IsSetConstant() const { return metaData_.IsSetConstant(); } bool IsGeneral() const { return metaData_.IsGeneral(); } bool needFallThrough() const { return !IsJump() && !IsReturn() && !IsThrow(); } bool IsGeneratorRelative() const { return metaData_.IsGeneratorRelative(); } size_t ComputeValueInputCount() const { return (AccIn() ? 1 : 0) + inputs.size(); } size_t ComputeOutCount() const { return (AccOut() ? 1 : 0) + vregOut.size(); } bool IsBc(EcmaOpcode ecmaOpcode) const { return metaData_.GetOpcode() == ecmaOpcode; } bool HasFuncIn() const { return metaData_.HasFuncIn(); } bool HasNewTargetIn() const { return metaData_.HasNewTargetIn(); } bool HasArgcIn() const { return metaData_.HasArgcIn(); } bool HasFrameArgs() const { return HasFuncIn() || HasNewTargetIn() || ThisObjectIn() || HasArgcIn(); } bool HasFrameState() const { return HasFrameArgs() || !NoThrow(); } bool IsCall() const { return metaData_.IsCall(); } bool IsAccessorBC() const { return metaData_.IsAccessorBC(); } bool HasDebuggerStmt() const { return metaData_.HasDebuggerStmt(); } inline EcmaOpcode GetOpcode() const { return metaData_.GetOpcode(); } static void InitBytecodeInfo(BytecodeCircuitBuilder *builder, BytecodeInfo &info, const uint8_t* pc); private: BytecodeMetaData metaData_ { 0 }; friend class BytecodeCircuitBuilder; }; class BytecodeIterator { public: static constexpr int INVALID_INDEX = -1; BytecodeIterator() = default; BytecodeIterator(BytecodeCircuitBuilder *builder, uint32_t start, uint32_t end) : builder_(builder), start_(start), end_(end) {} void Reset(BytecodeCircuitBuilder *builder, uint32_t start, uint32_t end) { builder_ = builder; start_ = static_cast<int32_t>(start); end_ = static_cast<int32_t>(end); } BytecodeIterator& operator++() { if (InRange()) { index_++; } return *this; } BytecodeIterator& operator--() { if (InRange()) { index_--; } return *this; } void Goto(uint32_t i) { index_ = static_cast<int32_t>(i); } void GotoStart() { index_ = start_; } void GotoEnd() { index_ = end_; ASSERT(InRange()); } bool IsInRange(int idx) const { return (idx <= end_) && (idx >= start_); } bool InRange() const { return (index_ <= end_) && (index_ >= start_); } bool Done() const { return !InRange(); } uint32_t Index() const { return static_cast<uint32_t>(index_); } const BytecodeInfo &GetBytecodeInfo() const; const uint8_t *PeekNextPc(size_t i) const; const uint8_t *PeekPrevPc(size_t i) const; private: BytecodeCircuitBuilder *builder_ {nullptr}; int32_t start_ {0}; int32_t end_ {0}; int32_t index_{ INVALID_INDEX }; }; class BytecodeCallArgc { public: static int ComputeCallArgc(int gateNumIn, EcmaOpcode op) { switch (op) { case EcmaOpcode::CALLTHIS1_IMM8_V8_V8: case EcmaOpcode::CALLTHIS2_IMM8_V8_V8_V8: case EcmaOpcode::CALLTHIS3_IMM8_V8_V8_V8_V8: case EcmaOpcode::CALLTHISRANGE_IMM8_IMM8_V8: case EcmaOpcode::WIDE_CALLTHISRANGE_PREF_IMM16_V8: case EcmaOpcode::CALLTHIS0_IMM8_V8: case EcmaOpcode::CALLRUNTIME_CALLINIT_PREF_IMM8_V8: { return gateNumIn + NUM_MANDATORY_JSFUNC_ARGS - 2; // 2: calltarget, this } default: { return gateNumIn + NUM_MANDATORY_JSFUNC_ARGS - 1; // 1: calltarget } } } }; } // panda::ecmascript::kungfu #endif // ECMASCRIPT_COMPILER_BYTECODES_H