Unit test of instruction selection for calls with deoptimization.

BUG=
R=bmeurer@chromium.org

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

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@23467 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
jarin@chromium.org 2014-08-27 15:56:11 +00:00
parent 71fbe7d4ec
commit 8eb5c1524d
13 changed files with 231 additions and 28 deletions

View File

@ -166,8 +166,8 @@ void CodeGenerator::AssembleArchInstruction(Instruction* instr) {
break; break;
} }
case kArchDeoptimize: { case kArchDeoptimize: {
int deoptimization_id = MiscField::decode(instr->opcode()); int deoptimization_id = BuildTranslation(instr, 0);
BuildTranslation(instr, 0, deoptimization_id);
Address deopt_entry = Deoptimizer::GetDeoptimizationEntry( Address deopt_entry = Deoptimizer::GetDeoptimizationEntry(
isolate(), deoptimization_id, Deoptimizer::LAZY); isolate(), deoptimization_id, Deoptimizer::LAZY);
__ Call(deopt_entry, RelocInfo::RUNTIME_ENTRY); __ Call(deopt_entry, RelocInfo::RUNTIME_ENTRY);

View File

@ -160,8 +160,8 @@ void CodeGenerator::AssembleArchInstruction(Instruction* instr) {
break; break;
} }
case kArchDeoptimize: { case kArchDeoptimize: {
int deoptimization_id = MiscField::decode(instr->opcode()); int deoptimization_id = BuildTranslation(instr, 0);
BuildTranslation(instr, 0, deoptimization_id);
Address deopt_entry = Deoptimizer::GetDeoptimizationEntry( Address deopt_entry = Deoptimizer::GetDeoptimizationEntry(
isolate(), deoptimization_id, Deoptimizer::LAZY); isolate(), deoptimization_id, Deoptimizer::LAZY);
__ Call(deopt_entry, RelocInfo::RUNTIME_ENTRY); __ Call(deopt_entry, RelocInfo::RUNTIME_ENTRY);

View File

@ -258,10 +258,9 @@ void CodeGenerator::AddSafepointAndDeopt(Instruction* instr) {
// If the frame state is present, it starts at argument 1 // If the frame state is present, it starts at argument 1
// (just after the code address). // (just after the code address).
InstructionOperandConverter converter(this, instr); InstructionOperandConverter converter(this, instr);
// Argument 1 is deoptimization id. // Deoptimization info starts at argument 1
int deoptimization_id = converter.ToConstant(instr->InputAt(1)).ToInt32(); int frame_state_offset = 1;
// The actual frame state values start with argument 2. int deoptimization_id = BuildTranslation(instr, frame_state_offset);
int first_state_value_offset = 2;
#if DEBUG #if DEBUG
// Make sure all the values live in stack slots or they are immediates. // Make sure all the values live in stack slots or they are immediates.
// (The values should not live in register because registers are clobbered // (The values should not live in register because registers are clobbered
@ -269,11 +268,10 @@ void CodeGenerator::AddSafepointAndDeopt(Instruction* instr) {
FrameStateDescriptor* descriptor = FrameStateDescriptor* descriptor =
code()->GetDeoptimizationEntry(deoptimization_id); code()->GetDeoptimizationEntry(deoptimization_id);
for (int i = 0; i < descriptor->size(); i++) { for (int i = 0; i < descriptor->size(); i++) {
InstructionOperand* op = instr->InputAt(first_state_value_offset + i); InstructionOperand* op = instr->InputAt(frame_state_offset + 1 + i);
CHECK(op->IsStackSlot() || op->IsImmediate()); CHECK(op->IsStackSlot() || op->IsImmediate());
} }
#endif #endif
BuildTranslation(instr, first_state_value_offset, deoptimization_id);
safepoints()->RecordLazyDeoptimizationIndex(deoptimization_id); safepoints()->RecordLazyDeoptimizationIndex(deoptimization_id);
} }
} }
@ -310,9 +308,12 @@ int CodeGenerator::DefineDeoptimizationLiteral(Handle<Object> literal) {
} }
void CodeGenerator::BuildTranslation(Instruction* instr, int CodeGenerator::BuildTranslation(Instruction* instr,
int first_argument_index, int frame_state_offset) {
int deoptimization_id) { InstructionOperandConverter i(this, instr);
int deoptimization_id = i.InputInt32(frame_state_offset);
frame_state_offset++;
// We should build translation only once. // We should build translation only once.
DCHECK_EQ(NULL, deoptimization_states_[deoptimization_id]); DCHECK_EQ(NULL, deoptimization_states_[deoptimization_id]);
@ -325,11 +326,13 @@ void CodeGenerator::BuildTranslation(Instruction* instr,
for (int i = 0; i < descriptor->size(); i++) { for (int i = 0; i < descriptor->size(); i++) {
AddTranslationForOperand(&translation, instr, AddTranslationForOperand(&translation, instr,
instr->InputAt(i + first_argument_index)); instr->InputAt(i + frame_state_offset));
} }
deoptimization_states_[deoptimization_id] = deoptimization_states_[deoptimization_id] =
new (zone()) DeoptimizationState(translation.index()); new (zone()) DeoptimizationState(translation.index());
return deoptimization_id;
} }

View File

@ -87,8 +87,7 @@ class CodeGenerator V8_FINAL : public GapResolver::Assembler {
Safepoint::Id safepoint_id); Safepoint::Id safepoint_id);
void PopulateDeoptimizationData(Handle<Code> code); void PopulateDeoptimizationData(Handle<Code> code);
int DefineDeoptimizationLiteral(Handle<Object> literal); int DefineDeoptimizationLiteral(Handle<Object> literal);
void BuildTranslation(Instruction* instr, int first_argument_index, int BuildTranslation(Instruction* instr, int frame_state_offset);
int deoptimization_id);
void AddTranslationForOperand(Translation* translation, Instruction* instr, void AddTranslationForOperand(Translation* translation, Instruction* instr,
InstructionOperand* op); InstructionOperand* op);
void AddNopForSmiCodeInlining(); void AddNopForSmiCodeInlining();

View File

@ -141,8 +141,7 @@ void CodeGenerator::AssembleArchInstruction(Instruction* instr) {
break; break;
} }
case kArchDeoptimize: { case kArchDeoptimize: {
int deoptimization_id = MiscField::decode(instr->opcode()); int deoptimization_id = BuildTranslation(instr, 0);
BuildTranslation(instr, 0, deoptimization_id);
Address deopt_entry = Deoptimizer::GetDeoptimizationEntry( Address deopt_entry = Deoptimizer::GetDeoptimizationEntry(
isolate(), deoptimization_id, Deoptimizer::LAZY); isolate(), deoptimization_id, Deoptimizer::LAZY);

View File

@ -1082,17 +1082,19 @@ void InstructionSelector::VisitDeoptimize(Node* deopt) {
DCHECK(deopt->op()->opcode() == IrOpcode::kDeoptimize); DCHECK(deopt->op()->opcode() == IrOpcode::kDeoptimize);
Node* state = deopt->InputAt(0); Node* state = deopt->InputAt(0);
FrameStateDescriptor* descriptor = GetFrameStateDescriptor(state); FrameStateDescriptor* descriptor = GetFrameStateDescriptor(state);
int deoptimization_id = sequence()->AddDeoptimizationEntry(descriptor);
InstructionOperandVector inputs(zone()); InstructionOperandVector inputs(zone());
inputs.reserve(descriptor->size()); inputs.reserve(descriptor->size() + 1);
OperandGenerator g(this);
inputs.push_back(g.TempImmediate(deoptimization_id));
AddFrameStateInputs(state, &inputs, descriptor); AddFrameStateInputs(state, &inputs, descriptor);
DCHECK_EQ(descriptor->size(), inputs.size()); DCHECK_EQ(descriptor->size() + 1, inputs.size());
int deoptimization_id = sequence()->AddDeoptimizationEntry(descriptor); Emit(kArchDeoptimize, 0, NULL, inputs.size(), &inputs.front(), 0, NULL);
Emit(kArchDeoptimize | MiscField::encode(deoptimization_id), 0, NULL,
inputs.size(), &inputs.front(), 0, NULL);
} }

View File

@ -83,6 +83,31 @@ void RawMachineAssembler::Deoptimize(Node* state) {
} }
Node* RawMachineAssembler::CallFunctionStub0(Node* function, Node* receiver,
Node* context, Node* frame_state,
Label* continuation,
Label* deoptimization,
CallFunctionFlags flags) {
CallFunctionStub stub(isolate(), 0, flags);
CodeStubInterfaceDescriptor* d = isolate()->code_stub_interface_descriptor(
reinterpret_cast<CodeStub*>(&stub)->MajorKey());
stub.InitializeInterfaceDescriptor(d);
CallDescriptor* desc = Linkage::GetStubCallDescriptor(
d, 1, static_cast<CallDescriptor::DeoptimizationSupport>(
CallDescriptor::kLazyDeoptimization |
CallDescriptor::kNeedsFrameState),
zone());
Node* stub_code = HeapConstant(stub.GetCode());
Node* call = graph()->NewNode(common()->Call(desc), stub_code, function,
receiver, context, frame_state);
schedule()->AddCall(CurrentBlock(), call, Use(continuation),
Use(deoptimization));
current_block_ = NULL;
return call;
}
Node* RawMachineAssembler::CallJS0(Node* function, Node* receiver, Node* RawMachineAssembler::CallJS0(Node* function, Node* receiver,
Label* continuation, Label* deoptimization) { Label* continuation, Label* deoptimization) {
CallDescriptor* descriptor = Linkage::GetJSCallDescriptor(1, zone()); CallDescriptor* descriptor = Linkage::GetJSCallDescriptor(1, zone());

View File

@ -71,6 +71,10 @@ class RawMachineAssembler : public GraphBuilder,
Label* Exit(); Label* Exit();
void Goto(Label* label); void Goto(Label* label);
void Branch(Node* condition, Label* true_val, Label* false_val); void Branch(Node* condition, Label* true_val, Label* false_val);
// Call through CallFunctionStub with lazy deopt and frame-state.
Node* CallFunctionStub0(Node* function, Node* receiver, Node* context,
Node* frame_state, Label* continuation,
Label* deoptimization, CallFunctionFlags flags);
// Call to a JS function with zero parameters. // Call to a JS function with zero parameters.
Node* CallJS0(Node* function, Node* receiver, Label* continuation, Node* CallJS0(Node* function, Node* receiver, Label* continuation,
Label* deoptimization); Label* deoptimization);

View File

@ -235,8 +235,8 @@ void CodeGenerator::AssembleArchInstruction(Instruction* instr) {
break; break;
} }
case kArchDeoptimize: { case kArchDeoptimize: {
int deoptimization_id = MiscField::decode(instr->opcode()); int deoptimization_id = BuildTranslation(instr, 0);
BuildTranslation(instr, 0, deoptimization_id);
Address deopt_entry = Deoptimizer::GetDeoptimizationEntry( Address deopt_entry = Deoptimizer::GetDeoptimizationEntry(
isolate(), deoptimization_id, Deoptimizer::LAZY); isolate(), deoptimization_id, Deoptimizer::LAZY);
__ call(deopt_entry, RelocInfo::RUNTIME_ENTRY); __ call(deopt_entry, RelocInfo::RUNTIME_ENTRY);

View File

@ -210,7 +210,6 @@ TEST(TurboTrivialDeoptCodegen) {
// Check that we deoptimize to the right AST id. // Check that we deoptimize to the right AST id.
CHECK_EQ(1, data->DeoptCount()); CHECK_EQ(1, data->DeoptCount());
CHECK_EQ(1, data->DeoptCount());
CHECK_EQ(t.bailout_id.ToInt(), data->AstId(0).ToInt()); CHECK_EQ(t.bailout_id.ToInt(), data->AstId(0).ToInt());
} }

View File

@ -1895,7 +1895,6 @@ TEST_F(InstructionSelectorTest, Word32AndWithWord32ShrWithImmediateForARMv7) {
} }
} }
} }
} // namespace compiler } // namespace compiler
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8

View File

@ -57,6 +57,9 @@ InstructionSelectorTest::Stream InstructionSelectorTest::StreamBuilder::Build(
continue; continue;
} }
} }
if (mode == kAllExceptNopInstructions && instr->arch_opcode() == kArchNop) {
continue;
}
for (size_t i = 0; i < instr->OutputCount(); ++i) { for (size_t i = 0; i < instr->OutputCount(); ++i) {
InstructionOperand* output = instr->OutputAt(i); InstructionOperand* output = instr->OutputAt(i);
EXPECT_NE(InstructionOperand::IMMEDIATE, output->kind()); EXPECT_NE(InstructionOperand::IMMEDIATE, output->kind());
@ -94,6 +97,9 @@ InstructionSelectorTest::Stream InstructionSelectorTest::StreamBuilder::Build(
s.references_.insert(virtual_register); s.references_.insert(virtual_register);
} }
} }
for (int i = 0; i < sequence.GetDeoptimizationEntryCount(); i++) {
s.deoptimization_entries_.push_back(sequence.GetDeoptimizationEntry(i));
}
return s; return s;
} }
@ -307,6 +313,159 @@ TARGET_TEST_F(InstructionSelectorTest, ValueEffect) {
} }
} }
// -----------------------------------------------------------------------------
// Calls with deoptimization.
TEST_F(InstructionSelectorTest, CallJSFunctionWithDeopt) {
StreamBuilder m(this, kMachAnyTagged, kMachAnyTagged, kMachAnyTagged);
BailoutId bailout_id(42);
Node* function_node = m.Parameter(0);
Node* receiver = m.Parameter(1);
StreamBuilder::Label deopt, cont;
// TODO(jarin) Add frame state.
Node* call = m.CallJS0(function_node, receiver, &cont, &deopt);
m.Bind(&cont);
m.NewNode(m.common()->Continuation(), call);
m.Return(call);
m.Bind(&deopt);
m.NewNode(m.common()->LazyDeoptimization(), call);
Node* parameters = m.NewNode(m.common()->StateValues(1), m.Int32Constant(1));
Node* locals = m.NewNode(m.common()->StateValues(0));
Node* stack = m.NewNode(m.common()->StateValues(0));
Node* state_node =
m.NewNode(m.common()->FrameState(bailout_id), parameters, locals, stack);
m.Deoptimize(state_node);
Stream s = m.Build(kAllExceptNopInstructions);
// Skip until kArchCallJSFunction.
size_t index = 0;
for (; index < s.size() && s[index]->arch_opcode() != kArchCallJSFunction;
index++) {
}
// Now we should have three instructions: call, return and deoptimize.
ASSERT_EQ(index + 3, s.size());
EXPECT_EQ(kArchCallJSFunction, s[index++]->arch_opcode());
EXPECT_EQ(kArchRet, s[index++]->arch_opcode());
EXPECT_EQ(kArchDeoptimize, s[index++]->arch_opcode());
EXPECT_EQ(index, s.size());
}
TEST_F(InstructionSelectorTest, CallFunctionStubWithDeopt) {
StreamBuilder m(this, kMachAnyTagged, kMachAnyTagged, kMachAnyTagged,
kMachAnyTagged);
BailoutId bailout_id_before(42);
BailoutId bailout_id_after(54);
// Some arguments for the call node.
Node* function_node = m.Parameter(0);
Node* receiver = m.Parameter(1);
Node* context = m.Int32Constant(1); // Context is ignored.
// Build frame state for the state before the call.
Node* parameters = m.NewNode(m.common()->StateValues(1), m.Int32Constant(43));
Node* locals = m.NewNode(m.common()->StateValues(1), m.Int32Constant(44));
Node* stack = m.NewNode(m.common()->StateValues(1), m.Int32Constant(45));
Node* frame_state_before = m.NewNode(
m.common()->FrameState(bailout_id_before), parameters, locals, stack);
StreamBuilder::Label deopt, cont;
// Build the call.
Node* call =
m.CallFunctionStub0(function_node, receiver, context, frame_state_before,
&cont, &deopt, CALL_AS_METHOD);
// Create the continuation branch.
m.Bind(&cont);
m.NewNode(m.common()->Continuation(), call);
m.Return(call);
// Create the lazy deoptimization block (with a different frame state).
m.Bind(&deopt);
m.NewNode(m.common()->LazyDeoptimization(), call);
Node* stack_after =
m.NewNode(m.common()->StateValues(2), m.Int32Constant(55), call);
Node* frame_state_after = m.NewNode(m.common()->FrameState(bailout_id_after),
parameters, locals, stack_after);
m.Deoptimize(frame_state_after);
Stream s = m.Build(kAllExceptNopInstructions);
// Skip until kArchCallJSFunction.
size_t index = 0;
for (; index < s.size() && s[index]->arch_opcode() != kArchCallCodeObject;
index++) {
}
// Now we should have three instructions: call, return and deoptimize.
ASSERT_EQ(index + 3, s.size());
// Check the call instruction
const Instruction* call_instr = s[index++];
EXPECT_EQ(kArchCallCodeObject, call_instr->arch_opcode());
size_t num_operands =
1 + // Code object.
1 +
3 + // Frame state deopt id + one input for each value in frame state.
1 + // Function.
1 + // Context.
2; // Continuation and deoptimization block labels.
ASSERT_EQ(num_operands, call_instr->InputCount());
// Code object.
EXPECT_TRUE(call_instr->InputAt(0)->IsImmediate());
// Deoptimization id.
int32_t deopt_id_before = s.ToInt32(call_instr->InputAt(1));
FrameStateDescriptor* desc_before = s.GetDeoptimizationEntry(deopt_id_before);
EXPECT_EQ(bailout_id_before, desc_before->bailout_id());
EXPECT_EQ(1, desc_before->parameters_count());
EXPECT_EQ(1, desc_before->locals_count());
EXPECT_EQ(1, desc_before->stack_count());
EXPECT_EQ(43, s.ToInt32(call_instr->InputAt(2)));
EXPECT_EQ(44, s.ToInt32(call_instr->InputAt(3)));
EXPECT_EQ(45, s.ToInt32(call_instr->InputAt(4)));
// Function.
EXPECT_EQ(function_node->id(), s.ToVreg(call_instr->InputAt(5)));
// Context.
EXPECT_EQ(context->id(), s.ToVreg(call_instr->InputAt(6)));
// Continuation.
EXPECT_EQ(cont.block()->id(), s.ToInt32(call_instr->InputAt(7)));
// Deoptimization.
EXPECT_EQ(deopt.block()->id(), s.ToInt32(call_instr->InputAt(8)));
EXPECT_EQ(kArchRet, s[index++]->arch_opcode());
// Check the deoptimize instruction.
const Instruction* deopt_instr = s[index++];
EXPECT_EQ(kArchDeoptimize, deopt_instr->arch_opcode());
ASSERT_EQ(5U, deopt_instr->InputCount());
int32_t deopt_id_after = s.ToInt32(deopt_instr->InputAt(0));
FrameStateDescriptor* desc_after = s.GetDeoptimizationEntry(deopt_id_after);
EXPECT_EQ(bailout_id_after, desc_after->bailout_id());
EXPECT_EQ(1, desc_after->parameters_count());
EXPECT_EQ(1, desc_after->locals_count());
EXPECT_EQ(2, desc_after->stack_count());
// Parameter value from the frame state.
EXPECT_EQ(43, s.ToInt32(deopt_instr->InputAt(1)));
EXPECT_EQ(44, s.ToInt32(deopt_instr->InputAt(2)));
EXPECT_EQ(55, s.ToInt32(deopt_instr->InputAt(3)));
EXPECT_EQ(call->id(), s.ToVreg(deopt_instr->InputAt(4)));
EXPECT_EQ(index, s.size());
}
} // namespace compiler } // namespace compiler
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8

View File

@ -26,7 +26,11 @@ class InstructionSelectorTest : public CompilerTest {
class Stream; class Stream;
enum StreamBuilderMode { kAllInstructions, kTargetInstructions }; enum StreamBuilderMode {
kAllInstructions,
kTargetInstructions,
kAllExceptNopInstructions
};
class StreamBuilder V8_FINAL : public RawMachineAssembler { class StreamBuilder V8_FINAL : public RawMachineAssembler {
public: public:
@ -146,6 +150,15 @@ class InstructionSelectorTest : public CompilerTest {
return UnallocatedOperand::cast(operand)->virtual_register(); return UnallocatedOperand::cast(operand)->virtual_register();
} }
FrameStateDescriptor* GetDeoptimizationEntry(int deoptimization_id) {
EXPECT_LT(deoptimization_id, GetDeoptimizationEntryCount());
return deoptimization_entries_[deoptimization_id];
}
int GetDeoptimizationEntryCount() {
return static_cast<int>(deoptimization_entries_.size());
}
private: private:
Constant ToConstant(const InstructionOperand* operand) const { Constant ToConstant(const InstructionOperand* operand) const {
ConstantMap::const_iterator i; ConstantMap::const_iterator i;
@ -170,6 +183,7 @@ class InstructionSelectorTest : public CompilerTest {
std::deque<Instruction*> instructions_; std::deque<Instruction*> instructions_;
std::set<int> doubles_; std::set<int> doubles_;
std::set<int> references_; std::set<int> references_;
std::deque<FrameStateDescriptor*> deoptimization_entries_;
}; };
base::RandomNumberGenerator rng_; base::RandomNumberGenerator rng_;