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:
parent
13d18c0079
commit
826f67bec3
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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]; }
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user