diff --git a/BUILD.gn b/BUILD.gn index be9a23ebb5..3a5660a145 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -1024,6 +1024,8 @@ v8_source_set("v8_base") { "src/compiler/bytecode-analysis.h", "src/compiler/bytecode-graph-builder.cc", "src/compiler/bytecode-graph-builder.h", + "src/compiler/bytecode-liveness-map.cc", + "src/compiler/bytecode-liveness-map.h", "src/compiler/c-linkage.cc", "src/compiler/checkpoint-elimination.cc", "src/compiler/checkpoint-elimination.h", diff --git a/src/DEPS b/src/DEPS index 63886fb017..dc924f799c 100644 --- a/src/DEPS +++ b/src/DEPS @@ -10,6 +10,7 @@ include_rules = [ "+src/heap/heap-inl.h", "-src/inspector", "-src/interpreter", + "+src/interpreter/bytecode-array-accessor.h", "+src/interpreter/bytecode-array-iterator.h", "+src/interpreter/bytecode-array-reverse-iterator.h", "+src/interpreter/bytecode-decoder.h", diff --git a/src/compiler/bytecode-analysis.cc b/src/compiler/bytecode-analysis.cc index ea5d71a7da..5b3db68906 100644 --- a/src/compiler/bytecode-analysis.cc +++ b/src/compiler/bytecode-analysis.cc @@ -4,6 +4,7 @@ #include "src/compiler/bytecode-analysis.h" +#include "src/interpreter/bytecode-array-iterator.h" #include "src/interpreter/bytecode-array-reverse-iterator.h" #include "src/objects-inl.h" @@ -11,30 +12,262 @@ namespace v8 { namespace internal { namespace compiler { +using namespace interpreter; + BytecodeAnalysis::BytecodeAnalysis(Handle bytecode_array, - Zone* zone) + Zone* zone, bool do_liveness_analysis) : bytecode_array_(bytecode_array), + do_liveness_analysis_(do_liveness_analysis), zone_(zone), loop_stack_(zone), end_to_header_(zone), - header_to_parent_(zone) {} + header_to_parent_(zone), + liveness_map_(bytecode_array->length(), zone) {} + +namespace { + +void UpdateInLiveness(Bytecode bytecode, BitVector& in_liveness, + const BytecodeArrayAccessor& accessor) { + int num_operands = Bytecodes::NumberOfOperands(bytecode); + const OperandType* operand_types = Bytecodes::GetOperandTypes(bytecode); + AccumulatorUse accumulator_use = Bytecodes::GetAccumulatorUse(bytecode); + + if (accumulator_use == AccumulatorUse::kWrite) { + in_liveness.Remove(in_liveness.length() - 1); + } + for (int i = 0; i < num_operands; ++i) { + switch (operand_types[i]) { + case OperandType::kRegOut: { + interpreter::Register r = accessor.GetRegisterOperand(i); + if (!r.is_parameter()) { + in_liveness.Remove(r.index()); + } + break; + } + case OperandType::kRegOutPair: { + interpreter::Register r = accessor.GetRegisterOperand(i); + if (!r.is_parameter()) { + DCHECK(!interpreter::Register(r.index() + 1).is_parameter()); + in_liveness.Remove(r.index()); + in_liveness.Remove(r.index() + 1); + } + break; + } + case OperandType::kRegOutTriple: { + interpreter::Register r = accessor.GetRegisterOperand(i); + if (!r.is_parameter()) { + DCHECK(!interpreter::Register(r.index() + 1).is_parameter()); + DCHECK(!interpreter::Register(r.index() + 2).is_parameter()); + in_liveness.Remove(r.index()); + in_liveness.Remove(r.index() + 1); + in_liveness.Remove(r.index() + 2); + } + break; + } + default: + DCHECK(!Bytecodes::IsRegisterOutputOperandType(operand_types[i])); + break; + } + } + + if (accumulator_use == AccumulatorUse::kRead) { + in_liveness.Add(in_liveness.length() - 1); + } + for (int i = 0; i < num_operands; ++i) { + switch (operand_types[i]) { + case OperandType::kReg: { + interpreter::Register r = accessor.GetRegisterOperand(i); + if (!r.is_parameter()) { + in_liveness.Add(r.index()); + } + break; + } + case OperandType::kRegPair: { + interpreter::Register r = accessor.GetRegisterOperand(i); + if (!r.is_parameter()) { + DCHECK(!interpreter::Register(r.index() + 1).is_parameter()); + in_liveness.Add(r.index()); + in_liveness.Add(r.index() + 1); + } + break; + } + case OperandType::kRegList: { + interpreter::Register r = accessor.GetRegisterOperand(i++); + uint32_t reg_count = accessor.GetRegisterCountOperand(i); + if (!r.is_parameter()) { + for (uint32_t j = 0; j < reg_count; ++j) { + DCHECK(!interpreter::Register(r.index() + j).is_parameter()); + in_liveness.Add(r.index() + j); + } + } + } + default: + DCHECK(!Bytecodes::IsRegisterInputOperandType(operand_types[i])); + break; + } + } +} + +void UpdateOutLiveness(Bytecode bytecode, BitVector& out_liveness, + const BytecodeArrayAccessor& accessor, + const BytecodeLivenessMap& liveness_map) { + int current_offset = accessor.current_offset(); + const Handle& bytecode_array = accessor.bytecode_array(); + + // Update from jump target (if any). Skip loops, we update these manually in + // the liveness iterations. + if (Bytecodes::IsForwardJump(bytecode)) { + int target_offset = accessor.GetJumpTargetOffset(); + out_liveness.Union(*liveness_map.GetInLiveness(target_offset)); + } + + // Update from next bytecode (unless this is an unconditional jump). + if (!Bytecodes::IsUnconditionalJump(bytecode)) { + int next_offset = current_offset + accessor.current_bytecode_size(); + if (next_offset < bytecode_array->length()) { + out_liveness.Union(*liveness_map.GetInLiveness(next_offset)); + } + } + + // Update from exception handler (if any). + if (!interpreter::Bytecodes::IsWithoutExternalSideEffects(bytecode)) { + int handler_context; + // TODO(leszeks): We should look up this range only once per entry. + HandlerTable* table = HandlerTable::cast(bytecode_array->handler_table()); + int handler_offset = + table->LookupRange(current_offset, &handler_context, nullptr); + + if (handler_offset != -1) { + out_liveness.Union(*liveness_map.GetInLiveness(handler_offset)); + out_liveness.Add(handler_context); + } + } +} + +} // namespace void BytecodeAnalysis::Analyze() { loop_stack_.push(-1); - interpreter::BytecodeArrayReverseIterator iterator(bytecode_array(), zone()); - while (!iterator.done()) { - interpreter::Bytecode bytecode = iterator.current_bytecode(); - if (bytecode == interpreter::Bytecode::kJumpLoop) { - PushLoop(iterator.GetJumpTargetOffset(), iterator.current_offset()); - } else if (iterator.current_offset() == loop_stack_.top()) { + // The last JumpLoop that we haven't done a guaranteed valid liveness pass + // over. See the below wall of text for a more thorough explanation. + int last_invalid_jumploop_offset = -1; + + BytecodeArrayReverseIterator iterator(bytecode_array(), zone()); + for (; !iterator.done(); iterator.Advance()) { + Bytecode bytecode = iterator.current_bytecode(); + int current_offset = iterator.current_offset(); + + if (bytecode == Bytecode::kJumpLoop) { + PushLoop(iterator.GetJumpTargetOffset(), current_offset); + + // Save the last offset so that we can do another pass later. + if (last_invalid_jumploop_offset == -1) { + last_invalid_jumploop_offset = current_offset; + } + } else if (current_offset == loop_stack_.top()) { loop_stack_.pop(); } - iterator.Advance(); + + if (do_liveness_analysis_) { + // The liveness vector had bits for the liveness of the registers, and one + // more bit for the liveness of the accumulator. + Liveness& liveness = liveness_map_.InitializeLiveness( + current_offset, bytecode_array()->register_count() + 1, zone()); + + UpdateOutLiveness(bytecode, *liveness.out, iterator, liveness_map_); + liveness.in->CopyFrom(*liveness.out); + UpdateInLiveness(bytecode, *liveness.in, iterator); + } } DCHECK_EQ(loop_stack_.size(), 1u); DCHECK_EQ(loop_stack_.top(), -1); + + if (!do_liveness_analysis_) return; + + // At this point, every bytecode has a valid in and out liveness, except for + // propagating liveness across back edges (i.e. JumpLoop). Subsequent liveness + // analysis iterations can only add additional liveness bits that are pulled + // across these back edges. + // + // Furthermore, a loop header's in-liveness can only change based on any + // bytecodes *after* the loop end -- it cannot change as a result of the + // JumpLoop liveness being updated, as the only liveness bits than can be + // added to the loop body are those of the loop header. + // + // So, if we know that the liveness of bytecodes after a loop header won't + // change (e.g. because there are no loops in them, or we have already ensured + // those loops are valid), we can safely update the loop end and pass over the + // loop body, and then never have to pass over that loop end again, because we + // have shown that its target, the loop header, can't change from the entries + // after the loop, and can't change from any loop body pass. + // + // This means that in a pass, we can iterate backwards over the bytecode + // array, process any loops that we encounter, and on subsequent passes we can + // skip processing those loops (though we still have to process inner loops). + + while (last_invalid_jumploop_offset != -1) { + // TODO(leszeks): We shouldn't need to iterate here, we should just have a + // random access iterator. + iterator.Reset(); + while (last_invalid_jumploop_offset < iterator.current_offset()) { + iterator.Advance(); + } + last_invalid_jumploop_offset = -1; + + DCHECK_EQ(iterator.current_bytecode(), Bytecode::kJumpLoop); + + for (; !iterator.done(); iterator.Advance()) { + Bytecode bytecode = iterator.current_bytecode(); + if (bytecode != Bytecode::kJumpLoop) { + // Skip bytecodes until we hit a JumpLoop. This check isn't needed for + // the first loop we see (thanks to saving its offset), but it is for + // subsequent ones we want to process on this pass. + continue; + } + + int header_offset = iterator.GetJumpTargetOffset(); + int end_offset = iterator.current_offset(); + + Liveness& header_liveness = liveness_map_.GetLiveness(header_offset); + Liveness& end_liveness = liveness_map_.GetLiveness(end_offset); + + if (end_liveness.out->UnionIsChanged(*header_liveness.in)) { + // Only update the loop body if the loop end liveness changed. + end_liveness.in->CopyFrom(*end_liveness.out); + + // Advance into the loop body. + iterator.Advance(); + for (; iterator.current_offset() > header_offset; iterator.Advance()) { + bytecode = iterator.current_bytecode(); + if (bytecode == Bytecode::kJumpLoop) { + // We can't validate this loop at the moment because we can't + // guarantee that its header is valid yet. Save it for later. + if (last_invalid_jumploop_offset == -1) { + last_invalid_jumploop_offset = iterator.current_offset(); + } + } + + int current_offset = iterator.current_offset(); + Liveness& liveness = liveness_map_.GetLiveness(current_offset); + + UpdateOutLiveness(bytecode, *liveness.out, iterator, liveness_map_); + liveness.in->CopyFrom(*liveness.out); + UpdateInLiveness(bytecode, *liveness.in, iterator); + } + // Now we are at the loop header. Since the in-liveness of the header + // can't change, we need only to update the out-liveness. + bytecode = iterator.current_bytecode(); + UpdateOutLiveness(bytecode, *header_liveness.out, iterator, + liveness_map_); + } + + // Keep the iterator going so that we can find other loops. + } + } + + DCHECK(LivenessIsValid()); } void BytecodeAnalysis::PushLoop(int loop_header, int loop_end) { @@ -92,6 +325,163 @@ int BytecodeAnalysis::GetParentLoopFor(int header_offset) const { return header_to_parent_.find(header_offset)->second; } +const BitVector* BytecodeAnalysis::GetInLivenessFor(int offset) const { + if (!do_liveness_analysis_) return nullptr; + + return liveness_map_.GetInLiveness(offset); +} + +const BitVector* BytecodeAnalysis::GetOutLivenessFor(int offset) const { + if (!do_liveness_analysis_) return nullptr; + + return liveness_map_.GetOutLiveness(offset); +} + +std::ostream& BytecodeAnalysis::PrintLivenessTo(std::ostream& os) const { + interpreter::BytecodeArrayIterator iterator(bytecode_array()); + + for (; !iterator.done(); iterator.Advance()) { + int current_offset = iterator.current_offset(); + + const BitVector* in_liveness = GetInLivenessFor(current_offset); + const BitVector* out_liveness = GetOutLivenessFor(current_offset); + + for (int i = 0; i < in_liveness->length(); ++i) { + os << (in_liveness->Contains(i) ? "L" : "."); + } + os << " -> "; + + for (int i = 0; i < out_liveness->length(); ++i) { + os << (out_liveness->Contains(i) ? "L" : "."); + } + + os << " | " << current_offset << ": "; + iterator.PrintTo(os) << std::endl; + } + + return os; +} + +#if DEBUG +bool BytecodeAnalysis::LivenessIsValid() { + BytecodeArrayReverseIterator iterator(bytecode_array(), zone()); + + BitVector previous_liveness(bytecode_array()->register_count() + 1, zone()); + + int invalid_offset = -1; + int which_invalid = -1; + + // Ensure that there are no liveness changes if we iterate one more time. + for (iterator.Reset(); !iterator.done(); iterator.Advance()) { + Bytecode bytecode = iterator.current_bytecode(); + + int current_offset = iterator.current_offset(); + + Liveness& liveness = liveness_map_.GetLiveness(current_offset); + + previous_liveness.CopyFrom(*liveness.out); + + UpdateOutLiveness(bytecode, *liveness.out, iterator, liveness_map_); + // UpdateOutLiveness skips kJumpLoop, so we update it manually. + if (bytecode == Bytecode::kJumpLoop) { + int target_offset = iterator.GetJumpTargetOffset(); + liveness.out->Union(*liveness_map_.GetInLiveness(target_offset)); + } + + if (!liveness.out->Equals(previous_liveness)) { + // Reset the invalid liveness. + 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)) { + // Reset the invalid liveness. + liveness.in->CopyFrom(previous_liveness); + invalid_offset = current_offset; + which_invalid = 0; + break; + } + } + + if (invalid_offset != -1) { + OFStream of(stderr); + of << "Invalid liveness:" << std::endl; + + // Dump the bytecode, annotated with the liveness and marking loops. + + int loop_indent = 0; + + BytecodeArrayIterator forward_iterator(bytecode_array()); + for (; !forward_iterator.done(); forward_iterator.Advance()) { + int current_offset = forward_iterator.current_offset(); + BitVector* in_liveness = liveness_map_.GetInLiveness(current_offset); + BitVector* out_liveness = liveness_map_.GetOutLiveness(current_offset); + + for (int i = 0; i < in_liveness->length(); ++i) { + of << (in_liveness->Contains(i) ? 'L' : '.'); + } + + of << " | "; + + for (int i = 0; i < out_liveness->length(); ++i) { + of << (out_liveness->Contains(i) ? 'L' : '.'); + } + + of << " : " << current_offset << " : "; + + // Draw loop back edges by indentin everything between loop headers and + // jump loop instructions. + 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 << " `-" << current_offset; + } else if (IsLoopHeader(current_offset)) { + of << " .>" << current_offset; + loop_indent++; + } + forward_iterator.PrintTo(of) << std::endl; + + if (current_offset == invalid_offset) { + // Underline the invalid liveness. + if (which_invalid == 0) { + for (int i = 0; i < in_liveness->length(); ++i) { + of << '^'; + } + } else { + for (int i = 0; i < in_liveness->length() + 3; ++i) { + of << ' '; + } + for (int i = 0; i < out_liveness->length(); ++i) { + of << '^'; + } + } + + // Make sure to draw the loop indentation marks on this additional line. + of << " : " << current_offset << " : "; + for (int i = 0; i < loop_indent; ++i) { + of << " | "; + } + + of << std::endl; + } + } + } + + return invalid_offset == -1; +} +#endif + } // namespace compiler } // namespace internal } // namespace v8 diff --git a/src/compiler/bytecode-analysis.h b/src/compiler/bytecode-analysis.h index f50b00f2c6..9baba84f92 100644 --- a/src/compiler/bytecode-analysis.h +++ b/src/compiler/bytecode-analysis.h @@ -5,6 +5,9 @@ #ifndef V8_COMPILER_BYTECODE_ANALYSIS_H_ #define V8_COMPILER_BYTECODE_ANALYSIS_H_ +#include "src/base/hashmap.h" +#include "src/bit-vector.h" +#include "src/compiler/bytecode-liveness-map.h" #include "src/handles.h" #include "src/zone/zone-containers.h" @@ -15,9 +18,10 @@ class BytecodeArray; namespace compiler { -class BytecodeAnalysis BASE_EMBEDDED { +class V8_EXPORT_PRIVATE BytecodeAnalysis BASE_EMBEDDED { public: - BytecodeAnalysis(Handle bytecode_array, Zone* zone); + BytecodeAnalysis(Handle bytecode_array, Zone* zone, + bool do_liveness_analysis); // Analyze the bytecodes to find the loop ranges and nesting. No other // methods in this class return valid information until this has been called. @@ -32,13 +36,33 @@ class BytecodeAnalysis BASE_EMBEDDED { // at {header_offset}, or -1 for outer-most loops. int GetParentLoopFor(int header_offset) const; + // Gets the in-liveness for the bytecode at {offset}. The liveness bit vector + // represents the liveness of the registers and the accumulator, with the last + // bit being the accumulator liveness bit, and so is (register count + 1) bits + // long. + const BitVector* GetInLivenessFor(int offset) const; + + // Gets the out-liveness for the bytecode at {offset}. The liveness bit vector + // represents the liveness of the registers and the accumulator, with the last + // bit being the accumulator liveness bit, and so is (register count + 1) bits + // long. + const BitVector* GetOutLivenessFor(int offset) const; + + std::ostream& PrintLivenessTo(std::ostream& os) const; + private: void PushLoop(int loop_header, int loop_end); +#if DEBUG + bool LivenessIsValid(); +#endif + Zone* zone() const { return zone_; } Handle bytecode_array() const { return bytecode_array_; } + private: Handle bytecode_array_; + bool do_liveness_analysis_; Zone* zone_; ZoneStack loop_stack_; @@ -46,6 +70,8 @@ class BytecodeAnalysis BASE_EMBEDDED { ZoneMap end_to_header_; ZoneMap header_to_parent_; + BytecodeLivenessMap liveness_map_; + DISALLOW_COPY_AND_ASSIGN(BytecodeAnalysis); }; diff --git a/src/compiler/bytecode-graph-builder.cc b/src/compiler/bytecode-graph-builder.cc index 80bf950940..558ef12684 100644 --- a/src/compiler/bytecode-graph-builder.cc +++ b/src/compiler/bytecode-graph-builder.cc @@ -35,7 +35,6 @@ class BytecodeGraphBuilder::Environment : public ZoneObject { Node* LookupAccumulator() const; Node* LookupRegister(interpreter::Register the_register) const; - void MarkAllRegistersLive(); void BindAccumulator(Node* node, FrameStateAttachmentMode mode = kDontAttachFrameState); @@ -56,7 +55,7 @@ class BytecodeGraphBuilder::Environment : public ZoneObject { // Preserve a checkpoint of the environment for the IR graph. Any // further mutation of the environment will not affect checkpoints. Node* Checkpoint(BailoutId bytecode_offset, OutputFrameStateCombine combine, - bool owner_has_exception); + bool owner_has_exception, const BitVector* liveness); // Control dependency tracked by this environment. Node* GetControlDependency() const { return control_dependency_; } @@ -76,21 +75,20 @@ class BytecodeGraphBuilder::Environment : public ZoneObject { void PrepareForLoopExit(Node* loop); private: - Environment(const Environment* copy, LivenessAnalyzerBlock* liveness_block); + explicit Environment(const Environment* copy); void PrepareForLoop(); - bool StateValuesRequireUpdate(Node** state_values, int offset, int count); - void UpdateStateValues(Node** state_values, int offset, int count); + bool StateValuesRequireUpdate(Node** state_values, Node** values, int count); + void UpdateStateValues(Node** state_values, Node** values, int count); + void UpdateStateValuesWithCache(Node** state_values, Node** values, + int count); int RegisterToValuesIndex(interpreter::Register the_register) const; - bool IsLivenessBlockConsistent() const; - Zone* zone() const { return builder_->local_zone(); } Graph* graph() const { return builder_->graph(); } CommonOperatorBuilder* common() const { return builder_->common(); } BytecodeGraphBuilder* builder() const { return builder_; } - LivenessAnalyzerBlock* liveness_block() const { return liveness_block_; } const NodeVector* values() const { return &values_; } NodeVector* values() { return &values_; } int register_base() const { return register_base_; } @@ -99,7 +97,6 @@ class BytecodeGraphBuilder::Environment : public ZoneObject { BytecodeGraphBuilder* builder_; int register_count_; int parameter_count_; - LivenessAnalyzerBlock* liveness_block_; Node* context_; Node* control_dependency_; Node* effect_dependency_; @@ -109,6 +106,10 @@ class BytecodeGraphBuilder::Environment : public ZoneObject { Node* accumulator_state_values_; int register_base_; int accumulator_base_; + + // A working area for writing maybe-dead values to when updating the state + // values for registers. + NodeVector state_value_working_area_; }; @@ -123,16 +124,14 @@ BytecodeGraphBuilder::Environment::Environment(BytecodeGraphBuilder* builder, : builder_(builder), register_count_(register_count), parameter_count_(parameter_count), - liveness_block_(builder->is_liveness_analysis_enabled_ - ? builder_->liveness_analyzer()->NewBlock() - : nullptr), context_(context), control_dependency_(control_dependency), effect_dependency_(control_dependency), values_(builder->local_zone()), parameters_state_values_(nullptr), registers_state_values_(nullptr), - accumulator_state_values_(nullptr) { + accumulator_state_values_(nullptr), + state_value_working_area_(builder->local_zone()) { // The layout of values_ is: // // [receiver] [parameters] [registers] [accumulator] @@ -157,15 +156,15 @@ BytecodeGraphBuilder::Environment::Environment(BytecodeGraphBuilder* builder, // Accumulator accumulator_base_ = static_cast(values()->size()); values()->push_back(undefined_constant); + + state_value_working_area_.resize(register_count_); } BytecodeGraphBuilder::Environment::Environment( - const BytecodeGraphBuilder::Environment* other, - LivenessAnalyzerBlock* liveness_block) + const BytecodeGraphBuilder::Environment* other) : builder_(other->builder_), register_count_(other->register_count_), parameter_count_(other->parameter_count_), - liveness_block_(liveness_block), context_(other->context_), control_dependency_(other->control_dependency_), effect_dependency_(other->effect_dependency_), @@ -174,7 +173,9 @@ BytecodeGraphBuilder::Environment::Environment( registers_state_values_(nullptr), accumulator_state_values_(nullptr), register_base_(other->register_base_), - accumulator_base_(other->accumulator_base_) { + accumulator_base_(other->accumulator_base_), + // Environments can share their working area. + state_value_working_area_(other->state_value_working_area_) { values_ = other->values_; } @@ -188,16 +189,7 @@ int BytecodeGraphBuilder::Environment::RegisterToValuesIndex( } } -bool BytecodeGraphBuilder::Environment::IsLivenessBlockConsistent() const { - return !builder_->IsLivenessAnalysisEnabled() == - (liveness_block() == nullptr); -} - Node* BytecodeGraphBuilder::Environment::LookupAccumulator() const { - DCHECK(IsLivenessBlockConsistent()); - if (liveness_block() != nullptr) { - liveness_block()->LookupAccumulator(); - } return values()->at(accumulator_base_); } @@ -212,32 +204,15 @@ Node* BytecodeGraphBuilder::Environment::LookupRegister( return builder()->GetNewTarget(); } else { int values_index = RegisterToValuesIndex(the_register); - if (liveness_block() != nullptr && !the_register.is_parameter()) { - DCHECK(IsLivenessBlockConsistent()); - liveness_block()->Lookup(the_register.index()); - } return values()->at(values_index); } } -void BytecodeGraphBuilder::Environment::MarkAllRegistersLive() { - DCHECK(IsLivenessBlockConsistent()); - if (liveness_block() != nullptr) { - for (int i = 0; i < register_count(); ++i) { - liveness_block()->Lookup(i); - } - } -} - void BytecodeGraphBuilder::Environment::BindAccumulator( Node* node, FrameStateAttachmentMode mode) { if (mode == FrameStateAttachmentMode::kAttachFrameState) { builder()->PrepareFrameState(node, OutputFrameStateCombine::PokeAt(0)); } - DCHECK(IsLivenessBlockConsistent()); - if (liveness_block() != nullptr) { - liveness_block()->BindAccumulator(); - } values()->at(accumulator_base_) = node; } @@ -250,10 +225,6 @@ void BytecodeGraphBuilder::Environment::BindRegister( accumulator_base_ - values_index)); } values()->at(values_index) = node; - if (liveness_block() != nullptr && !the_register.is_parameter()) { - DCHECK(IsLivenessBlockConsistent()); - liveness_block()->Bind(the_register.index()); - } } void BytecodeGraphBuilder::Environment::BindRegistersToProjections( @@ -281,41 +252,22 @@ void BytecodeGraphBuilder::Environment::RecordAfterState( BytecodeGraphBuilder::Environment* BytecodeGraphBuilder::Environment::CopyForLoop() { PrepareForLoop(); - if (liveness_block() != nullptr) { - // Finish the current block before copying. - liveness_block_ = builder_->liveness_analyzer()->NewBlock(liveness_block()); - } - return new (zone()) Environment(this, liveness_block()); + return new (zone()) Environment(this); } BytecodeGraphBuilder::Environment* BytecodeGraphBuilder::Environment::CopyForOsrEntry() { - return new (zone()) - Environment(this, builder_->liveness_analyzer()->NewBlock()); + return new (zone()) Environment(this); } BytecodeGraphBuilder::Environment* BytecodeGraphBuilder::Environment::CopyForConditional() { - LivenessAnalyzerBlock* copy_liveness_block = nullptr; - if (liveness_block() != nullptr) { - copy_liveness_block = - builder_->liveness_analyzer()->NewBlock(liveness_block()); - liveness_block_ = builder_->liveness_analyzer()->NewBlock(liveness_block()); - } - return new (zone()) Environment(this, copy_liveness_block); + return new (zone()) Environment(this); } void BytecodeGraphBuilder::Environment::Merge( BytecodeGraphBuilder::Environment* other) { - if (builder_->is_liveness_analysis_enabled_) { - if (GetControlDependency()->opcode() != IrOpcode::kLoop) { - liveness_block_ = - builder()->liveness_analyzer()->NewBlock(liveness_block()); - } - liveness_block()->AddPredecessor(other->liveness_block()); - } - // Create a merge of the control dependencies of both environments and update // the current environment's control dependency accordingly. Node* control = builder()->MergeControl(GetControlDependency(), @@ -383,7 +335,7 @@ void BytecodeGraphBuilder::Environment::PrepareForOsrEntry() { BailoutId loop_id(builder_->bytecode_iterator().current_offset()); Node* frame_state = - Checkpoint(loop_id, OutputFrameStateCombine::Ignore(), false); + Checkpoint(loop_id, OutputFrameStateCombine::Ignore(), false, nullptr); Node* checkpoint = graph()->NewNode(common()->Checkpoint(), frame_state, entry, entry); UpdateEffectDependency(checkpoint); @@ -401,15 +353,13 @@ void BytecodeGraphBuilder::Environment::PrepareForOsrEntry() { } bool BytecodeGraphBuilder::Environment::StateValuesRequireUpdate( - Node** state_values, int offset, int count) { + Node** state_values, Node** values, int count) { if (*state_values == nullptr) { return true; } DCHECK_EQ((*state_values)->InputCount(), count); - DCHECK_LE(static_cast(offset + count), values()->size()); - Node** env_values = (count == 0) ? nullptr : &values()->at(offset); for (int i = 0; i < count; i++) { - if ((*state_values)->InputAt(i) != env_values[i]) { + if ((*state_values)->InputAt(i) != values[i]) { return true; } } @@ -443,21 +393,51 @@ void BytecodeGraphBuilder::Environment::PrepareForLoopExit(Node* loop) { } void BytecodeGraphBuilder::Environment::UpdateStateValues(Node** state_values, - int offset, + Node** values, int count) { - if (StateValuesRequireUpdate(state_values, offset, count)) { + if (StateValuesRequireUpdate(state_values, values, count)) { const Operator* op = common()->StateValues(count); - (*state_values) = graph()->NewNode(op, count, &values()->at(offset)); + (*state_values) = graph()->NewNode(op, count, values); } } +void BytecodeGraphBuilder::Environment::UpdateStateValuesWithCache( + Node** state_values, Node** values, int count) { + *state_values = builder_->state_values_cache_.GetNodeForValues( + values, static_cast(count)); +} + Node* BytecodeGraphBuilder::Environment::Checkpoint( BailoutId bailout_id, OutputFrameStateCombine combine, - bool owner_has_exception) { - UpdateStateValues(¶meters_state_values_, 0, parameter_count()); - UpdateStateValues(®isters_state_values_, register_base(), - register_count()); - UpdateStateValues(&accumulator_state_values_, accumulator_base(), 1); + bool owner_has_exception, const BitVector* liveness) { + UpdateStateValues(¶meters_state_values_, &values()->at(0), + parameter_count()); + + if (liveness) { + Node* optimized_out = builder()->jsgraph()->OptimizedOutConstant(); + + for (int i = 0; i < register_count(); ++i) { + state_value_working_area_[i] = liveness->Contains(i) + ? values()->at(register_base() + i) + : optimized_out; + } + + Node* accumulator_value = liveness->Contains(register_count()) + ? values()->at(accumulator_base()) + : optimized_out; + + UpdateStateValuesWithCache(®isters_state_values_, + state_value_working_area_.data(), + register_count()); + + UpdateStateValues(&accumulator_state_values_, &accumulator_value, 1); + } else { + UpdateStateValuesWithCache(®isters_state_values_, + &values()->at(register_base()), + register_count()); + UpdateStateValues(&accumulator_state_values_, + &values()->at(accumulator_base()), 1); + } const Operator* op = common()->FrameState( bailout_id, combine, builder()->frame_state_function_info()); @@ -466,18 +446,6 @@ Node* BytecodeGraphBuilder::Environment::Checkpoint( accumulator_state_values_, Context(), builder()->GetFunctionClosure(), builder()->graph()->start()); - if (liveness_block() != nullptr) { - // If the owning node has an exception, register the checkpoint to the - // predecessor so that the checkpoint is used for both the normal and the - // exceptional paths. Yes, this is a terrible hack and we might want - // to use an explicit frame state for the exceptional path. - if (owner_has_exception) { - liveness_block()->GetPredecessor()->Checkpoint(result); - } else { - liveness_block()->Checkpoint(result); - } - } - return result; } @@ -505,9 +473,6 @@ BytecodeGraphBuilder::BytecodeGraphBuilder( exit_controls_(local_zone), is_liveness_analysis_enabled_(FLAG_analyze_environment_liveness), state_values_cache_(jsgraph), - liveness_analyzer_( - static_cast(bytecode_array()->register_count()), true, - local_zone), source_positions_(source_positions), start_position_(info->shared_info()->start_position(), inlining_id) { // Bytecode graph builder assumes deoptimziation is enabled. @@ -588,8 +553,6 @@ bool BytecodeGraphBuilder::CreateGraph(bool stack_check) { Node* end = graph()->NewNode(common()->End(input_count), input_count, inputs); graph()->SetEnd(end); - ClearNonLiveSlotsInFrameStates(); - return true; } @@ -602,8 +565,12 @@ void BytecodeGraphBuilder::PrepareEagerCheckpoint() { DCHECK_EQ(IrOpcode::kDead, NodeProperties::GetFrameStateInput(node)->opcode()); BailoutId bailout_id(bytecode_iterator().current_offset()); + + const BitVector* liveness_before = bytecode_analysis()->GetInLivenessFor( + bytecode_iterator().current_offset()); + Node* frame_state_before = environment()->Checkpoint( - bailout_id, OutputFrameStateCombine::Ignore(), false); + bailout_id, OutputFrameStateCombine::Ignore(), false, liveness_before); NodeProperties::ReplaceFrameStateInput(node, frame_state_before); } } @@ -618,28 +585,19 @@ void BytecodeGraphBuilder::PrepareFrameState(Node* node, NodeProperties::GetFrameStateInput(node)->opcode()); BailoutId bailout_id(bytecode_iterator().current_offset()); bool has_exception = NodeProperties::IsExceptionalCall(node); - Node* frame_state_after = - environment()->Checkpoint(bailout_id, combine, has_exception); + + const BitVector* liveness_after = bytecode_analysis()->GetOutLivenessFor( + bytecode_iterator().current_offset()); + + Node* frame_state_after = environment()->Checkpoint( + bailout_id, combine, has_exception, liveness_after); NodeProperties::ReplaceFrameStateInput(node, frame_state_after); } } -void BytecodeGraphBuilder::ClearNonLiveSlotsInFrameStates() { - if (!IsLivenessAnalysisEnabled()) { - return; - } - NonLiveFrameStateSlotReplacer replacer( - &state_values_cache_, jsgraph()->OptimizedOutConstant(), - liveness_analyzer()->local_count(), true, local_zone()); - liveness_analyzer()->Run(&replacer); - if (FLAG_trace_environment_liveness) { - OFStream os(stdout); - liveness_analyzer()->Print(os); - } -} - void BytecodeGraphBuilder::VisitBytecodes(bool stack_check) { - BytecodeAnalysis bytecode_analysis(bytecode_array(), local_zone()); + BytecodeAnalysis bytecode_analysis(bytecode_array(), local_zone(), + FLAG_analyze_environment_liveness); bytecode_analysis.Analyze(); set_bytecode_analysis(&bytecode_analysis); @@ -648,7 +606,14 @@ void BytecodeGraphBuilder::VisitBytecodes(bool stack_check) { SourcePositionTableIterator source_position_iterator( bytecode_array()->source_position_table()); + if (FLAG_trace_environment_liveness) { + OFStream of(stdout); + + bytecode_analysis.PrintLivenessTo(of); + } + BuildOSRNormalEntryPoint(); + for (; !iterator.done(); iterator.Advance()) { int current_offset = iterator.current_offset(); UpdateCurrentSourcePosition(&source_position_iterator, current_offset); @@ -1769,7 +1734,6 @@ void BytecodeGraphBuilder::VisitDebugger() { Node* call = NewNode(javascript()->CallRuntime(Runtime::kHandleDebuggerStatement)); environment()->BindAccumulator(call, Environment::kAttachFrameState); - environment()->MarkAllRegistersLive(); } // We cannot create a graph from the debugger copy of the bytecode array. diff --git a/src/compiler/bytecode-graph-builder.h b/src/compiler/bytecode-graph-builder.h index f26ab210e8..1a39ee05f2 100644 --- a/src/compiler/bytecode-graph-builder.h +++ b/src/compiler/bytecode-graph-builder.h @@ -135,10 +135,6 @@ class BytecodeGraphBuilder { // Conceptually this frame state is "after" a given operation. void PrepareFrameState(Node* node, OutputFrameStateCombine combine); - // Computes register liveness and replaces dead ones in frame states with the - // undefined values. - void ClearNonLiveSlotsInFrameStates(); - void BuildCreateArguments(CreateArgumentsType type); Node* BuildLoadGlobal(Handle name, uint32_t feedback_slot_index, TypeofMode typeof_mode); @@ -260,8 +256,6 @@ class BytecodeGraphBuilder { bytecode_analysis_ = bytecode_analysis; } - LivenessAnalyzer* liveness_analyzer() { return &liveness_analyzer_; } - bool IsLivenessAnalysisEnabled() const { return this->is_liveness_analysis_enabled_; } @@ -307,9 +301,6 @@ class BytecodeGraphBuilder { StateValuesCache state_values_cache_; - // Analyzer of register liveness. - LivenessAnalyzer liveness_analyzer_; - // The Turbofan source position table, to be populated. SourcePositionTable* source_positions_; diff --git a/src/compiler/bytecode-liveness-map.cc b/src/compiler/bytecode-liveness-map.cc new file mode 100644 index 0000000000..899e155b88 --- /dev/null +++ b/src/compiler/bytecode-liveness-map.cc @@ -0,0 +1,41 @@ +// Copyright 2016 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/compiler/bytecode-liveness-map.h" + +namespace v8 { +namespace internal { +namespace compiler { + +Liveness::Liveness(int size, Zone* zone) + : in(new (zone) BitVector(size, zone)), + out(new (zone) BitVector(size, zone)) {} + +BytecodeLivenessMap::BytecodeLivenessMap(int bytecode_size, Zone* zone) + : liveness_map_(base::bits::RoundUpToPowerOfTwo32(bytecode_size / 4 + 1), + base::KeyEqualityMatcher(), + ZoneAllocationPolicy(zone)) {} + +uint32_t OffsetHash(int offset) { return offset; } + +Liveness& BytecodeLivenessMap::InitializeLiveness(int offset, int size, + Zone* zone) { + return liveness_map_ + .LookupOrInsert(offset, OffsetHash(offset), + [&]() { return Liveness(size, zone); }, + ZoneAllocationPolicy(zone)) + ->value; +} + +Liveness& BytecodeLivenessMap::GetLiveness(int offset) { + return liveness_map_.Lookup(offset, OffsetHash(offset))->value; +} + +const Liveness& BytecodeLivenessMap::GetLiveness(int offset) const { + return liveness_map_.Lookup(offset, OffsetHash(offset))->value; +} + +} // namespace compiler +} // namespace internal +} // namespace v8 diff --git a/src/compiler/bytecode-liveness-map.h b/src/compiler/bytecode-liveness-map.h new file mode 100644 index 0000000000..e5c7a38996 --- /dev/null +++ b/src/compiler/bytecode-liveness-map.h @@ -0,0 +1,55 @@ +// Copyright 2016 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_COMPILER_BYTECODE_LIVENESS_MAP_H_ +#define V8_COMPILER_BYTECODE_LIVENESS_MAP_H_ + +#include "src/base/hashmap.h" +#include "src/bit-vector.h" +#include "src/zone/zone.h" + +namespace v8 { +namespace internal { + +class Zone; + +namespace compiler { + +struct Liveness { + BitVector* in; + BitVector* out; + + Liveness(int size, Zone* zone); +}; + +class V8_EXPORT_PRIVATE BytecodeLivenessMap { + public: + BytecodeLivenessMap(int size, Zone* zone); + + Liveness& InitializeLiveness(int offset, int size, Zone* zone); + + Liveness& GetLiveness(int offset); + const Liveness& GetLiveness(int offset) const; + + BitVector* GetInLiveness(int offset) { return GetLiveness(offset).in; } + const BitVector* GetInLiveness(int offset) const { + return GetLiveness(offset).in; + } + + BitVector* GetOutLiveness(int offset) { return GetLiveness(offset).out; } + const BitVector* GetOutLiveness(int offset) const { + return GetLiveness(offset).out; + } + + private: + base::TemplateHashMapImpl, + ZoneAllocationPolicy> + liveness_map_; +}; + +} // namespace compiler +} // namespace internal +} // namespace v8 + +#endif // V8_COMPILER_BYTECODE_LIVENESS_MAP_H_ diff --git a/src/interpreter/bytecode-array-accessor.cc b/src/interpreter/bytecode-array-accessor.cc index d351e22395..1d19f7d4b0 100644 --- a/src/interpreter/bytecode-array-accessor.cc +++ b/src/interpreter/bytecode-array-accessor.cc @@ -189,6 +189,12 @@ int BytecodeArrayAccessor::GetJumpTargetOffset() const { } } +std::ostream& BytecodeArrayAccessor::PrintTo(std::ostream& os) const { + return BytecodeDecoder::Decode( + os, bytecode_array()->GetFirstBytecodeAddress() + bytecode_offset_, + bytecode_array()->parameter_count()); +} + } // namespace interpreter } // namespace internal } // namespace v8 diff --git a/src/interpreter/bytecode-array-accessor.h b/src/interpreter/bytecode-array-accessor.h index efe9e6605e..c34d6a554c 100644 --- a/src/interpreter/bytecode-array-accessor.h +++ b/src/interpreter/bytecode-array-accessor.h @@ -48,6 +48,8 @@ class V8_EXPORT_PRIVATE BytecodeArrayAccessor { // not for a jump or conditional jump. int GetJumpTargetOffset() const; + std::ostream& PrintTo(std::ostream& os) const; + private: bool OffsetInBounds() const; diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc index 1200cacd07..6568b22643 100644 --- a/src/interpreter/bytecode-generator.cc +++ b/src/interpreter/bytecode-generator.cc @@ -722,22 +722,25 @@ void BytecodeGenerator::VisitIterationHeader(IterationStatement* stmt, LoopBuilder* loop_builder) { // Recall that stmt->yield_count() is always zero inside ordinary // (i.e. non-generator) functions. + if (stmt->yield_count() == 0) { + loop_builder->LoopHeader(); + } else { + // Collect all labels for generator resume points within the loop (if any) + // so that they can be bound to the loop header below. Also create fresh + // labels for these resume points, to be used inside the loop. + ZoneVector resume_points_in_loop(zone()); + size_t first_yield = stmt->first_yield_id(); + DCHECK_LE(first_yield + stmt->yield_count(), + generator_resume_points_.size()); + for (size_t id = first_yield; id < first_yield + stmt->yield_count(); + id++) { + auto& label = generator_resume_points_[id]; + resume_points_in_loop.push_back(label); + generator_resume_points_[id] = BytecodeLabel(); + } - // Collect all labels for generator resume points within the loop (if any) so - // that they can be bound to the loop header below. Also create fresh labels - // for these resume points, to be used inside the loop. - ZoneVector resume_points_in_loop(zone()); - size_t first_yield = stmt->first_yield_id(); - DCHECK_LE(first_yield + stmt->yield_count(), generator_resume_points_.size()); - for (size_t id = first_yield; id < first_yield + stmt->yield_count(); id++) { - auto& label = generator_resume_points_[id]; - resume_points_in_loop.push_back(label); - generator_resume_points_[id] = BytecodeLabel(); - } + loop_builder->LoopHeader(&resume_points_in_loop); - loop_builder->LoopHeader(&resume_points_in_loop); - - if (stmt->yield_count() > 0) { // If we are not resuming, fall through to loop body. // If we are resuming, perform state dispatch. BytecodeLabel not_resuming; diff --git a/src/interpreter/bytecode-label.h b/src/interpreter/bytecode-label.h index b5f602d216..4ef6265eb2 100644 --- a/src/interpreter/bytecode-label.h +++ b/src/interpreter/bytecode-label.h @@ -17,7 +17,7 @@ class BytecodeArrayBuilder; // label is bound, it represents a known position in the bytecode // array. For labels that are forward references there can be at most // one reference whilst it is unbound. -class BytecodeLabel final { +class V8_EXPORT_PRIVATE BytecodeLabel final { public: BytecodeLabel() : bound_(false), offset_(kInvalidOffset) {} @@ -54,7 +54,7 @@ class BytecodeLabel final { }; // Class representing a branch target of multiple jumps. -class BytecodeLabels { +class V8_EXPORT_PRIVATE BytecodeLabels { public: explicit BytecodeLabels(Zone* zone) : labels_(zone) {} diff --git a/src/interpreter/bytecodes.h b/src/interpreter/bytecodes.h index bc54145950..05c0afe8f6 100644 --- a/src/interpreter/bytecodes.h +++ b/src/interpreter/bytecodes.h @@ -471,6 +471,12 @@ class V8_EXPORT_PRIVATE Bytecodes final { IsConditionalJumpConstant(bytecode); } + // Returns true if the bytecode is an unconditional jump. + static CONSTEXPR bool IsUnconditionalJump(Bytecode bytecode) { + return bytecode == Bytecode::kJump || bytecode == Bytecode::kJumpConstant || + bytecode == Bytecode::kJumpLoop; + } + // Returns true if the bytecode is a jump or a conditional jump taking // an immediate byte operand (OperandType::kImm). static CONSTEXPR bool IsJumpImmediate(Bytecode bytecode) { @@ -500,6 +506,12 @@ class V8_EXPORT_PRIVATE Bytecodes final { return IsJumpImmediate(bytecode) || IsJumpConstant(bytecode); } + // Returns true if the bytecode is a forward jump or conditional jump taking + // any kind of operand. + static CONSTEXPR bool IsForwardJump(Bytecode bytecode) { + return bytecode != Bytecode::kJumpLoop && IsJump(bytecode); + } + // Returns true if the bytecode is a conditional jump, a jump, or a return. static CONSTEXPR bool IsJumpOrReturn(Bytecode bytecode) { return bytecode == Bytecode::kReturn || IsJump(bytecode); diff --git a/src/interpreter/control-flow-builders.cc b/src/interpreter/control-flow-builders.cc index 0e71b96cce..866c6a3446 100644 --- a/src/interpreter/control-flow-builders.cc +++ b/src/interpreter/control-flow-builders.cc @@ -55,8 +55,10 @@ void LoopBuilder::LoopHeader(ZoneVector* additional_labels) { // and misplaced between the headers. DCHECK(break_labels_.empty() && continue_labels_.empty()); builder()->Bind(&loop_header_); - for (auto& label : *additional_labels) { - builder()->Bind(&label); + if (additional_labels != nullptr) { + for (auto& label : *additional_labels) { + builder()->Bind(&label); + } } } diff --git a/src/interpreter/control-flow-builders.h b/src/interpreter/control-flow-builders.h index 3174db5da1..68c28c70d1 100644 --- a/src/interpreter/control-flow-builders.h +++ b/src/interpreter/control-flow-builders.h @@ -14,7 +14,7 @@ namespace v8 { namespace internal { namespace interpreter { -class ControlFlowBuilder BASE_EMBEDDED { +class V8_EXPORT_PRIVATE ControlFlowBuilder BASE_EMBEDDED { public: explicit ControlFlowBuilder(BytecodeArrayBuilder* builder) : builder_(builder) {} @@ -29,7 +29,8 @@ class ControlFlowBuilder BASE_EMBEDDED { DISALLOW_COPY_AND_ASSIGN(ControlFlowBuilder); }; -class BreakableControlFlowBuilder : public ControlFlowBuilder { +class V8_EXPORT_PRIVATE BreakableControlFlowBuilder + : public ControlFlowBuilder { public: explicit BreakableControlFlowBuilder(BytecodeArrayBuilder* builder) : ControlFlowBuilder(builder), break_labels_(builder->zone()) {} @@ -63,7 +64,8 @@ class BreakableControlFlowBuilder : public ControlFlowBuilder { // Class to track control flow for block statements (which can break in JS). -class BlockBuilder final : public BreakableControlFlowBuilder { +class V8_EXPORT_PRIVATE BlockBuilder final + : public BreakableControlFlowBuilder { public: explicit BlockBuilder(BytecodeArrayBuilder* builder) : BreakableControlFlowBuilder(builder) {} @@ -77,7 +79,7 @@ class BlockBuilder final : public BreakableControlFlowBuilder { // A class to help with co-ordinating break and continue statements with // their loop. -class LoopBuilder final : public BreakableControlFlowBuilder { +class V8_EXPORT_PRIVATE LoopBuilder final : public BreakableControlFlowBuilder { public: explicit LoopBuilder(BytecodeArrayBuilder* builder) : BreakableControlFlowBuilder(builder), @@ -85,7 +87,7 @@ class LoopBuilder final : public BreakableControlFlowBuilder { header_labels_(builder->zone()) {} ~LoopBuilder(); - void LoopHeader(ZoneVector* additional_labels); + void LoopHeader(ZoneVector* additional_labels = nullptr); void JumpToHeader(int loop_depth); void BindContinueTarget(); void EndLoop(); @@ -109,7 +111,8 @@ class LoopBuilder final : public BreakableControlFlowBuilder { // A class to help with co-ordinating break statements with their switch. -class SwitchBuilder final : public BreakableControlFlowBuilder { +class V8_EXPORT_PRIVATE SwitchBuilder final + : public BreakableControlFlowBuilder { public: explicit SwitchBuilder(BytecodeArrayBuilder* builder, int number_of_cases) : BreakableControlFlowBuilder(builder), @@ -139,7 +142,7 @@ class SwitchBuilder final : public BreakableControlFlowBuilder { // A class to help with co-ordinating control flow in try-catch statements. -class TryCatchBuilder final : public ControlFlowBuilder { +class V8_EXPORT_PRIVATE TryCatchBuilder final : public ControlFlowBuilder { public: explicit TryCatchBuilder(BytecodeArrayBuilder* builder, HandlerTable::CatchPrediction catch_prediction) @@ -160,7 +163,7 @@ class TryCatchBuilder final : public ControlFlowBuilder { // A class to help with co-ordinating control flow in try-finally statements. -class TryFinallyBuilder final : public ControlFlowBuilder { +class V8_EXPORT_PRIVATE TryFinallyBuilder final : public ControlFlowBuilder { public: explicit TryFinallyBuilder(BytecodeArrayBuilder* builder, HandlerTable::CatchPrediction catch_prediction) diff --git a/src/interpreter/handler-table-builder.h b/src/interpreter/handler-table-builder.h index 25147ca26b..50061949dc 100644 --- a/src/interpreter/handler-table-builder.h +++ b/src/interpreter/handler-table-builder.h @@ -19,7 +19,7 @@ class Isolate; namespace interpreter { // A helper class for constructing exception handler tables for the interpreter. -class HandlerTableBuilder final BASE_EMBEDDED { +class V8_EXPORT_PRIVATE HandlerTableBuilder final BASE_EMBEDDED { public: explicit HandlerTableBuilder(Zone* zone); diff --git a/src/v8.gyp b/src/v8.gyp index a30bf58dd2..7cde255811 100644 --- a/src/v8.gyp +++ b/src/v8.gyp @@ -550,10 +550,12 @@ 'compiler/basic-block-instrumentor.h', 'compiler/branch-elimination.cc', 'compiler/branch-elimination.h', - 'compiler/bytecode-graph-builder.cc', - 'compiler/bytecode-graph-builder.h', 'compiler/bytecode-analysis.cc', 'compiler/bytecode-analysis.h', + 'compiler/bytecode-graph-builder.cc', + 'compiler/bytecode-graph-builder.h', + 'compiler/bytecode-liveness-map.cc', + 'compiler/bytecode-liveness-map.h', 'compiler/c-linkage.cc', 'compiler/checkpoint-elimination.cc', 'compiler/checkpoint-elimination.h', diff --git a/test/debugger/debug/debug-evaluate-with.js b/test/debugger/debug/debug-evaluate-with.js index 06b627c517..260114d94d 100644 --- a/test/debugger/debug/debug-evaluate-with.js +++ b/test/debugger/debug/debug-evaluate-with.js @@ -24,6 +24,8 @@ // 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. +// +// Flags: --noanalyze-environment-liveness Debug = debug.Debug diff --git a/test/debugger/debug/debug-liveedit-restart-frame.js b/test/debugger/debug/debug-liveedit-restart-frame.js index 0c79fcb261..659e3c74e6 100644 --- a/test/debugger/debug/debug-liveedit-restart-frame.js +++ b/test/debugger/debug/debug-liveedit-restart-frame.js @@ -24,6 +24,8 @@ // 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. +// +// Flags: --noanalyze-environment-liveness Debug = debug.Debug diff --git a/test/debugger/debug/es6/default-parameters-debug.js b/test/debugger/debug/es6/default-parameters-debug.js index bdbea84815..87ff386b83 100644 --- a/test/debugger/debug/es6/default-parameters-debug.js +++ b/test/debugger/debug/es6/default-parameters-debug.js @@ -1,6 +1,8 @@ // Copyright 2015 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. +// +// Flags: --noanalyze-environment-liveness Debug = debug.Debug diff --git a/test/debugger/debug/regress-5207.js b/test/debugger/debug/regress-5207.js index 3d6f875f02..f2b3d7c28f 100644 --- a/test/debugger/debug/regress-5207.js +++ b/test/debugger/debug/regress-5207.js @@ -1,6 +1,8 @@ // Copyright 2012 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. +// +// Flags: --noanalyze-environment-liveness 'use strict'; diff --git a/test/debugger/debug/regress/regress-131994.js b/test/debugger/debug/regress/regress-131994.js index ed90f62b6f..66bde74b25 100644 --- a/test/debugger/debug/regress/regress-131994.js +++ b/test/debugger/debug/regress/regress-131994.js @@ -24,6 +24,8 @@ // 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. +// +// Flags: --noanalyze-environment-liveness // Test that a variable in the local scope that shadows a context-allocated diff --git a/test/debugger/debug/regress/regress-5071.js b/test/debugger/debug/regress/regress-5071.js index 4ad1d7ed7c..54557fe366 100644 --- a/test/debugger/debug/regress/regress-5071.js +++ b/test/debugger/debug/regress/regress-5071.js @@ -1,6 +1,8 @@ // Copyright 2016 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. +// +// Flags: --noanalyze-environment-liveness var Debug = debug.Debug; diff --git a/test/debugger/debug/regress/regress-crbug-222893.js b/test/debugger/debug/regress/regress-crbug-222893.js index 0a531ab60b..a0fc6d56fb 100644 --- a/test/debugger/debug/regress/regress-crbug-222893.js +++ b/test/debugger/debug/regress/regress-crbug-222893.js @@ -24,7 +24,8 @@ // 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. - +// +// Flags: --noanalyze-environment-liveness Debug = debug.Debug diff --git a/test/mjsunit/debug-evaluate-bool-constructor.js b/test/mjsunit/debug-evaluate-bool-constructor.js index 69cba775c7..e23a01d71f 100644 --- a/test/mjsunit/debug-evaluate-bool-constructor.js +++ b/test/mjsunit/debug-evaluate-bool-constructor.js @@ -25,7 +25,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// Flags: --expose-debug-as debug +// Flags: --expose-debug-as debug --noanalyze-environment-liveness // Get the Debug object exposed from the debug context global object. Debug = debug.Debug diff --git a/test/mjsunit/debug-evaluate-with-context.js b/test/mjsunit/debug-evaluate-with-context.js index 4fae8990f3..60bfac78f5 100644 --- a/test/mjsunit/debug-evaluate-with-context.js +++ b/test/mjsunit/debug-evaluate-with-context.js @@ -25,7 +25,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// Flags: --expose-debug-as debug +// Flags: --expose-debug-as debug --noanalyze-environment-liveness // Get the Debug object exposed from the debug context global object. Debug = debug.Debug diff --git a/test/mjsunit/debug-scopes.js b/test/mjsunit/debug-scopes.js index de72e1d7c0..70fd337166 100644 --- a/test/mjsunit/debug-scopes.js +++ b/test/mjsunit/debug-scopes.js @@ -25,7 +25,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// Flags: --expose-debug-as debug --allow-natives-syntax +// Flags: --expose-debug-as debug --allow-natives-syntax --noanalyze-environment-liveness // The functions used for testing backtraces. They are at the top to make the // testing of source line/column easier. diff --git a/test/mjsunit/es6/debug-blockscopes.js b/test/mjsunit/es6/debug-blockscopes.js index 3f2985ad10..47a5d6a0fe 100644 --- a/test/mjsunit/es6/debug-blockscopes.js +++ b/test/mjsunit/es6/debug-blockscopes.js @@ -25,7 +25,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// Flags: --expose-debug-as debug --allow-natives-syntax +// Flags: --expose-debug-as debug --allow-natives-syntax --noanalyze-environment-liveness // The functions used for testing backtraces. They are at the top to make the // testing of source line/column easier. diff --git a/test/mjsunit/es6/generators-debug-scopes.js b/test/mjsunit/es6/generators-debug-scopes.js index 42e03ee6e2..0cebaeefc2 100644 --- a/test/mjsunit/es6/generators-debug-scopes.js +++ b/test/mjsunit/es6/generators-debug-scopes.js @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Flags: --expose-debug-as debug +// Flags: --expose-debug-as debug --noanalyze-environment-liveness var Debug = debug.Debug; diff --git a/test/mjsunit/modules-debug-scopes1.js b/test/mjsunit/modules-debug-scopes1.js index 6973e19cb9..fd094e36a2 100644 --- a/test/mjsunit/modules-debug-scopes1.js +++ b/test/mjsunit/modules-debug-scopes1.js @@ -3,7 +3,7 @@ // found in the LICENSE file. // MODULE -// Flags: --expose-debug-as debug --allow-natives-syntax +// Flags: --expose-debug-as debug --allow-natives-syntax --noanalyze-environment-liveness // These tests are copied from mjsunit/debug-scopes.js and adapted for modules. diff --git a/test/unittests/BUILD.gn b/test/unittests/BUILD.gn index a05a47114e..36d1079e52 100644 --- a/test/unittests/BUILD.gn +++ b/test/unittests/BUILD.gn @@ -31,6 +31,7 @@ v8_executable("unittests") { "compiler-dispatcher/compiler-dispatcher-job-unittest.cc", "compiler-dispatcher/compiler-dispatcher-tracer-unittest.cc", "compiler/branch-elimination-unittest.cc", + "compiler/bytecode-analysis-unittest.cc", "compiler/checkpoint-elimination-unittest.cc", "compiler/common-operator-reducer-unittest.cc", "compiler/common-operator-unittest.cc", diff --git a/test/unittests/compiler/bytecode-analysis-unittest.cc b/test/unittests/compiler/bytecode-analysis-unittest.cc new file mode 100644 index 0000000000..1d925b6fc4 --- /dev/null +++ b/test/unittests/compiler/bytecode-analysis-unittest.cc @@ -0,0 +1,415 @@ +// Copyright 2015 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/v8.h" + +#include "src/compiler/bytecode-analysis.h" +#include "src/interpreter/bytecode-array-builder.h" +#include "src/interpreter/bytecode-array-iterator.h" +#include "src/interpreter/bytecode-decoder.h" +#include "src/interpreter/bytecode-label.h" +#include "src/interpreter/control-flow-builders.h" +#include "test/unittests/test-utils.h" + +namespace v8 { +namespace internal { +namespace compiler { + +class BytecodeAnalysisTest : public TestWithIsolateAndZone { + public: + BytecodeAnalysisTest() {} + ~BytecodeAnalysisTest() override {} + + static void SetUpTestCase() { + old_FLAG_ignition_peephole_ = i::FLAG_ignition_peephole; + i::FLAG_ignition_peephole = false; + + old_FLAG_ignition_reo_ = i::FLAG_ignition_reo; + i::FLAG_ignition_reo = false; + + TestWithIsolateAndZone::SetUpTestCase(); + } + + static void TearDownTestCase() { + TestWithIsolateAndZone::TearDownTestCase(); + i::FLAG_ignition_peephole = old_FLAG_ignition_peephole_; + i::FLAG_ignition_reo = old_FLAG_ignition_reo_; + } + + std::string ToLivenessString(const BitVector* liveness) const { + std::string out; + out.resize(liveness->length()); + for (int i = 0; i < liveness->length(); ++i) { + if (liveness->Contains(i)) { + out[i] = 'L'; + } else { + out[i] = '.'; + } + } + return out; + } + + void EnsureLivenessMatches( + Handle bytecode, + const std::vector>& + expected_liveness) { + BytecodeAnalysis analysis(bytecode, zone(), true); + analysis.Analyze(); + + interpreter::BytecodeArrayIterator iterator(bytecode); + for (auto liveness : expected_liveness) { + std::stringstream ss; + ss << std::setw(4) << iterator.current_offset() << " : "; + iterator.PrintTo(ss); + + EXPECT_EQ(liveness.first, ToLivenessString(analysis.GetInLivenessFor( + iterator.current_offset()))) + << " at bytecode " << ss.str(); + + EXPECT_EQ(liveness.second, ToLivenessString(analysis.GetOutLivenessFor( + iterator.current_offset()))) + << " at bytecode " << ss.str(); + + iterator.Advance(); + } + + EXPECT_TRUE(iterator.done()); + } + + private: + static bool old_FLAG_ignition_peephole_; + static bool old_FLAG_ignition_reo_; + + DISALLOW_COPY_AND_ASSIGN(BytecodeAnalysisTest); +}; + +bool BytecodeAnalysisTest::old_FLAG_ignition_peephole_; +bool BytecodeAnalysisTest::old_FLAG_ignition_reo_; + +TEST_F(BytecodeAnalysisTest, EmptyBlock) { + interpreter::BytecodeArrayBuilder builder(isolate(), zone(), 3, 0, 3); + std::vector> expected_liveness; + + interpreter::Register reg_0(0); + + builder.Return(); + expected_liveness.emplace_back("...L", "...."); + + Handle bytecode = builder.ToBytecodeArray(isolate()); + + EnsureLivenessMatches(bytecode, expected_liveness); +} + +TEST_F(BytecodeAnalysisTest, SimpleLoad) { + interpreter::BytecodeArrayBuilder builder(isolate(), zone(), 3, 0, 3); + std::vector> expected_liveness; + + interpreter::Register reg_0(0); + + builder.LoadAccumulatorWithRegister(reg_0); + expected_liveness.emplace_back("L...", "...L"); + + builder.Return(); + expected_liveness.emplace_back("...L", "...."); + + Handle bytecode = builder.ToBytecodeArray(isolate()); + + EnsureLivenessMatches(bytecode, expected_liveness); +} + +TEST_F(BytecodeAnalysisTest, StoreThenLoad) { + interpreter::BytecodeArrayBuilder builder(isolate(), zone(), 3, 0, 3); + std::vector> expected_liveness; + + interpreter::Register reg_0(0); + + builder.StoreAccumulatorInRegister(reg_0); + expected_liveness.emplace_back("...L", "L..."); + + builder.LoadNull(); + expected_liveness.emplace_back("L...", "L..."); + + builder.LoadAccumulatorWithRegister(reg_0); + expected_liveness.emplace_back("L...", "...L"); + + builder.Return(); + expected_liveness.emplace_back("...L", "...."); + + Handle bytecode = builder.ToBytecodeArray(isolate()); + + EnsureLivenessMatches(bytecode, expected_liveness); +} + +TEST_F(BytecodeAnalysisTest, DiamondLoad) { + interpreter::BytecodeArrayBuilder builder(isolate(), zone(), 3, 0, 3); + std::vector> expected_liveness; + + interpreter::Register reg_0(0); + interpreter::Register reg_1(1); + interpreter::Register reg_2(2); + + interpreter::BytecodeLabel ld1_label; + interpreter::BytecodeLabel end_label; + + builder.JumpIfTrue(&ld1_label); + expected_liveness.emplace_back("LLLL", "LLL."); + + builder.LoadAccumulatorWithRegister(reg_0); + expected_liveness.emplace_back("L.L.", "..L."); + + builder.Jump(&end_label); + expected_liveness.emplace_back("..L.", "..L."); + + builder.Bind(&ld1_label); + builder.LoadAccumulatorWithRegister(reg_1); + expected_liveness.emplace_back(".LL.", "..L."); + + builder.Bind(&end_label); + + builder.LoadAccumulatorWithRegister(reg_2); + expected_liveness.emplace_back("..L.", "...L"); + + builder.Return(); + expected_liveness.emplace_back("...L", "...."); + + Handle bytecode = builder.ToBytecodeArray(isolate()); + + EnsureLivenessMatches(bytecode, expected_liveness); +} + +TEST_F(BytecodeAnalysisTest, DiamondLookupsAndBinds) { + interpreter::BytecodeArrayBuilder builder(isolate(), zone(), 3, 0, 3); + std::vector> expected_liveness; + + interpreter::Register reg_0(0); + interpreter::Register reg_1(1); + interpreter::Register reg_2(2); + + interpreter::BytecodeLabel ld1_label; + interpreter::BytecodeLabel end_label; + + builder.StoreAccumulatorInRegister(reg_0); + expected_liveness.emplace_back(".LLL", "LLLL"); + + builder.JumpIfTrue(&ld1_label); + expected_liveness.emplace_back("LLLL", "LLL."); + + { + builder.LoadAccumulatorWithRegister(reg_0); + expected_liveness.emplace_back("L...", "...L"); + + builder.StoreAccumulatorInRegister(reg_2); + expected_liveness.emplace_back("...L", "..L."); + + builder.Jump(&end_label); + expected_liveness.emplace_back("..L.", "..L."); + } + + builder.Bind(&ld1_label); + { + builder.LoadAccumulatorWithRegister(reg_1); + expected_liveness.emplace_back(".LL.", "..L."); + } + + builder.Bind(&end_label); + + builder.LoadAccumulatorWithRegister(reg_2); + expected_liveness.emplace_back("..L.", "...L"); + + builder.Return(); + expected_liveness.emplace_back("...L", "...."); + + Handle bytecode = builder.ToBytecodeArray(isolate()); + + EnsureLivenessMatches(bytecode, expected_liveness); +} + +TEST_F(BytecodeAnalysisTest, SimpleLoop) { + interpreter::BytecodeArrayBuilder builder(isolate(), zone(), 3, 0, 3); + std::vector> expected_liveness; + + interpreter::Register reg_0(0); + interpreter::Register reg_1(1); + interpreter::Register reg_2(2); + + builder.StoreAccumulatorInRegister(reg_0); + expected_liveness.emplace_back("..LL", "L.LL"); + + interpreter::LoopBuilder loop_builder(&builder); + loop_builder.LoopHeader(); + { + builder.JumpIfTrue(loop_builder.break_labels()->New()); + expected_liveness.emplace_back("L.LL", "L.L."); + + builder.LoadAccumulatorWithRegister(reg_0); + expected_liveness.emplace_back("L...", "L..L"); + + builder.StoreAccumulatorInRegister(reg_2); + expected_liveness.emplace_back("L..L", "L.LL"); + + loop_builder.BindContinueTarget(); + loop_builder.JumpToHeader(0); + expected_liveness.emplace_back("L.LL", "L.LL"); + } + loop_builder.EndLoop(); + + builder.LoadAccumulatorWithRegister(reg_2); + expected_liveness.emplace_back("..L.", "...L"); + + builder.Return(); + expected_liveness.emplace_back("...L", "...."); + + Handle bytecode = builder.ToBytecodeArray(isolate()); + + EnsureLivenessMatches(bytecode, expected_liveness); +} + +TEST_F(BytecodeAnalysisTest, TryCatch) { + interpreter::BytecodeArrayBuilder builder(isolate(), zone(), 3, 0, 3); + std::vector> expected_liveness; + + interpreter::Register reg_0(0); + interpreter::Register reg_1(1); + interpreter::Register reg_context(2); + + builder.StoreAccumulatorInRegister(reg_0); + expected_liveness.emplace_back(".LLL", "LLL."); + + interpreter::TryCatchBuilder try_builder(&builder, HandlerTable::CAUGHT); + try_builder.BeginTry(reg_context); + { + builder.LoadAccumulatorWithRegister(reg_0); + expected_liveness.emplace_back("LLL.", ".LLL"); + + builder.StoreAccumulatorInRegister(reg_0); + expected_liveness.emplace_back(".LLL", ".LL."); + + builder.CallRuntime(Runtime::kThrow); + expected_liveness.emplace_back(".LL.", ".LLL"); + + builder.StoreAccumulatorInRegister(reg_0); + // Star can't throw, so doesn't take handler liveness + expected_liveness.emplace_back("...L", "...L"); + } + try_builder.EndTry(); + expected_liveness.emplace_back("...L", "...L"); + + // Catch + { + builder.LoadAccumulatorWithRegister(reg_1); + expected_liveness.emplace_back(".L..", "...L"); + } + try_builder.EndCatch(); + + builder.Return(); + expected_liveness.emplace_back("...L", "...."); + + Handle bytecode = builder.ToBytecodeArray(isolate()); + + EnsureLivenessMatches(bytecode, expected_liveness); +} + +TEST_F(BytecodeAnalysisTest, DiamondInLoop) { + interpreter::BytecodeArrayBuilder builder(isolate(), zone(), 3, 0, 3); + std::vector> expected_liveness; + + interpreter::Register reg_0(0); + interpreter::Register reg_1(1); + interpreter::Register reg_2(2); + + builder.StoreAccumulatorInRegister(reg_0); + expected_liveness.emplace_back("...L", "L..L"); + + interpreter::LoopBuilder loop_builder(&builder); + loop_builder.LoopHeader(); + { + builder.JumpIfTrue(loop_builder.break_labels()->New()); + expected_liveness.emplace_back("L..L", "L..L"); + + interpreter::BytecodeLabel ld1_label; + interpreter::BytecodeLabel end_label; + builder.JumpIfTrue(&ld1_label); + expected_liveness.emplace_back("L..L", "L..L"); + + { + builder.Jump(&end_label); + expected_liveness.emplace_back("L..L", "L..L"); + } + + builder.Bind(&ld1_label); + { + builder.LoadAccumulatorWithRegister(reg_0); + expected_liveness.emplace_back("L...", "L..L"); + } + + builder.Bind(&end_label); + + loop_builder.BindContinueTarget(); + loop_builder.JumpToHeader(0); + expected_liveness.emplace_back("L..L", "L..L"); + } + loop_builder.EndLoop(); + + builder.Return(); + expected_liveness.emplace_back("...L", "...."); + + Handle bytecode = builder.ToBytecodeArray(isolate()); + + EnsureLivenessMatches(bytecode, expected_liveness); +} + +TEST_F(BytecodeAnalysisTest, KillingLoopInsideLoop) { + interpreter::BytecodeArrayBuilder builder(isolate(), zone(), 3, 0, 3); + std::vector> expected_liveness; + + interpreter::Register reg_0(0); + interpreter::Register reg_1(1); + + builder.StoreAccumulatorInRegister(reg_0); + expected_liveness.emplace_back(".L.L", "LL.."); + + interpreter::LoopBuilder loop_builder(&builder); + loop_builder.LoopHeader(); + { + builder.LoadAccumulatorWithRegister(reg_0); + expected_liveness.emplace_back("LL..", ".L.."); + + builder.LoadAccumulatorWithRegister(reg_1); + expected_liveness.emplace_back(".L..", ".L.L"); + + builder.JumpIfTrue(loop_builder.break_labels()->New()); + expected_liveness.emplace_back(".L.L", ".L.L"); + + interpreter::LoopBuilder inner_loop_builder(&builder); + inner_loop_builder.LoopHeader(); + { + builder.StoreAccumulatorInRegister(reg_0); + expected_liveness.emplace_back(".L.L", "LL.L"); + + builder.JumpIfTrue(inner_loop_builder.break_labels()->New()); + expected_liveness.emplace_back("LL.L", "LL.L"); + + inner_loop_builder.BindContinueTarget(); + inner_loop_builder.JumpToHeader(1); + expected_liveness.emplace_back(".L.L", ".L.L"); + } + inner_loop_builder.EndLoop(); + + loop_builder.BindContinueTarget(); + loop_builder.JumpToHeader(0); + expected_liveness.emplace_back("LL..", "LL.."); + } + loop_builder.EndLoop(); + + builder.Return(); + expected_liveness.emplace_back("...L", "...."); + + Handle bytecode = builder.ToBytecodeArray(isolate()); + + EnsureLivenessMatches(bytecode, expected_liveness); +} + +} // namespace compiler +} // namespace internal +} // namespace v8 diff --git a/test/unittests/unittests.gyp b/test/unittests/unittests.gyp index 01bda47145..4d5ab4e660 100644 --- a/test/unittests/unittests.gyp +++ b/test/unittests/unittests.gyp @@ -27,6 +27,7 @@ 'cancelable-tasks-unittest.cc', 'char-predicates-unittest.cc', 'compiler/branch-elimination-unittest.cc', + 'compiler/bytecode-analysis-unittest.cc', 'compiler/checkpoint-elimination-unittest.cc', 'compiler/common-operator-reducer-unittest.cc', 'compiler/common-operator-unittest.cc',