| // Copyright 2019 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. |
| |
| #include "src/objects/code.h" |
| |
| #include <iomanip> |
| |
| #include "src/codegen/assembler-inl.h" |
| #include "src/codegen/flush-instruction-cache.h" |
| #include "src/codegen/reloc-info-inl.h" |
| #include "src/codegen/source-position-table.h" |
| #include "src/codegen/source-position.h" |
| #include "src/deoptimizer/deoptimizer.h" |
| #include "src/objects/code-inl.h" |
| |
| #ifdef ENABLE_DISASSEMBLER |
| #include "src/diagnostics/disassembler.h" |
| #include "src/diagnostics/eh-frame.h" |
| #endif |
| |
| namespace v8 { |
| namespace internal { |
| |
| Tagged<Object> Code::raw_deoptimization_data_or_interpreter_data() const { |
| return RawProtectedPointerField(kDeoptimizationDataOrInterpreterDataOffset) |
| .load(); |
| } |
| |
| Tagged<Object> Code::raw_position_table() const { |
| return RawProtectedPointerField(kPositionTableOffset).load(); |
| } |
| |
| void Code::ClearEmbeddedObjectsAndJSDispatchHandles(Heap* heap) { |
| DisallowGarbageCollection no_gc; |
| Tagged<HeapObject> undefined = ReadOnlyRoots(heap).undefined_value(); |
| Tagged<InstructionStream> istream = unchecked_instruction_stream(); |
| int mode_mask = RelocInfo::EmbeddedObjectModeMask(); |
| mode_mask |= RelocInfo::JSDispatchHandleModeMask(); |
| { |
| WritableJitAllocation jit_allocation = ThreadIsolation::LookupJitAllocation( |
| istream->address(), istream->Size(), |
| ThreadIsolation::JitAllocationType::kInstructionStream, true); |
| for (WritableRelocIterator it(jit_allocation, istream, constant_pool(), |
| mode_mask); |
| !it.done(); it.next()) { |
| const auto mode = it.rinfo()->rmode(); |
| if (RelocInfo::IsEmbeddedObjectMode(mode)) { |
| it.rinfo()->set_target_object(istream, undefined, SKIP_WRITE_BARRIER); |
| } else { |
| it.rinfo()->set_js_dispatch_handle(istream, kNullJSDispatchHandle, |
| SKIP_WRITE_BARRIER); |
| } |
| } |
| } |
| set_embedded_objects_cleared(true); |
| } |
| |
| void Code::FlushICache() const { |
| FlushInstructionCache(instruction_start(), instruction_size()); |
| } |
| |
| int Code::SourcePosition(int offset) const { |
| CHECK_NE(kind(), CodeKind::BASELINE); |
| |
| // Subtract one because the current PC is one instruction after the call site. |
| offset--; |
| |
| int position = 0; |
| if (!has_source_position_table()) return position; |
| for (SourcePositionTableIterator it( |
| source_position_table(), |
| SourcePositionTableIterator::kJavaScriptOnly, |
| SourcePositionTableIterator::kDontSkipFunctionEntry); |
| !it.done() && it.code_offset() <= offset; it.Advance()) { |
| position = it.source_position().ScriptOffset(); |
| } |
| return position; |
| } |
| |
| int Code::SourceStatementPosition(int offset) const { |
| CHECK_NE(kind(), CodeKind::BASELINE); |
| |
| // Subtract one because the current PC is one instruction after the call site. |
| offset--; |
| |
| int position = 0; |
| if (!has_source_position_table()) return position; |
| for (SourcePositionTableIterator it(source_position_table()); |
| !it.done() && it.code_offset() <= offset; it.Advance()) { |
| if (it.is_statement()) { |
| position = it.source_position().ScriptOffset(); |
| } |
| } |
| return position; |
| } |
| |
| SafepointEntry Code::GetSafepointEntry(Isolate* isolate, Address pc) { |
| DCHECK(!is_maglevved()); |
| SafepointTable table(isolate, pc, *this); |
| return table.FindEntry(pc); |
| } |
| |
| MaglevSafepointEntry Code::GetMaglevSafepointEntry(Isolate* isolate, |
| Address pc) { |
| DCHECK(is_maglevved()); |
| MaglevSafepointTable table(isolate, pc, *this); |
| return table.FindEntry(pc); |
| } |
| |
| bool Code::IsIsolateIndependent(Isolate* isolate) { |
| static constexpr int kModeMask = |
| RelocInfo::AllRealModesMask() & |
| ~RelocInfo::ModeMask(RelocInfo::CONST_POOL) & |
| ~RelocInfo::ModeMask(RelocInfo::OFF_HEAP_TARGET) & |
| ~RelocInfo::ModeMask(RelocInfo::VENEER_POOL) & |
| ~RelocInfo::ModeMask(RelocInfo::WASM_CANONICAL_SIG_ID) & |
| ~RelocInfo::ModeMask(RelocInfo::WASM_CODE_POINTER_TABLE_ENTRY); |
| static_assert(kModeMask == |
| (RelocInfo::ModeMask(RelocInfo::CODE_TARGET) | |
| RelocInfo::ModeMask(RelocInfo::RELATIVE_CODE_TARGET) | |
| RelocInfo::ModeMask(RelocInfo::COMPRESSED_EMBEDDED_OBJECT) | |
| RelocInfo::ModeMask(RelocInfo::FULL_EMBEDDED_OBJECT) | |
| RelocInfo::ModeMask(RelocInfo::EXTERNAL_REFERENCE) | |
| RelocInfo::ModeMask(RelocInfo::INTERNAL_REFERENCE) | |
| RelocInfo::ModeMask(RelocInfo::INTERNAL_REFERENCE_ENCODED) | |
| RelocInfo::ModeMask(RelocInfo::JS_DISPATCH_HANDLE) | |
| RelocInfo::ModeMask(RelocInfo::NEAR_BUILTIN_ENTRY) | |
| RelocInfo::ModeMask(RelocInfo::WASM_CALL) | |
| RelocInfo::ModeMask(RelocInfo::WASM_STUB_CALL))); |
| |
| #if defined(V8_TARGET_ARCH_PPC64) || defined(V8_TARGET_ARCH_MIPS64) |
| return RelocIterator(*this, kModeMask).done(); |
| #elif defined(V8_TARGET_ARCH_X64) || defined(V8_TARGET_ARCH_ARM64) || \ |
| defined(V8_TARGET_ARCH_ARM) || defined(V8_TARGET_ARCH_S390X) || \ |
| defined(V8_TARGET_ARCH_IA32) || defined(V8_TARGET_ARCH_RISCV64) || \ |
| defined(V8_TARGET_ARCH_LOONG64) || defined(V8_TARGET_ARCH_RISCV32) |
| for (RelocIterator it(*this, kModeMask); !it.done(); it.next()) { |
| // On these platforms we emit relative builtin-to-builtin |
| // jumps for isolate independent builtins in the snapshot. They are later |
| // rewritten as pc-relative jumps to the off-heap instruction stream and are |
| // thus process-independent. See also: FinalizeEmbeddedCodeTargets. |
| if (RelocInfo::IsCodeTargetMode(it.rinfo()->rmode())) { |
| Address target_address = it.rinfo()->target_address(); |
| if (OffHeapInstructionStream::PcIsOffHeap(isolate, target_address)) |
| continue; |
| |
| Tagged<Code> target = Code::FromTargetAddress(target_address); |
| if (Builtins::IsIsolateIndependentBuiltin(target)) { |
| continue; |
| } |
| } |
| return false; |
| } |
| return true; |
| #else |
| #error Unsupported architecture. |
| #endif |
| } |
| |
| bool Code::Inlines(Tagged<SharedFunctionInfo> sfi) { |
| // We can only check for inlining for optimized code. |
| DCHECK(is_optimized_code()); |
| DisallowGarbageCollection no_gc; |
| Tagged<DeoptimizationData> const data = deoptimization_data(); |
| if (data->ulength().value() == 0) return false; |
| if (data->GetSharedFunctionInfo() == sfi) return true; |
| Tagged<DeoptimizationLiteralArray> const literals = data->LiteralArray(); |
| int const inlined_count = data->InlinedFunctionCount().value(); |
| for (int i = 0; i < inlined_count; ++i) { |
| if (Cast<SharedFunctionInfo>(literals->get(i)) == sfi) return true; |
| } |
| return false; |
| } |
| |
| void Code::SetMarkedForDeoptimization(Isolate* isolate, |
| LazyDeoptimizeReason reason) { |
| set_marked_for_deoptimization(true); |
| // Eager deopts are already logged by the deoptimizer. |
| if (reason != LazyDeoptimizeReason::kEagerDeopt && |
| V8_UNLIKELY(v8_flags.trace_deopt || v8_flags.log_deopt)) { |
| TraceMarkForDeoptimization(isolate, reason); |
| } |
| JSDispatchHandle handle = js_dispatch_handle(); |
| if (handle != kNullJSDispatchHandle) { |
| JSDispatchTable& jdt = isolate->js_dispatch_table(); |
| Tagged<Code> cur = jdt.GetCode(handle); |
| if (SafeEquals(cur)) { |
| if (v8_flags.reopt_after_lazy_deopts && |
| isolate->concurrent_recompilation_enabled()) { |
| jdt.SetCodeNoWriteBarrier( |
| handle, *BUILTIN_CODE(isolate, InterpreterEntryTrampoline)); |
| // Somewhat arbitrary list of lazy deopt reasons which we expect to be |
| // stable enough to warrant either immediate re-optimization, or |
| // re-optimization after one invocation (to detect potential follow-up |
| // IC changes). |
| // TODO(olivf): We should also work on reducing the number of |
| // dependencies we create in the compilers to require less of these |
| // quick re-compilations. |
| switch (reason) { |
| case LazyDeoptimizeReason::kAllocationSiteTenuringChange: |
| case LazyDeoptimizeReason::kAllocationSiteTransitionChange: |
| case LazyDeoptimizeReason::kEmptyContextExtensionChange: |
| case LazyDeoptimizeReason::kFrameValueMaterialized: |
| case LazyDeoptimizeReason::kPropertyCellChange: |
| case LazyDeoptimizeReason::kContextCellChange: |
| case LazyDeoptimizeReason::kPrototypeChange: |
| case LazyDeoptimizeReason::kExceptionCaught: |
| case LazyDeoptimizeReason::kFieldTypeConstChange: |
| case LazyDeoptimizeReason::kFieldRepresentationChange: |
| case LazyDeoptimizeReason::kFieldTypeChange: |
| case LazyDeoptimizeReason::kInitialMapChange: |
| case LazyDeoptimizeReason::kMapDeprecated: |
| jdt.SetTieringRequest( |
| handle, TieringBuiltin::kMarkReoptimizeLazyDeoptimized, |
| isolate); |
| break; |
| default: |
| // TODO(olivf): This trampoline is just used to reset the budget. If |
| // we knew the feedback cell and the bytecode size here, we could |
| // directly reset the budget. |
| jdt.SetTieringRequest(handle, TieringBuiltin::kMarkLazyDeoptimized, |
| isolate); |
| break; |
| } |
| } else { |
| jdt.SetCodeNoWriteBarrier(handle, *BUILTIN_CODE(isolate, CompileLazy)); |
| } |
| } |
| // Ensure we don't try to patch the entry multiple times. |
| set_js_dispatch_handle(kNullJSDispatchHandle); |
| } |
| Tagged<ProtectedFixedArray> tmp = deoptimization_data(); |
| // TODO(422951610): Zapping code discovered a bug in |
| // --maglev-inline-api-calls. Remove the flag check here once the bug is |
| // fixed. |
| if (tmp->ulength().value() > 0 && !v8_flags.maglev_inline_api_calls) { |
| Address start = instruction_start(); |
| Address end = start + deoptimization_data()->DeoptExitStart().value(); |
| RelocIterator it(instruction_stream(), RelocIterator::kAllModesMask); |
| Deoptimizer::ZapCode(start, end, it); |
| } |
| } |
| |
| #ifdef ENABLE_DISASSEMBLER |
| |
| namespace { |
| |
| void DisassembleCodeRange(Isolate* isolate, std::ostream& os, Tagged<Code> code, |
| Address begin, size_t size, Address current_pc, |
| size_t range_limit = 0) { |
| Address end = begin + size; |
| AllowHandleAllocation allow_handles; |
| DisallowGarbageCollection no_gc; |
| HandleScope handle_scope(isolate); |
| Disassembler::Decode(isolate, os, reinterpret_cast<uint8_t*>(begin), |
| reinterpret_cast<uint8_t*>(end), |
| CodeReference(handle(code, isolate)), current_pc, |
| range_limit); |
| } |
| |
| void DisassembleOnlyCode(const char* name, std::ostream& os, Isolate* isolate, |
| Tagged<Code> code, Address current_pc, |
| size_t range_limit) { |
| int code_size = code->instruction_size(); |
| DisassembleCodeRange(isolate, os, code, code->instruction_start(), code_size, |
| current_pc, range_limit); |
| } |
| |
| void Disassemble(const char* name, std::ostream& os, Isolate* isolate, |
| Tagged<Code> code, Address current_pc) { |
| CodeKind kind = code->kind(); |
| os << "kind = " << CodeKindToString(kind) << "\n"; |
| if (name == nullptr && code->is_builtin()) { |
| name = Builtins::name(code->builtin_id()); |
| } |
| if ((name != nullptr) && (name[0] != '\0')) { |
| os << "name = " << name << "\n"; |
| } |
| os << "compiler = " |
| << (code->is_turbofanned() ? "turbofan" |
| : code->is_maglevved() ? "maglev" |
| : kind == CodeKind::BASELINE ? "baseline" |
| : "unknown") |
| << "\n"; |
| os << "address = " << reinterpret_cast<void*>(code.ptr()) << "\n\n"; |
| |
| { |
| int code_size = code->instruction_size(); |
| os << "Instructions (size = " << code_size << ")\n"; |
| DisassembleCodeRange(isolate, os, code, code->instruction_start(), |
| code_size, current_pc); |
| |
| if (int pool_size = code->constant_pool_size()) { |
| DCHECK_EQ(pool_size & kPointerAlignmentMask, 0); |
| os << "\nConstant Pool (size = " << pool_size << ")\n"; |
| base::Vector<char> buf = base::Vector<char>::New(50); |
| intptr_t* ptr = reinterpret_cast<intptr_t*>(code->constant_pool()); |
| for (int i = 0; i < pool_size; i += kSystemPointerSize, ptr++) { |
| SNPrintF(buf, "%4d %08" V8PRIxPTR, i, *ptr); |
| os << static_cast<const void*>(ptr) << " " << buf.begin() << "\n"; |
| } |
| } |
| } |
| os << "\n"; |
| |
| // TODO(cbruni): add support for baseline code. |
| if (code->has_source_position_table()) { |
| { |
| SourcePositionTableIterator it( |
| code->source_position_table(), |
| SourcePositionTableIterator::kJavaScriptOnly); |
| if (!it.done()) { |
| os << "Source positions:\n pc offset position\n"; |
| for (; !it.done(); it.Advance()) { |
| os << std::setw(10) << std::hex << it.code_offset() << std::dec |
| << std::setw(10) << it.source_position().ScriptOffset() |
| << (it.is_statement() ? " statement" : "") << "\n"; |
| } |
| os << "\n"; |
| } |
| } |
| |
| { |
| SourcePositionTableIterator it( |
| code->source_position_table(), |
| SourcePositionTableIterator::kExternalOnly); |
| if (!it.done()) { |
| os << "External Source positions:\n pc offset fileid line\n"; |
| for (; !it.done(); it.Advance()) { |
| DCHECK(it.source_position().IsExternal()); |
| os << std::setw(10) << std::hex << it.code_offset() << std::dec |
| << std::setw(10) << it.source_position().ExternalFileId() |
| << std::setw(10) << it.source_position().ExternalLine() << "\n"; |
| } |
| os << "\n"; |
| } |
| } |
| } |
| |
| if (code->uses_deoptimization_data()) { |
| Tagged<DeoptimizationData> data = code->deoptimization_data(); |
| data->PrintDeoptimizationData(os); |
| } |
| os << "\n"; |
| |
| if (code->uses_safepoint_table()) { |
| if (code->is_maglevved()) { |
| MaglevSafepointTable table(isolate, current_pc, code); |
| table.Print(os); |
| } else { |
| SafepointTable table(isolate, current_pc, code); |
| table.Print(os); |
| } |
| os << "\n"; |
| } |
| |
| if (code->has_handler_table()) { |
| HandlerTable table(code); |
| os << "Handler Table (size = " << table.NumberOfReturnEntries() << ")\n"; |
| table.HandlerTableReturnPrint(os); |
| os << "\n"; |
| } |
| |
| os << "RelocInfo (size = " << code->relocation_size() << ")\n"; |
| if (code->has_instruction_stream()) { |
| for (RelocIterator it(code); !it.done(); it.next()) { |
| it.rinfo()->Print(isolate, os); |
| } |
| } |
| os << "\n"; |
| |
| if (code->has_unwinding_info()) { |
| os << "UnwindingInfo (size = " << code->unwinding_info_size() << ")\n"; |
| EhFrameDisassembler eh_frame_disassembler( |
| reinterpret_cast<uint8_t*>(code->unwinding_info_start()), |
| reinterpret_cast<uint8_t*>(code->unwinding_info_end())); |
| eh_frame_disassembler.DisassembleToStream(os); |
| os << "\n"; |
| } |
| } |
| |
| } // namespace |
| |
| void Code::Disassemble(const char* name, std::ostream& os, Isolate* isolate, |
| Address current_pc) { |
| i::Disassemble(name, os, isolate, *this, current_pc); |
| } |
| |
| void Code::DisassembleOnlyCode(const char* name, std::ostream& os, |
| Isolate* isolate, Address current_pc, |
| size_t range_limit) { |
| i::DisassembleOnlyCode(name, os, isolate, *this, current_pc, range_limit); |
| } |
| |
| #endif // ENABLE_DISASSEMBLER |
| |
| void Code::TraceMarkForDeoptimization(Isolate* isolate, |
| LazyDeoptimizeReason reason) { |
| Deoptimizer::TraceMarkForDeoptimization(isolate, *this, reason); |
| } |
| |
| #if V8_ENABLE_GEARBOX |
| void Code::CopyFieldsWithGearboxForSerialization(Tagged<Code> dst, |
| Tagged<Code> src, |
| Isolate* isolate) { |
| Builtin src_id = src->builtin_id(); |
| DCHECK(dst->is_gearbox_placeholder_builtin()); |
| DCHECK(Builtins::IsISXVariant(src_id) || Builtins::IsGenericVariant(src_id) || |
| src_id == Builtin::kIllegal); |
| dst->set_builtin_id(src_id); |
| dst->set_instruction_size(src->instruction_size()); |
| dst->set_metadata_size(src->metadata_size()); |
| dst->set_handler_table_offset(src->handler_table_offset()); |
| dst->set_jump_table_info_offset(src->jump_table_info_offset()); |
| dst->set_unwinding_info_offset(src->unwinding_info_offset()); |
| dst->set_parameter_count(src->parameter_count()); |
| dst->set_code_comments_offset(src->code_comments_offset()); |
| dst->set_constant_pool_offset(src->constant_pool_offset()); |
| } |
| |
| void Code::CopyFieldsWithGearboxForDeserialization(Tagged<Code> dst, |
| Tagged<Code> src, |
| Isolate* isolate) { |
| CopyFieldsWithGearboxForSerialization(dst, src, isolate); |
| // We only set instruction_start field when we're doing deserialization, |
| // because in the serialization it was already be cleaned. |
| dst->SetInstructionStartForOffHeapBuiltin(isolate, src->instruction_start()); |
| } |
| #endif // V8_ENABLE_GEARBOX |
| |
| } // namespace internal |
| } // namespace v8 |