Skip to content

Commit

Permalink
[ignition] Fast construct calls
Browse files Browse the repository at this point in the history
We create a new interpreter builtin that pushes the arguments to
the stack only once.

Bug: v8:14192

Change-Id: I906246e8bb9d0f10dd9b1c703a72a767b20944cd
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4701010
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Commit-Queue: Victor Gomes <victorgomes@chromium.org>
Cr-Commit-Position: refs/heads/main@{#89133}
  • Loading branch information
victorgomes authored and V8 LUCI CQ committed Jul 24, 2023
1 parent 46ef270 commit b2bb565
Show file tree
Hide file tree
Showing 14 changed files with 760 additions and 21 deletions.
157 changes: 157 additions & 0 deletions src/builtins/arm/builtins-arm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1497,6 +1497,163 @@ void Builtins::Generate_InterpreterPushArgsThenConstructImpl(
}
}

namespace {

void NewImplicitReceiver(MacroAssembler* masm) {
// ----------- S t a t e -------------
// -- r0 : argument count
// -- r1 : constructor to call (checked to be a JSFunction)
// -- r3 : new target
//
// Stack:
// -- Implicit Receiver
// -- [arguments without receiver]
// -- Implicit Receiver
// -- Context
// -- FastConstructMarker
// -- FramePointer
// -----------------------------------
Register implicit_receiver = r4;

// Save live registers.
__ SmiTag(r0);
__ Push(r0, r1, r3);
__ Call(BUILTIN_CODE(masm->isolate(), FastNewObject), RelocInfo::CODE_TARGET);
// Save result.
__ Move(implicit_receiver, r0);
// Restore live registers.
__ Pop(r0, r1, r3);
__ SmiUntag(r0);

// Patch implicit receiver (in arguments)
__ str(implicit_receiver, MemOperand(sp, 0 * kPointerSize));
// Patch second implicit (in construct frame)
__ str(implicit_receiver,
MemOperand(fp, FastConstructFrameConstants::kImplicitReceiverOffset));

// Restore context.
__ ldr(cp, MemOperand(fp, FastConstructFrameConstants::kContextOffset));
}

} // namespace

// static
void Builtins::Generate_InterpreterPushArgsThenFastConstructFunction(
MacroAssembler* masm) {
// ----------- S t a t e -------------
// -- r0 : argument count
// -- r1 : constructor to call (checked to be a JSFunction)
// -- r3 : new target
// -- r4 : address of the first argument
// -- cp/r7 : context pointer
// -----------------------------------
__ AssertFunction(r1);

// Check if target has a [[Construct]] internal method.
Label non_constructor;
__ LoadMap(r2, r1);
__ ldrb(r2, FieldMemOperand(r2, Map::kBitFieldOffset));
__ tst(r2, Operand(Map::Bits1::IsConstructorBit::kMask));
__ b(eq, &non_constructor);

// Add a stack check before pushing arguments.
Label stack_overflow;
__ StackOverflowCheck(r0, r2, &stack_overflow);

// Enter a construct frame.
FrameScope scope(masm, StackFrame::MANUAL);
__ EnterFrame(StackFrame::FAST_CONSTRUCT);
// Implicit receiver stored in the construct frame.
__ LoadRoot(r2, RootIndex::kTheHoleValue);
__ Push(cp, r2);

// Push arguments + implicit receiver.
Register argc_without_receiver = r6;
__ sub(argc_without_receiver, r0, Operand(kJSArgcReceiverSlots));
// Push the arguments. r4 and r5 will be modified.
GenerateInterpreterPushArgs(masm, argc_without_receiver, r4, r5);
// Implicit receiver as part of the arguments (patched later if needed).
__ push(r2);

// Check if it is a builtin call.
Label builtin_call;
__ ldr(r2, FieldMemOperand(r1, JSFunction::kSharedFunctionInfoOffset));
__ ldr(r2, FieldMemOperand(r2, SharedFunctionInfo::kFlagsOffset));
__ tst(r2, Operand(SharedFunctionInfo::ConstructAsBuiltinBit::kMask));
__ b(ne, &builtin_call);

// Check if we need to create an implicit receiver.
Label not_create_implicit_receiver;
__ DecodeField<SharedFunctionInfo::FunctionKindBits>(r2);
__ JumpIfIsInRange(
r2, static_cast<uint32_t>(FunctionKind::kDefaultDerivedConstructor),
static_cast<uint32_t>(FunctionKind::kDerivedConstructor),
&not_create_implicit_receiver);
NewImplicitReceiver(masm);
__ bind(&not_create_implicit_receiver);

// Call the function.
__ InvokeFunctionWithNewTarget(r1, r3, r0, InvokeType::kCall);

// If the result is an object (in the ECMA sense), we should get rid
// of the receiver and use the result; see ECMA-262 section 13.2.2-7
// on page 74.
Label use_receiver, do_throw, leave_and_return, check_receiver;

// If the result is undefined, we jump out to using the implicit receiver.
__ JumpIfNotRoot(r0, RootIndex::kUndefinedValue, &check_receiver);

// Otherwise we do a smi check and fall through to check if the return value
// is a valid receiver.

// Throw away the result of the constructor invocation and use the
// on-stack receiver as the result.
__ bind(&use_receiver);
__ ldr(r0,
MemOperand(fp, FastConstructFrameConstants::kImplicitReceiverOffset));
__ JumpIfRoot(r0, RootIndex::kTheHoleValue, &do_throw);

__ bind(&leave_and_return);
// Leave construct frame.
__ LeaveFrame(StackFrame::CONSTRUCT);
__ Jump(lr);

__ bind(&check_receiver);
// If the result is a smi, it is *not* an object in the ECMA sense.
__ JumpIfSmi(r0, &use_receiver);

// If the type of the result (stored in its map) is less than
// FIRST_JS_RECEIVER_TYPE, it is not an object in the ECMA sense.
static_assert(LAST_JS_RECEIVER_TYPE == LAST_TYPE);
__ CompareObjectType(r0, r4, r5, FIRST_JS_RECEIVER_TYPE);
__ b(ge, &leave_and_return);
__ b(&use_receiver);

__ bind(&builtin_call);
// TODO(victorgomes): Check the possibility to turn this into a tailcall.
__ InvokeFunctionWithNewTarget(r1, r3, r0, InvokeType::kCall);
__ LeaveFrame(StackFrame::FAST_CONSTRUCT);
__ Jump(lr);

__ bind(&do_throw);
// Restore the context from the frame.
__ ldr(cp, MemOperand(fp, FastConstructFrameConstants::kContextOffset));
__ CallRuntime(Runtime::kThrowConstructorReturnedNonObject);
__ bkpt(0);

__ bind(&stack_overflow);
// Restore the context from the frame.
__ CallRuntime(Runtime::kThrowStackOverflow);
// Unreachable code.
__ bkpt(0);

// Called Construct on an Object that doesn't have a [[Construct]] internal
// method.
__ bind(&non_constructor);
__ Jump(BUILTIN_CODE(masm->isolate(), ConstructedNonConstructable),
RelocInfo::CODE_TARGET);
}

static void Generate_InterpreterEnterBytecode(MacroAssembler* masm) {
// Set the return address to the correct point in the interpreter entry
// trampoline.
Expand Down
183 changes: 173 additions & 10 deletions src/builtins/arm64/builtins-arm64.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1546,7 +1546,8 @@ static void GenerateInterpreterPushArgs(MacroAssembler* masm, Register num_args,
Register first_arg_index,
Register spread_arg_out,
ConvertReceiverMode receiver_mode,
InterpreterPushArgsMode mode) {
InterpreterPushArgsMode mode,
bool stack_check_already_done = false) {
ASM_CODE_COMMENT(masm);
Register last_arg_addr = x10;
Register stack_addr = x11;
Expand All @@ -1568,17 +1569,18 @@ static void GenerateInterpreterPushArgs(MacroAssembler* masm, Register num_args,
__ Add(slots_to_claim, num_args, 1);
__ Bic(slots_to_claim, slots_to_claim, 1);

// Add a stack check before pushing arguments.
Label stack_overflow, done;
__ StackOverflowCheck(slots_to_claim, &stack_overflow);
__ B(&done);
__ Bind(&stack_overflow);
__ TailCallRuntime(Runtime::kThrowStackOverflow);
__ Unreachable();
__ Bind(&done);
if (!stack_check_already_done) {
// Add a stack check before pushing arguments.
Label stack_overflow, done;
__ StackOverflowCheck(slots_to_claim, &stack_overflow);
__ B(&done);
__ Bind(&stack_overflow);
__ TailCallRuntime(Runtime::kThrowStackOverflow);
__ Unreachable();
__ Bind(&done);
}

__ Claim(slots_to_claim);

{
// Store padding, which may be overwritten.
UseScratchRegisterScope temps(masm);
Expand Down Expand Up @@ -1689,6 +1691,167 @@ void Builtins::Generate_InterpreterPushArgsThenConstructImpl(
}
}

namespace {

void NewImplicitReceiver(MacroAssembler* masm) {
// ----------- S t a t e -------------
// -- x0 : the number of arguments
// -- x1 : constructor to call (checked to be a JSFunction)
// -- x3 : new target
//
// Stack:
// -- Implicit Receiver
// -- [arguments without receiver]
// -- Implicit Receiver
// -- Context
// -- FastConstructMarker
// -- FramePointer
// -----------------------------------
Register implicit_receiver = x4;

// Save live registers.
__ SmiTag(x0);
__ Push(x0, x1, x3, padreg);
__ Call(BUILTIN_CODE(masm->isolate(), FastNewObject), RelocInfo::CODE_TARGET);
// Save result.
__ Mov(implicit_receiver, x0);
// Restore live registers.
__ Pop(padreg, x3, x1, x0);
__ SmiUntag(x0);

// Patch implicit receiver (in arguments)
__ Poke(implicit_receiver, 0 * kSystemPointerSize);
// Patch second implicit (in construct frame)
__ Str(implicit_receiver,
MemOperand(fp, FastConstructFrameConstants::kImplicitReceiverOffset));

// Restore context.
__ Ldr(cp, MemOperand(fp, FastConstructFrameConstants::kContextOffset));
}

} // namespace

// static
void Builtins::Generate_InterpreterPushArgsThenFastConstructFunction(
MacroAssembler* masm) {
// ----------- S t a t e -------------
// -- x0 : argument count
// -- x1 : constructor to call (checked to be a JSFunction)
// -- x3 : new target
// -- x4 : address of the first argument
// -- cp : context pointer
// -----------------------------------
__ AssertFunction(x1);

// Check if target has a [[Construct]] internal method.
Label non_constructor;
__ LoadMap(x2, x1);
__ Ldrb(x2, FieldMemOperand(x2, Map::kBitFieldOffset));
__ TestAndBranchIfAllClear(x2, Map::Bits1::IsConstructorBit::kMask,
&non_constructor);

// Add a stack check before pushing arguments.
Label stack_overflow;
__ StackOverflowCheck(x0, &stack_overflow);

// Enter a construct frame.
FrameScope scope(masm, StackFrame::MANUAL);
__ EnterFrame(StackFrame::FAST_CONSTRUCT);

if (v8_flags.debug_code) {
// Check that FrameScope pushed the context on to the stack already.
__ Peek(x2, 0);
__ Cmp(x2, cp);
__ Check(eq, AbortReason::kUnexpectedValue);
}

// Implicit receiver stored in the construct frame.
__ LoadRoot(x2, RootIndex::kTheHoleValue);
__ Push(x2, padreg);

// Push arguments + implicit receiver.
GenerateInterpreterPushArgs(masm, x0, x4, Register::no_reg(),
ConvertReceiverMode::kNullOrUndefined,
InterpreterPushArgsMode::kOther, true);
__ Poke(x2, 0 * kSystemPointerSize);

// Check if it is a builtin call.
Label builtin_call;
__ LoadTaggedField(
x2, FieldMemOperand(x1, JSFunction::kSharedFunctionInfoOffset));
__ Ldr(w2, FieldMemOperand(x2, SharedFunctionInfo::kFlagsOffset));
__ TestAndBranchIfAnySet(w2, SharedFunctionInfo::ConstructAsBuiltinBit::kMask,
&builtin_call);

// Check if we need to create an implicit receiver.
Label not_create_implicit_receiver;
__ DecodeField<SharedFunctionInfo::FunctionKindBits>(w2);
__ JumpIfIsInRange(
w2, static_cast<uint32_t>(FunctionKind::kDefaultDerivedConstructor),
static_cast<uint32_t>(FunctionKind::kDerivedConstructor),
&not_create_implicit_receiver);
NewImplicitReceiver(masm);
__ bind(&not_create_implicit_receiver);

// Call the function.
__ InvokeFunctionWithNewTarget(x1, x3, x0, InvokeType::kCall);

// If the result is an object (in the ECMA sense), we should get rid
// of the receiver and use the result; see ECMA-262 section 13.2.2-7
// on page 74.
Label use_receiver, do_throw, leave_and_return, check_receiver;

// If the result is undefined, we jump out to using the implicit receiver.
__ CompareRoot(x0, RootIndex::kUndefinedValue);
__ B(ne, &check_receiver);

// Throw away the result of the constructor invocation and use the
// on-stack receiver as the result.
__ Bind(&use_receiver);
__ Ldr(x0,
MemOperand(fp, FastConstructFrameConstants::kImplicitReceiverOffset));
__ CompareRoot(x0, RootIndex::kTheHoleValue);
__ B(eq, &do_throw);

__ Bind(&leave_and_return);
// Leave construct frame.
__ LeaveFrame(StackFrame::FAST_CONSTRUCT);
__ Ret();

// Otherwise we do a smi check and fall through to check if the return value
// is a valid receiver.
__ bind(&check_receiver);

// If the result is a smi, it is *not* an object in the ECMA sense.
__ JumpIfSmi(x0, &use_receiver);

// Check if the type of the result is not an object in the ECMA sense.
__ JumpIfJSAnyIsNotPrimitive(x0, x4, &leave_and_return);
__ B(&use_receiver);

__ bind(&builtin_call);
// TODO(victorgomes): Check the possibility to turn this into a tailcall.
__ InvokeFunctionWithNewTarget(x1, x3, x0, InvokeType::kCall);
__ LeaveFrame(StackFrame::FAST_CONSTRUCT);
__ Ret();

__ Bind(&do_throw);
// Restore the context from the frame.
__ Ldr(cp, MemOperand(fp, FastConstructFrameConstants::kContextOffset));
__ CallRuntime(Runtime::kThrowConstructorReturnedNonObject);
__ Unreachable();

__ Bind(&stack_overflow);
__ CallRuntime(Runtime::kThrowStackOverflow);
__ Unreachable();

// Called Construct on an Object that doesn't have a [[Construct]] internal
// method.
__ bind(&non_constructor);
__ Jump(BUILTIN_CODE(masm->isolate(), ConstructedNonConstructable),
RelocInfo::CODE_TARGET);
}

static void Generate_InterpreterEnterBytecode(MacroAssembler* masm) {
// Initialize the dispatch table register.
__ Mov(
Expand Down
2 changes: 2 additions & 0 deletions src/builtins/builtins-definitions.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ namespace internal {
ASM(InterpreterPushUndefinedAndArgsThenCall, InterpreterPushArgsThenCall) \
ASM(InterpreterPushArgsThenCallWithFinalSpread, InterpreterPushArgsThenCall) \
ASM(InterpreterPushArgsThenConstruct, InterpreterPushArgsThenConstruct) \
ASM(InterpreterPushArgsThenFastConstructFunction, \
InterpreterPushArgsThenConstruct) \
ASM(InterpreterPushArgsThenConstructArrayFunction, \
InterpreterPushArgsThenConstruct) \
ASM(InterpreterPushArgsThenConstructWithFinalSpread, \
Expand Down
Loading

0 comments on commit b2bb565

Please sign in to comment.