| // Copyright (c) 1994-2006 Sun Microsystems Inc. | |
| // All Rights Reserved. | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are | |
| // met: | |
| // | |
| // - Redistributions of source code must retain the above copyright notice, | |
| // this list of conditions and the following disclaimer. | |
| // | |
| // - Redistribution in binary form must reproduce the above copyright | |
| // notice, this list of conditions and the following disclaimer in the | |
| // documentation and/or other materials provided with the distribution. | |
| // | |
| // - Neither the name of Sun Microsystems or the names of contributors may | |
| // be used to endorse or promote products derived from this software without | |
| // specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS | |
| // IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, | |
| // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
| // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |
| // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
| // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
| // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
| // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
| // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
| // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
| // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| // The original source code covered by the above license above has been | |
| // modified significantly by Google Inc. | |
| // Copyright 2012 the V8 project authors. All rights reserved. | |
| #include "src/assembler.h" | |
| #include "src/assembler-inl.h" | |
| #include "src/code-stubs.h" | |
| #include "src/deoptimizer.h" | |
| #include "src/disassembler.h" | |
| #include "src/instruction-stream.h" | |
| #include "src/isolate.h" | |
| #include "src/ostreams.h" | |
| #include "src/simulator.h" // For flushing instruction cache. | |
| #include "src/snapshot/serializer-common.h" | |
| #include "src/snapshot/snapshot.h" | |
| namespace v8 { | |
| namespace internal { | |
| AssemblerOptions AssemblerOptions::Default( | |
| Isolate* isolate, bool explicitly_support_serialization) { | |
| AssemblerOptions options; | |
| bool serializer = | |
| isolate->serializer_enabled() || explicitly_support_serialization; | |
| options.record_reloc_info_for_serialization = serializer; | |
| options.enable_root_array_delta_access = !serializer; | |
| #ifdef USE_SIMULATOR | |
| // Don't generate simulator specific code if we are building a snapshot, which | |
| // might be run on real hardware. | |
| options.enable_simulator_code = !serializer; | |
| #endif | |
| options.inline_offheap_trampolines = !serializer; | |
| #if V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_ARM64 | |
| options.code_range_start = | |
| isolate->heap()->memory_allocator()->code_range()->start(); | |
| #endif | |
| return options; | |
| } | |
| // ----------------------------------------------------------------------------- | |
| // Implementation of AssemblerBase | |
| AssemblerBase::AssemblerBase(const AssemblerOptions& options, void* buffer, | |
| int buffer_size) | |
| : options_(options), | |
| enabled_cpu_features_(0), | |
| emit_debug_code_(FLAG_debug_code), | |
| predictable_code_size_(false), | |
| constant_pool_available_(false), | |
| jump_optimization_info_(nullptr) { | |
| own_buffer_ = buffer == nullptr; | |
| if (buffer_size == 0) buffer_size = kMinimalBufferSize; | |
| DCHECK_GT(buffer_size, 0); | |
| if (own_buffer_) buffer = NewArray<byte>(buffer_size); | |
| buffer_ = static_cast<byte*>(buffer); | |
| buffer_size_ = buffer_size; | |
| pc_ = buffer_; | |
| } | |
| AssemblerBase::~AssemblerBase() { | |
| if (own_buffer_) DeleteArray(buffer_); | |
| } | |
| void AssemblerBase::FlushICache(void* start, size_t size) { | |
| if (size == 0) return; | |
| #if defined(USE_SIMULATOR) | |
| base::LockGuard<base::Mutex> lock_guard(Simulator::i_cache_mutex()); | |
| Simulator::FlushICache(Simulator::i_cache(), start, size); | |
| #else | |
| CpuFeatures::FlushICache(start, size); | |
| #endif // USE_SIMULATOR | |
| } | |
| void AssemblerBase::Print(Isolate* isolate) { | |
| StdoutStream os; | |
| v8::internal::Disassembler::Decode(isolate, &os, buffer_, pc_); | |
| } | |
| // ----------------------------------------------------------------------------- | |
| // Implementation of PredictableCodeSizeScope | |
| PredictableCodeSizeScope::PredictableCodeSizeScope(AssemblerBase* assembler, | |
| int expected_size) | |
| : assembler_(assembler), | |
| expected_size_(expected_size), | |
| start_offset_(assembler->pc_offset()), | |
| old_value_(assembler->predictable_code_size()) { | |
| assembler_->set_predictable_code_size(true); | |
| } | |
| PredictableCodeSizeScope::~PredictableCodeSizeScope() { | |
| CHECK_EQ(expected_size_, assembler_->pc_offset() - start_offset_); | |
| assembler_->set_predictable_code_size(old_value_); | |
| } | |
| // ----------------------------------------------------------------------------- | |
| // Implementation of CpuFeatureScope | |
| #ifdef DEBUG | |
| CpuFeatureScope::CpuFeatureScope(AssemblerBase* assembler, CpuFeature f, | |
| CheckPolicy check) | |
| : assembler_(assembler) { | |
| DCHECK_IMPLIES(check == kCheckSupported, CpuFeatures::IsSupported(f)); | |
| old_enabled_ = assembler_->enabled_cpu_features(); | |
| assembler_->EnableCpuFeature(f); | |
| } | |
| CpuFeatureScope::~CpuFeatureScope() { | |
| assembler_->set_enabled_cpu_features(old_enabled_); | |
| } | |
| #endif | |
| bool CpuFeatures::initialized_ = false; | |
| unsigned CpuFeatures::supported_ = 0; | |
| unsigned CpuFeatures::icache_line_size_ = 0; | |
| unsigned CpuFeatures::dcache_line_size_ = 0; | |
| ConstantPoolBuilder::ConstantPoolBuilder(int ptr_reach_bits, | |
| int double_reach_bits) { | |
| info_[ConstantPoolEntry::INTPTR].entries.reserve(64); | |
| info_[ConstantPoolEntry::INTPTR].regular_reach_bits = ptr_reach_bits; | |
| info_[ConstantPoolEntry::DOUBLE].regular_reach_bits = double_reach_bits; | |
| } | |
| ConstantPoolEntry::Access ConstantPoolBuilder::NextAccess( | |
| ConstantPoolEntry::Type type) const { | |
| const PerTypeEntryInfo& info = info_[type]; | |
| if (info.overflow()) return ConstantPoolEntry::OVERFLOWED; | |
| int dbl_count = info_[ConstantPoolEntry::DOUBLE].regular_count; | |
| int dbl_offset = dbl_count * kDoubleSize; | |
| int ptr_count = info_[ConstantPoolEntry::INTPTR].regular_count; | |
| int ptr_offset = ptr_count * kPointerSize + dbl_offset; | |
| if (type == ConstantPoolEntry::DOUBLE) { | |
| // Double overflow detection must take into account the reach for both types | |
| int ptr_reach_bits = info_[ConstantPoolEntry::INTPTR].regular_reach_bits; | |
| if (!is_uintn(dbl_offset, info.regular_reach_bits) || | |
| (ptr_count > 0 && | |
| !is_uintn(ptr_offset + kDoubleSize - kPointerSize, ptr_reach_bits))) { | |
| return ConstantPoolEntry::OVERFLOWED; | |
| } | |
| } else { | |
| DCHECK(type == ConstantPoolEntry::INTPTR); | |
| if (!is_uintn(ptr_offset, info.regular_reach_bits)) { | |
| return ConstantPoolEntry::OVERFLOWED; | |
| } | |
| } | |
| return ConstantPoolEntry::REGULAR; | |
| } | |
| ConstantPoolEntry::Access ConstantPoolBuilder::AddEntry( | |
| ConstantPoolEntry& entry, ConstantPoolEntry::Type type) { | |
| DCHECK(!emitted_label_.is_bound()); | |
| PerTypeEntryInfo& info = info_[type]; | |
| const int entry_size = ConstantPoolEntry::size(type); | |
| bool merged = false; | |
| if (entry.sharing_ok()) { | |
| // Try to merge entries | |
| std::vector<ConstantPoolEntry>::iterator it = info.shared_entries.begin(); | |
| int end = static_cast<int>(info.shared_entries.size()); | |
| for (int i = 0; i < end; i++, it++) { | |
| if ((entry_size == kPointerSize) ? entry.value() == it->value() | |
| : entry.value64() == it->value64()) { | |
| // Merge with found entry. | |
| entry.set_merged_index(i); | |
| merged = true; | |
| break; | |
| } | |
| } | |
| } | |
| // By definition, merged entries have regular access. | |
| DCHECK(!merged || entry.merged_index() < info.regular_count); | |
| ConstantPoolEntry::Access access = | |
| (merged ? ConstantPoolEntry::REGULAR : NextAccess(type)); | |
| // Enforce an upper bound on search time by limiting the search to | |
| // unique sharable entries which fit in the regular section. | |
| if (entry.sharing_ok() && !merged && access == ConstantPoolEntry::REGULAR) { | |
| info.shared_entries.push_back(entry); | |
| } else { | |
| info.entries.push_back(entry); | |
| } | |
| // We're done if we found a match or have already triggered the | |
| // overflow state. | |
| if (merged || info.overflow()) return access; | |
| if (access == ConstantPoolEntry::REGULAR) { | |
| info.regular_count++; | |
| } else { | |
| info.overflow_start = static_cast<int>(info.entries.size()) - 1; | |
| } | |
| return access; | |
| } | |
| void ConstantPoolBuilder::EmitSharedEntries(Assembler* assm, | |
| ConstantPoolEntry::Type type) { | |
| PerTypeEntryInfo& info = info_[type]; | |
| std::vector<ConstantPoolEntry>& shared_entries = info.shared_entries; | |
| const int entry_size = ConstantPoolEntry::size(type); | |
| int base = emitted_label_.pos(); | |
| DCHECK_GT(base, 0); | |
| int shared_end = static_cast<int>(shared_entries.size()); | |
| std::vector<ConstantPoolEntry>::iterator shared_it = shared_entries.begin(); | |
| for (int i = 0; i < shared_end; i++, shared_it++) { | |
| int offset = assm->pc_offset() - base; | |
| shared_it->set_offset(offset); // Save offset for merged entries. | |
| if (entry_size == kPointerSize) { | |
| assm->dp(shared_it->value()); | |
| } else { | |
| assm->dq(shared_it->value64()); | |
| } | |
| DCHECK(is_uintn(offset, info.regular_reach_bits)); | |
| // Patch load sequence with correct offset. | |
| assm->PatchConstantPoolAccessInstruction(shared_it->position(), offset, | |
| ConstantPoolEntry::REGULAR, type); | |
| } | |
| } | |
| void ConstantPoolBuilder::EmitGroup(Assembler* assm, | |
| ConstantPoolEntry::Access access, | |
| ConstantPoolEntry::Type type) { | |
| PerTypeEntryInfo& info = info_[type]; | |
| const bool overflow = info.overflow(); | |
| std::vector<ConstantPoolEntry>& entries = info.entries; | |
| std::vector<ConstantPoolEntry>& shared_entries = info.shared_entries; | |
| const int entry_size = ConstantPoolEntry::size(type); | |
| int base = emitted_label_.pos(); | |
| DCHECK_GT(base, 0); | |
| int begin; | |
| int end; | |
| if (access == ConstantPoolEntry::REGULAR) { | |
| // Emit any shared entries first | |
| EmitSharedEntries(assm, type); | |
| } | |
| if (access == ConstantPoolEntry::REGULAR) { | |
| begin = 0; | |
| end = overflow ? info.overflow_start : static_cast<int>(entries.size()); | |
| } else { | |
| DCHECK(access == ConstantPoolEntry::OVERFLOWED); | |
| if (!overflow) return; | |
| begin = info.overflow_start; | |
| end = static_cast<int>(entries.size()); | |
| } | |
| std::vector<ConstantPoolEntry>::iterator it = entries.begin(); | |
| if (begin > 0) std::advance(it, begin); | |
| for (int i = begin; i < end; i++, it++) { | |
| // Update constant pool if necessary and get the entry's offset. | |
| int offset; | |
| ConstantPoolEntry::Access entry_access; | |
| if (!it->is_merged()) { | |
| // Emit new entry | |
| offset = assm->pc_offset() - base; | |
| entry_access = access; | |
| if (entry_size == kPointerSize) { | |
| assm->dp(it->value()); | |
| } else { | |
| assm->dq(it->value64()); | |
| } | |
| } else { | |
| // Retrieve offset from shared entry. | |
| offset = shared_entries[it->merged_index()].offset(); | |
| entry_access = ConstantPoolEntry::REGULAR; | |
| } | |
| DCHECK(entry_access == ConstantPoolEntry::OVERFLOWED || | |
| is_uintn(offset, info.regular_reach_bits)); | |
| // Patch load sequence with correct offset. | |
| assm->PatchConstantPoolAccessInstruction(it->position(), offset, | |
| entry_access, type); | |
| } | |
| } | |
| // Emit and return position of pool. Zero implies no constant pool. | |
| int ConstantPoolBuilder::Emit(Assembler* assm) { | |
| bool emitted = emitted_label_.is_bound(); | |
| bool empty = IsEmpty(); | |
| if (!emitted) { | |
| // Mark start of constant pool. Align if necessary. | |
| if (!empty) assm->DataAlign(kDoubleSize); | |
| assm->bind(&emitted_label_); | |
| if (!empty) { | |
| // Emit in groups based on access and type. | |
| // Emit doubles first for alignment purposes. | |
| EmitGroup(assm, ConstantPoolEntry::REGULAR, ConstantPoolEntry::DOUBLE); | |
| EmitGroup(assm, ConstantPoolEntry::REGULAR, ConstantPoolEntry::INTPTR); | |
| if (info_[ConstantPoolEntry::DOUBLE].overflow()) { | |
| assm->DataAlign(kDoubleSize); | |
| EmitGroup(assm, ConstantPoolEntry::OVERFLOWED, | |
| ConstantPoolEntry::DOUBLE); | |
| } | |
| if (info_[ConstantPoolEntry::INTPTR].overflow()) { | |
| EmitGroup(assm, ConstantPoolEntry::OVERFLOWED, | |
| ConstantPoolEntry::INTPTR); | |
| } | |
| } | |
| } | |
| return !empty ? emitted_label_.pos() : 0; | |
| } | |
| HeapObjectRequest::HeapObjectRequest(double heap_number, int offset) | |
| : kind_(kHeapNumber), offset_(offset) { | |
| value_.heap_number = heap_number; | |
| DCHECK(!IsSmiDouble(value_.heap_number)); | |
| } | |
| HeapObjectRequest::HeapObjectRequest(CodeStub* code_stub, int offset) | |
| : kind_(kCodeStub), offset_(offset) { | |
| value_.code_stub = code_stub; | |
| DCHECK_NOT_NULL(value_.code_stub); | |
| } | |
| // Platform specific but identical code for all the platforms. | |
| void Assembler::RecordDeoptReason(DeoptimizeReason reason, | |
| SourcePosition position, int id) { | |
| EnsureSpace ensure_space(this); | |
| RecordRelocInfo(RelocInfo::DEOPT_SCRIPT_OFFSET, position.ScriptOffset()); | |
| RecordRelocInfo(RelocInfo::DEOPT_INLINING_ID, position.InliningId()); | |
| RecordRelocInfo(RelocInfo::DEOPT_REASON, static_cast<int>(reason)); | |
| RecordRelocInfo(RelocInfo::DEOPT_ID, id); | |
| } | |
| void Assembler::RecordComment(const char* msg) { | |
| if (FLAG_code_comments) { | |
| EnsureSpace ensure_space(this); | |
| RecordRelocInfo(RelocInfo::COMMENT, reinterpret_cast<intptr_t>(msg)); | |
| } | |
| } | |
| void Assembler::DataAlign(int m) { | |
| DCHECK(m >= 2 && base::bits::IsPowerOfTwo(m)); | |
| while ((pc_offset() & (m - 1)) != 0) { | |
| db(0); | |
| } | |
| } | |
| void AssemblerBase::RequestHeapObject(HeapObjectRequest request) { | |
| request.set_offset(pc_offset()); | |
| heap_object_requests_.push_front(request); | |
| } | |
| int AssemblerBase::AddCodeTarget(Handle<Code> target) { | |
| int current = static_cast<int>(code_targets_.size()); | |
| if (current > 0 && !target.is_null() && | |
| code_targets_.back().address() == target.address()) { | |
| // Optimization if we keep jumping to the same code target. | |
| return current - 1; | |
| } else { | |
| code_targets_.push_back(target); | |
| return current; | |
| } | |
| } | |
| Handle<Code> AssemblerBase::GetCodeTarget(intptr_t code_target_index) const { | |
| DCHECK_LE(0, code_target_index); | |
| DCHECK_LT(code_target_index, code_targets_.size()); | |
| return code_targets_[code_target_index]; | |
| } | |
| void AssemblerBase::UpdateCodeTarget(intptr_t code_target_index, | |
| Handle<Code> code) { | |
| DCHECK_LE(0, code_target_index); | |
| DCHECK_LT(code_target_index, code_targets_.size()); | |
| code_targets_[code_target_index] = code; | |
| } | |
| } // namespace internal | |
| } // namespace v8 |