[turbofan] Deopt support for escape analysis

Deopt support is added on two levels. On the IR level,
a new ObjectState node is added, which represenents an
object to be materialized. ObjectState nodes appear as
inputs of FrameState and StateValues nodes. On the
instruction select/code-generation level, the
FrameStateDescriptor class handles the nesting
introduced by ObjectState, and ensures that deopt code
with CAPTURED_OBJECT/DUPLICATED_OBJECT entries are
generated similarly to what crankshaft's escape
analysis does.

Two unittests test correctness of the IR level implementation.

Correctness for instruction selection / code generation
is tested by mjsunit tests.

R=jarin@chromium.org,mstarzinger@chromium.org
BUG=v8:4586
LOG=n

Review URL: https://codereview.chromium.org/1485183002

Cr-Commit-Position: refs/heads/master@{#33115}
This commit is contained in:
sigurds 2016-01-05 05:30:32 -08:00 committed by Commit bot
parent 06738d6410
commit 3b473d7aad
24 changed files with 919 additions and 193 deletions

View File

@ -216,8 +216,10 @@ Handle<Code> CodeGenerator::GenerateCode() {
bool CodeGenerator::IsNextInAssemblyOrder(RpoNumber block) const {
return code()->InstructionBlockAt(current_block_)->ao_number().IsNext(
code()->InstructionBlockAt(block)->ao_number());
return code()
->InstructionBlockAt(current_block_)
->ao_number()
.IsNext(code()->InstructionBlockAt(block)->ao_number());
}
@ -489,64 +491,84 @@ FrameStateDescriptor* CodeGenerator::GetFrameStateDescriptor(
}
namespace {
struct OperandAndType {
InstructionOperand* const operand;
MachineType const type;
};
OperandAndType TypedOperandForFrameState(FrameStateDescriptor* descriptor,
Instruction* instr,
size_t frame_state_offset,
size_t index,
OutputFrameStateCombine combine) {
DCHECK(index < descriptor->GetSize(combine));
switch (combine.kind()) {
case OutputFrameStateCombine::kPushOutput: {
DCHECK(combine.GetPushCount() <= instr->OutputCount());
size_t size_without_output =
descriptor->GetSize(OutputFrameStateCombine::Ignore());
// If the index is past the existing stack items, return the output.
if (index >= size_without_output) {
return {instr->OutputAt(index - size_without_output),
MachineType::AnyTagged()};
}
break;
void CodeGenerator::TranslateStateValueDescriptor(
StateValueDescriptor* desc, Translation* translation,
InstructionOperandIterator* iter) {
if (desc->IsNested()) {
translation->BeginCapturedObject(static_cast<int>(desc->size()));
for (size_t index = 0; index < desc->fields().size(); index++) {
TranslateStateValueDescriptor(&desc->fields()[index], translation, iter);
}
case OutputFrameStateCombine::kPokeAt:
size_t index_from_top =
descriptor->GetSize(combine) - 1 - combine.GetOffsetToPokeAt();
if (index >= index_from_top &&
index < index_from_top + instr->OutputCount()) {
return {instr->OutputAt(index - index_from_top),
MachineType::AnyTagged()};
}
break;
} else if (desc->IsDuplicate()) {
translation->DuplicateObject(static_cast<int>(desc->id()));
} else {
DCHECK(desc->IsPlain());
AddTranslationForOperand(translation, iter->instruction(), iter->Advance(),
desc->type());
}
return {instr->InputAt(frame_state_offset + index),
descriptor->GetType(index)};
}
} // namespace
void CodeGenerator::TranslateFrameStateDescriptorOperands(
FrameStateDescriptor* desc, InstructionOperandIterator* iter,
OutputFrameStateCombine combine, Translation* translation) {
for (size_t index = 0; index < desc->GetSize(combine); index++) {
switch (combine.kind()) {
case OutputFrameStateCombine::kPushOutput: {
DCHECK(combine.GetPushCount() <= iter->instruction()->OutputCount());
size_t size_without_output =
desc->GetSize(OutputFrameStateCombine::Ignore());
// If the index is past the existing stack items in values_.
if (index >= size_without_output) {
// Materialize the result of the call instruction in this slot.
AddTranslationForOperand(
translation, iter->instruction(),
iter->instruction()->OutputAt(index - size_without_output),
MachineType::AnyTagged());
continue;
}
break;
}
case OutputFrameStateCombine::kPokeAt:
// The result of the call should be placed at position
// [index_from_top] in the stack (overwriting whatever was
// previously there).
size_t index_from_top =
desc->GetSize(combine) - 1 - combine.GetOffsetToPokeAt();
if (index >= index_from_top &&
index < index_from_top + iter->instruction()->OutputCount()) {
AddTranslationForOperand(
translation, iter->instruction(),
iter->instruction()->OutputAt(index - index_from_top),
MachineType::AnyTagged());
iter->Advance(); // We do not use this input, but we need to
// advace, as the input got replaced.
continue;
}
break;
}
StateValueDescriptor* value_desc = desc->GetStateValueDescriptor();
TranslateStateValueDescriptor(&value_desc->fields()[index], translation,
iter);
}
}
void CodeGenerator::BuildTranslationForFrameStateDescriptor(
FrameStateDescriptor* descriptor, Instruction* instr,
Translation* translation, size_t frame_state_offset,
OutputFrameStateCombine state_combine) {
FrameStateDescriptor* descriptor, InstructionOperandIterator* iter,
Translation* translation, OutputFrameStateCombine state_combine) {
// Outer-most state must be added to translation first.
if (descriptor->outer_state() != nullptr) {
BuildTranslationForFrameStateDescriptor(descriptor->outer_state(), instr,
translation, frame_state_offset,
BuildTranslationForFrameStateDescriptor(descriptor->outer_state(), iter,
translation,
OutputFrameStateCombine::Ignore());
}
frame_state_offset += descriptor->outer_state()->GetTotalSize();
Handle<SharedFunctionInfo> shared_info;
if (!descriptor->shared_info().ToHandle(&shared_info)) {
if (!info()->has_shared_info()) return; // Stub with no SharedFunctionInfo.
if (!info()->has_shared_info()) {
return; // Stub with no SharedFunctionInfo.
}
shared_info = info()->shared_info();
}
int shared_info_id = DefineDeoptimizationLiteral(shared_info);
@ -575,11 +597,8 @@ void CodeGenerator::BuildTranslationForFrameStateDescriptor(
break;
}
for (size_t i = 0; i < descriptor->GetSize(state_combine); i++) {
OperandAndType op = TypedOperandForFrameState(
descriptor, instr, frame_state_offset, i, state_combine);
AddTranslationForOperand(translation, instr, op.operand, op.type);
}
TranslateFrameStateDescriptorOperands(descriptor, iter, state_combine,
translation);
}
@ -593,8 +612,9 @@ int CodeGenerator::BuildTranslation(Instruction* instr, int pc_offset,
Translation translation(
&translations_, static_cast<int>(descriptor->GetFrameCount()),
static_cast<int>(descriptor->GetJSFrameCount()), zone());
BuildTranslationForFrameStateDescriptor(descriptor, instr, &translation,
frame_state_offset, state_combine);
InstructionOperandIterator iter(instr, frame_state_offset);
BuildTranslationForFrameStateDescriptor(descriptor, &iter, &translation,
state_combine);
int deoptimization_id = static_cast<int>(deoptimization_states_.size());

View File

@ -28,6 +28,20 @@ struct BranchInfo {
};
class InstructionOperandIterator {
public:
InstructionOperandIterator(Instruction* instr, size_t pos)
: instr_(instr), pos_(pos) {}
Instruction* instruction() const { return instr_; }
InstructionOperand* Advance() { return instr_->InputAt(pos_++); }
private:
Instruction* instr_;
size_t pos_;
};
// Generates native code for a sequence of instructions.
class CodeGenerator final : public GapResolver::Assembler {
public:
@ -136,9 +150,15 @@ class CodeGenerator final : public GapResolver::Assembler {
size_t frame_access_state_offset,
OutputFrameStateCombine state_combine);
void BuildTranslationForFrameStateDescriptor(
FrameStateDescriptor* descriptor, Instruction* instr,
Translation* translation, size_t frame_access_state_offset,
OutputFrameStateCombine state_combine);
FrameStateDescriptor* descriptor, InstructionOperandIterator* iter,
Translation* translation, OutputFrameStateCombine state_combine);
void TranslateStateValueDescriptor(StateValueDescriptor* desc,
Translation* translation,
InstructionOperandIterator* iter);
void TranslateFrameStateDescriptorOperands(FrameStateDescriptor* desc,
InstructionOperandIterator* iter,
OutputFrameStateCombine combine,
Translation* translation);
void AddTranslationForOperand(Translation* translation, Instruction* instr,
InstructionOperand* op, MachineType type);
void AddNopForSmiCodeInlining();

View File

@ -753,6 +753,14 @@ const Operator* CommonOperatorBuilder::StateValues(int arguments) {
}
const Operator* CommonOperatorBuilder::ObjectState(int pointer_slots, int id) {
return new (zone()) Operator1<int>( // --
IrOpcode::kObjectState, Operator::kPure, // opcode
"ObjectState", // name
pointer_slots, 0, 0, 1, 0, 0, id); // counts
}
const Operator* CommonOperatorBuilder::TypedStateValues(
const ZoneVector<MachineType>* types) {
return new (zone()) Operator1<const ZoneVector<MachineType>*>( // --

View File

@ -166,6 +166,7 @@ class CommonOperatorBuilder final : public ZoneObject {
const Operator* BeginRegion();
const Operator* FinishRegion();
const Operator* StateValues(int arguments);
const Operator* ObjectState(int pointer_slots, int id);
const Operator* TypedStateValues(const ZoneVector<MachineType>* types);
const Operator* FrameState(BailoutId bailout_id,
OutputFrameStateCombine state_combine,

View File

@ -36,10 +36,13 @@ Reduction EscapeAnalysisReducer::Reduce(Node* node) {
return ReduceReferenceEqual(node);
case IrOpcode::kObjectIsSmi:
return ReduceObjectIsSmi(node);
case IrOpcode::kStateValues:
case IrOpcode::kFrameState:
return ReplaceWithDeoptDummy(node);
default:
// TODO(sigurds): Change this to GetFrameStateInputCount once
// it is working. For now we use EffectInputCount > 0 to determine
// whether a node might have a frame state input.
if (node->op()->EffectInputCount() > 0) {
return ReduceFrameStateUses(node);
}
break;
}
return NoChange();
@ -117,23 +120,14 @@ Reduction EscapeAnalysisReducer::ReduceReferenceEqual(Node* node) {
Node* left = NodeProperties::GetValueInput(node, 0);
Node* right = NodeProperties::GetValueInput(node, 1);
if (escape_analysis()->IsVirtual(left)) {
if (escape_analysis()->IsVirtual(right)) {
if (Node* rep = escape_analysis()->GetReplacement(left)) {
left = rep;
}
if (Node* rep = escape_analysis()->GetReplacement(right)) {
right = rep;
}
// TODO(sigurds): What to do if either is a PHI?
if (left == right) {
ReplaceWithValue(node, jsgraph()->TrueConstant());
if (FLAG_trace_turbo_escape) {
PrintF("Replaced ref eq #%d with true\n", node->id());
}
return Replace(node);
if (escape_analysis()->IsVirtual(right) &&
escape_analysis()->CompareVirtualObjects(left, right)) {
ReplaceWithValue(node, jsgraph()->TrueConstant());
if (FLAG_trace_turbo_escape) {
PrintF("Replaced ref eq #%d with true\n", node->id());
}
}
// Right-hand side is either not virtual, or a different node.
// Right-hand side is not a virtual object, or a different one.
ReplaceWithValue(node, jsgraph()->FalseConstant());
if (FLAG_trace_turbo_escape) {
PrintF("Replaced ref eq #%d with false\n", node->id());
@ -164,28 +158,132 @@ Reduction EscapeAnalysisReducer::ReduceObjectIsSmi(Node* node) {
}
// TODO(sigurds): This is a temporary solution until escape analysis
// supports deoptimization.
Reduction EscapeAnalysisReducer::ReplaceWithDeoptDummy(Node* node) {
DCHECK(node->opcode() == IrOpcode::kStateValues ||
node->opcode() == IrOpcode::kFrameState);
Reduction r = NoChange();
for (int i = 0; i < node->op()->ValueInputCount(); ++i) {
Node* input = NodeProperties::GetValueInput(node, i);
if (input->opcode() == IrOpcode::kFinishRegion ||
input->opcode() == IrOpcode::kAllocate ||
input->opcode() == IrOpcode::kPhi) {
if (escape_analysis()->IsVirtual(input)) {
NodeProperties::ReplaceValueInput(node, jsgraph()->UndefinedConstant(),
i);
if (FLAG_trace_turbo_escape) {
PrintF("Replaced state value (#%d) input with dummy\n", node->id());
}
r = Changed(node);
Reduction EscapeAnalysisReducer::ReduceFrameStateUses(Node* node) {
DCHECK_GE(node->op()->EffectInputCount(), 1);
bool changed = false;
for (int i = 0; i < node->InputCount(); ++i) {
Node* input = node->InputAt(i);
if (input->opcode() == IrOpcode::kFrameState) {
if (Node* ret = ReduceFrameState(input, node, false)) {
node->ReplaceInput(i, ret);
changed = true;
}
}
}
return r;
if (changed) {
return Changed(node);
}
return NoChange();
}
// Returns the clone if it duplicated the node, and null otherwise.
Node* EscapeAnalysisReducer::ReduceFrameState(Node* node, Node* effect,
bool multiple_users) {
DCHECK(node->opcode() == IrOpcode::kFrameState);
if (FLAG_trace_turbo_escape) {
PrintF("Reducing FrameState %d\n", node->id());
}
Node* clone = nullptr;
for (int i = 0; i < node->op()->ValueInputCount(); ++i) {
Node* input = NodeProperties::GetValueInput(node, i);
Node* ret =
input->opcode() == IrOpcode::kStateValues
? ReduceStateValueInputs(input, effect, node->UseCount() > 1)
: ReduceStateValueInput(node, i, effect, node->UseCount() > 1);
if (ret) {
if (node->UseCount() > 1 || multiple_users) {
if (FLAG_trace_turbo_escape) {
PrintF(" Cloning #%d", node->id());
}
node = clone = jsgraph()->graph()->CloneNode(node);
if (FLAG_trace_turbo_escape) {
PrintF(" to #%d\n", node->id());
}
multiple_users = false; // Don't clone anymore.
}
NodeProperties::ReplaceValueInput(node, ret, i);
}
}
Node* outer_frame_state = NodeProperties::GetFrameStateInput(node, 0);
if (outer_frame_state->opcode() == IrOpcode::kFrameState) {
if (Node* ret =
ReduceFrameState(outer_frame_state, effect, node->UseCount() > 1)) {
if (node->UseCount() > 1 || multiple_users) {
if (FLAG_trace_turbo_escape) {
PrintF(" Cloning #%d", node->id());
}
node = clone = jsgraph()->graph()->CloneNode(node);
if (FLAG_trace_turbo_escape) {
PrintF(" to #%d\n", node->id());
}
multiple_users = false;
}
NodeProperties::ReplaceFrameStateInput(node, 0, ret);
}
}
return clone;
}
// Returns the clone if it duplicated the node, and null otherwise.
Node* EscapeAnalysisReducer::ReduceStateValueInputs(Node* node, Node* effect,
bool multiple_users) {
if (FLAG_trace_turbo_escape) {
PrintF("Reducing StateValue #%d\n", node->id());
}
DCHECK(node->opcode() == IrOpcode::kStateValues);
DCHECK_NOT_NULL(effect);
Node* clone = nullptr;
for (int i = 0; i < node->op()->ValueInputCount(); ++i) {
if (Node* ret = ReduceStateValueInput(node, i, effect, multiple_users)) {
node = ret;
DCHECK_NULL(clone);
clone = ret;
multiple_users = false;
}
}
return clone;
}
// Returns the clone if it duplicated the node, and null otherwise.
Node* EscapeAnalysisReducer::ReduceStateValueInput(Node* node, int node_index,
Node* effect,
bool multiple_users) {
Node* input = NodeProperties::GetValueInput(node, node_index);
if (FLAG_trace_turbo_escape) {
PrintF("Reducing State Input #%d (%s)\n", input->id(),
input->op()->mnemonic());
}
Node* clone = nullptr;
if (input->opcode() == IrOpcode::kFinishRegion ||
input->opcode() == IrOpcode::kAllocate) {
if (escape_analysis()->IsVirtual(input)) {
if (Node* object_state =
escape_analysis()->GetOrCreateObjectState(effect, input)) {
if (node->UseCount() > 1 || multiple_users) {
if (FLAG_trace_turbo_escape) {
PrintF("Cloning #%d", node->id());
}
node = clone = jsgraph()->graph()->CloneNode(node);
if (FLAG_trace_turbo_escape) {
PrintF(" to #%d\n", node->id());
}
}
NodeProperties::ReplaceValueInput(node, object_state, node_index);
if (FLAG_trace_turbo_escape) {
PrintF("Replaced state #%d input #%d with object state #%d\n",
node->id(), input->id(), object_state->id());
}
} else {
if (FLAG_trace_turbo_escape) {
PrintF("No object state replacement available.\n");
}
}
}
}
return clone;
}

View File

@ -35,8 +35,11 @@ class EscapeAnalysisReducer final : public AdvancedReducer {
Reduction ReduceFinishRegion(Node* node);
Reduction ReduceReferenceEqual(Node* node);
Reduction ReduceObjectIsSmi(Node* node);
Reduction ReplaceWithDeoptDummy(Node* node);
Reduction ReduceFrameStateUses(Node* node);
Node* ReduceFrameState(Node* node, Node* effect, bool multiple_users);
Node* ReduceStateValueInputs(Node* node, Node* effect, bool multiple_users);
Node* ReduceStateValueInput(Node* node, int node_index, Node* effect,
bool multiple_users);
JSGraph* jsgraph() const { return jsgraph_; }
EscapeAnalysis* escape_analysis() const { return escape_analysis_; }

View File

@ -21,24 +21,29 @@ namespace v8 {
namespace internal {
namespace compiler {
// ------------------------------VirtualObject----------------------------------
class VirtualObject : public ZoneObject {
public:
enum Status { kUntracked = 0, kTracked = 1 };
VirtualObject(NodeId id, Zone* zone)
: id_(id), status_(kUntracked), fields_(zone), phi_(zone) {}
: id_(id),
status_(kUntracked),
fields_(zone),
phi_(zone),
object_state_(nullptr) {}
VirtualObject(const VirtualObject& other)
: id_(other.id_),
status_(other.status_),
fields_(other.fields_),
phi_(other.phi_) {}
phi_(other.phi_),
object_state_(other.object_state_) {}
VirtualObject(NodeId id, Zone* zone, size_t field_number)
: id_(id), status_(kTracked), fields_(zone), phi_(zone) {
: id_(id),
status_(kTracked),
fields_(zone),
phi_(zone),
object_state_(nullptr) {
fields_.resize(field_number);
phi_.resize(field_number, false);
}
@ -92,6 +97,8 @@ class VirtualObject : public ZoneObject {
return changed;
}
bool UpdateFrom(const VirtualObject& other);
void SetObjectState(Node* node) { object_state_ = node; }
Node* GetObjectState() { return object_state_; }
NodeId id() { return id_; }
void id(NodeId id) { id_ = id; }
@ -101,6 +108,7 @@ class VirtualObject : public ZoneObject {
Status status_;
ZoneVector<Node*> fields_;
ZoneVector<bool> phi_;
Node* object_state_;
};
@ -121,9 +129,6 @@ bool VirtualObject::UpdateFrom(const VirtualObject& other) {
}
// ------------------------------VirtualState-----------------------------------
class VirtualState : public ZoneObject {
public:
VirtualState(Zone* zone, size_t size);
@ -224,15 +229,12 @@ Node* MergeCache::GetFields(size_t pos) {
VirtualState::VirtualState(Zone* zone, size_t size)
: info_(zone), last_changed_(nullptr) {
info_.resize(size);
}
: info_(size, nullptr, zone), last_changed_(nullptr) {}
VirtualState::VirtualState(const VirtualState& state)
: info_(state.info_.get_allocator().zone()),
: info_(state.info_.size(), nullptr, state.info_.get_allocator().zone()),
last_changed_(state.last_changed_) {
info_.resize(state.info_.size());
for (size_t i = 0; i < state.info_.size(); ++i) {
if (state.info_[i] && state.info_[i]->id() == i) {
info_[i] = new (state.info_.get_allocator().zone())
@ -428,8 +430,10 @@ bool VirtualState::MergeFrom(MergeCache* cache, Zone* zone, Graph* graph,
}
}
if (rep->op()->ValueInputCount() != value_input_count) {
PrintF(" Widening Phi #%d of arity %d to %d", rep->id(),
rep->op()->ValueInputCount(), value_input_count);
if (FLAG_trace_turbo_escape) {
PrintF(" Widening Phi #%d of arity %d to %d", rep->id(),
rep->op()->ValueInputCount(), value_input_count);
}
NodeProperties::ChangeOp(
rep, common->Phi(MachineRepresentation::kTagged,
value_input_count));
@ -440,7 +444,6 @@ bool VirtualState::MergeFrom(MergeCache* cache, Zone* zone, Graph* graph,
}
}
}
} else {
SetVirtualObject(id, nullptr);
}
@ -457,9 +460,6 @@ bool VirtualState::MergeFrom(MergeCache* cache, Zone* zone, Graph* graph,
}
// ------------------------------EscapeStatusAnalysis---------------------------
EscapeStatusAnalysis::EscapeStatusAnalysis(EscapeAnalysis* object_analysis,
Graph* graph, Zone* zone)
: object_analysis_(object_analysis),
@ -571,6 +571,10 @@ void EscapeStatusAnalysis::Process(Node* node) {
case IrOpcode::kPhi:
if (!HasEntry(node)) {
info_[node->id()] = kVirtual;
if (!IsAllocationPhi(node)) {
SetEscaped(node);
RevisitUses(node);
}
}
CheckUsesForEscape(node);
default:
@ -579,6 +583,17 @@ void EscapeStatusAnalysis::Process(Node* node) {
}
bool EscapeStatusAnalysis::IsAllocationPhi(Node* node) {
for (Edge edge : node->input_edges()) {
Node* input = edge.to();
if (input->opcode() == IrOpcode::kPhi && !IsEscaped(input)) continue;
if (IsAllocation(input)) continue;
return false;
}
return true;
}
void EscapeStatusAnalysis::ProcessStoreField(Node* node) {
DCHECK_EQ(node->opcode(), IrOpcode::kStoreField);
Node* to = NodeProperties::GetValueInput(node, 0);
@ -618,6 +633,10 @@ void EscapeStatusAnalysis::ProcessAllocate(Node* node) {
node->op()->mnemonic());
}
NumberMatcher size(node->InputAt(0));
DCHECK(node->InputAt(0)->opcode() != IrOpcode::kInt32Constant &&
node->InputAt(0)->opcode() != IrOpcode::kInt64Constant &&
node->InputAt(0)->opcode() != IrOpcode::kFloat32Constant &&
node->InputAt(0)->opcode() != IrOpcode::kFloat64Constant);
if (!size.HasValue() && SetEscaped(node)) {
RevisitUses(node);
if (FLAG_trace_turbo_escape) {
@ -724,9 +743,6 @@ void EscapeStatusAnalysis::DebugPrint() {
}
// -----------------------------EscapeAnalysis----------------------------------
EscapeAnalysis::EscapeAnalysis(Graph* graph, CommonOperatorBuilder* common,
Zone* zone)
: graph_(graph),
@ -1005,6 +1021,10 @@ void EscapeAnalysis::ProcessAllocation(Node* node) {
if (virtual_states_[node->id()]->GetVirtualObject(node)) return;
NumberMatcher size(node->InputAt(0));
DCHECK(node->InputAt(0)->opcode() != IrOpcode::kInt32Constant &&
node->InputAt(0)->opcode() != IrOpcode::kInt64Constant &&
node->InputAt(0)->opcode() != IrOpcode::kFloat32Constant &&
node->InputAt(0)->opcode() != IrOpcode::kFloat64Constant);
if (size.HasValue()) {
virtual_states_[node->id()]->SetVirtualObject(
node->id(), new (zone()) VirtualObject(node->id(), zone(),
@ -1136,6 +1156,17 @@ VirtualObject* EscapeAnalysis::ResolveVirtualObject(VirtualState* state,
}
bool EscapeAnalysis::CompareVirtualObjects(Node* left, Node* right) {
DCHECK(IsVirtual(left) && IsVirtual(right));
left = ResolveReplacement(left);
right = ResolveReplacement(right);
if (IsEquivalentPhi(left, right)) {
return true;
}
return false;
}
int EscapeAnalysis::OffsetFromAccess(Node* node) {
DCHECK(OpParameter<FieldAccess>(node).offset % kPointerSize == 0);
return OpParameter<FieldAccess>(node).offset / kPointerSize;
@ -1215,6 +1246,10 @@ void EscapeAnalysis::ProcessLoadElement(Node* node) {
VirtualState* state = virtual_states_[node->id()];
Node* index_node = node->InputAt(1);
NumberMatcher index(index_node);
DCHECK(index_node->opcode() != IrOpcode::kInt32Constant &&
index_node->opcode() != IrOpcode::kInt64Constant &&
index_node->opcode() != IrOpcode::kFloat32Constant &&
index_node->opcode() != IrOpcode::kFloat64Constant);
ElementAccess access = OpParameter<ElementAccess>(node);
if (index.HasValue()) {
int offset = index.Value() + access.header_size / kPointerSize;
@ -1273,6 +1308,10 @@ void EscapeAnalysis::ProcessStoreElement(Node* node) {
Node* to = NodeProperties::GetValueInput(node, 0);
Node* index_node = node->InputAt(1);
NumberMatcher index(index_node);
DCHECK(index_node->opcode() != IrOpcode::kInt32Constant &&
index_node->opcode() != IrOpcode::kInt64Constant &&
index_node->opcode() != IrOpcode::kFloat32Constant &&
index_node->opcode() != IrOpcode::kFloat64Constant);
ElementAccess access = OpParameter<ElementAccess>(node);
Node* val = NodeProperties::GetValueInput(node, 2);
if (index.HasValue()) {
@ -1303,6 +1342,51 @@ void EscapeAnalysis::ProcessStoreElement(Node* node) {
}
Node* EscapeAnalysis::GetOrCreateObjectState(Node* effect, Node* node) {
if ((node->opcode() == IrOpcode::kFinishRegion ||
node->opcode() == IrOpcode::kAllocate) &&
IsVirtual(node)) {
if (VirtualObject* vobj =
ResolveVirtualObject(virtual_states_[effect->id()], node)) {
if (Node* object_state = vobj->GetObjectState()) {
return object_state;
} else {
cache_->fields().clear();
for (size_t i = 0; i < vobj->field_count(); ++i) {
if (Node* field = vobj->GetField(i)) {
cache_->fields().push_back(field);
}
}
int input_count = static_cast<int>(cache_->fields().size());
Node* new_object_state =
graph()->NewNode(common()->ObjectState(input_count, vobj->id()),
input_count, &cache_->fields().front());
vobj->SetObjectState(new_object_state);
if (FLAG_trace_turbo_escape) {
PrintF(
"Creating object state #%d for vobj %p (from node #%d) at effect "
"#%d\n",
new_object_state->id(), static_cast<void*>(vobj), node->id(),
effect->id());
}
// Now fix uses of other objects.
for (size_t i = 0; i < vobj->field_count(); ++i) {
if (Node* field = vobj->GetField(i)) {
if (Node* field_object_state =
GetOrCreateObjectState(effect, field)) {
NodeProperties::ReplaceValueInput(
new_object_state, field_object_state, static_cast<int>(i));
}
}
}
return new_object_state;
}
}
}
return nullptr;
}
void EscapeAnalysis::DebugPrintObject(VirtualObject* object, NodeId id) {
PrintF(" Object #%d with %zu fields\n", id, object->field_count());
for (size_t i = 0; i < object->field_count(); ++i) {

View File

@ -59,6 +59,7 @@ class EscapeStatusAnalysis {
bool HasEntry(Node* node);
void Resize();
size_t size();
bool IsAllocationPhi(Node* node);
Graph* graph() const { return graph_; }
Zone* zone() const { return zone_; }
@ -92,6 +93,8 @@ class EscapeAnalysis {
Node* GetReplacement(Node* node);
bool IsVirtual(Node* node);
bool IsEscaped(Node* node);
bool CompareVirtualObjects(Node* left, Node* right);
Node* GetOrCreateObjectState(Node* effect, Node* node);
private:
void RunObjectAnalysis();
@ -110,7 +113,6 @@ class EscapeAnalysis {
VirtualState* states);
void ForwardVirtualState(Node* node);
bool IsEffectBranchPoint(Node* node);
bool IsDanglingEffectNode(Node* node);
int OffsetFromAccess(Node* node);

View File

@ -2,8 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/base/functional.h"
#include "src/compiler/frame-states.h"
#include "src/base/functional.h"
#include "src/handles-inl.h"
namespace v8 {

View File

@ -307,6 +307,9 @@ InstructionOperand OperandForDeopt(OperandGenerator* g, Node* input,
case IrOpcode::kFloat64Constant:
case IrOpcode::kHeapConstant:
return g->UseImmediate(input);
case IrOpcode::kObjectState:
UNREACHABLE();
break;
default:
switch (kind) {
case FrameStateInputKind::kStackSlot:
@ -314,21 +317,94 @@ InstructionOperand OperandForDeopt(OperandGenerator* g, Node* input,
case FrameStateInputKind::kAny:
return g->UseAny(input);
}
UNREACHABLE();
return InstructionOperand();
}
UNREACHABLE();
return InstructionOperand();
}
class StateObjectDeduplicator {
public:
explicit StateObjectDeduplicator(Zone* zone) : objects_(zone) {}
static const size_t kNotDuplicated = SIZE_MAX;
size_t GetObjectId(Node* node) {
for (size_t i = 0; i < objects_.size(); ++i) {
if (objects_[i] == node) {
return i;
}
}
return kNotDuplicated;
}
size_t InsertObject(Node* node) {
size_t id = objects_.size();
objects_.push_back(node);
return id;
}
private:
ZoneVector<Node*> objects_;
};
// Returns the number of instruction operands added to inputs.
size_t AddOperandToStateValueDescriptor(StateValueDescriptor* descriptor,
InstructionOperandVector* inputs,
OperandGenerator* g,
StateObjectDeduplicator* deduplicator,
Node* input, MachineType type,
FrameStateInputKind kind, Zone* zone) {
switch (input->opcode()) {
case IrOpcode::kObjectState: {
size_t id = deduplicator->GetObjectId(input);
if (id == StateObjectDeduplicator::kNotDuplicated) {
size_t entries = 0;
id = deduplicator->InsertObject(input);
descriptor->fields().push_back(
StateValueDescriptor::Recursive(zone, id));
StateValueDescriptor* new_desc = &descriptor->fields().back();
for (Edge edge : input->input_edges()) {
entries += AddOperandToStateValueDescriptor(
new_desc, inputs, g, deduplicator, edge.to(),
MachineType::AnyTagged(), kind, zone);
}
return entries;
} else {
// Crankshaft counts duplicate objects for the running id, so we have
// to push the input again.
deduplicator->InsertObject(input);
descriptor->fields().push_back(
StateValueDescriptor::Duplicate(zone, id));
return 0;
}
break;
}
default: {
inputs->push_back(OperandForDeopt(g, input, kind));
descriptor->fields().push_back(StateValueDescriptor::Plain(zone, type));
return 1;
}
}
}
void AddFrameStateInputs(Node* state, OperandGenerator* g,
InstructionOperandVector* inputs,
FrameStateDescriptor* descriptor,
FrameStateInputKind kind, Zone* zone) {
// Returns the number of instruction operands added to inputs.
size_t AddInputsToFrameStateDescriptor(FrameStateDescriptor* descriptor,
Node* state, OperandGenerator* g,
StateObjectDeduplicator* deduplicator,
InstructionOperandVector* inputs,
FrameStateInputKind kind, Zone* zone) {
DCHECK_EQ(IrOpcode::kFrameState, state->op()->opcode());
size_t entries = 0;
size_t initial_size = inputs->size();
USE(initial_size); // initial_size is only used for debug.
if (descriptor->outer_state()) {
AddFrameStateInputs(state->InputAt(kFrameStateOuterStateInput), g, inputs,
descriptor->outer_state(), kind, zone);
entries += AddInputsToFrameStateDescriptor(
descriptor->outer_state(), state->InputAt(kFrameStateOuterStateInput),
g, deduplicator, inputs, kind, zone);
}
Node* parameters = state->InputAt(kFrameStateParametersInput);
@ -342,32 +418,34 @@ void AddFrameStateInputs(Node* state, OperandGenerator* g,
DCHECK_EQ(descriptor->locals_count(), StateValuesAccess(locals).size());
DCHECK_EQ(descriptor->stack_count(), StateValuesAccess(stack).size());
ZoneVector<MachineType> types(zone);
types.reserve(descriptor->GetSize());
size_t value_index = 0;
inputs->push_back(
OperandForDeopt(g, function, FrameStateInputKind::kStackSlot));
descriptor->SetType(value_index++, MachineType::AnyTagged());
StateValueDescriptor* values_descriptor =
descriptor->GetStateValueDescriptor();
entries += AddOperandToStateValueDescriptor(
values_descriptor, inputs, g, deduplicator, function,
MachineType::AnyTagged(), FrameStateInputKind::kStackSlot, zone);
for (StateValuesAccess::TypedNode input_node :
StateValuesAccess(parameters)) {
inputs->push_back(OperandForDeopt(g, input_node.node, kind));
descriptor->SetType(value_index++, input_node.type);
entries += AddOperandToStateValueDescriptor(values_descriptor, inputs, g,
deduplicator, input_node.node,
input_node.type, kind, zone);
}
if (descriptor->HasContext()) {
inputs->push_back(
OperandForDeopt(g, context, FrameStateInputKind::kStackSlot));
descriptor->SetType(value_index++, MachineType::AnyTagged());
entries += AddOperandToStateValueDescriptor(
values_descriptor, inputs, g, deduplicator, context,
MachineType::AnyTagged(), FrameStateInputKind::kStackSlot, zone);
}
for (StateValuesAccess::TypedNode input_node : StateValuesAccess(locals)) {
inputs->push_back(OperandForDeopt(g, input_node.node, kind));
descriptor->SetType(value_index++, input_node.type);
entries += AddOperandToStateValueDescriptor(values_descriptor, inputs, g,
deduplicator, input_node.node,
input_node.type, kind, zone);
}
for (StateValuesAccess::TypedNode input_node : StateValuesAccess(stack)) {
inputs->push_back(OperandForDeopt(g, input_node.node, kind));
descriptor->SetType(value_index++, input_node.type);
entries += AddOperandToStateValueDescriptor(values_descriptor, inputs, g,
deduplicator, input_node.node,
input_node.type, kind, zone);
}
DCHECK(value_index == descriptor->GetSize());
DCHECK_EQ(initial_size + entries, inputs->size());
return entries;
}
} // namespace
@ -500,6 +578,8 @@ void InstructionSelector::InitializeCallBuffer(Node* call, CallBuffer* buffer,
// follows (n is the number of value inputs to the frame state):
// arg 1 : deoptimization id.
// arg 2 - arg (n + 1) : value inputs to the frame state.
size_t frame_state_entries = 0;
USE(frame_state_entries); // frame_state_entries is only used for debug.
if (buffer->frame_state_descriptor != NULL) {
InstructionSequence::StateId state_id =
sequence()->AddFrameStateDescriptor(buffer->frame_state_descriptor);
@ -507,12 +587,17 @@ void InstructionSelector::InitializeCallBuffer(Node* call, CallBuffer* buffer,
Node* frame_state =
call->InputAt(static_cast<int>(buffer->descriptor->InputCount()));
AddFrameStateInputs(frame_state, &g, &buffer->instruction_args,
buffer->frame_state_descriptor,
FrameStateInputKind::kStackSlot, instruction_zone());
StateObjectDeduplicator deduplicator(instruction_zone());
frame_state_entries =
1 + AddInputsToFrameStateDescriptor(
buffer->frame_state_descriptor, frame_state, &g, &deduplicator,
&buffer->instruction_args, FrameStateInputKind::kStackSlot,
instruction_zone());
DCHECK_EQ(1 + frame_state_entries, buffer->instruction_args.size());
}
DCHECK(1 + buffer->frame_state_value_count() ==
buffer->instruction_args.size());
size_t input_count = static_cast<size_t>(buffer->input_count());
@ -549,7 +634,7 @@ void InstructionSelector::InitializeCallBuffer(Node* call, CallBuffer* buffer,
}
}
DCHECK_EQ(input_count, buffer->instruction_args.size() + pushed_count -
buffer->frame_state_value_count());
frame_state_entries);
if (V8_TARGET_ARCH_STORES_RETURN_ADDRESS_ON_STACK && call_tail &&
stack_param_delta != 0) {
// For tail calls that change the size of their parameter list and keep
@ -752,6 +837,7 @@ void InstructionSelector::VisitNode(Node* node) {
return VisitCall(node);
case IrOpcode::kFrameState:
case IrOpcode::kStateValues:
case IrOpcode::kObjectState:
return;
case IrOpcode::kLoad: {
LoadRepresentation type = LoadRepresentationOf(node->op());
@ -1470,19 +1556,19 @@ void InstructionSelector::VisitDeoptimize(DeoptimizeKind kind, Node* value) {
OperandGenerator g(this);
FrameStateDescriptor* desc = GetFrameStateDescriptor(value);
size_t arg_count = desc->GetTotalSize() + 1; // Include deopt id.
InstructionOperandVector args(instruction_zone());
args.reserve(arg_count);
args.reserve(desc->GetTotalSize() + 1); // Include deopt id.
InstructionSequence::StateId state_id =
sequence()->AddFrameStateDescriptor(desc);
args.push_back(g.TempImmediate(state_id.ToInt()));
AddFrameStateInputs(value, &g, &args, desc, FrameStateInputKind::kAny,
instruction_zone());
StateObjectDeduplicator deduplicator(instruction_zone());
DCHECK_EQ(args.size(), arg_count);
AddInputsToFrameStateDescriptor(desc, value, &g, &deduplicator, &args,
FrameStateInputKind::kAny,
instruction_zone());
InstructionCode opcode = kArchDeoptimize;
switch (kind) {
@ -1493,7 +1579,7 @@ void InstructionSelector::VisitDeoptimize(DeoptimizeKind kind, Node* value) {
opcode |= MiscField::encode(Deoptimizer::SOFT);
break;
}
Emit(opcode, 0, nullptr, arg_count, &args.front(), 0, nullptr);
Emit(opcode, 0, nullptr, args.size(), &args.front(), 0, nullptr);
}

View File

@ -26,8 +26,6 @@ class Linkage;
class OperandGenerator;
struct SwitchInfo;
typedef ZoneVector<InstructionOperand> InstructionOperandVector;
// This struct connects nodes of parameters which are going to be pushed on the
// call stack with their parameter index in the call descriptor of the callee.
class PushParameter {

View File

@ -6,6 +6,7 @@
#include "src/compiler/graph.h"
#include "src/compiler/instruction.h"
#include "src/compiler/schedule.h"
#include "src/compiler/state-values-utils.h"
namespace v8 {
namespace internal {
@ -831,11 +832,9 @@ FrameStateDescriptor::FrameStateDescriptor(
parameters_count_(parameters_count),
locals_count_(locals_count),
stack_count_(stack_count),
types_(zone),
values_(zone),
shared_info_(shared_info),
outer_state_(outer_state) {
types_.resize(GetSize(), MachineType::None());
}
outer_state_(outer_state) {}
size_t FrameStateDescriptor::GetSize(OutputFrameStateCombine combine) const {
@ -884,17 +883,6 @@ size_t FrameStateDescriptor::GetJSFrameCount() const {
}
MachineType FrameStateDescriptor::GetType(size_t index) const {
return types_[index];
}
void FrameStateDescriptor::SetType(size_t index, MachineType type) {
DCHECK(index < GetSize());
types_[index] = type;
}
std::ostream& operator<<(std::ostream& os, const RpoNumber& rpo) {
return os << rpo.ToSize();
}

View File

@ -23,8 +23,10 @@ namespace v8 {
namespace internal {
namespace compiler {
// Forward declarations.
class Schedule;
class InstructionOperand {
public:
static const int kInvalidVirtualRegister = -1;
@ -108,6 +110,9 @@ class InstructionOperand {
};
typedef ZoneVector<InstructionOperand> InstructionOperandVector;
struct PrintableInstructionOperand {
const RegisterConfiguration* register_configuration_;
InstructionOperand op_;
@ -941,6 +946,59 @@ class Constant final {
};
std::ostream& operator<<(std::ostream& os, const Constant& constant);
// Forward declarations.
class FrameStateDescriptor;
enum class StateValueKind { kPlain, kNested, kDuplicate };
class StateValueDescriptor {
public:
explicit StateValueDescriptor(Zone* zone)
: kind_(StateValueKind::kPlain),
type_(MachineType::AnyTagged()),
id_(0),
fields_(zone) {}
static StateValueDescriptor Plain(Zone* zone, MachineType type) {
return StateValueDescriptor(StateValueKind::kPlain, zone, type, 0);
}
static StateValueDescriptor Recursive(Zone* zone, size_t id) {
return StateValueDescriptor(StateValueKind::kNested, zone,
MachineType::AnyTagged(), id);
}
static StateValueDescriptor Duplicate(Zone* zone, size_t id) {
return StateValueDescriptor(StateValueKind::kDuplicate, zone,
MachineType::AnyTagged(), id);
}
size_t size() { return fields_.size(); }
ZoneVector<StateValueDescriptor>& fields() { return fields_; }
int IsPlain() { return kind_ == StateValueKind::kPlain; }
int IsNested() { return kind_ == StateValueKind::kNested; }
int IsDuplicate() { return kind_ == StateValueKind::kDuplicate; }
MachineType type() const { return type_; }
MachineType GetOperandType(size_t index) const {
return fields_[index].type_;
}
size_t id() const { return id_; }
private:
StateValueDescriptor(StateValueKind kind, Zone* zone, MachineType type,
size_t id)
: kind_(kind), type_(type), id_(id), fields_(zone) {}
StateValueKind kind_;
MachineType type_;
size_t id_;
ZoneVector<StateValueDescriptor> fields_;
};
class FrameStateDescriptor : public ZoneObject {
public:
FrameStateDescriptor(Zone* zone, FrameStateType type, BailoutId bailout_id,
@ -968,8 +1026,10 @@ class FrameStateDescriptor : public ZoneObject {
size_t GetFrameCount() const;
size_t GetJSFrameCount() const;
MachineType GetType(size_t index) const;
void SetType(size_t index, MachineType type);
MachineType GetType(size_t index) const {
return values_.GetOperandType(index);
}
StateValueDescriptor* GetStateValueDescriptor() { return &values_; }
private:
FrameStateType type_;
@ -978,12 +1038,13 @@ class FrameStateDescriptor : public ZoneObject {
size_t parameters_count_;
size_t locals_count_;
size_t stack_count_;
ZoneVector<MachineType> types_;
StateValueDescriptor values_;
MaybeHandle<SharedFunctionInfo> const shared_info_;
FrameStateDescriptor* outer_state_;
};
std::ostream& operator<<(std::ostream& os, const Constant& constant);
typedef ZoneVector<FrameStateDescriptor*> DeoptimizationVector;
class PhiInstruction final : public ZoneObject {
@ -1100,9 +1161,10 @@ typedef std::map<int, Constant, std::less<int>,
typedef ZoneDeque<Instruction*> InstructionDeque;
typedef ZoneDeque<ReferenceMap*> ReferenceMapDeque;
typedef ZoneVector<FrameStateDescriptor*> DeoptimizationVector;
typedef ZoneVector<InstructionBlock*> InstructionBlocks;
// Forward declarations.
struct PrintableInstructionSequence;

View File

@ -50,6 +50,7 @@
V(FrameState) \
V(StateValues) \
V(TypedStateValues) \
V(ObjectState) \
V(Call) \
V(Parameter) \
V(OsrValue) \

View File

@ -636,6 +636,11 @@ Type* Typer::Visitor::TypeStateValues(Node* node) {
}
Type* Typer::Visitor::TypeObjectState(Node* node) {
return Type::Internal(zone());
}
Type* Typer::Visitor::TypeTypedStateValues(Node* node) {
return Type::Internal(zone());
}

View File

@ -436,6 +436,7 @@ void Verifier::Visitor::Check(Node* node) {
CHECK_EQ(6, input_count);
break;
case IrOpcode::kStateValues:
case IrOpcode::kObjectState:
case IrOpcode::kTypedStateValues:
// TODO(jarin): what are the constraints on these?
break;

View File

@ -3486,6 +3486,37 @@ Handle<Object> TranslatedState::MaterializeAt(int frame_index,
object->set_length(*length);
return object;
}
case FIXED_ARRAY_TYPE: {
Handle<Object> lengthObject = MaterializeAt(frame_index, value_index);
int32_t length = 0;
CHECK(lengthObject->ToInt32(&length));
Handle<FixedArray> object =
isolate_->factory()->NewFixedArray(length);
slot->value_ = object;
for (int i = 0; i < length; ++i) {
Handle<Object> value = MaterializeAt(frame_index, value_index);
object->set(i, *value);
}
return object;
}
case FIXED_DOUBLE_ARRAY_TYPE: {
Handle<Object> lengthObject = MaterializeAt(frame_index, value_index);
int32_t length = 0;
CHECK(lengthObject->ToInt32(&length));
Handle<FixedArrayBase> object =
isolate_->factory()->NewFixedDoubleArray(length);
slot->value_ = object;
if (length > 0) {
Handle<FixedDoubleArray> double_array =
Handle<FixedDoubleArray>::cast(object);
for (int i = 0; i < length; ++i) {
Handle<Object> value = MaterializeAt(frame_index, value_index);
CHECK(value->IsNumber());
double_array->set(i, value->Number());
}
}
return object;
}
default:
PrintF(stderr, "[couldn't handle instance type %d]\n",
map->instance_type());

View File

@ -0,0 +1,47 @@
// Copyright 2013 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// 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: --allow-natives-syntax --turbo-escape
// Test deoptimization with captured objects in local variables.
(function testDeoptLocal() {
"use strict";
function constructor1(a) {
return arguments;
}
function func(a) {
var o1 = constructor1(1,2,3);
if (a) { %DeoptimizeNow(); }
assertEquals(1, o1[0]);
assertEquals(2, o1[1]);
assertEquals(3, o1[2]);
}
func(false);
func(false);
%OptimizeFunctionOnNextCall(func);
func(true);
})();

View File

@ -0,0 +1,50 @@
// Copyright 2013 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// 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: --allow-natives-syntax --turbo-escape
// Test deoptimization with captured objects in local variables.
(function testDeoptLocal() {
"use strict";
function constructor1(a) {
return arguments;
}
function func() {
var o1 = constructor1(1,2,3);
var o2 = constructor1(4,o1);
%DeoptimizeNow();
assertEquals(1, o1[0]);
assertEquals(2, o1[1]);
assertEquals(3, o1[2]);
assertEquals(4, o2[0]);
assertEquals(o1, o2[1]);
}
func();
func();
%OptimizeFunctionOnNextCall(func);
func();
})();

View File

@ -0,0 +1,51 @@
// Copyright 2013 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// 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: --allow-natives-syntax --turbo-escape
// Test deoptimization with captured objects in local variables.
(function testDeoptLocal() {
"use strict";
function constructor1(a) {
return arguments;
}
function func() {
var o1 = constructor1(1,2,3);
var o2 = constructor1(4,o1);
o1[0] = o1;
%DeoptimizeNow();
assertEquals(o1, o1[0]);
assertEquals(2, o1[1]);
assertEquals(3, o1[2]);
assertEquals(4, o2[0]);
assertEquals(o1, o2[1]);
}
func();
func();
%OptimizeFunctionOnNextCall(func);
func();
})();

View File

@ -0,0 +1,57 @@
// Copyright 2013 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// 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: --allow-natives-syntax --turbo-escape
// Test deoptimization with captured objects in local variables.
(function testDeoptLocal() {
"use strict";
function constructor1() {
this.x=1;
this.y=2;
this.z=3;
}
function constructor2(x) {
this.a=x;
this.b=4;
}
function func() {
var o1 = new constructor1();
var o2 = new constructor2(o1);
o1.x = o1;
%DeoptimizeNow();
assertEquals(o1, o1.x);
assertEquals(2, o1.y);
assertEquals(3, o1.z);
assertEquals(o1, o2.a);
assertEquals(4, o2.b);
}
func();
func();
%OptimizeFunctionOnNextCall(func);
func();
})();

View File

@ -0,0 +1,41 @@
// Copyright 2016 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// 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: --allow-natives-syntax --turbo-escape
function f() {
var x = new Array(2);
x[0] = 23.1234;
x[1] = 25.1234;
%DeoptimizeNow();
return x[0];
}
assertEquals(f(), 23.1234);
assertEquals(f(), 23.1234);
%OptimizeFunctionOnNextCall(f);
assertEquals(f(), 23.1234);

View File

@ -156,19 +156,6 @@
# TODO(titzer): too slow in --turbo mode due to O(n^2) graph verification.
'regress/regress-1122': [PASS, NO_VARIANTS],
# TODO(sigurds): Tests that may fail because of missing deopt support.
'compiler/escape-analysis-1': [PASS, NO_VARIANTS],
'compiler/escape-analysis-2': [PASS, NO_VARIANTS],
'compiler/escape-analysis-3': [PASS, NO_VARIANTS],
'compiler/escape-analysis-4': [PASS, NO_VARIANTS],
'compiler/escape-analysis-5': [PASS, NO_VARIANTS],
'compiler/escape-analysis-6': [PASS, NO_VARIANTS],
'compiler/escape-analysis-7': [PASS, NO_VARIANTS],
'compiler/escape-analysis-9': [PASS, NO_VARIANTS],
'compiler/escape-analysis-10': [PASS, NO_VARIANTS],
# TODO(sigurds): Tests that fail because of incomplete use handling (i.e. select).
'compiler/escape-analysis-8': [PASS, NO_VARIANTS],
# Assumptions about optimization need investigation in TurboFan.
'regress-sync-optimized-lists': [PASS, NO_VARIANTS],
'regress/regress-store-uncacheable': [PASS, NO_VARIANTS],

View File

@ -300,6 +300,90 @@ TEST_F(EscapeAnalysisTest, DanglingLoadOrder) {
ASSERT_EQ(object1, NodeProperties::GetValueInput(result, 0));
}
TEST_F(EscapeAnalysisTest, DeoptReplacement) {
Node* object1 = Constant(1);
BeginRegion();
Node* allocation = Allocate(Constant(kPointerSize));
Store(AccessAtIndex(0), allocation, object1);
Node* finish = FinishRegion(allocation);
Node* effect1 = Store(AccessAtIndex(0), allocation, object1, finish);
Branch();
Node* ifFalse = IfFalse();
Node* state_values1 = graph()->NewNode(common()->StateValues(1), finish);
Node* state_values2 = graph()->NewNode(common()->StateValues(0));
Node* state_values3 = graph()->NewNode(common()->StateValues(0));
Node* frame_state = graph()->NewNode(
common()->FrameState(BailoutId::None(), OutputFrameStateCombine::Ignore(),
nullptr),
state_values1, state_values2, state_values3, UndefinedConstant(),
graph()->start(), graph()->start());
Node* deopt = graph()->NewNode(common()->Deoptimize(DeoptimizeKind::kEager),
frame_state, effect1, ifFalse);
Node* ifTrue = IfTrue();
Node* load = Load(AccessAtIndex(0), finish, effect1, ifTrue);
Node* result = Return(load, effect1, ifTrue);
EndGraph();
graph()->end()->AppendInput(zone(), deopt);
Analysis();
ExpectVirtual(allocation);
ExpectReplacement(load, object1);
Transformation();
ASSERT_EQ(object1, NodeProperties::GetValueInput(result, 0));
Node* object_state = NodeProperties::GetValueInput(state_values1, 0);
ASSERT_EQ(object_state->opcode(), IrOpcode::kObjectState);
ASSERT_EQ(1, object_state->op()->ValueInputCount());
ASSERT_EQ(object1, NodeProperties::GetValueInput(object_state, 0));
}
TEST_F(EscapeAnalysisTest, DeoptReplacementIdentity) {
Node* object1 = Constant(1);
BeginRegion();
Node* allocation = Allocate(Constant(kPointerSize * 2));
Store(AccessAtIndex(0), allocation, object1);
Store(AccessAtIndex(kPointerSize), allocation, allocation);
Node* finish = FinishRegion(allocation);
Node* effect1 = Store(AccessAtIndex(0), allocation, object1, finish);
Branch();
Node* ifFalse = IfFalse();
Node* state_values1 = graph()->NewNode(common()->StateValues(1), finish);
Node* state_values2 = graph()->NewNode(common()->StateValues(1), finish);
Node* state_values3 = graph()->NewNode(common()->StateValues(0));
Node* frame_state = graph()->NewNode(
common()->FrameState(BailoutId::None(), OutputFrameStateCombine::Ignore(),
nullptr),
state_values1, state_values2, state_values3, UndefinedConstant(),
graph()->start(), graph()->start());
Node* deopt = graph()->NewNode(common()->Deoptimize(DeoptimizeKind::kEager),
frame_state, effect1, ifFalse);
Node* ifTrue = IfTrue();
Node* load = Load(AccessAtIndex(0), finish, effect1, ifTrue);
Node* result = Return(load, effect1, ifTrue);
EndGraph();
graph()->end()->AppendInput(zone(), deopt);
Analysis();
ExpectVirtual(allocation);
ExpectReplacement(load, object1);
Transformation();
ASSERT_EQ(object1, NodeProperties::GetValueInput(result, 0));
Node* object_state = NodeProperties::GetValueInput(state_values1, 0);
ASSERT_EQ(object_state->opcode(), IrOpcode::kObjectState);
ASSERT_EQ(2, object_state->op()->ValueInputCount());
ASSERT_EQ(object1, NodeProperties::GetValueInput(object_state, 0));
ASSERT_EQ(object_state, NodeProperties::GetValueInput(object_state, 1));
Node* object_state2 = NodeProperties::GetValueInput(state_values1, 0);
ASSERT_EQ(object_state, object_state2);
}
} // namespace compiler
} // namespace internal
} // namespace v8