blob: e4539f219e8bc1811679cf1210ae73e060705f5c [file] [log] [blame]
// Copyright 2023 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/deoptimization-data.h"
#include <iomanip>
#include "src/deoptimizer/translated-state.h"
#include "src/interpreter/bytecode-array-iterator.h"
#include "src/objects/code.h"
#include "src/objects/deoptimization-data-inl.h"
#include "src/objects/shared-function-info.h"
#ifdef V8_USE_ZLIB
#include "third_party/zlib/google/compression_utils_portable.h"
#endif // V8_USE_ZLIB
namespace v8 {
namespace internal {
Handle<DeoptimizationData> DeoptimizationData::New(Isolate* isolate,
int deopt_entry_count) {
return Handle<DeoptimizationData>::cast(
isolate->factory()->NewProtectedFixedArray(LengthFor(deopt_entry_count)));
}
Handle<DeoptimizationData> DeoptimizationData::New(LocalIsolate* isolate,
int deopt_entry_count) {
return Handle<DeoptimizationData>::cast(
isolate->factory()->NewProtectedFixedArray(LengthFor(deopt_entry_count)));
}
Handle<DeoptimizationData> DeoptimizationData::Empty(Isolate* isolate) {
return Handle<DeoptimizationData>::cast(
isolate->factory()->empty_protected_fixed_array());
}
Handle<DeoptimizationData> DeoptimizationData::Empty(LocalIsolate* isolate) {
return Handle<DeoptimizationData>::cast(
isolate->factory()->empty_protected_fixed_array());
}
Tagged<SharedFunctionInfo> DeoptimizationData::GetInlinedFunction(int index) {
if (index == -1) {
return SharedFunctionInfo::cast(SharedFunctionInfo());
} else {
return SharedFunctionInfo::cast(LiteralArray()->get(index));
}
}
#ifdef DEBUG
void DeoptimizationData::Verify(Handle<BytecodeArray> bytecode) const {
#ifdef V8_USE_ZLIB
if (V8_UNLIKELY(v8_flags.turbo_compress_frame_translations)) {
return;
}
#endif // V8_USE_ZLIB
for (int i = 0; i < DeoptCount(); ++i) {
// Check the frame count and identify the bailout id of the top compilation
// unit.
int idx = TranslationIndex(i).value();
DeoptimizationFrameTranslation::Iterator iterator(FrameTranslation(), idx);
auto [frame_count, jsframe_count] = iterator.EnterBeginOpcode();
DCHECK_GE(frame_count, jsframe_count);
BytecodeOffset bailout = BytecodeOffset::None();
bool first_frame = true;
while (frame_count > 0) {
TranslationOpcode frame = iterator.SeekNextFrame();
frame_count--;
if (IsTranslationJsFrameOpcode(frame)) {
jsframe_count--;
if (first_frame) {
bailout = BytecodeOffset(iterator.NextOperand());
first_frame = false;
iterator.SkipOperands(TranslationOpcodeOperandCount(frame) - 1);
continue;
}
}
iterator.SkipOperands(TranslationOpcodeOperandCount(frame));
}
CHECK_EQ(frame_count, 0);
CHECK_EQ(jsframe_count, 0);
// Check the bytecode offset exists in the bytecode array
if (bailout != BytecodeOffset::None()) {
#ifdef ENABLE_SLOW_DCHECKS
interpreter::BytecodeArrayIterator bytecode_iterator(bytecode);
while (bytecode_iterator.current_offset() < bailout.ToInt()) {
bytecode_iterator.Advance();
DCHECK_LE(bytecode_iterator.current_offset(), bailout.ToInt());
}
#else
DCHECK_GE(bailout.ToInt(), 0);
DCHECK_LT(bailout.ToInt(), bytecode->length());
#endif // ENABLE_SLOW_DCHECKS
}
}
}
#endif // DEBUG
#ifdef ENABLE_DISASSEMBLER
namespace {
void print_pc(std::ostream& os, int pc) {
if (pc == -1) {
os << "NA";
} else {
os << std::hex << pc << std::dec;
}
}
} // namespace
void DeoptimizationData::PrintDeoptimizationData(std::ostream& os) const {
if (length() == 0) {
os << "Deoptimization Input Data invalidated by lazy deoptimization\n";
return;
}
int const inlined_function_count = InlinedFunctionCount().value();
os << "Inlined functions (count = " << inlined_function_count << ")\n";
for (int id = 0; id < inlined_function_count; ++id) {
Tagged<Object> info = LiteralArray()->get(id);
os << " " << Brief(SharedFunctionInfo::cast(info)) << "\n";
}
os << "\n";
int deopt_count = DeoptCount();
os << "Deoptimization Input Data (deopt points = " << deopt_count << ")\n";
if (0 != deopt_count) {
#ifdef DEBUG
os << " index bytecode-offset node-id pc";
#else // DEBUG
os << " index bytecode-offset pc";
#endif // DEBUG
if (v8_flags.print_code_verbose) os << " commands";
os << "\n";
}
for (int i = 0; i < deopt_count; i++) {
os << std::setw(6) << i << " " << std::setw(15)
<< GetBytecodeOffsetOrBuiltinContinuationId(i).ToInt() << " "
#ifdef DEBUG
<< std::setw(7) << NodeId(i).value() << " "
#endif // DEBUG
<< std::setw(4);
print_pc(os, Pc(i).value());
os << std::setw(2) << "\n";
if (v8_flags.print_code_verbose) {
FrameTranslation()->PrintFrameTranslation(os, TranslationIndex(i).value(),
LiteralArray());
}
}
}
#endif // ENABLE_DISASSEMBLER
DeoptTranslationIterator::DeoptTranslationIterator(
base::Vector<const uint8_t> buffer, int index)
: buffer_(buffer), index_(index) {
#ifdef V8_USE_ZLIB
if (V8_UNLIKELY(v8_flags.turbo_compress_frame_translations)) {
const int size =
base::ReadUnalignedValue<uint32_t>(reinterpret_cast<Address>(
&buffer_[DeoptimizationFrameTranslation::kUncompressedSizeOffset]));
uncompressed_contents_.insert(uncompressed_contents_.begin(), size, 0);
uLongf uncompressed_size = size *
DeoptimizationFrameTranslation::
kDeoptimizationFrameTranslationElementSize;
CHECK_EQ(zlib_internal::UncompressHelper(
zlib_internal::ZRAW,
reinterpret_cast<Bytef*>(uncompressed_contents_.data()),
&uncompressed_size,
buffer_.begin() +
DeoptimizationFrameTranslation::kCompressedDataOffset,
buffer_.length()),
Z_OK);
DCHECK(index >= 0 && index < size);
return;
}
#endif // V8_USE_ZLIB
DCHECK(!v8_flags.turbo_compress_frame_translations);
DCHECK(index >= 0 && index < buffer_.length());
// Starting at a location other than a BEGIN would make
// MATCH_PREVIOUS_TRANSLATION instructions not work.
DCHECK(
TranslationOpcodeIsBegin(static_cast<TranslationOpcode>(buffer_[index])));
}
DeoptimizationFrameTranslation::Iterator::Iterator(
Tagged<DeoptimizationFrameTranslation> buffer, int index)
: DeoptTranslationIterator(
base::Vector<uint8_t>(buffer->AddressOfElementAt(0),
buffer->length()),
index) {}
int32_t DeoptTranslationIterator::NextOperand() {
if (V8_UNLIKELY(v8_flags.turbo_compress_frame_translations)) {
return uncompressed_contents_[index_++];
} else if (remaining_ops_to_use_from_previous_translation_) {
int32_t value = base::VLQDecode(buffer_.begin(), &previous_index_);
DCHECK_LT(previous_index_, index_);
return value;
} else {
int32_t value = base::VLQDecode(buffer_.begin(), &index_);
DCHECK_LE(index_, buffer_.length());
return value;
}
}
TranslationOpcode DeoptTranslationIterator::NextOpcodeAtPreviousIndex() {
TranslationOpcode opcode =
static_cast<TranslationOpcode>(buffer_[previous_index_++]);
DCHECK_LT(static_cast<uint32_t>(opcode), kNumTranslationOpcodes);
DCHECK_NE(opcode, TranslationOpcode::MATCH_PREVIOUS_TRANSLATION);
DCHECK_LT(previous_index_, index_);
return opcode;
}
uint32_t DeoptTranslationIterator::NextUnsignedOperandAtPreviousIndex() {
uint32_t value = base::VLQDecodeUnsigned(buffer_.begin(), &previous_index_);
DCHECK_LT(previous_index_, index_);
return value;
}
uint32_t DeoptTranslationIterator::NextOperandUnsigned() {
if (V8_UNLIKELY(v8_flags.turbo_compress_frame_translations)) {
return uncompressed_contents_[index_++];
} else if (remaining_ops_to_use_from_previous_translation_) {
return NextUnsignedOperandAtPreviousIndex();
} else {
uint32_t value = base::VLQDecodeUnsigned(buffer_.begin(), &index_);
DCHECK_LE(index_, buffer_.length());
return value;
}
}
TranslationOpcode DeoptTranslationIterator::NextOpcode() {
if (V8_UNLIKELY(v8_flags.turbo_compress_frame_translations)) {
return static_cast<TranslationOpcode>(NextOperandUnsigned());
}
if (remaining_ops_to_use_from_previous_translation_) {
--remaining_ops_to_use_from_previous_translation_;
}
if (remaining_ops_to_use_from_previous_translation_) {
return NextOpcodeAtPreviousIndex();
}
CHECK_LT(index_, buffer_.length());
uint8_t opcode_byte = buffer_[index_++];
// If the opcode byte is greater than any valid opcode, then the opcode is
// implicitly MATCH_PREVIOUS_TRANSLATION and the operand is the opcode byte
// minus kNumTranslationOpcodes. This special-case encoding of the most common
// opcode saves some memory.
if (opcode_byte >= kNumTranslationOpcodes) {
remaining_ops_to_use_from_previous_translation_ =
opcode_byte - kNumTranslationOpcodes;
opcode_byte =
static_cast<uint8_t>(TranslationOpcode::MATCH_PREVIOUS_TRANSLATION);
} else if (opcode_byte ==
static_cast<uint8_t>(
TranslationOpcode::MATCH_PREVIOUS_TRANSLATION)) {
remaining_ops_to_use_from_previous_translation_ = NextOperandUnsigned();
}
TranslationOpcode opcode = static_cast<TranslationOpcode>(opcode_byte);
DCHECK_LE(index_, buffer_.length());
DCHECK_LT(static_cast<uint32_t>(opcode), kNumTranslationOpcodes);
if (TranslationOpcodeIsBegin(opcode)) {
int temp_index = index_;
// The first argument for BEGIN is the distance, in bytes, since the
// previous BEGIN, or zero to indicate that MATCH_PREVIOUS_TRANSLATION will
// not be used in this translation.
uint32_t lookback_distance =
base::VLQDecodeUnsigned(buffer_.begin(), &temp_index);
if (lookback_distance) {
previous_index_ = index_ - 1 - lookback_distance;
DCHECK(TranslationOpcodeIsBegin(
static_cast<TranslationOpcode>(buffer_[previous_index_])));
// The previous BEGIN should specify zero as its lookback distance,
// meaning it won't use MATCH_PREVIOUS_TRANSLATION.
DCHECK_EQ(buffer_[previous_index_ + 1], 0);
}
ops_since_previous_index_was_updated_ = 1;
} else if (opcode == TranslationOpcode::MATCH_PREVIOUS_TRANSLATION) {
for (int i = 0; i < ops_since_previous_index_was_updated_; ++i) {
SkipOpcodeAndItsOperandsAtPreviousIndex();
}
ops_since_previous_index_was_updated_ = 0;
opcode = NextOpcodeAtPreviousIndex();
} else {
++ops_since_previous_index_was_updated_;
}
return opcode;
}
DeoptimizationFrameTranslation::FrameCount
DeoptTranslationIterator::EnterBeginOpcode() {
TranslationOpcode opcode = NextOpcode();
DCHECK(TranslationOpcodeIsBegin(opcode));
USE(opcode);
NextOperand(); // Skip lookback distance.
int frame_count = NextOperand();
int jsframe_count = NextOperand();
return {frame_count, jsframe_count};
}
TranslationOpcode DeoptTranslationIterator::SeekNextJSFrame() {
while (HasNextOpcode()) {
TranslationOpcode opcode = NextOpcode();
DCHECK(!TranslationOpcodeIsBegin(opcode));
if (IsTranslationJsFrameOpcode(opcode)) {
return opcode;
} else {
// Skip over operands to advance to the next opcode.
SkipOperands(TranslationOpcodeOperandCount(opcode));
}
}
UNREACHABLE();
}
TranslationOpcode DeoptTranslationIterator::SeekNextFrame() {
while (HasNextOpcode()) {
TranslationOpcode opcode = NextOpcode();
DCHECK(!TranslationOpcodeIsBegin(opcode));
if (IsTranslationFrameOpcode(opcode)) {
return opcode;
} else {
// Skip over operands to advance to the next opcode.
SkipOperands(TranslationOpcodeOperandCount(opcode));
}
}
UNREACHABLE();
}
bool DeoptTranslationIterator::HasNextOpcode() const {
if (V8_UNLIKELY(v8_flags.turbo_compress_frame_translations)) {
return index_ < static_cast<int>(uncompressed_contents_.size());
} else {
return index_ < buffer_.length() ||
remaining_ops_to_use_from_previous_translation_ > 1;
}
}
void DeoptTranslationIterator::SkipOpcodeAndItsOperandsAtPreviousIndex() {
TranslationOpcode opcode = NextOpcodeAtPreviousIndex();
for (int count = TranslationOpcodeOperandCount(opcode); count != 0; --count) {
NextUnsignedOperandAtPreviousIndex();
}
}
#ifdef ENABLE_DISASSEMBLER
void DeoptimizationFrameTranslation::PrintFrameTranslation(
std::ostream& os, int index,
Tagged<DeoptimizationLiteralArray> literal_array) const {
DisallowGarbageCollection gc_oh_noes;
DeoptimizationFrameTranslation::Iterator iterator(*this, index);
TranslationOpcode opcode = iterator.NextOpcode();
DCHECK(TranslationOpcodeIsBegin(opcode));
os << opcode << " ";
DeoptimizationFrameTranslationPrintSingleOpcode(os, opcode, iterator,
literal_array);
while (iterator.HasNextOpcode()) {
TranslationOpcode opcode = iterator.NextOpcode();
if (TranslationOpcodeIsBegin(opcode)) {
break;
}
os << opcode << " ";
DeoptimizationFrameTranslationPrintSingleOpcode(os, opcode, iterator,
literal_array);
}
}
#endif // ENABLE_DISASSEMBLER
} // namespace internal
} // namespace v8