[ignition/turbo] Perform liveness analysis on the bytecodes
Replaces the graph-based liveness analyzer in the bytecode graph builder with an initial bytecode-based liveness analysis pass, which is added to the existing loop extent analysis. Now the StateValues in the graph have their inputs initialised to optimized_out, rather than being modified after the graph is built. Review-Url: https://codereview.chromium.org/2523893003 Cr-Commit-Position: refs/heads/master@{#41355}
This commit is contained in:
parent
5fd2b71236
commit
2bf71f888f
2
BUILD.gn
2
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",
|
||||
|
1
src/DEPS
1
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",
|
||||
|
@ -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<BytecodeArray> 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<BytecodeArray>& 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
|
||||
|
@ -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<BytecodeArray> bytecode_array, Zone* zone);
|
||||
BytecodeAnalysis(Handle<BytecodeArray> 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<BytecodeArray> bytecode_array() const { return bytecode_array_; }
|
||||
|
||||
private:
|
||||
Handle<BytecodeArray> bytecode_array_;
|
||||
bool do_liveness_analysis_;
|
||||
Zone* zone_;
|
||||
|
||||
ZoneStack<int> loop_stack_;
|
||||
@ -46,6 +70,8 @@ class BytecodeAnalysis BASE_EMBEDDED {
|
||||
ZoneMap<int, int> end_to_header_;
|
||||
ZoneMap<int, int> header_to_parent_;
|
||||
|
||||
BytecodeLivenessMap liveness_map_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(BytecodeAnalysis);
|
||||
};
|
||||
|
||||
|
@ -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<int>(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<size_t>(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<size_t>(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<size_t>(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.
|
||||
|
@ -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> 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_;
|
||||
|
||||
|
41
src/compiler/bytecode-liveness-map.cc
Normal file
41
src/compiler/bytecode-liveness-map.cc
Normal file
@ -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<int>(),
|
||||
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
|
55
src/compiler/bytecode-liveness-map.h
Normal file
55
src/compiler/bytecode-liveness-map.h
Normal file
@ -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<int, Liveness, base::KeyEqualityMatcher<int>,
|
||||
ZoneAllocationPolicy>
|
||||
liveness_map_;
|
||||
};
|
||||
|
||||
} // namespace compiler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
#endif // V8_COMPILER_BYTECODE_LIVENESS_MAP_H_
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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<BytecodeLabel> 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<BytecodeLabel> 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;
|
||||
|
@ -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) {}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -55,8 +55,10 @@ void LoopBuilder::LoopHeader(ZoneVector<BytecodeLabel>* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<BytecodeLabel>* additional_labels);
|
||||
void LoopHeader(ZoneVector<BytecodeLabel>* 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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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';
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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",
|
||||
|
415
test/unittests/compiler/bytecode-analysis-unittest.cc
Normal file
415
test/unittests/compiler/bytecode-analysis-unittest.cc
Normal file
@ -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<BytecodeArray> bytecode,
|
||||
const std::vector<std::pair<std::string, std::string>>&
|
||||
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<std::pair<std::string, std::string>> expected_liveness;
|
||||
|
||||
interpreter::Register reg_0(0);
|
||||
|
||||
builder.Return();
|
||||
expected_liveness.emplace_back("...L", "....");
|
||||
|
||||
Handle<BytecodeArray> bytecode = builder.ToBytecodeArray(isolate());
|
||||
|
||||
EnsureLivenessMatches(bytecode, expected_liveness);
|
||||
}
|
||||
|
||||
TEST_F(BytecodeAnalysisTest, SimpleLoad) {
|
||||
interpreter::BytecodeArrayBuilder builder(isolate(), zone(), 3, 0, 3);
|
||||
std::vector<std::pair<std::string, std::string>> 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<BytecodeArray> bytecode = builder.ToBytecodeArray(isolate());
|
||||
|
||||
EnsureLivenessMatches(bytecode, expected_liveness);
|
||||
}
|
||||
|
||||
TEST_F(BytecodeAnalysisTest, StoreThenLoad) {
|
||||
interpreter::BytecodeArrayBuilder builder(isolate(), zone(), 3, 0, 3);
|
||||
std::vector<std::pair<std::string, std::string>> 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<BytecodeArray> bytecode = builder.ToBytecodeArray(isolate());
|
||||
|
||||
EnsureLivenessMatches(bytecode, expected_liveness);
|
||||
}
|
||||
|
||||
TEST_F(BytecodeAnalysisTest, DiamondLoad) {
|
||||
interpreter::BytecodeArrayBuilder builder(isolate(), zone(), 3, 0, 3);
|
||||
std::vector<std::pair<std::string, std::string>> 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<BytecodeArray> bytecode = builder.ToBytecodeArray(isolate());
|
||||
|
||||
EnsureLivenessMatches(bytecode, expected_liveness);
|
||||
}
|
||||
|
||||
TEST_F(BytecodeAnalysisTest, DiamondLookupsAndBinds) {
|
||||
interpreter::BytecodeArrayBuilder builder(isolate(), zone(), 3, 0, 3);
|
||||
std::vector<std::pair<std::string, std::string>> 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<BytecodeArray> bytecode = builder.ToBytecodeArray(isolate());
|
||||
|
||||
EnsureLivenessMatches(bytecode, expected_liveness);
|
||||
}
|
||||
|
||||
TEST_F(BytecodeAnalysisTest, SimpleLoop) {
|
||||
interpreter::BytecodeArrayBuilder builder(isolate(), zone(), 3, 0, 3);
|
||||
std::vector<std::pair<std::string, std::string>> 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<BytecodeArray> bytecode = builder.ToBytecodeArray(isolate());
|
||||
|
||||
EnsureLivenessMatches(bytecode, expected_liveness);
|
||||
}
|
||||
|
||||
TEST_F(BytecodeAnalysisTest, TryCatch) {
|
||||
interpreter::BytecodeArrayBuilder builder(isolate(), zone(), 3, 0, 3);
|
||||
std::vector<std::pair<std::string, std::string>> 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<BytecodeArray> bytecode = builder.ToBytecodeArray(isolate());
|
||||
|
||||
EnsureLivenessMatches(bytecode, expected_liveness);
|
||||
}
|
||||
|
||||
TEST_F(BytecodeAnalysisTest, DiamondInLoop) {
|
||||
interpreter::BytecodeArrayBuilder builder(isolate(), zone(), 3, 0, 3);
|
||||
std::vector<std::pair<std::string, std::string>> 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<BytecodeArray> bytecode = builder.ToBytecodeArray(isolate());
|
||||
|
||||
EnsureLivenessMatches(bytecode, expected_liveness);
|
||||
}
|
||||
|
||||
TEST_F(BytecodeAnalysisTest, KillingLoopInsideLoop) {
|
||||
interpreter::BytecodeArrayBuilder builder(isolate(), zone(), 3, 0, 3);
|
||||
std::vector<std::pair<std::string, std::string>> 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<BytecodeArray> bytecode = builder.ToBytecodeArray(isolate());
|
||||
|
||||
EnsureLivenessMatches(bytecode, expected_liveness);
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user