PPC: [crankshaft] Support ES6 tail call elimination.

Port 22938040fd

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.

R=ishell@chromium.org, joransiu@ca.ibm.com, jyan@ca.ibm.com, michael_dawson@ca.ibm.com
BUG=v8:4698
LOG=N

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

Cr-Commit-Position: refs/heads/master@{#34563}
This commit is contained in:
mbrandy 2016-03-07 12:55:03 -08:00 committed by Commit bot
parent 13d18c0079
commit 826f67bec3
6 changed files with 178 additions and 70 deletions

View File

@ -3348,14 +3348,25 @@ void LCodeGen::DoApplyArguments(LApplyArguments* instr) {
__ bdnz(&loop);
__ 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(r3);
// It is safe to use r6, r7 and r8 as scratch registers here given that
// 1) we are not going to return to caller function anyway,
// 2) r6 (new.target) will be initialized below.
PrepareForTailCall(actual, r6, r7, r8);
}
DCHECK(instr->HasPointerMap());
LPointerMap* pointers = instr->pointer_map();
SafepointGenerator safepoint_generator(this, pointers, Safepoint::kLazyDeopt);
// The number of arguments is stored in receiver which is r3, as expected
// by InvokeFunction.
ParameterCount actual(receiver);
__ InvokeFunction(function, no_reg, actual, CALL_FUNCTION,
safepoint_generator);
__ InvokeFunction(function, no_reg, actual, flag, safepoint_generator);
}
@ -3400,10 +3411,9 @@ void LCodeGen::DoDeclareGlobals(LDeclareGlobals* instr) {
CallRuntime(Runtime::kDeclareGlobals, instr);
}
void LCodeGen::CallKnownFunction(Handle<JSFunction> function,
int formal_parameter_count, int arity,
LInstruction* instr) {
bool is_tail_call, LInstruction* instr) {
bool dont_adapt_arguments =
formal_parameter_count == SharedFunctionInfo::kDontAdaptArgumentsSentinel;
bool can_invoke_directly =
@ -3425,19 +3435,31 @@ void LCodeGen::CallKnownFunction(Handle<JSFunction> function,
// Invoke function.
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 {
__ LoadP(ip, FieldMemOperand(function_reg, JSFunction::kCodeEntryOffset));
__ CallJSEntry(ip);
if (is_tail_call) {
__ JumpToJSEntry(ip);
} else {
__ CallJSEntry(ip);
}
}
// Set up deoptimization.
RecordSafepointWithLazyDeopt(instr, RECORD_SIMPLE_SAFEPOINT);
if (!is_tail_call) {
// Set up deoptimization.
RecordSafepointWithLazyDeopt(instr, RECORD_SIMPLE_SAFEPOINT);
}
} else {
SafepointGenerator generator(this, pointers, Safepoint::kLazyDeopt);
ParameterCount count(arity);
ParameterCount actual(arity);
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);
}
}
@ -3766,22 +3788,76 @@ void LCodeGen::DoMathClz32(LMathClz32* instr) {
__ cntlzw_(result, input);
}
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;
__ LoadP(scratch2, MemOperand(fp, StandardFrameConstants::kCallerFPOffset));
__ LoadP(scratch3,
MemOperand(scratch2, StandardFrameConstants::kContextOffset));
__ CmpSmiLiteral(scratch3, Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR), r0);
__ bne(&no_arguments_adaptor);
// Drop current frame and load arguments count from arguments adaptor frame.
__ mr(fp, scratch2);
__ LoadP(caller_args_count_reg,
MemOperand(fp, ArgumentsAdaptorFrameConstants::kLengthOffset));
__ SmiUntag(caller_args_count_reg);
__ b(&formal_parameter_count_loaded);
__ bind(&no_arguments_adaptor);
// Load caller's formal parameter count
__ mov(caller_args_count_reg, Operand(info()->literal()->parameter_count()));
__ bind(&formal_parameter_count_loaded);
__ PrepareForTailCall(actual, caller_args_count_reg, scratch2, scratch3);
Comment(";;; }");
}
void LCodeGen::DoInvokeFunction(LInvokeFunction* instr) {
HInvokeFunction* hinstr = instr->hydrogen();
DCHECK(ToRegister(instr->context()).is(cp));
DCHECK(ToRegister(instr->function()).is(r4));
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 r6, r7 and r8 as scratch registers here given that
// 1) we are not going to return to caller function anyway,
// 2) r6 (new.target) will be initialized below.
PrepareForTailCall(actual, r6, r7, r8);
}
Handle<JSFunction> known_function = hinstr->known_function();
if (known_function.is_null()) {
LPointerMap* pointers = instr->pointer_map();
SafepointGenerator generator(this, pointers, Safepoint::kLazyDeopt);
ParameterCount count(instr->arity());
__ InvokeFunction(r4, no_reg, count, CALL_FUNCTION, generator);
ParameterCount actual(instr->arity());
InvokeFlag flag = is_tail_call ? JUMP_FUNCTION : CALL_FUNCTION;
__ InvokeFunction(r4, no_reg, actual, flag, generator);
} else {
CallKnownFunction(known_function,
instr->hydrogen()->formal_parameter_count(),
instr->arity(), instr);
CallKnownFunction(known_function, hinstr->formal_parameter_count(),
instr->arity(), is_tail_call, instr);
}
}

View File

@ -194,11 +194,14 @@ class LCodeGen : public LCodeGenBase {
void CallRuntimeFromDeferred(Runtime::FunctionId id, int argc,
LInstruction* instr, LOperand* context);
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 r4.
void CallKnownFunction(Handle<JSFunction> function,
int formal_parameter_count, int arity,
LInstruction* instr);
bool is_tail_call, LInstruction* instr);
void RecordSafepointWithLazyDeopt(LInstruction* instr,
SafepointMode safepoint_mode);

View File

@ -523,6 +523,7 @@ class LApplyArguments final : public LTemplateInstruction<1, 4, 0> {
}
DECLARE_CONCRETE_INSTRUCTION(ApplyArguments, "apply-arguments")
DECLARE_HYDROGEN_ACCESSOR(ApplyArguments)
LOperand* function() { return inputs_[0]; }
LOperand* receiver() { return inputs_[1]; }

View File

@ -2008,6 +2008,7 @@ void PrepareForTailCall(MacroAssembler* masm, Register args_reg,
}
// Check if next frame is an arguments adaptor frame.
Register caller_args_count_reg = scratch1;
Label no_arguments_adaptor, formal_parameter_count_loaded;
__ LoadP(scratch2, MemOperand(fp, StandardFrameConstants::kCallerFPOffset));
__ LoadP(scratch3,
@ -2015,11 +2016,11 @@ void PrepareForTailCall(MacroAssembler* masm, Register args_reg,
__ CmpSmiLiteral(scratch3, Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR), r0);
__ bne(&no_arguments_adaptor);
// Drop arguments adaptor frame and load arguments count.
// Drop current frame and load arguments count from arguments adaptor frame.
__ mr(fp, scratch2);
__ LoadP(scratch1,
__ LoadP(caller_args_count_reg,
MemOperand(fp, ArgumentsAdaptorFrameConstants::kLengthOffset));
__ SmiUntag(scratch1);
__ SmiUntag(caller_args_count_reg);
__ b(&formal_parameter_count_loaded);
__ bind(&no_arguments_adaptor);
@ -2028,55 +2029,18 @@ void PrepareForTailCall(MacroAssembler* masm, Register args_reg,
__ LoadP(scratch1,
FieldMemOperand(scratch1, JSFunction::kSharedFunctionInfoOffset));
__ LoadWordArith(
scratch1, FieldMemOperand(
scratch1, SharedFunctionInfo::kFormalParameterCountOffset));
caller_args_count_reg,
FieldMemOperand(scratch1,
SharedFunctionInfo::kFormalParameterCountOffset));
#if !V8_TARGET_ARCH_PPC64
__ SmiUntag(scratch1);
__ SmiUntag(caller_args_count_reg);
#endif
__ bind(&formal_parameter_count_loaded);
// Calculate the end of destination area where we will put the arguments
// after we drop current frame. We add kPointerSize to count the receiver
// argument which is not included into formal parameters count.
Register dst_reg = scratch2;
__ ShiftLeftImm(dst_reg, scratch1, Operand(kPointerSizeLog2));
__ add(dst_reg, fp, dst_reg);
__ addi(dst_reg, dst_reg,
Operand(StandardFrameConstants::kCallerSPOffset + kPointerSize));
Register src_reg = scratch1;
__ ShiftLeftImm(src_reg, args_reg, Operand(kPointerSizeLog2));
__ add(src_reg, sp, src_reg);
// Count receiver argument as well (not included in args_reg).
__ addi(src_reg, src_reg, Operand(kPointerSize));
if (FLAG_debug_code) {
__ cmpl(src_reg, dst_reg);
__ Check(lt, kStackAccessBelowStackPointer);
}
// Restore caller's frame pointer and return address now as they will be
// overwritten by the copying loop.
__ RestoreFrameStateForTailCall();
// Now copy callee arguments to the caller frame going backwards to avoid
// callee arguments corruption (source and destination areas could overlap).
// Both src_reg and dst_reg are pointing to the word after the one to copy,
// so they must be pre-decremented in the loop.
Register tmp_reg = scratch3;
Label loop;
__ addi(tmp_reg, args_reg, Operand(1)); // +1 for receiver
__ mtctr(tmp_reg);
__ bind(&loop);
__ LoadPU(tmp_reg, MemOperand(src_reg, -kPointerSize));
__ StorePU(tmp_reg, MemOperand(dst_reg, -kPointerSize));
__ bdnz(&loop);
// Leave current frame.
__ mr(sp, dst_reg);
ParameterCount callee_args_count(args_reg);
__ PrepareForTailCall(callee_args_count, caller_args_count_reg, scratch2,
scratch3);
__ bind(&done);
}
} // namespace

View File

@ -1093,6 +1093,67 @@ void MacroAssembler::MovFromFloatParameter(const DoubleRegister dst) {
Move(dst, d1);
}
void MacroAssembler::PrepareForTailCall(const ParameterCount& callee_args_count,
Register caller_args_count_reg,
Register scratch0, Register scratch1) {
#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 end of destination area where we will put the arguments
// after we drop current frame. We add kPointerSize to count the receiver
// argument which is not included into formal parameters count.
Register dst_reg = scratch0;
ShiftLeftImm(dst_reg, caller_args_count_reg, Operand(kPointerSizeLog2));
add(dst_reg, fp, dst_reg);
addi(dst_reg, dst_reg,
Operand(StandardFrameConstants::kCallerSPOffset + kPointerSize));
Register src_reg = caller_args_count_reg;
// Calculate the end of source area. +kPointerSize is for the receiver.
if (callee_args_count.is_reg()) {
ShiftLeftImm(src_reg, callee_args_count.reg(), Operand(kPointerSizeLog2));
add(src_reg, sp, src_reg);
addi(src_reg, src_reg, Operand(kPointerSize));
} else {
Add(src_reg, sp, (callee_args_count.immediate() + 1) * kPointerSize, r0);
}
if (FLAG_debug_code) {
cmpl(src_reg, dst_reg);
Check(lt, kStackAccessBelowStackPointer);
}
// Restore caller's frame pointer and return address now as they will be
// overwritten by the copying loop.
RestoreFrameStateForTailCall();
// Now copy callee arguments to the caller frame going backwards to avoid
// callee arguments corruption (source and destination areas could overlap).
// Both src_reg and dst_reg are pointing to the word after the one to copy,
// so they must be pre-decremented in the loop.
Register tmp_reg = scratch1;
Label loop;
if (callee_args_count.is_reg()) {
addi(tmp_reg, callee_args_count.reg(), Operand(1)); // +1 for receiver
} else {
mov(tmp_reg, Operand(callee_args_count.immediate() + 1));
}
mtctr(tmp_reg);
bind(&loop);
LoadPU(tmp_reg, MemOperand(src_reg, -kPointerSize));
StorePU(tmp_reg, MemOperand(dst_reg, -kPointerSize));
bdnz(&loop);
// Leave current frame.
mr(sp, dst_reg);
}
void MacroAssembler::InvokePrologue(const ParameterCount& expected,
const ParameterCount& actual, Label* done,

View File

@ -156,12 +156,6 @@ class MacroAssembler : public Assembler {
void Call(Label* target);
// Emit call to the code we are currently generating.
void CallSelf() {
Handle<Code> self(reinterpret_cast<Code**>(CodeObject().location()));
Call(self, RelocInfo::CODE_TARGET);
}
// Register move. May do nothing if the registers are identical.
void Move(Register dst, Smi* smi) { LoadSmiLiteral(dst, smi); }
void Move(Register dst, Handle<Object> value);
@ -564,6 +558,15 @@ class MacroAssembler : public Assembler {
// ---------------------------------------------------------------------------
// 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.
// 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);
// Invoke the JavaScript function code by either calling or jumping.
void InvokeFunctionCode(Register function, Register new_target,
const ParameterCount& expected,