Commit 802a906e authored by zhengxing.li's avatar zhengxing.li Committed by Commit bot

X87: [crankshaft] Support ES6 tail call elimination.

  port 22938040 (r34542)

  original commit message:
  HInvokeFunction and HApplyArguments instructions now support tail calling.

  Inlining of calls at tail position is not supported yet and therefore still disabled.

  The tail-call-megatest was modified so that the usages of "arguments" object do not disable Crankshaft.

BUG=

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

Cr-Commit-Position: refs/heads/master@{#34590}
parent fcaa643d
...@@ -3207,13 +3207,24 @@ void LCodeGen::DoApplyArguments(LApplyArguments* instr) { ...@@ -3207,13 +3207,24 @@ void LCodeGen::DoApplyArguments(LApplyArguments* instr) {
// Invoke the function. // Invoke the function.
__ bind(&invoke); __ bind(&invoke);
InvokeFlag flag = CALL_FUNCTION;
if (instr->hydrogen()->tail_call_mode() == TailCallMode::kAllow) {
// TODO(ishell): drop current frame before pushing arguments to the stack.
flag = JUMP_FUNCTION;
ParameterCount actual(eax);
// It is safe to use ebx, ecx and edx as scratch registers here given that
// 1) we are not going to return to caller function anyway,
// 2) ebx (expected arguments count) and edx (new.target) will be
// initialized below.
PrepareForTailCall(actual, ebx, ecx, edx);
}
DCHECK(instr->HasPointerMap()); DCHECK(instr->HasPointerMap());
LPointerMap* pointers = instr->pointer_map(); LPointerMap* pointers = instr->pointer_map();
SafepointGenerator safepoint_generator( SafepointGenerator safepoint_generator(this, pointers, Safepoint::kLazyDeopt);
this, pointers, Safepoint::kLazyDeopt);
ParameterCount actual(eax); ParameterCount actual(eax);
__ InvokeFunction(function, no_reg, actual, CALL_FUNCTION, __ InvokeFunction(function, no_reg, actual, flag, safepoint_generator);
safepoint_generator);
} }
...@@ -3257,10 +3268,9 @@ void LCodeGen::DoDeclareGlobals(LDeclareGlobals* instr) { ...@@ -3257,10 +3268,9 @@ void LCodeGen::DoDeclareGlobals(LDeclareGlobals* instr) {
CallRuntime(Runtime::kDeclareGlobals, instr); CallRuntime(Runtime::kDeclareGlobals, instr);
} }
void LCodeGen::CallKnownFunction(Handle<JSFunction> function, void LCodeGen::CallKnownFunction(Handle<JSFunction> function,
int formal_parameter_count, int arity, int formal_parameter_count, int arity,
LInstruction* instr) { bool is_tail_call, LInstruction* instr) {
bool dont_adapt_arguments = bool dont_adapt_arguments =
formal_parameter_count == SharedFunctionInfo::kDontAdaptArgumentsSentinel; formal_parameter_count == SharedFunctionInfo::kDontAdaptArgumentsSentinel;
bool can_invoke_directly = bool can_invoke_directly =
...@@ -3276,21 +3286,38 @@ void LCodeGen::CallKnownFunction(Handle<JSFunction> function, ...@@ -3276,21 +3286,38 @@ void LCodeGen::CallKnownFunction(Handle<JSFunction> function,
__ mov(edx, factory()->undefined_value()); __ mov(edx, factory()->undefined_value());
__ mov(eax, arity); __ mov(eax, arity);
bool is_self_call = function.is_identical_to(info()->closure());
// Invoke function directly. // Invoke function directly.
if (function.is_identical_to(info()->closure())) { if (is_self_call) {
__ CallSelf(); Handle<Code> self(reinterpret_cast<Code**>(__ CodeObject().location()));
if (is_tail_call) {
__ Jump(self, RelocInfo::CODE_TARGET);
} else {
__ Call(self, RelocInfo::CODE_TARGET);
}
} else { } else {
__ call(FieldOperand(function_reg, JSFunction::kCodeEntryOffset)); Operand target = FieldOperand(function_reg, JSFunction::kCodeEntryOffset);
if (is_tail_call) {
__ jmp(target);
} else {
__ call(target);
}
}
if (!is_tail_call) {
// Set up deoptimization.
RecordSafepointWithLazyDeopt(instr, RECORD_SIMPLE_SAFEPOINT);
} }
RecordSafepointWithLazyDeopt(instr, RECORD_SIMPLE_SAFEPOINT);
} else { } else {
// We need to adapt arguments. // We need to adapt arguments.
LPointerMap* pointers = instr->pointer_map(); LPointerMap* pointers = instr->pointer_map();
SafepointGenerator generator( SafepointGenerator generator(
this, pointers, Safepoint::kLazyDeopt); this, pointers, Safepoint::kLazyDeopt);
ParameterCount count(arity); ParameterCount actual(arity);
ParameterCount expected(formal_parameter_count); ParameterCount expected(formal_parameter_count);
__ InvokeFunction(function_reg, expected, count, CALL_FUNCTION, generator); InvokeFlag flag = is_tail_call ? JUMP_FUNCTION : CALL_FUNCTION;
__ InvokeFunction(function_reg, expected, actual, flag, generator);
} }
} }
...@@ -3764,23 +3791,77 @@ void LCodeGen::DoMathExp(LMathExp* instr) { ...@@ -3764,23 +3791,77 @@ void LCodeGen::DoMathExp(LMathExp* instr) {
X87CommitWrite(result_reg); X87CommitWrite(result_reg);
} }
void LCodeGen::PrepareForTailCall(const ParameterCount& actual,
Register scratch1, Register scratch2,
Register scratch3) {
#if DEBUG
if (actual.is_reg()) {
DCHECK(!AreAliased(actual.reg(), scratch1, scratch2, scratch3));
} else {
DCHECK(!AreAliased(scratch1, scratch2, scratch3));
}
#endif
if (FLAG_code_comments) {
if (actual.is_reg()) {
Comment(";;; PrepareForTailCall, actual: %s {", actual.reg().ToString());
} else {
Comment(";;; PrepareForTailCall, actual: %d {", actual.immediate());
}
}
// Check if next frame is an arguments adaptor frame.
Register caller_args_count_reg = scratch1;
Label no_arguments_adaptor, formal_parameter_count_loaded;
__ mov(scratch2, Operand(ebp, StandardFrameConstants::kCallerFPOffset));
__ cmp(Operand(scratch2, StandardFrameConstants::kContextOffset),
Immediate(Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR)));
__ j(not_equal, &no_arguments_adaptor, Label::kNear);
// Drop current frame and load arguments count from arguments adaptor frame.
__ mov(ebp, scratch2);
__ mov(caller_args_count_reg,
Operand(ebp, ArgumentsAdaptorFrameConstants::kLengthOffset));
__ SmiUntag(caller_args_count_reg);
__ jmp(&formal_parameter_count_loaded, Label::kNear);
__ bind(&no_arguments_adaptor);
// Load caller's formal parameter count.
__ mov(caller_args_count_reg,
Immediate(info()->literal()->parameter_count()));
__ bind(&formal_parameter_count_loaded);
__ PrepareForTailCall(actual, caller_args_count_reg, scratch2, scratch3,
ReturnAddressState::kNotOnStack);
Comment(";;; }");
}
void LCodeGen::DoInvokeFunction(LInvokeFunction* instr) { void LCodeGen::DoInvokeFunction(LInvokeFunction* instr) {
HInvokeFunction* hinstr = instr->hydrogen();
DCHECK(ToRegister(instr->context()).is(esi)); DCHECK(ToRegister(instr->context()).is(esi));
DCHECK(ToRegister(instr->function()).is(edi)); DCHECK(ToRegister(instr->function()).is(edi));
DCHECK(instr->HasPointerMap()); DCHECK(instr->HasPointerMap());
Handle<JSFunction> known_function = instr->hydrogen()->known_function(); bool is_tail_call = hinstr->tail_call_mode() == TailCallMode::kAllow;
if (is_tail_call) {
ParameterCount actual(instr->arity());
// It is safe to use ebx, ecx and edx as scratch registers here given that
// 1) we are not going to return to caller function anyway,
// 2) ebx (expected arguments count) and edx (new.target) will be
// initialized below.
PrepareForTailCall(actual, ebx, ecx, edx);
}
Handle<JSFunction> known_function = hinstr->known_function();
if (known_function.is_null()) { if (known_function.is_null()) {
LPointerMap* pointers = instr->pointer_map(); LPointerMap* pointers = instr->pointer_map();
SafepointGenerator generator( SafepointGenerator generator(this, pointers, Safepoint::kLazyDeopt);
this, pointers, Safepoint::kLazyDeopt); ParameterCount actual(instr->arity());
ParameterCount count(instr->arity()); InvokeFlag flag = is_tail_call ? JUMP_FUNCTION : CALL_FUNCTION;
__ InvokeFunction(edi, no_reg, count, CALL_FUNCTION, generator); __ InvokeFunction(edi, no_reg, actual, flag, generator);
} else { } else {
CallKnownFunction(known_function, CallKnownFunction(known_function, hinstr->formal_parameter_count(),
instr->hydrogen()->formal_parameter_count(), instr->arity(), is_tail_call, instr);
instr->arity(), instr);
} }
} }
......
...@@ -219,11 +219,14 @@ class LCodeGen: public LCodeGenBase { ...@@ -219,11 +219,14 @@ class LCodeGen: public LCodeGenBase {
void LoadContextFromDeferred(LOperand* context); void LoadContextFromDeferred(LOperand* context);
// Generate a direct call to a known function. Expects the function void PrepareForTailCall(const ParameterCount& actual, Register scratch1,
Register scratch2, Register scratch3);
// Generate a direct call to a known function. Expects the function
// to be in edi. // to be in edi.
void CallKnownFunction(Handle<JSFunction> function, void CallKnownFunction(Handle<JSFunction> function,
int formal_parameter_count, int arity, int formal_parameter_count, int arity,
LInstruction* instr); bool is_tail_call, LInstruction* instr);
void RecordSafepointWithLazyDeopt(LInstruction* instr, void RecordSafepointWithLazyDeopt(LInstruction* instr,
SafepointMode safepoint_mode); SafepointMode safepoint_mode);
......
...@@ -553,6 +553,7 @@ class LApplyArguments final : public LTemplateInstruction<1, 4, 0> { ...@@ -553,6 +553,7 @@ class LApplyArguments final : public LTemplateInstruction<1, 4, 0> {
LOperand* elements() { return inputs_[3]; } LOperand* elements() { return inputs_[3]; }
DECLARE_CONCRETE_INSTRUCTION(ApplyArguments, "apply-arguments") DECLARE_CONCRETE_INSTRUCTION(ApplyArguments, "apply-arguments")
DECLARE_HYDROGEN_ACCESSOR(ApplyArguments)
}; };
......
...@@ -1907,16 +1907,18 @@ void PrepareForTailCall(MacroAssembler* masm, Register args_reg, ...@@ -1907,16 +1907,18 @@ void PrepareForTailCall(MacroAssembler* masm, Register args_reg,
} }
// Check if next frame is an arguments adaptor frame. // Check if next frame is an arguments adaptor frame.
Register caller_args_count_reg = scratch1;
Label no_arguments_adaptor, formal_parameter_count_loaded; Label no_arguments_adaptor, formal_parameter_count_loaded;
__ mov(scratch2, Operand(ebp, StandardFrameConstants::kCallerFPOffset)); __ mov(scratch2, Operand(ebp, StandardFrameConstants::kCallerFPOffset));
__ cmp(Operand(scratch2, StandardFrameConstants::kContextOffset), __ cmp(Operand(scratch2, StandardFrameConstants::kContextOffset),
Immediate(Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR))); Immediate(Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR)));
__ j(not_equal, &no_arguments_adaptor, Label::kNear); __ j(not_equal, &no_arguments_adaptor, Label::kNear);
// Drop arguments adaptor frame and load arguments count. // Drop current frame and load arguments count from arguments adaptor frame.
__ mov(ebp, scratch2); __ mov(ebp, scratch2);
__ mov(scratch1, Operand(ebp, ArgumentsAdaptorFrameConstants::kLengthOffset)); __ mov(caller_args_count_reg,
__ SmiUntag(scratch1); Operand(ebp, ArgumentsAdaptorFrameConstants::kLengthOffset));
__ SmiUntag(caller_args_count_reg);
__ jmp(&formal_parameter_count_loaded, Label::kNear); __ jmp(&formal_parameter_count_loaded, Label::kNear);
__ bind(&no_arguments_adaptor); __ bind(&no_arguments_adaptor);
...@@ -1925,57 +1927,15 @@ void PrepareForTailCall(MacroAssembler* masm, Register args_reg, ...@@ -1925,57 +1927,15 @@ void PrepareForTailCall(MacroAssembler* masm, Register args_reg,
__ mov(scratch1, __ mov(scratch1,
FieldOperand(scratch1, JSFunction::kSharedFunctionInfoOffset)); FieldOperand(scratch1, JSFunction::kSharedFunctionInfoOffset));
__ mov( __ mov(
scratch1, caller_args_count_reg,
FieldOperand(scratch1, SharedFunctionInfo::kFormalParameterCountOffset)); FieldOperand(scratch1, SharedFunctionInfo::kFormalParameterCountOffset));
__ SmiUntag(scratch1); __ SmiUntag(caller_args_count_reg);
__ bind(&formal_parameter_count_loaded); __ bind(&formal_parameter_count_loaded);
// Calculate the destination address where we will put the return address ParameterCount callee_args_count(args_reg);
// after we drop current frame. __ PrepareForTailCall(callee_args_count, caller_args_count_reg, scratch2,
Register new_sp_reg = scratch2; scratch3, ReturnAddressState::kOnStack);
__ sub(scratch1, args_reg);
__ lea(new_sp_reg, Operand(ebp, scratch1, times_pointer_size,
StandardFrameConstants::kCallerPCOffset));
if (FLAG_debug_code) {
__ cmp(esp, new_sp_reg);
__ Check(below, kStackAccessBelowStackPointer);
}
// Copy receiver and return address as well.
Register count_reg = scratch1;
__ lea(count_reg, Operand(args_reg, 2));
// Copy return address from caller's frame to current frame's return address
// to avoid its trashing and let the following loop copy it to the right
// place.
Register tmp_reg = scratch3;
__ mov(tmp_reg, Operand(ebp, StandardFrameConstants::kCallerPCOffset));
__ mov(Operand(esp, 0), tmp_reg);
// Restore caller's frame pointer now as it could be overwritten by
// the copying loop.
__ mov(ebp, Operand(ebp, StandardFrameConstants::kCallerFPOffset));
Operand src(esp, count_reg, times_pointer_size, 0);
Operand dst(new_sp_reg, count_reg, times_pointer_size, 0);
// Now copy callee arguments to the caller frame going backwards to avoid
// callee arguments corruption (source and destination areas could overlap).
Label loop, entry;
__ jmp(&entry, Label::kNear);
__ bind(&loop);
__ dec(count_reg);
__ mov(tmp_reg, src);
__ mov(dst, tmp_reg);
__ bind(&entry);
__ cmp(count_reg, Immediate(0));
__ j(not_equal, &loop, Label::kNear);
// Leave current frame.
__ mov(esp, new_sp_reg);
__ bind(&done); __ bind(&done);
} }
} // namespace } // namespace
......
...@@ -2024,6 +2024,77 @@ void MacroAssembler::JumpToExternalReference(const ExternalReference& ext) { ...@@ -2024,6 +2024,77 @@ void MacroAssembler::JumpToExternalReference(const ExternalReference& ext) {
jmp(ces.GetCode(), RelocInfo::CODE_TARGET); jmp(ces.GetCode(), RelocInfo::CODE_TARGET);
} }
void MacroAssembler::PrepareForTailCall(const ParameterCount& callee_args_count,
Register caller_args_count_reg,
Register scratch0, Register scratch1,
ReturnAddressState ra_state) {
#if DEBUG
if (callee_args_count.is_reg()) {
DCHECK(!AreAliased(callee_args_count.reg(), caller_args_count_reg, scratch0,
scratch1));
} else {
DCHECK(!AreAliased(caller_args_count_reg, scratch0, scratch1));
}
#endif
// Calculate the destination address where we will put the return address
// after we drop current frame.
Register new_sp_reg = scratch0;
if (callee_args_count.is_reg()) {
sub(caller_args_count_reg, callee_args_count.reg());
lea(new_sp_reg, Operand(ebp, caller_args_count_reg, times_pointer_size,
StandardFrameConstants::kCallerPCOffset));
} else {
lea(new_sp_reg, Operand(ebp, caller_args_count_reg, times_pointer_size,
StandardFrameConstants::kCallerPCOffset -
callee_args_count.immediate() * kPointerSize));
}
if (FLAG_debug_code) {
cmp(esp, new_sp_reg);
Check(below, kStackAccessBelowStackPointer);
}
// Copy return address from caller's frame to current frame's return address
// to avoid its trashing and let the following loop copy it to the right
// place.
Register tmp_reg = scratch1;
if (ra_state == ReturnAddressState::kOnStack) {
mov(tmp_reg, Operand(ebp, StandardFrameConstants::kCallerPCOffset));
mov(Operand(esp, 0), tmp_reg);
} else {
DCHECK(ReturnAddressState::kNotOnStack == ra_state);
Push(Operand(ebp, StandardFrameConstants::kCallerPCOffset));
}
// Restore caller's frame pointer now as it could be overwritten by
// the copying loop.
mov(ebp, Operand(ebp, StandardFrameConstants::kCallerFPOffset));
// +2 here is to copy both receiver and return address.
Register count_reg = caller_args_count_reg;
if (callee_args_count.is_reg()) {
lea(count_reg, Operand(callee_args_count.reg(), 2));
} else {
mov(count_reg, Immediate(callee_args_count.immediate() + 2));
// TODO(ishell): Unroll copying loop for small immediate values.
}
// Now copy callee arguments to the caller frame going backwards to avoid
// callee arguments corruption (source and destination areas could overlap).
Label loop, entry;
jmp(&entry, Label::kNear);
bind(&loop);
dec(count_reg);
mov(tmp_reg, Operand(esp, count_reg, times_pointer_size, 0));
mov(Operand(new_sp_reg, count_reg, times_pointer_size, 0), tmp_reg);
bind(&entry);
cmp(count_reg, Immediate(0));
j(not_equal, &loop, Label::kNear);
// Leave current frame.
mov(esp, new_sp_reg);
}
void MacroAssembler::InvokePrologue(const ParameterCount& expected, void MacroAssembler::InvokePrologue(const ParameterCount& expected,
const ParameterCount& actual, const ParameterCount& actual,
......
...@@ -44,6 +44,8 @@ enum PointersToHereCheck { ...@@ -44,6 +44,8 @@ enum PointersToHereCheck {
enum RegisterValueType { REGISTER_VALUE_IS_SMI, REGISTER_VALUE_IS_INT32 }; enum RegisterValueType { REGISTER_VALUE_IS_SMI, REGISTER_VALUE_IS_INT32 };
enum class ReturnAddressState { kOnStack, kNotOnStack };
#ifdef DEBUG #ifdef DEBUG
bool AreAliased(Register reg1, Register reg2, Register reg3 = no_reg, bool AreAliased(Register reg1, Register reg2, Register reg3 = no_reg,
Register reg4 = no_reg, Register reg5 = no_reg, Register reg4 = no_reg, Register reg5 = no_reg,
...@@ -318,6 +320,16 @@ class MacroAssembler: public Assembler { ...@@ -318,6 +320,16 @@ class MacroAssembler: public Assembler {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// JavaScript invokes // JavaScript invokes
// Removes current frame and its arguments from the stack preserving
// the arguments and a return address pushed to the stack for the next call.
// |ra_state| defines whether return address is already pushed to stack or
// not. Both |callee_args_count| and |caller_args_count_reg| do not include
// receiver. |callee_args_count| is not modified, |caller_args_count_reg|
// is trashed.
void PrepareForTailCall(const ParameterCount& callee_args_count,
Register caller_args_count_reg, Register scratch0,
Register scratch1, ReturnAddressState ra_state);
// Invoke the JavaScript function code by either calling or jumping. // Invoke the JavaScript function code by either calling or jumping.
void InvokeFunctionCode(Register function, Register new_target, void InvokeFunctionCode(Register function, Register new_target,
...@@ -757,12 +769,6 @@ class MacroAssembler: public Assembler { ...@@ -757,12 +769,6 @@ class MacroAssembler: public Assembler {
void Popcnt(Register dst, Register src) { Popcnt(dst, Operand(src)); } void Popcnt(Register dst, Register src) { Popcnt(dst, Operand(src)); }
void Popcnt(Register dst, const Operand& src); void Popcnt(Register dst, const Operand& src);
// Emit call to the code we are currently generating.
void CallSelf() {
Handle<Code> self(reinterpret_cast<Code**>(CodeObject().location()));
call(self, RelocInfo::CODE_TARGET);
}
// Move if the registers are not identical. // Move if the registers are not identical.
void Move(Register target, Register source); void Move(Register target, Register source);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment