// Copyright 2018 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef V8_CODEGEN_CONSTANT_POOL_H_ #define V8_CODEGEN_CONSTANT_POOL_H_ #include #include "src/codegen/label.h" #include "src/codegen/reloc-info.h" #include "src/common/globals.h" #include "src/numbers/double.h" namespace v8 { namespace internal { class Instruction; // ----------------------------------------------------------------------------- // Constant pool support class ConstantPoolEntry { public: ConstantPoolEntry() = default; ConstantPoolEntry(int position, intptr_t value, bool sharing_ok, RelocInfo::Mode rmode = RelocInfo::NONE) : position_(position), merged_index_(sharing_ok ? SHARING_ALLOWED : SHARING_PROHIBITED), value_(value), rmode_(rmode) {} ConstantPoolEntry(int position, Double value, RelocInfo::Mode rmode = RelocInfo::NONE) : position_(position), merged_index_(SHARING_ALLOWED), value64_(value.AsUint64()), rmode_(rmode) {} int position() const { return position_; } bool sharing_ok() const { return merged_index_ != SHARING_PROHIBITED; } bool is_merged() const { return merged_index_ >= 0; } int merged_index() const { DCHECK(is_merged()); return merged_index_; } void set_merged_index(int index) { DCHECK(sharing_ok()); merged_index_ = index; DCHECK(is_merged()); } int offset() const { DCHECK_GE(merged_index_, 0); return merged_index_; } void set_offset(int offset) { DCHECK_GE(offset, 0); merged_index_ = offset; } intptr_t value() const { return value_; } uint64_t value64() const { return value64_; } RelocInfo::Mode rmode() const { return rmode_; } enum Type { INTPTR, DOUBLE, NUMBER_OF_TYPES }; static int size(Type type) { return (type == INTPTR) ? kSystemPointerSize : kDoubleSize; } enum Access { REGULAR, OVERFLOWED }; private: int position_; int merged_index_; union { intptr_t value_; uint64_t value64_; }; // TODO(leszeks): The way we use this, it could probably be packed into // merged_index_ if size is a concern. RelocInfo::Mode rmode_; enum { SHARING_PROHIBITED = -2, SHARING_ALLOWED = -1 }; }; #if defined(V8_TARGET_ARCH_PPC) // ----------------------------------------------------------------------------- // Embedded constant pool support class ConstantPoolBuilder { public: ConstantPoolBuilder(int ptr_reach_bits, int double_reach_bits); #ifdef DEBUG ~ConstantPoolBuilder() { // Unused labels to prevent DCHECK failures. emitted_label_.Unuse(); emitted_label_.UnuseNear(); } #endif // Add pointer-sized constant to the embedded constant pool ConstantPoolEntry::Access AddEntry(int position, intptr_t value, bool sharing_ok) { ConstantPoolEntry entry(position, value, sharing_ok); return AddEntry(&entry, ConstantPoolEntry::INTPTR); } // Add double constant to the embedded constant pool ConstantPoolEntry::Access AddEntry(int position, Double value) { ConstantPoolEntry entry(position, value); return AddEntry(&entry, ConstantPoolEntry::DOUBLE); } // Add double constant to the embedded constant pool ConstantPoolEntry::Access AddEntry(int position, double value) { return AddEntry(position, Double(value)); } // Previews the access type required for the next new entry to be added. ConstantPoolEntry::Access NextAccess(ConstantPoolEntry::Type type) const; bool IsEmpty() { return info_[ConstantPoolEntry::INTPTR].entries.empty() && info_[ConstantPoolEntry::INTPTR].shared_entries.empty() && info_[ConstantPoolEntry::DOUBLE].entries.empty() && info_[ConstantPoolEntry::DOUBLE].shared_entries.empty(); } // Emit the constant pool. Invoke only after all entries have been // added and all instructions have been emitted. // Returns position of the emitted pool (zero implies no constant pool). int Emit(Assembler* assm); // Returns the label associated with the start of the constant pool. // Linking to this label in the function prologue may provide an // efficient means of constant pool pointer register initialization // on some architectures. inline Label* EmittedPosition() { return &emitted_label_; } private: ConstantPoolEntry::Access AddEntry(ConstantPoolEntry* entry, ConstantPoolEntry::Type type); void EmitSharedEntries(Assembler* assm, ConstantPoolEntry::Type type); void EmitGroup(Assembler* assm, ConstantPoolEntry::Access access, ConstantPoolEntry::Type type); struct PerTypeEntryInfo { PerTypeEntryInfo() : regular_count(0), overflow_start(-1) {} bool overflow() const { return (overflow_start >= 0 && overflow_start < static_cast(entries.size())); } int regular_reach_bits; int regular_count; int overflow_start; std::vector entries; std::vector shared_entries; }; Label emitted_label_; // Records pc_offset of emitted pool PerTypeEntryInfo info_[ConstantPoolEntry::NUMBER_OF_TYPES]; }; #endif // defined(V8_TARGET_ARCH_PPC) #if defined(V8_TARGET_ARCH_ARM64) class ConstantPoolKey { public: explicit ConstantPoolKey(uint64_t value, RelocInfo::Mode rmode = RelocInfo::NONE) : is_value32_(false), value64_(value), rmode_(rmode) {} explicit ConstantPoolKey(uint32_t value, RelocInfo::Mode rmode = RelocInfo::NONE) : is_value32_(true), value32_(value), rmode_(rmode) {} uint64_t value64() const { CHECK(!is_value32_); return value64_; } uint32_t value32() const { CHECK(is_value32_); return value32_; } bool is_value32() const { return is_value32_; } RelocInfo::Mode rmode() const { return rmode_; } bool AllowsDeduplication() const { DCHECK(rmode_ != RelocInfo::CONST_POOL && rmode_ != RelocInfo::VENEER_POOL && rmode_ != RelocInfo::DEOPT_SCRIPT_OFFSET && rmode_ != RelocInfo::DEOPT_INLINING_ID && rmode_ != RelocInfo::DEOPT_REASON && rmode_ != RelocInfo::DEOPT_ID); // CODE_TARGETs can be shared because they aren't patched anymore, // and we make sure we emit only one reloc info for them (thus delta // patching) will apply the delta only once. At the moment, we do not dedup // code targets if they are wrapped in a heap object request (value == 0). bool is_sharable_code_target = rmode_ == RelocInfo::CODE_TARGET && (is_value32() ? (value32() != 0) : (value64() != 0)); bool is_sharable_embedded_object = RelocInfo::IsEmbeddedObjectMode(rmode_); return RelocInfo::IsShareableRelocMode(rmode_) || is_sharable_code_target || is_sharable_embedded_object; } private: bool is_value32_; union { uint64_t value64_; uint32_t value32_; }; RelocInfo::Mode rmode_; }; // Order for pool entries. 64bit entries go first. inline bool operator<(const ConstantPoolKey& a, const ConstantPoolKey& b) { if (a.is_value32() < b.is_value32()) return true; if (a.is_value32() > b.is_value32()) return false; if (a.rmode() < b.rmode()) return true; if (a.rmode() > b.rmode()) return false; if (a.is_value32()) return a.value32() < b.value32(); return a.value64() < b.value64(); } inline bool operator==(const ConstantPoolKey& a, const ConstantPoolKey& b) { if (a.rmode() != b.rmode() || a.is_value32() != b.is_value32()) { return false; } if (a.is_value32()) return a.value32() == b.value32(); return a.value64() == b.value64(); } // Constant pool generation enum class Jump { kOmitted, kRequired }; enum class Emission { kIfNeeded, kForced }; enum class Alignment { kOmitted, kRequired }; enum class RelocInfoStatus { kMustRecord, kMustOmitForDuplicate }; enum class PoolEmissionCheck { kSkip }; // Pools are emitted in the instruction stream, preferably after unconditional // jumps or after returns from functions (in dead code locations). // If a long code sequence does not contain unconditional jumps, it is // necessary to emit the constant pool before the pool gets too far from the // location it is accessed from. In this case, we emit a jump over the emitted // constant pool. // Constants in the pool may be addresses of functions that gets relocated; // if so, a relocation info entry is associated to the constant pool entry. class ConstantPool { public: explicit ConstantPool(Assembler* assm); ~ConstantPool(); // Returns true when we need to write RelocInfo and false when we do not. RelocInfoStatus RecordEntry(uint32_t data, RelocInfo::Mode rmode); RelocInfoStatus RecordEntry(uint64_t data, RelocInfo::Mode rmode); size_t Entry32Count() const { return entry32_count_; } size_t Entry64Count() const { return entry64_count_; } bool IsEmpty() const { return entries_.empty(); } // Check if pool will be out of range at {pc_offset}. bool IsInImmRangeIfEmittedAt(int pc_offset); // Size in bytes of the constant pool. Depending on parameters, the size will // include the branch over the pool and alignment padding. int ComputeSize(Jump require_jump, Alignment require_alignment) const; // Emit the pool at the current pc with a branch over the pool if requested. void EmitAndClear(Jump require); bool ShouldEmitNow(Jump require_jump, size_t margin = 0) const; V8_EXPORT_PRIVATE void Check(Emission force_emission, Jump require_jump, size_t margin = 0); V8_EXPORT_PRIVATE void MaybeCheck(); void Clear(); // Constant pool emisssion can be blocked temporarily. bool IsBlocked() const; // Repeated checking whether the constant pool should be emitted is expensive; // only check once a number of instructions have been generated. void SetNextCheckIn(size_t instructions); // Class for scoping postponing the constant pool generation. class V8_EXPORT_PRIVATE BlockScope { public: // BlockScope immediatelly emits the pool if necessary to ensure that // during the block scope at least {margin} bytes can be emitted without // pool emission becomming necessary. explicit BlockScope(Assembler* pool, size_t margin = 0); BlockScope(Assembler* pool, PoolEmissionCheck); ~BlockScope(); private: ConstantPool* pool_; DISALLOW_IMPLICIT_CONSTRUCTORS(BlockScope); }; // Hard limit to the const pool which must not be exceeded. static const size_t kMaxDistToPool32; static const size_t kMaxDistToPool64; // Approximate distance where the pool should be emitted. static const size_t kApproxDistToPool32; V8_EXPORT_PRIVATE static const size_t kApproxDistToPool64; // Approximate distance where the pool may be emitted if // no jump is required (due to a recent unconditional jump). static const size_t kOpportunityDistToPool32; static const size_t kOpportunityDistToPool64; // PC distance between constant pool checks. V8_EXPORT_PRIVATE static const size_t kCheckInterval; // Number of entries in the pool which trigger a check. static const size_t kApproxMaxEntryCount; private: void StartBlock(); void EndBlock(); void EmitEntries(); void EmitPrologue(Alignment require_alignment); int PrologueSize(Jump require_jump) const; RelocInfoStatus RecordKey(ConstantPoolKey key, int offset); RelocInfoStatus GetRelocInfoStatusFor(const ConstantPoolKey& key); void Emit(const ConstantPoolKey& key); void SetLoadOffsetToConstPoolEntry(int load_offset, Instruction* entry_offset, const ConstantPoolKey& key); Alignment IsAlignmentRequiredIfEmittedAt(Jump require_jump, int pc_offset) const; Assembler* assm_; // Keep track of the first instruction requiring a constant pool entry // since the previous constant pool was emitted. int first_use_32_ = -1; int first_use_64_ = -1; // We sort not according to insertion order, but since we do not insert // addresses (for heap objects we insert an index which is created in // increasing order), the order is deterministic. We map each entry to the // pc offset of the load. We use a multimap because we need to record the // pc offset of each load of the same constant so that the immediate of the // loads can be back-patched when the pool is emitted. std::multimap entries_; size_t entry32_count_ = 0; size_t entry64_count_ = 0; int next_check_ = 0; int blocked_nesting_ = 0; }; #endif // defined(V8_TARGET_ARCH_ARM64) } // namespace internal } // namespace v8 #endif // V8_CODEGEN_CONSTANT_POOL_H_