#include "src/compiler/bytecode-analysis.h"
#include <algorithm>
#include <utility>
#include "src/compiler/bytecode-liveness-map.h"
#include "src/interpreter/bytecode-array-iterator.h"
#include "src/interpreter/bytecode-array-random-iterator.h"
#include "src/interpreter/bytecodes.h"
#include "src/objects/objects-inl.h"
#include "src/utils/ostreams.h"
namespace v8 {
namespace internal {
namespace compiler {
using interpreter::Bytecode;
using interpreter::BytecodeOperands;
using interpreter::Bytecodes;
using interpreter::ImplicitRegisterUse;
using interpreter::OperandType;
using interpreter::Register;
BytecodeLoopAssignments::BytecodeLoopAssignments(int parameter_count,
int register_count, Zone* zone)
: parameter_count_(parameter_count),
bit_vector_(
zone->New<BitVector>(parameter_count + register_count, zone)) {}
void BytecodeLoopAssignments::Add(interpreter::Register r) {
if (r.is_parameter()) {
bit_vector_->Add(r.ToParameterIndex());
} else {
bit_vector_->Add(parameter_count_ + r.index());
}
}
void BytecodeLoopAssignments::AddList(interpreter::Register r, uint32_t count) {
if (r.is_parameter()) {
for (uint32_t i = 0; i < count; i++) {
DCHECK(interpreter::Register(r.index() + i).is_parameter());
bit_vector_->Add(r.ToParameterIndex() + i);
}
} else {
for (uint32_t i = 0; i < count; i++) {
DCHECK(!interpreter::Register(r.index() + i).is_parameter());
bit_vector_->Add(parameter_count_ + r.index() + i);
}
}
}
void BytecodeLoopAssignments::Union(const BytecodeLoopAssignments& other) {
bit_vector_->Union(*other.bit_vector_);
}
bool BytecodeLoopAssignments::ContainsParameter(int index) const {
DCHECK_GE(index, 0);
DCHECK_LT(index, parameter_count());
return bit_vector_->Contains(index);
}
bool BytecodeLoopAssignments::ContainsLocal(int index) const {
DCHECK_GE(index, 0);
DCHECK_LT(index, local_count());
return bit_vector_->Contains(parameter_count_ + index);
}
ResumeJumpTarget::ResumeJumpTarget(int suspend_id, int target_offset,
int final_target_offset)
: suspend_id_(suspend_id),
target_offset_(target_offset),
final_target_offset_(final_target_offset) {}
ResumeJumpTarget ResumeJumpTarget::Leaf(int suspend_id, int target_offset) {
return ResumeJumpTarget(suspend_id, target_offset, target_offset);
}
ResumeJumpTarget ResumeJumpTarget::AtLoopHeader(int loop_header_offset,
const ResumeJumpTarget& next) {
return ResumeJumpTarget(next.suspend_id(), loop_header_offset,
next.target_offset());
}
namespace {
template <Bytecode bytecode, OperandType operand_type, size_t i>
void UpdateInLivenessForOutOperand(
BytecodeLivenessState* in_liveness,
const interpreter::BytecodeArrayIterator& iterator) {
if constexpr (operand_type == OperandType::kRegOut ||
operand_type == OperandType::kRegInOut) {
Register r = iterator.GetRegisterOperand(i);
if (!r.is_parameter()) {
in_liveness->MarkRegisterDead(r.index());
}
} else if constexpr (operand_type == OperandType::kRegOutList) {
Register r = iterator.GetRegisterOperand(i);
uint32_t reg_count = iterator.GetRegisterCountOperand(i + 1);
if (!r.is_parameter()) {
for (uint32_t j = 0; j < reg_count; ++j) {
DCHECK(!Register(r.index() + j).is_parameter());
in_liveness->MarkRegisterDead(r.index() + j);
}
}
} else if constexpr (operand_type == OperandType::kRegOutPair) {
Register r = iterator.GetRegisterOperand(i);
if (!r.is_parameter()) {
DCHECK(!Register(r.index() + 1).is_parameter());
in_liveness->MarkRegisterDead(r.index());
in_liveness->MarkRegisterDead(r.index() + 1);
}
} else if constexpr (operand_type == OperandType::kRegOutTriple) {
Register r = iterator.GetRegisterOperand(i);
if (!r.is_parameter()) {
DCHECK(!Register(r.index() + 1).is_parameter());
DCHECK(!Register(r.index() + 2).is_parameter());
in_liveness->MarkRegisterDead(r.index());
in_liveness->MarkRegisterDead(r.index() + 1);
in_liveness->MarkRegisterDead(r.index() + 2);
}
} else {
DCHECK(!Bytecodes::IsRegisterOutputOperandType(operand_type));
}
}
template <Bytecode bytecode, OperandType operand_type, size_t i>
void UpdateInLivenessForInOperand(
BytecodeLivenessState* in_liveness,
const interpreter::BytecodeArrayIterator& iterator) {
if constexpr (operand_type == OperandType::kReg ||
operand_type == OperandType::kRegInOut) {
Register r = iterator.GetRegisterOperand(i);
if (!r.is_parameter()) {
in_liveness->MarkRegisterLive(r.index());
}
} else if constexpr (operand_type == OperandType::kRegPair) {
Register r = iterator.GetRegisterOperand(i);
if (!r.is_parameter()) {
DCHECK(!Register(r.index() + 1).is_parameter());
in_liveness->MarkRegisterLive(r.index());
in_liveness->MarkRegisterLive(r.index() + 1);
}
} else if constexpr (operand_type == OperandType::kRegList) {
Register r = iterator.GetRegisterOperand(i);
uint32_t reg_count = iterator.GetRegisterCountOperand(i + 1);
if (!r.is_parameter()) {
for (uint32_t j = 0; j < reg_count; ++j) {
DCHECK(!interpreter::Register(r.index() + j).is_parameter());
in_liveness->MarkRegisterLive(r.index() + j);
}
}
} else {
DCHECK(!Bytecodes::IsRegisterInputOperandType(operand_type));
}
}
template <Bytecode bytecode, ImplicitRegisterUse implicit_register_use,
OperandType... operand_types, size_t... operand_index>
void UpdateInLiveness(BytecodeLivenessState* in_liveness,
const interpreter::BytecodeArrayIterator& iterator,
std::index_sequence<operand_index...>) {
if constexpr (bytecode == Bytecode::kSuspendGenerator) {
in_liveness->MarkRegisterLive(iterator.GetRegisterOperand(0).index());
DCHECK(Bytecodes::ReadsAccumulator(bytecode));
in_liveness->MarkAccumulatorLive();
return;
} else if constexpr (bytecode == Bytecode::kResumeGenerator) {
in_liveness->MarkRegisterLive(iterator.GetRegisterOperand(0).index());
return;
}
if constexpr (BytecodeOperands::WritesAccumulator(implicit_register_use)) {
in_liveness->MarkAccumulatorDead();
}
DCHECK_IMPLIES(BytecodeOperands::ClobbersAccumulator(implicit_register_use),
!in_liveness->AccumulatorIsLive());
(UpdateInLivenessForOutOperand<bytecode, operand_types, operand_index>(
in_liveness, iterator),
...);
if constexpr (BytecodeOperands::WritesImplicitRegister(
implicit_register_use)) {
in_liveness->MarkRegisterDead(Register::FromShortStar(bytecode).index());
}
if constexpr (BytecodeOperands::ReadsAccumulator(implicit_register_use)) {
in_liveness->MarkAccumulatorLive();
}
(UpdateInLivenessForInOperand<bytecode, operand_types, operand_index>(
in_liveness, iterator),
...);
}
template <Bytecode bytecode, ImplicitRegisterUse implicit_register_use,
OperandType... operand_types>
void UpdateInLiveness(BytecodeLivenessState* in_liveness,
const interpreter::BytecodeArrayIterator& iterator) {
UpdateInLiveness<bytecode, implicit_register_use, operand_types...>(
in_liveness, iterator,
std::make_index_sequence<sizeof...(operand_types)>());
}
#ifdef DEBUG
void UpdateInLiveness(Bytecode bytecode, BytecodeLivenessState* in_liveness,
const interpreter::BytecodeArrayIterator& iterator) {
switch (bytecode) {
#define BYTECODE_UPDATE_IN_LIVENESS(Name, ...) \
case Bytecode::k##Name: \
return UpdateInLiveness<Bytecode::k##Name, __VA_ARGS__>(in_liveness, \
iterator);
BYTECODE_LIST(BYTECODE_UPDATE_IN_LIVENESS, BYTECODE_UPDATE_IN_LIVENESS)
#undef BYTECODE_UPDATE_IN_LIVENESS
}
}
#endif
template <bool IsFirstUpdate = false>
void EnsureOutLivenessIsNotAlias(
BytecodeLiveness& liveness,
BytecodeLivenessState* next_bytecode_in_liveness, Zone* zone) {
if (!IsFirstUpdate) {
DCHECK_NE(liveness.out, next_bytecode_in_liveness);
return;
}
if (liveness.out == next_bytecode_in_liveness) {
liveness.out =
zone->New<BytecodeLivenessState>(*next_bytecode_in_liveness, zone);
}
}
template <bool IsFirstUpdate, Bytecode bytecode>
void UpdateOutLiveness(BytecodeLiveness& liveness,
BytecodeLivenessState* next_bytecode_in_liveness,
const interpreter::BytecodeArrayIterator& iterator,
DirectHandle<BytecodeArray> bytecode_array,
const BytecodeLivenessMap& liveness_map, Zone* zone) {
if (!IsFirstUpdate && liveness.out == next_bytecode_in_liveness) return;
if (bytecode == Bytecode::kSuspendGenerator ||
bytecode == Bytecode::kResumeGenerator) {
DCHECK_NOT_NULL(next_bytecode_in_liveness);
if (IsFirstUpdate) {
liveness.out = next_bytecode_in_liveness;
} else {
liveness.out->Union(*next_bytecode_in_liveness);
}
return;
}
if (bytecode == Bytecode::kSwitchOnGeneratorState) {
DCHECK_NOT_NULL(next_bytecode_in_liveness);
if (IsFirstUpdate) {
int generator_reg_index = iterator.GetRegisterOperand(0).index();
DCHECK(!next_bytecode_in_liveness->RegisterIsLive(generator_reg_index));
liveness.out =
zone->New<BytecodeLivenessState>(*next_bytecode_in_liveness, zone);
liveness.out->MarkRegisterLive(generator_reg_index);
} else {
liveness.out->Union(*next_bytecode_in_liveness);
}
return;
}
if (next_bytecode_in_liveness != nullptr &&
!Bytecodes::IsUnconditionalJump(bytecode) &&
!Bytecodes::Returns(bytecode) &&
!Bytecodes::UnconditionallyThrows(bytecode)) {
if (IsFirstUpdate) {
DCHECK_NULL(liveness.out);
liveness.out = next_bytecode_in_liveness;
} else {
liveness.out->Union(*next_bytecode_in_liveness);
}
} else if (IsFirstUpdate) {
DCHECK_NULL(liveness.out);
liveness.out = zone->New<BytecodeLivenessState>(
bytecode_array->register_count(), zone);
}
DCHECK_NOT_NULL(liveness.out);
if (Bytecodes::IsForwardJump(bytecode)) {
int target_offset = iterator.GetJumpTargetOffset();
EnsureOutLivenessIsNotAlias<IsFirstUpdate>(liveness,
next_bytecode_in_liveness, zone);
liveness.out->Union(*liveness_map.GetInLiveness(target_offset));
} else if (Bytecodes::IsSwitch(bytecode)) {
EnsureOutLivenessIsNotAlias<IsFirstUpdate>(liveness,
next_bytecode_in_liveness, zone);
for (interpreter::JumpTableTargetOffset entry :
iterator.GetJumpTableTargetOffsets()) {
liveness.out->Union(*liveness_map.GetInLiveness(entry.target_offset));
}
}
if (!interpreter::Bytecodes::IsWithoutExternalSideEffects(bytecode)) {
HandlerTable table(*bytecode_array);
int handler_index =
table.LookupHandlerIndexForRange(iterator.current_offset());
if (handler_index != HandlerTable::kNoHandlerFound) {
EnsureOutLivenessIsNotAlias<IsFirstUpdate>(
liveness, next_bytecode_in_liveness, zone);
bool was_accumulator_live = liveness.out->AccumulatorIsLive();
liveness.out->Union(
*liveness_map.GetInLiveness(table.GetRangeHandler(handler_index)));
liveness.out->MarkRegisterLive(table.GetRangeData(handler_index));
if (!was_accumulator_live) {
liveness.out->MarkAccumulatorDead();
}
}
}
}
template <bool IsFirstUpdate = false>
void UpdateOutLiveness(Bytecode bytecode, BytecodeLiveness& liveness,
BytecodeLivenessState* next_bytecode_in_liveness,
const interpreter::BytecodeArrayIterator& iterator,
DirectHandle<BytecodeArray> bytecode_array,
const BytecodeLivenessMap& liveness_map, Zone* zone) {
switch (bytecode) {
#define BYTECODE_UPDATE_OUT_LIVENESS(Name, ...) \
case Bytecode::k##Name: \
return UpdateOutLiveness<IsFirstUpdate, Bytecode::k##Name>( \
liveness, next_bytecode_in_liveness, iterator, bytecode_array, \
liveness_map, zone);
BYTECODE_LIST(BYTECODE_UPDATE_OUT_LIVENESS, BYTECODE_UPDATE_OUT_LIVENESS)
#undef BYTECODE_UPDATE_OUT_LIVENESS
}
}
template <bool IsFirstUpdate, Bytecode bytecode,
ImplicitRegisterUse implicit_register_use,
OperandType... operand_types>
void UpdateLiveness(BytecodeLiveness& liveness,
BytecodeLivenessState** next_bytecode_in_liveness,
const interpreter::BytecodeArrayIterator& iterator,
DirectHandle<BytecodeArray> bytecode_array,
const BytecodeLivenessMap& liveness_map, Zone* zone) {
UpdateOutLiveness<IsFirstUpdate, bytecode>(
liveness, *next_bytecode_in_liveness, iterator, bytecode_array,
liveness_map, zone);
if (IsFirstUpdate) {
DCHECK_NULL(liveness.in);
liveness.in = zone->New<BytecodeLivenessState>(*liveness.out, zone);
} else {
liveness.in->CopyFrom(*liveness.out);
}
UpdateInLiveness<bytecode, implicit_register_use, operand_types...>(
liveness.in, iterator);
*next_bytecode_in_liveness = liveness.in;
}
template <bool IsFirstUpdate = false>
void UpdateLiveness(Bytecode bytecode, BytecodeLiveness& liveness,
BytecodeLivenessState** next_bytecode_in_liveness,
const interpreter::BytecodeArrayIterator& iterator,
DirectHandle<BytecodeArray> bytecode_array,
const BytecodeLivenessMap& liveness_map, Zone* zone) {
switch (bytecode) {
#define BYTECODE_UPDATE_LIVENESS(Name, ...) \
case Bytecode::k##Name: \
return UpdateLiveness<IsFirstUpdate, Bytecode::k##Name, __VA_ARGS__>( \
liveness, next_bytecode_in_liveness, iterator, bytecode_array, \
liveness_map, zone);
BYTECODE_LIST(BYTECODE_UPDATE_LIVENESS, BYTECODE_UPDATE_LIVENESS)
#undef BYTECODE_UPDATE_LIVENESS
}
}
void UpdateAssignments(Bytecode bytecode, BytecodeLoopAssignments* assignments,
const interpreter::BytecodeArrayIterator& iterator) {
int num_operands = Bytecodes::NumberOfOperands(bytecode);
const OperandType* operand_types = Bytecodes::GetOperandTypes(bytecode);
for (int i = 0; i < num_operands; ++i) {
switch (operand_types[i]) {
case OperandType::kRegInOut:
case OperandType::kRegOut: {
assignments->Add(iterator.GetRegisterOperand(i));
break;
}
case OperandType::kRegOutList: {
interpreter::Register r = iterator.GetRegisterOperand(i++);
uint32_t reg_count = iterator.GetRegisterCountOperand(i);
assignments->AddList(r, reg_count);
break;
}
case OperandType::kRegOutPair: {
assignments->AddList(iterator.GetRegisterOperand(i), 2);
break;
}
case OperandType::kRegOutTriple: {
assignments->AddList(iterator.GetRegisterOperand(i), 3);
break;
}
default:
DCHECK(!Bytecodes::IsRegisterOutputOperandType(operand_types[i]));
break;
}
}
if (Bytecodes::WritesImplicitRegister(bytecode)) {
assignments->Add(interpreter::Register::FromShortStar(bytecode));
}
}
}
class BytecodeAnalysis::BytecodeAnalysisImpl {
public:
std::ostream& PrintLivenessTo(std::ostream& os) const;
BytecodeAnalysisImpl(BytecodeAnalysis& res,
Handle<BytecodeArray> bytecode_array, Zone* zone)
: res_(res),
zone_(zone),
bytecode_array_(bytecode_array),
loop_infos_(zone),
loop_stack_(zone),
loop_end_index_queue_(zone),
iterator_(bytecode_array, zone) {}
void Analyze();
private:
template <Bytecode BC>
inline void AnalyzeBCInLoop(int current_offset, LoopInfo* current_loop_info) {
}
void PushLoop(int loop_header, int loop_end) {
DCHECK_LT(loop_header, loop_end);
DCHECK_LT(loop_stack_.top().header_offset, loop_header);
DCHECK_EQ(res_.end_to_header_.find(loop_end), res_.end_to_header_.end());
DCHECK_EQ(
std::ranges::find(loop_infos_, loop_header, &LoopInfo::loop_start),
loop_infos_.end());
int parent_offset = loop_stack_.top().header_offset;
res_.end_to_header_.insert({loop_end, loop_header});
loop_infos_.emplace_back(parent_offset, loop_header, loop_end,
bytecode_array()->parameter_count(),
bytecode_array()->register_count(), zone());
if (loop_stack_.top().header_offset >= 0) {
loop_infos_[loop_stack_.top().loop_info_index].mark_not_innermost();
}
loop_stack_.push({loop_header, static_cast<int>(loop_infos_.size() - 1)});
}
#if DEBUG
bool ResumeJumpTargetsAreValid();
bool ResumeJumpTargetLeavesResolveSuspendIds(
int parent_offset,
const ZoneVector<ResumeJumpTarget>& resume_jump_targets,
std::map<int, int>* unresolved_suspend_ids);
bool LivenessIsValid();
#endif
bool analyze_liveness() const { return res_.analyze_liveness_; }
Zone* zone() const { return zone_; }
DirectHandle<BytecodeArray> bytecode_array() const { return bytecode_array_; }
BytecodeLivenessMap& liveness_map() { return *res_.liveness_map_; }
struct LoopStackEntry {
int header_offset;
int loop_info_index;
};
BytecodeAnalysis& res_;
Zone* zone_;
Handle<BytecodeArray> const bytecode_array_;
ZoneVector<LoopInfo> loop_infos_;
ZoneStack<LoopStackEntry> loop_stack_;
ZoneVector<int> loop_end_index_queue_;
interpreter::BytecodeArrayRandomIterator iterator_;
};
template <>
inline void BytecodeAnalysis::BytecodeAnalysisImpl::AnalyzeBCInLoop<
Bytecode::kSuspendGenerator>(int current_offset,
LoopInfo* current_loop_info) {
int suspend_id = iterator_.GetUnsignedImmediateOperand(3);
int resume_offset = current_offset + iterator_.current_bytecode_size();
current_loop_info->AddResumeTarget(
ResumeJumpTarget::Leaf(suspend_id, resume_offset));
}
template <>
inline void BytecodeAnalysis::BytecodeAnalysisImpl::AnalyzeBCInLoop<
Bytecode::kResumeGenerator>(int current_offset,
LoopInfo* current_loop_info) {
current_loop_info->mark_resumable();
}
void BytecodeAnalysis::BytecodeAnalysisImpl::Analyze() {
DCHECK_EQ(res_.bytecode_count_, -1);
res_.bytecode_count_ = iterator_.size();
loop_stack_.push({-1, -1});
BytecodeLivenessState* next_bytecode_in_liveness = nullptr;
int osr_loop_end_offset_ = res_.osr_bailout_id_.ToInt();
DCHECK_EQ(osr_loop_end_offset_ < 0, res_.osr_bailout_id_.IsNone());
if (analyze_liveness()) {
res_.liveness_map_.emplace(bytecode_array()->length(), zone());
}
for (iterator_.GoToEnd(); iterator_.IsValid(); --iterator_) {
Bytecode bytecode = iterator_.current_bytecode();
int current_offset = iterator_.current_offset();
if (bytecode == Bytecode::kJumpLoop) {
int loop_end = current_offset + iterator_.current_bytecode_size();
int loop_header = iterator_.GetJumpTargetOffset();
PushLoop(loop_header, loop_end);
if (current_offset == osr_loop_end_offset_) {
res_.osr_entry_point_ = loop_header;
} else if (current_offset < osr_loop_end_offset_) {
DCHECK_LE(0, res_.osr_entry_point_);
}
if (analyze_liveness()) {
loop_end_index_queue_.push_back(iterator_.current_index());
}
}
bool in_loop = loop_stack_.size() > 1 &&
(bytecode != Bytecode::kJumpLoop ||
iterator_.GetJumpTargetOffset() == current_offset);
if (in_loop) {
LoopStackEntry& current_loop = loop_stack_.top();
LoopInfo* current_loop_info = &loop_infos_[current_loop.loop_info_index];
UpdateAssignments(bytecode, ¤t_loop_info->assignments(), iterator_);
switch (bytecode) {
#define CASE(BC, ...) \
case Bytecode::k##BC: \
AnalyzeBCInLoop<Bytecode::k##BC>(current_offset, current_loop_info); \
break;
BYTECODE_LIST(CASE, CASE)
#undef CASE
}
if (current_offset == current_loop.header_offset) {
loop_stack_.pop();
if (loop_stack_.size() > 1) {
LoopInfo* parent_loop_info =
&loop_infos_[loop_stack_.top().loop_info_index];
if (current_loop_info->resumable()) {
parent_loop_info->mark_resumable();
}
parent_loop_info->assignments().Union(
current_loop_info->assignments());
for (const auto& target : current_loop_info->resume_jump_targets()) {
parent_loop_info->AddResumeTarget(
ResumeJumpTarget::AtLoopHeader(current_offset, target));
}
} else {
for (const auto& target : current_loop_info->resume_jump_targets()) {
res_.resume_jump_targets_.push_back(
ResumeJumpTarget::AtLoopHeader(current_offset, target));
}
}
}
} else if (bytecode == Bytecode::kSuspendGenerator) {
int suspend_id = iterator_.GetUnsignedImmediateOperand(3);
int resume_offset = current_offset + iterator_.current_bytecode_size();
res_.resume_jump_targets_.push_back(
ResumeJumpTarget::Leaf(suspend_id, resume_offset));
}
if (analyze_liveness()) {
BytecodeLiveness& liveness =
liveness_map().InsertNewLiveness(current_offset);
UpdateLiveness<true>(bytecode, liveness, &next_bytecode_in_liveness,
iterator_, bytecode_array(), liveness_map(), zone());
}
}
std::ranges::sort(loop_infos_, {}, &LoopInfo::loop_start);
res_.loop_infos_ = std::move(loop_infos_).Release();
DCHECK_EQ(loop_stack_.size(), 1u);
DCHECK_EQ(loop_stack_.top().header_offset, -1);
DCHECK(ResumeJumpTargetsAreValid());
if (!analyze_liveness()) return;
for (int loop_end_index : loop_end_index_queue_) {
iterator_.GoToIndex(loop_end_index);
DCHECK_EQ(iterator_.current_bytecode(), Bytecode::kJumpLoop);
int header_offset = iterator_.GetJumpTargetOffset();
int end_offset = iterator_.current_offset();
BytecodeLiveness& header_liveness =
liveness_map().GetLiveness(header_offset);
BytecodeLiveness& end_liveness = liveness_map().GetLiveness(end_offset);
if (!end_liveness.out->UnionIsChanged(*header_liveness.in)) {
continue;
}
end_liveness.in->CopyFrom(*end_liveness.out);
next_bytecode_in_liveness = end_liveness.in;
--iterator_;
for (; iterator_.current_offset() > header_offset; --iterator_) {
Bytecode bytecode = iterator_.current_bytecode();
int current_offset = iterator_.current_offset();
BytecodeLiveness& liveness = liveness_map().GetLiveness(current_offset);
UpdateLiveness(bytecode, liveness, &next_bytecode_in_liveness, iterator_,
bytecode_array(), liveness_map(), zone());
}
UpdateOutLiveness(iterator_.current_bytecode(), header_liveness,
next_bytecode_in_liveness, iterator_, bytecode_array(),
liveness_map(), zone());
}
DCHECK(analyze_liveness());
if (v8_flags.trace_environment_liveness) {
StdoutStream of;
PrintLivenessTo(of);
}
DCHECK(LivenessIsValid());
}
bool BytecodeAnalysis::IsLoopHeader(int offset) const {
auto it =
std::ranges::lower_bound(loop_infos_, offset, {}, &LoopInfo::loop_start);
return it != loop_infos_.end() && it->loop_start() == offset;
}
int BytecodeAnalysis::GetLoopOffsetFor(int offset) const {
auto loop_end_to_header = end_to_header_.upper_bound(offset);
if (loop_end_to_header == end_to_header_.end()) {
return -1;
}
if (loop_end_to_header->second <= offset) {
return loop_end_to_header->second;
}
auto it =
std::ranges::upper_bound(loop_infos_, offset, {}, &LoopInfo::loop_start);
DCHECK_NE(it, loop_infos_.end());
return it->parent_offset();
}
int BytecodeAnalysis::GetLoopEndOffsetForInnermost(int header_offset) const {
DCHECK(GetLoopInfoFor(header_offset).innermost());
auto loop_end_to_header = end_to_header_.upper_bound(header_offset + 1);
DCHECK_EQ(loop_end_to_header->second, header_offset);
return loop_end_to_header->first;
}
const LoopInfo& BytecodeAnalysis::GetLoopInfoFor(int header_offset) const {
DCHECK(IsLoopHeader(header_offset));
auto it = std::ranges::lower_bound(loop_infos_, header_offset, {},
&LoopInfo::loop_start);
DCHECK_EQ(it->loop_start(), header_offset);
return *it;
}
const LoopInfo* BytecodeAnalysis::TryGetLoopInfoFor(int header_offset) const {
auto it = std::ranges::lower_bound(loop_infos_, header_offset, {},
&LoopInfo::loop_start);
if (it == loop_infos_.end() || it->loop_start() != header_offset)
return nullptr;
return &*it;
}
const BytecodeLivenessState* BytecodeAnalysis::GetInLivenessFor(
int offset) const {
if (!analyze_liveness_) return nullptr;
return liveness_map().GetInLiveness(offset);
}
const BytecodeLivenessState* BytecodeAnalysis::GetOutLivenessFor(
int offset) const {
if (!analyze_liveness_) return nullptr;
return liveness_map().GetOutLiveness(offset);
}
std::ostream& BytecodeAnalysis::BytecodeAnalysisImpl::PrintLivenessTo(
std::ostream& os) const {
interpreter::BytecodeArrayIterator iterator(bytecode_array_);
for (; !iterator.done(); iterator.Advance()) {
int current_offset = iterator.current_offset();
const BytecodeLivenessState* in_liveness =
res_.GetInLivenessFor(current_offset);
const BytecodeLivenessState* out_liveness =
res_.GetOutLivenessFor(current_offset);
os << ToString(*in_liveness) << " -> " << ToString(*out_liveness) << " | "
<< current_offset << ": ";
iterator.PrintCurrentBytecodeTo(os) << std::endl;
}
return os;
}
#if DEBUG
bool BytecodeAnalysis::BytecodeAnalysisImpl::ResumeJumpTargetsAreValid() {
bool valid = true;
interpreter::BytecodeArrayRandomIterator iterator(bytecode_array_, zone());
for (iterator.GoToStart(); iterator.IsValid(); ++iterator) {
if (iterator.current_bytecode() == Bytecode::kSwitchOnGeneratorState) {
break;
}
}
if (!iterator.IsValid()) {
if (!res_.resume_jump_targets().empty()) {
PrintF(stderr,
"Found %zu top-level resume targets but no resume switch\n",
res_.resume_jump_targets().size());
valid = false;
}
for (const auto& loop_info : res_.loop_infos_) {
if (!loop_info.resume_jump_targets().empty()) {
PrintF(stderr,
"Found %zu resume targets at loop at offset %d, but no resume "
"switch\n",
loop_info.resume_jump_targets().size(), loop_info.loop_start());
valid = false;
}
}
return valid;
}
std::map<int, int> unresolved_suspend_ids;
for (interpreter::JumpTableTargetOffset offset :
iterator.GetJumpTableTargetOffsets()) {
int suspend_id = offset.case_value;
int resume_offset = offset.target_offset;
unresolved_suspend_ids[suspend_id] = resume_offset;
}
if (!ResumeJumpTargetLeavesResolveSuspendIds(-1, res_.resume_jump_targets(),
&unresolved_suspend_ids)) {
valid = false;
}
for (const auto& loop_info : res_.loop_infos_) {
if (!ResumeJumpTargetLeavesResolveSuspendIds(
loop_info.loop_start(), loop_info.resume_jump_targets(),
&unresolved_suspend_ids)) {
valid = false;
}
}
if (!unresolved_suspend_ids.empty()) {
PrintF(stderr,
"Found suspend ids that are not resolved by a final leaf resume "
"jump:\n");
for (const std::pair<const int, int>& target : unresolved_suspend_ids) {
PrintF(stderr, " %d -> %d\n", target.first, target.second);
}
valid = false;
}
return valid;
}
bool BytecodeAnalysis::BytecodeAnalysisImpl::
ResumeJumpTargetLeavesResolveSuspendIds(
int parent_offset,
const ZoneVector<ResumeJumpTarget>& resume_jump_targets,
std::map<int, int>* unresolved_suspend_ids) {
bool valid = true;
for (const ResumeJumpTarget& target : resume_jump_targets) {
std::map<int, int>::iterator it =
unresolved_suspend_ids->find(target.suspend_id());
if (it == unresolved_suspend_ids->end()) {
PrintF(
stderr,
"No unresolved suspend found for resume target with suspend id %d\n",
target.suspend_id());
valid = false;
continue;
}
int expected_target = it->second;
if (target.is_leaf()) {
if (target.target_offset() != expected_target) {
PrintF(
stderr,
"Expected leaf resume target for id %d to have target offset %d, "
"but had %d\n",
target.suspend_id(), expected_target, target.target_offset());
valid = false;
} else {
interpreter::BytecodeArrayIterator iterator(bytecode_array_,
target.target_offset());
if (iterator.current_bytecode() != Bytecode::kResumeGenerator) {
PrintF(stderr,
"Expected resume target for id %d, offset %d, to be "
"ResumeGenerator, but found %s\n",
target.suspend_id(), target.target_offset(),
Bytecodes::ToString(iterator.current_bytecode()));
valid = false;
}
}
unresolved_suspend_ids->erase(it);
} else {
if (!res_.IsLoopHeader(target.target_offset())) {
PrintF(stderr,
"Expected non-leaf resume target for id %d to have a loop "
"header at target offset %d\n",
target.suspend_id(), target.target_offset());
valid = false;
} else {
LoopInfo loop_info = res_.GetLoopInfoFor(target.target_offset());
if (loop_info.parent_offset() != parent_offset) {
PrintF(stderr,
"Expected non-leaf resume target for id %d to have a direct "
"inner loop at target offset %d\n",
target.suspend_id(), target.target_offset());
valid = false;
}
}
}
}
return valid;
}
bool BytecodeAnalysis::BytecodeAnalysisImpl::LivenessIsValid() {
interpreter::BytecodeArrayRandomIterator iterator(bytecode_array_, zone());
BytecodeLivenessState previous_liveness(bytecode_array_->register_count(),
zone());
int invalid_offset = -1;
int which_invalid = -1;
BytecodeLivenessState invalid_liveness(bytecode_array_->register_count(),
zone());
BytecodeLivenessState* next_bytecode_in_liveness = nullptr;
for (iterator.GoToEnd(); iterator.IsValid(); --iterator) {
Bytecode bytecode = iterator.current_bytecode();
int current_offset = iterator.current_offset();
BytecodeLiveness& liveness = liveness_map().GetLiveness(current_offset);
previous_liveness.CopyFrom(*liveness.out);
UpdateOutLiveness(bytecode, liveness, next_bytecode_in_liveness, iterator,
bytecode_array_, liveness_map(), zone());
if (bytecode == Bytecode::kJumpLoop) {
int target_offset = iterator.GetJumpTargetOffset();
liveness.out->Union(*liveness_map().GetInLiveness(target_offset));
}
if (!liveness.out->Equals(previous_liveness)) {
invalid_liveness.CopyFrom(*liveness.out);
liveness.out->CopyFrom(previous_liveness);
invalid_offset = current_offset;
which_invalid = 1;
break;
}
previous_liveness.CopyFrom(*liveness.in);
liveness.in->CopyFrom(*liveness.out);
UpdateInLiveness(bytecode, liveness.in, iterator);
if (!liveness.in->Equals(previous_liveness)) {
invalid_liveness.CopyFrom(*liveness.in);
liveness.in->CopyFrom(previous_liveness);
invalid_offset = current_offset;
which_invalid = 0;
break;
}
next_bytecode_in_liveness = liveness.in;
}
for (iterator.GoToStart(); iterator.IsValid() && invalid_offset == -1;
++iterator) {
Bytecode bytecode = iterator.current_bytecode();
int current_offset = iterator.current_offset();
int loop_header = res_.GetLoopOffsetFor(current_offset);
if (loop_header == -1) continue;
if (!Bytecodes::IsJump(bytecode)) continue;
int jump_target = iterator.GetJumpTargetOffset();
if (Bytecodes::IsForwardJump(bytecode) &&
res_.GetLoopOffsetFor(jump_target) == loop_header) {
continue;
}
if (liveness_map().GetLiveness(jump_target).in->AccumulatorIsLive()) {
invalid_offset = jump_target;
which_invalid = 0;
break;
}
}
if (invalid_offset != -1) {
OFStream of(stderr);
of << "Invalid liveness:" << std::endl;
int loop_indent = 0;
interpreter::BytecodeArrayIterator forward_iterator(bytecode_array_);
for (; !forward_iterator.done(); forward_iterator.Advance()) {
int current_offset = forward_iterator.current_offset();
const BytecodeLivenessState* in_liveness =
res_.GetInLivenessFor(current_offset);
const BytecodeLivenessState* out_liveness =
res_.GetOutLivenessFor(current_offset);
std::string in_liveness_str = ToString(*in_liveness);
std::string out_liveness_str = ToString(*out_liveness);
of << in_liveness_str << " | " << out_liveness_str << " : "
<< current_offset << " : ";
if (forward_iterator.current_bytecode() == Bytecode::kJumpLoop) {
loop_indent--;
}
for (int i = 0; i < loop_indent; ++i) {
of << "| ";
}
if (forward_iterator.current_bytecode() == Bytecode::kJumpLoop) {
of << "`-";
} else if (res_.IsLoopHeader(current_offset)) {
of << ".>";
loop_indent++;
}
forward_iterator.PrintCurrentBytecodeTo(of);
if (Bytecodes::IsJump(forward_iterator.current_bytecode())) {
of << " (@" << forward_iterator.GetJumpTargetOffset() << ")";
}
of << std::endl;
if (current_offset == invalid_offset) {
char in_underline = which_invalid == 0 ? '^' : ' ';
char out_underline = which_invalid == 0 ? ' ' : '^';
of << std::string(in_liveness_str.size(), in_underline) << " "
<< std::string(out_liveness_str.size(), out_underline);
of << " : " << current_offset << " : ";
for (int i = 0; i < loop_indent; ++i) {
of << "| ";
}
of << std::endl;
if (which_invalid == 0) {
of << ToString(invalid_liveness) << " "
<< std::string(out_liveness_str.size(), ' ');
} else {
of << std::string(in_liveness_str.size(), ' ') << " "
<< ToString(invalid_liveness);
}
of << " : " << current_offset << " : ";
for (int i = 0; i < loop_indent; ++i) {
of << "| ";
}
of << std::endl;
}
}
}
return invalid_offset == -1;
}
#endif
BytecodeAnalysis::BytecodeAnalysis(Handle<BytecodeArray> bytecode_array,
Zone* zone, BytecodeOffset osr_bailout_id,
bool analyze_liveness)
: osr_bailout_id_(osr_bailout_id),
analyze_liveness_(analyze_liveness),
resume_jump_targets_(zone),
end_to_header_(zone),
osr_entry_point_(-1) {
BytecodeAnalysisImpl analysis(*this, bytecode_array, zone);
analysis.Analyze();
DCHECK_IMPLIES(analyze_liveness_, liveness_map_.has_value());
DCHECK_NE(bytecode_count_, -1);
}
}
}
}