Commit 00746406 authored by ishell@chromium.org's avatar ishell@chromium.org Committed by V8 LUCI CQ

[masm][cleanup] Refactor call related assembler options

... which affect how builtin calls are generated.

This CL replaces the following boolean options
 - builtin_calls_as_table_load,
 - inline_offheap_trampolines,
 - short_builtin_calls,
 - use_pc_relative_calls_and_jumps,

with an enum BuiltinCallJumpMode and a boolean option
use_pc_relative_calls_and_jumps_for_mksnapshot.

Bug: v8:11880, v8:11527
Change-Id: Ia842b1d126c99dbe83e5b4f6118dcd44082ed168
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3820063Reviewed-by: 's avatarJakob Linke <jgruber@chromium.org>
Commit-Queue: Igor Sheludko <ishell@chromium.org>
Cr-Commit-Position: refs/heads/main@{#82330}
parent 0e42fd29
......@@ -129,25 +129,6 @@ void BaselineAssembler::JumpIfNotSmi(Register value, Label* target,
__ JumpIfNotSmi(value, target);
}
void BaselineAssembler::CallBuiltin(Builtin builtin) {
// __ CallBuiltin(static_cast<int>(builtin));
ASM_CODE_COMMENT_STRING(masm_,
__ CommentForOffHeapTrampoline("call", builtin));
ScratchRegisterScope temps(this);
Register temp = temps.AcquireScratch();
__ LoadEntryFromBuiltin(builtin, temp);
__ Call(temp);
}
void BaselineAssembler::TailCallBuiltin(Builtin builtin) {
ASM_CODE_COMMENT_STRING(masm_,
__ CommentForOffHeapTrampoline("tail call", builtin));
ScratchRegisterScope temps(this);
Register temp = temps.AcquireScratch();
__ LoadEntryFromBuiltin(builtin, temp);
__ Jump(temp);
}
void BaselineAssembler::TestAndBranch(Register value, int mask, Condition cc,
Label* target, Label::Distance) {
__ tst(value, Operand(mask));
......
......@@ -14,6 +14,11 @@ namespace baseline {
#define __ basm_.
// A builtin call/jump mode that is used then short builtin calls feature is
// not enabled.
constexpr BuiltinCallJumpMode kFallbackBuiltinCallJumpModeForBaseline =
BuiltinCallJumpMode::kIndirect;
void BaselineCompiler::Prologue() {
// Enter the frame here, since CallBuiltin will override lr.
__ masm()->EnterFrame(StackFrame::BASELINE);
......
......@@ -123,43 +123,6 @@ void BaselineAssembler::JumpIfImmediate(Condition cc, Register left, int right,
JumpIf(cc, left, Immediate(right), target, distance);
}
void BaselineAssembler::CallBuiltin(Builtin builtin) {
if (masm()->options().short_builtin_calls) {
// Generate pc-relative call.
__ CallBuiltin(builtin);
} else {
ScratchRegisterScope temps(this);
Register temp = temps.AcquireScratch();
__ LoadEntryFromBuiltin(builtin, temp);
__ Call(temp);
}
}
void BaselineAssembler::TailCallBuiltin(Builtin builtin) {
if (masm()->options().short_builtin_calls) {
// Generate pc-relative call.
__ TailCallBuiltin(builtin);
} else {
// The control flow integrity (CFI) feature allows us to "sign" code entry
// points as a target for calls, jumps or both. Arm64 has special
// instructions for this purpose, so-called "landing pads" (see
// TurboAssembler::CallTarget(), TurboAssembler::JumpTarget() and
// TurboAssembler::JumpOrCallTarget()). Currently, we generate "Call"
// landing pads for CPP builtins. In order to allow tail calling to those
// builtins we have to use a workaround.
// x17 is used to allow using "Call" (i.e. `bti c`) rather than "Jump" (i.e.
// `bti j`) landing pads for the tail-called code.
Register temp = x17;
// Make sure we're don't use this register as a temporary.
UseScratchRegisterScope temps(masm());
temps.Exclude(temp);
__ LoadEntryFromBuiltin(builtin, temp);
__ Jump(temp);
}
}
void BaselineAssembler::TestAndBranch(Register value, int mask, Condition cc,
Label* target, Label::Distance) {
__ Tst(value, Immediate(mask));
......
......@@ -13,6 +13,11 @@ namespace baseline {
#define __ basm_.
// A builtin call/jump mode that is used then short builtin calls feature is
// not enabled.
constexpr BuiltinCallJumpMode kFallbackBuiltinCallJumpModeForBaseline =
BuiltinCallJumpMode::kIndirect;
void BaselineCompiler::Prologue() {
ASM_CODE_COMMENT(&masm_);
// Enter the frame here, since CallBuiltin will override lr.
......
......@@ -66,6 +66,16 @@ void BaselineAssembler::CallRuntime(Runtime::FunctionId function, int nargs) {
__ CallRuntime(function, nargs);
}
void BaselineAssembler::CallBuiltin(Builtin builtin) {
// BaselineAssemblerOptions defines how builtin calls are generated.
__ CallBuiltin(builtin);
}
void BaselineAssembler::TailCallBuiltin(Builtin builtin) {
// BaselineAssemblerOptions defines how builtin tail calls are generated.
__ TailCallBuiltin(builtin);
}
MemOperand BaselineAssembler::ContextOperand() {
return RegisterFrameOperand(interpreter::Register::current_context());
}
......
......@@ -246,6 +246,16 @@ void MoveArgumentsForBuiltin(BaselineAssembler* masm, Args... args) {
} // namespace detail
namespace {
AssemblerOptions BaselineAssemblerOptions(Isolate* isolate) {
AssemblerOptions options = AssemblerOptions::Default(isolate);
options.builtin_call_jump_mode =
isolate->is_short_builtin_calls_enabled()
? BuiltinCallJumpMode::kPCRelative
: kFallbackBuiltinCallJumpModeForBaseline;
return options;
}
// Rough upper-bound estimate. Copying the data is most likely more expensive
// than pre-allocating a large enough buffer.
#ifdef V8_TARGET_ARCH_IA32
......@@ -272,8 +282,10 @@ BaselineCompiler::BaselineCompiler(
stats_(local_isolate->runtime_call_stats()),
shared_function_info_(shared_function_info),
bytecode_(bytecode),
masm_(local_isolate->GetMainThreadIsolateUnsafe(),
CodeObjectRequired::kNo, AllocateBuffer(bytecode)),
masm_(
local_isolate->GetMainThreadIsolateUnsafe(),
BaselineAssemblerOptions(local_isolate->GetMainThreadIsolateUnsafe()),
CodeObjectRequired::kNo, AllocateBuffer(bytecode)),
basm_(&masm_),
iterator_(bytecode_),
zone_(local_isolate->allocator(), ZONE_NAME),
......
......@@ -132,18 +132,6 @@ void BaselineAssembler::JumpIfNotSmi(Register value, Label* target,
__ JumpIfNotSmi(value, target, distance);
}
void BaselineAssembler::CallBuiltin(Builtin builtin) {
ASM_CODE_COMMENT_STRING(masm_,
__ CommentForOffHeapTrampoline("call", builtin));
__ Call(__ EntryFromBuiltinAsOperand(builtin));
}
void BaselineAssembler::TailCallBuiltin(Builtin builtin) {
ASM_CODE_COMMENT_STRING(masm_,
__ CommentForOffHeapTrampoline("tail call", builtin));
__ jmp(__ EntryFromBuiltinAsOperand(builtin));
}
void BaselineAssembler::TestAndBranch(Register value, int mask, Condition cc,
Label* target, Label::Distance distance) {
if ((mask & 0xff) == mask) {
......
......@@ -15,6 +15,11 @@ namespace baseline {
#define __ basm_.
// A builtin call/jump mode that is used then short builtin calls feature is
// not enabled.
constexpr BuiltinCallJumpMode kFallbackBuiltinCallJumpModeForBaseline =
BuiltinCallJumpMode::kIndirect;
void BaselineCompiler::Prologue() {
DCHECK_EQ(kJSFunctionRegister, kJavaScriptCallTargetRegister);
int max_frame_size =
......
......@@ -123,28 +123,6 @@ void BaselineAssembler::JumpIfNotSmi(Register value, Label* target,
__ JumpIfNotSmi(value, target, distance);
}
void BaselineAssembler::CallBuiltin(Builtin builtin) {
if (masm()->options().short_builtin_calls) {
// Generate pc-relative call.
__ CallBuiltin(builtin);
} else {
ASM_CODE_COMMENT_STRING(masm_,
__ CommentForOffHeapTrampoline("call", builtin));
__ Call(__ EntryFromBuiltinAsOperand(builtin));
}
}
void BaselineAssembler::TailCallBuiltin(Builtin builtin) {
if (masm()->options().short_builtin_calls) {
// Generate pc-relative jump.
__ TailCallBuiltin(builtin);
} else {
ASM_CODE_COMMENT_STRING(
masm_, __ CommentForOffHeapTrampoline("tail call", builtin));
__ Jump(__ EntryFromBuiltinAsOperand(builtin));
}
}
void BaselineAssembler::TestAndBranch(Register value, int mask, Condition cc,
Label* target, Label::Distance distance) {
if ((mask & 0xff) == mask) {
......
......@@ -15,6 +15,11 @@ namespace baseline {
#define __ basm_.
// A builtin call/jump mode that is used then short builtin calls feature is
// not enabled.
constexpr BuiltinCallJumpMode kFallbackBuiltinCallJumpModeForBaseline =
BuiltinCallJumpMode::kIndirect;
void BaselineCompiler::Prologue() {
ASM_CODE_COMMENT(&masm_);
DCHECK_EQ(kJSFunctionRegister, kJavaScriptCallTargetRegister);
......
......@@ -36,7 +36,6 @@ const int kBufferSize = 128 * KB;
AssemblerOptions BuiltinAssemblerOptions(Isolate* isolate, Builtin builtin) {
AssemblerOptions options = AssemblerOptions::Default(isolate);
CHECK(!options.isolate_independent_code);
CHECK(!options.use_pc_relative_calls_and_jumps);
CHECK(!options.collect_win64_unwind_info);
if (!isolate->IsGeneratingEmbeddedBuiltins()) {
......@@ -49,8 +48,17 @@ AssemblerOptions BuiltinAssemblerOptions(Isolate* isolate, Builtin builtin) {
std::ceil(static_cast<float>(code_region.size() / MB)) <=
kMaxPCRelativeCodeRangeInMB;
// Mksnapshot ensures that the code range is small enough to guarantee that
// PC-relative call/jump instructions can be used for builtin to builtin
// calls/tail calls. The embedded builtins blob generator also ensures that.
// However, there are serializer tests, where we force isolate creation at
// runtime and at this point, Code space isn't restricted to a size s.t.
// PC-relative calls may be used. So, we fall back to an indirect mode.
options.use_pc_relative_calls_and_jumps_for_mksnapshot =
pc_relative_calls_fit_in_code_range;
options.builtin_call_jump_mode = BuiltinCallJumpMode::kForMksnapshot;
options.isolate_independent_code = true;
options.use_pc_relative_calls_and_jumps = pc_relative_calls_fit_in_code_range;
options.collect_win64_unwind_info = true;
if (builtin == Builtin::kInterpreterEntryTrampolineForProfiling) {
......@@ -58,8 +66,7 @@ AssemblerOptions BuiltinAssemblerOptions(Isolate* isolate, Builtin builtin) {
// independent way because it might be necessary to create a copy of the
// builtin in the code space if the FLAG_interpreted_frames_native_stack is
// enabled.
options.short_builtin_calls = false;
options.builtin_calls_as_table_load = true;
options.builtin_call_jump_mode = BuiltinCallJumpMode::kIndirect;
}
return options;
......
......@@ -137,31 +137,10 @@ void TurboAssembler::Jump(Handle<Code> code, RelocInfo::Mode rmode,
DCHECK(RelocInfo::IsCodeTarget(rmode));
DCHECK_IMPLIES(options().isolate_independent_code,
Builtins::IsIsolateIndependentBuiltin(*code));
DCHECK_IMPLIES(options().use_pc_relative_calls_and_jumps,
Builtins::IsIsolateIndependentBuiltin(*code));
Builtin builtin = Builtin::kNoBuiltinId;
bool target_is_builtin =
isolate()->builtins()->IsBuiltinHandle(code, &builtin);
if (options().use_pc_relative_calls_and_jumps && target_is_builtin) {
int32_t code_target_index = AddCodeTarget(code);
b(code_target_index * kInstrSize, cond, RelocInfo::RELATIVE_CODE_TARGET);
return;
} else if (root_array_available_ && options().isolate_independent_code) {
// This branch is taken only for specific cctests, where we force isolate
// creation at runtime. At this point, Code space isn't restricted to a
// size s.t. pc-relative calls may be used.
ldr(ip, EntryFromBuiltinAsOperand(code->builtin_id()));
Jump(ip, cond);
return;
} else if (options().inline_offheap_trampolines && target_is_builtin) {
// Inline the trampoline.
RecordCommentForOffHeapTrampoline(builtin);
// Use ip directly instead of using UseScratchRegisterScope, as we do not
// preserve scratch registers across calls.
mov(ip, Operand(BuiltinEntry(builtin), RelocInfo::OFF_HEAP_TARGET));
Jump(ip, cond);
if (isolate()->builtins()->IsBuiltinHandle(code, &builtin)) {
TailCallBuiltin(builtin, cond);
return;
}
......@@ -223,29 +202,9 @@ void TurboAssembler::Call(Handle<Code> code, RelocInfo::Mode rmode,
DCHECK(RelocInfo::IsCodeTarget(rmode));
DCHECK_IMPLIES(options().isolate_independent_code,
Builtins::IsIsolateIndependentBuiltin(*code));
DCHECK_IMPLIES(options().use_pc_relative_calls_and_jumps,
Builtins::IsIsolateIndependentBuiltin(*code));
Builtin builtin = Builtin::kNoBuiltinId;
bool target_is_builtin =
isolate()->builtins()->IsBuiltinHandle(code, &builtin);
if (target_is_builtin && options().builtin_calls_as_table_load) {
ldr(ip, EntryFromBuiltinAsOperand(builtin));
Call(ip, cond);
return;
} else if (target_is_builtin && options().use_pc_relative_calls_and_jumps) {
int32_t code_target_index = AddCodeTarget(code);
bl(code_target_index * kInstrSize, cond, RelocInfo::RELATIVE_CODE_TARGET);
return;
} else if (root_array_available_ && options().isolate_independent_code) {
// This branch is taken only for specific cctests, where we force isolate
// creation at runtime. At this point, Code space isn't restricted to a
// size s.t. pc-relative calls may be used.
ldr(ip, EntryFromBuiltinAsOperand(builtin));
Call(ip, cond);
return;
} else if (target_is_builtin && options().inline_offheap_trampolines) {
// Inline the trampoline.
if (isolate()->builtins()->IsBuiltinHandle(code, &builtin)) {
CallBuiltin(builtin);
return;
}
......@@ -293,12 +252,62 @@ void TurboAssembler::CallBuiltin(Builtin builtin, Condition cond) {
ASM_CODE_COMMENT_STRING(this, CommentForOffHeapTrampoline("call", builtin));
// Use ip directly instead of using UseScratchRegisterScope, as we do not
// preserve scratch registers across calls.
if (options().builtin_calls_as_table_load) {
LoadEntryFromBuiltin(builtin, ip);
Call(ip, cond);
} else {
mov(ip, Operand(BuiltinEntry(builtin), RelocInfo::OFF_HEAP_TARGET));
Call(ip, cond);
switch (options().builtin_call_jump_mode) {
case BuiltinCallJumpMode::kAbsolute: {
mov(ip, Operand(BuiltinEntry(builtin), RelocInfo::OFF_HEAP_TARGET));
Call(ip, cond);
break;
}
case BuiltinCallJumpMode::kPCRelative:
UNREACHABLE();
case BuiltinCallJumpMode::kIndirect:
ldr(ip, EntryFromBuiltinAsOperand(builtin));
Call(ip, cond);
break;
case BuiltinCallJumpMode::kForMksnapshot: {
if (options().use_pc_relative_calls_and_jumps_for_mksnapshot) {
Handle<CodeT> code = isolate()->builtins()->code_handle(builtin);
int32_t code_target_index = AddCodeTarget(code);
bl(code_target_index * kInstrSize, cond,
RelocInfo::RELATIVE_CODE_TARGET);
} else {
ldr(ip, EntryFromBuiltinAsOperand(builtin));
Call(ip, cond);
}
break;
}
}
}
void TurboAssembler::TailCallBuiltin(Builtin builtin, Condition cond) {
ASM_CODE_COMMENT_STRING(this,
CommentForOffHeapTrampoline("tail call", builtin));
// Use ip directly instead of using UseScratchRegisterScope, as we do not
// preserve scratch registers across calls.
switch (options().builtin_call_jump_mode) {
case BuiltinCallJumpMode::kAbsolute: {
mov(ip, Operand(BuiltinEntry(builtin), RelocInfo::OFF_HEAP_TARGET));
Jump(ip, cond);
break;
}
case BuiltinCallJumpMode::kPCRelative:
UNREACHABLE();
case BuiltinCallJumpMode::kIndirect:
ldr(ip, EntryFromBuiltinAsOperand(builtin));
Jump(ip, cond);
break;
case BuiltinCallJumpMode::kForMksnapshot: {
if (options().use_pc_relative_calls_and_jumps_for_mksnapshot) {
Handle<CodeT> code = isolate()->builtins()->code_handle(builtin);
int32_t code_target_index = AddCodeTarget(code);
b(code_target_index * kInstrSize, cond,
RelocInfo::RELATIVE_CODE_TARGET);
} else {
ldr(ip, EntryFromBuiltinAsOperand(builtin));
Jump(ip, cond);
}
break;
}
}
}
......@@ -739,13 +748,7 @@ void TurboAssembler::CallRecordWriteStub(Register object, Register slot_address,
#endif
} else {
Builtin builtin = Builtins::GetRecordWriteStub(fp_mode);
if (options().inline_offheap_trampolines ||
options().builtin_calls_as_table_load) {
CallBuiltin(builtin);
} else {
Handle<Code> code_target = isolate()->builtins()->code_handle(builtin);
Call(code_target, RelocInfo::CODE_TARGET);
}
CallBuiltin(builtin);
}
}
......@@ -1920,10 +1923,8 @@ void TurboAssembler::TruncateDoubleToI(Isolate* isolate, Zone* zone,
// For balance.
if (false) {
#endif // V8_ENABLE_WEBASSEMBLY
} else if (options().inline_offheap_trampolines) {
CallBuiltin(Builtin::kDoubleToI);
} else {
Call(BUILTIN_CODE(isolate, DoubleToI), RelocInfo::CODE_TARGET);
CallBuiltin(Builtin::kDoubleToI);
}
ldr(result, MemOperand(sp, 0));
......
......@@ -312,6 +312,7 @@ class V8_EXPORT_PRIVATE TurboAssembler : public TurboAssemblerBase {
void LoadEntryFromBuiltinIndex(Register builtin_index);
void CallBuiltinByIndex(Register builtin_index);
void CallBuiltin(Builtin builtin, Condition cond = al);
void TailCallBuiltin(Builtin builtin, Condition cond = al);
void LoadCodeObjectEntry(Register destination, Register code_object);
void CallCodeObject(Register code_object);
......
......@@ -2077,18 +2077,12 @@ void TurboAssembler::Jump(Handle<CodeT> code, RelocInfo::Mode rmode,
DCHECK_IMPLIES(options().isolate_independent_code,
Builtins::IsIsolateIndependentBuiltin(*code));
if (options().inline_offheap_trampolines) {
Builtin builtin = Builtin::kNoBuiltinId;
if (isolate()->builtins()->IsBuiltinHandle(code, &builtin)) {
// Inline the trampoline.
CHECK_EQ(cond, Condition::al); // Implement if necessary.
TailCallBuiltin(builtin);
return;
}
Builtin builtin = Builtin::kNoBuiltinId;
if (isolate()->builtins()->IsBuiltinHandle(code, &builtin)) {
TailCallBuiltin(builtin, cond);
return;
}
DCHECK(RelocInfo::IsCodeTarget(rmode));
DCHECK(!options().builtin_calls_as_table_load);
if (CanUseNearCallOrJump(rmode)) {
EmbeddedObjectIndex index = AddEmbeddedObject(code);
DCHECK(is_int32(index));
......@@ -2126,19 +2120,14 @@ void TurboAssembler::Call(Handle<CodeT> code, RelocInfo::Mode rmode) {
Builtins::IsIsolateIndependentBuiltin(*code));
BlockPoolsScope scope(this);
if (options().inline_offheap_trampolines ||
options().builtin_calls_as_table_load) {
Builtin builtin = Builtin::kNoBuiltinId;
if (isolate()->builtins()->IsBuiltinHandle(code, &builtin)) {
// Inline the trampoline.
CallBuiltin(builtin);
return;
}
Builtin builtin = Builtin::kNoBuiltinId;
if (isolate()->builtins()->IsBuiltinHandle(code, &builtin)) {
CallBuiltin(builtin);
return;
}
DCHECK(FromCodeT(*code).IsExecutable());
DCHECK(RelocInfo::IsCodeTarget(rmode));
DCHECK(!options().builtin_calls_as_table_load);
if (CanUseNearCallOrJump(rmode)) {
EmbeddedObjectIndex index = AddEmbeddedObject(code);
......@@ -2198,51 +2187,83 @@ void TurboAssembler::CallBuiltinByIndex(Register builtin_index) {
}
void TurboAssembler::CallBuiltin(Builtin builtin) {
ASM_CODE_COMMENT(this);
DCHECK(Builtins::IsBuiltinId(builtin));
RecordCommentForOffHeapTrampoline(builtin);
CHECK_NE(builtin, Builtin::kNoBuiltinId);
if (options().short_builtin_calls) {
Call(BuiltinEntry(builtin), RelocInfo::RUNTIME_ENTRY);
} else {
UseScratchRegisterScope temps(this);
Register scratch = temps.AcquireX();
if (options().builtin_calls_as_table_load) {
LoadEntryFromBuiltin(builtin, scratch);
} else {
ASM_CODE_COMMENT_STRING(this, CommentForOffHeapTrampoline("call", builtin));
switch (options().builtin_call_jump_mode) {
case BuiltinCallJumpMode::kAbsolute: {
UseScratchRegisterScope temps(this);
Register scratch = temps.AcquireX();
Ldr(scratch, Operand(BuiltinEntry(builtin), RelocInfo::OFF_HEAP_TARGET));
Call(scratch);
break;
}
case BuiltinCallJumpMode::kPCRelative:
Call(BuiltinEntry(builtin), RelocInfo::RUNTIME_ENTRY);
break;
case BuiltinCallJumpMode::kIndirect: {
UseScratchRegisterScope temps(this);
Register scratch = temps.AcquireX();
LoadEntryFromBuiltin(builtin, scratch);
Call(scratch);
break;
}
case BuiltinCallJumpMode::kForMksnapshot: {
if (options().use_pc_relative_calls_and_jumps_for_mksnapshot) {
Handle<CodeT> code = isolate()->builtins()->code_handle(builtin);
EmbeddedObjectIndex index = AddEmbeddedObject(code);
DCHECK(is_int32(index));
near_call(static_cast<int32_t>(index), RelocInfo::CODE_TARGET);
} else {
UseScratchRegisterScope temps(this);
Register scratch = temps.AcquireX();
LoadEntryFromBuiltin(builtin, scratch);
Call(scratch);
}
break;
}
Call(scratch);
}
}
void TurboAssembler::TailCallBuiltin(Builtin builtin) {
ASM_CODE_COMMENT(this);
DCHECK(Builtins::IsBuiltinId(builtin));
RecordCommentForOffHeapTrampoline(builtin);
CHECK_NE(builtin, Builtin::kNoBuiltinId);
if (options().short_builtin_calls) {
Jump(BuiltinEntry(builtin), RelocInfo::RUNTIME_ENTRY);
void TurboAssembler::TailCallBuiltin(Builtin builtin, Condition cond) {
ASM_CODE_COMMENT_STRING(this,
CommentForOffHeapTrampoline("tail call", builtin));
} else {
// The control flow integrity (CFI) feature allows us to "sign" code entry
// points as a target for calls, jumps or both. Arm64 has special
// instructions for this purpose, so-called "landing pads" (see
// TurboAssembler::CallTarget(), TurboAssembler::JumpTarget() and
// TurboAssembler::JumpOrCallTarget()). Currently, we generate "Call"
// landing pads for CPP builtins. In order to allow tail calling to those
// builtins we have to use a workaround.
// x17 is used to allow using "Call" (i.e. `bti c`) rather than "Jump"
// (i.e. `bti j`) landing pads for the tail-called code.
Register temp = x17;
if (options().builtin_calls_as_table_load) {
LoadEntryFromBuiltin(builtin, temp);
} else {
// The control flow integrity (CFI) feature allows us to "sign" code entry
// points as a target for calls, jumps or both. Arm64 has special
// instructions for this purpose, so-called "landing pads" (see
// TurboAssembler::CallTarget(), TurboAssembler::JumpTarget() and
// TurboAssembler::JumpOrCallTarget()). Currently, we generate "Call"
// landing pads for CPP builtins. In order to allow tail calling to those
// builtins we have to use a workaround.
// x17 is used to allow using "Call" (i.e. `bti c`) rather than "Jump"
// (i.e. `bti j`) landing pads for the tail-called code.
Register temp = x17;
switch (options().builtin_call_jump_mode) {
case BuiltinCallJumpMode::kAbsolute: {
Ldr(temp, Operand(BuiltinEntry(builtin), RelocInfo::OFF_HEAP_TARGET));
Jump(temp, cond);
break;
}
case BuiltinCallJumpMode::kPCRelative:
Jump(BuiltinEntry(builtin), RelocInfo::RUNTIME_ENTRY, cond);
break;
case BuiltinCallJumpMode::kIndirect: {
LoadEntryFromBuiltin(builtin, temp);
Jump(temp, cond);
break;
}
case BuiltinCallJumpMode::kForMksnapshot: {
if (options().use_pc_relative_calls_and_jumps_for_mksnapshot) {
Handle<CodeT> code = isolate()->builtins()->code_handle(builtin);
EmbeddedObjectIndex index = AddEmbeddedObject(code);
DCHECK(is_int32(index));
JumpHelper(static_cast<int64_t>(index), RelocInfo::CODE_TARGET, cond);
} else {
LoadEntryFromBuiltin(builtin, temp);
Jump(temp, cond);
}
break;
}
Jump(temp);
}
}
......@@ -2803,10 +2824,8 @@ void TurboAssembler::TruncateDoubleToI(Isolate* isolate, Zone* zone,
// For balance.
if (false) {
#endif // V8_ENABLE_WEBASSEMBLY
} else if (options().inline_offheap_trampolines) {
CallBuiltin(Builtin::kDoubleToI);
} else {
Call(BUILTIN_CODE(isolate, DoubleToI), RelocInfo::CODE_TARGET);
CallBuiltin(Builtin::kDoubleToI);
}
Ldr(result, MemOperand(sp, 0));
......@@ -3445,12 +3464,7 @@ void TurboAssembler::CallRecordWriteStub(Register object, Register slot_address,
#endif
} else {
Builtin builtin = Builtins::GetRecordWriteStub(fp_mode);
if (options().inline_offheap_trampolines) {
CallBuiltin(builtin);
} else {
Handle<CodeT> code_target = isolate()->builtins()->code_handle(builtin);
Call(code_target, RelocInfo::CODE_TARGET);
}
CallBuiltin(builtin);
}
}
......
......@@ -962,7 +962,7 @@ class V8_EXPORT_PRIVATE TurboAssembler : public TurboAssemblerBase {
MemOperand EntryFromBuiltinAsOperand(Builtin builtin);
void CallBuiltinByIndex(Register builtin);
void CallBuiltin(Builtin builtin);
void TailCallBuiltin(Builtin builtin);
void TailCallBuiltin(Builtin builtin, Condition cond = al);
void LoadCodeObjectEntry(Register destination, Register code_object);
void CallCodeObject(Register code_object);
......
......@@ -70,16 +70,19 @@ AssemblerOptions AssemblerOptions::Default(Isolate* isolate) {
// if we are but we are targetting the simulator *only*.
options.enable_simulator_code = !serializer || FLAG_target_is_simulator;
#endif
options.inline_offheap_trampolines &= !generating_embedded_builtin;
#if V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_ARM64
options.code_range_base = isolate->heap()->code_range_base();
#endif
options.short_builtin_calls =
bool short_builtin_calls =
isolate->is_short_builtin_calls_enabled() &&
!generating_embedded_builtin &&
(options.code_range_base != kNullAddress) &&
// Serialization of RUNTIME_ENTRY reloc infos is not supported yet.
!serializer;
if (short_builtin_calls) {
options.builtin_call_jump_mode = BuiltinCallJumpMode::kPCRelative;
}
return options;
}
......
......@@ -148,6 +148,28 @@ class HeapObjectRequest {
enum class CodeObjectRequired { kNo, kYes };
enum class BuiltinCallJumpMode {
// The builtin entry point address is embedded into the instruction stream as
// an absolute address.
kAbsolute,
// Generate builtin calls/jumps using PC-relative instructions. This mode
// assumes that the target is guaranteed to be within the
// kMaxPCRelativeCodeRangeInMB distance.
kPCRelative,
// Generate builtin calls/jumps as an indirect instruction which loads the
// target address from the builtins entry point table.
kIndirect,
// Same as kPCRelative but used only for generating embedded builtins.
// Currently we use RelocInfo::RUNTIME_ENTRY for generating kPCRelative but
// it's not supported yet for mksnapshot yet because of various reasons:
// 1) we encode the target as an offset from the code range which is not
// always available (32-bit architectures don't have it),
// 2) serialization of RelocInfo::RUNTIME_ENTRY is not implemented yet.
// TODO(v8:11527): Address the resons above and remove the kForMksnapshot in
// favor of kPCRelative or kIndirect.
kForMksnapshot,
};
struct V8_EXPORT_PRIVATE AssemblerOptions {
// Recording reloc info for external references and off-heap targets is
// needed whenever code is serialized, e.g. into the snapshot or as a Wasm
......@@ -158,22 +180,6 @@ struct V8_EXPORT_PRIVATE AssemblerOptions {
// assembler is used on existing code directly (e.g. JumpTableAssembler)
// without any buffer to hold reloc information.
bool disable_reloc_info_for_patching = false;
// Generate calls/jumps to builtins via builtins entry table instead of
// using RelocInfo::CODE_TARGET. Currently, it's enabled only for
// InterpreterEntryTrampolineForProfiling builtin which is used as a template
// for creation of interpreter entry trampoline Code objects when
// FLAG_interpreted_frames_native_stack is enabled.
// By default builtins use RelocInfo::CODE_TARGET for calling other builtins
// but it's not allowed to use the same instruction stream for Code objects
// because the builtins are generated in assumption that it's allowed to use
// PC-relative instructions for builtin-to-builtins calls/jumps. However,
// this is not the case for regular V8 instance because the code range might
// be bigger than the maximum PC-relative call/jump distance which means that
// the InterpreterEntryTrampoline builtin can't be used as a template.
// TODO(ishell): consider combining builtin_calls_as_table_load,
// short_builtin_calls, inline_offheap_trampolines and
// use_pc_relative_calls_and_jumps flags to an enum.
bool builtin_calls_as_table_load = false;
// Enables root-relative access to arbitrary untagged addresses (usually
// external references). Only valid if code will not survive the process.
bool enable_root_relative_access = false;
......@@ -183,21 +189,21 @@ struct V8_EXPORT_PRIVATE AssemblerOptions {
// root array.
// (macro assembler feature).
bool isolate_independent_code = false;
// Enables the use of isolate-independent builtins through an off-heap
// trampoline. (macro assembler feature).
bool inline_offheap_trampolines = true;
// Enables generation of pc-relative calls to builtins if the off-heap
// builtins are guaranteed to be within the reach of pc-relative call or jump
// instructions. For example, when the bultins code is re-embedded into the
// code range.
bool short_builtin_calls = false;
// Defines how builtin calls and tail calls should be generated.
BuiltinCallJumpMode builtin_call_jump_mode = BuiltinCallJumpMode::kAbsolute;
// Mksnapshot ensures that the code range is small enough to guarantee that
// PC-relative call/jump instructions can be used for builtin to builtin
// calls/tail calls. The embedded builtins blob generator also ensures that.
// However, there are serializer tests, where we force isolate creation at
// runtime and at this point, Code space isn't restricted to a size s.t.
// PC-relative calls may be used. So, we fall back to an indirect mode.
// TODO(v8:11527): remove once kForMksnapshot is removed.
bool use_pc_relative_calls_and_jumps_for_mksnapshot = false;
// On some platforms, all code is created within a certain address range in
// the process, and the base of this code range is configured here.
Address code_range_base = 0;
// Enable pc-relative calls/jumps on platforms that support it. When setting
// this flag, the code range must be small enough to fit all offsets into
// the instruction immediates.
bool use_pc_relative_calls_and_jumps = false;
// Enables the collection of information useful for the generation of unwind
// info. This is useful in some platform (Win64) where the unwind info depends
// on a function prologue/epilogue.
......
......@@ -491,13 +491,7 @@ void TurboAssembler::CallRecordWriteStub(Register object, Register slot_address,
#endif
} else {
Builtin builtin = Builtins::GetRecordWriteStub(fp_mode);
if (options().inline_offheap_trampolines ||
options().builtin_calls_as_table_load) {
CallBuiltin(builtin);
} else {
Handle<Code> code_target = isolate()->builtins()->code_handle(builtin);
Call(code_target, RelocInfo::CODE_TARGET);
}
CallBuiltin(builtin);
}
}
......@@ -2031,14 +2025,10 @@ void TurboAssembler::Call(Handle<Code> code_object, RelocInfo::Mode rmode) {
ASM_CODE_COMMENT(this);
DCHECK_IMPLIES(options().isolate_independent_code,
Builtins::IsIsolateIndependentBuiltin(*code_object));
if (options().inline_offheap_trampolines ||
options().builtin_calls_as_table_load) {
Builtin builtin = Builtin::kNoBuiltinId;
if (isolate()->builtins()->IsBuiltinHandle(code_object, &builtin)) {
// Inline the trampoline.
CallBuiltin(builtin);
return;
}
Builtin builtin = Builtin::kNoBuiltinId;
if (isolate()->builtins()->IsBuiltinHandle(code_object, &builtin)) {
CallBuiltin(builtin);
return;
}
DCHECK(RelocInfo::IsCodeTarget(rmode));
call(code_object, rmode);
......@@ -2068,20 +2058,42 @@ void TurboAssembler::CallBuiltinByIndex(Register builtin_index) {
void TurboAssembler::CallBuiltin(Builtin builtin) {
ASM_CODE_COMMENT_STRING(this, CommentForOffHeapTrampoline("call", builtin));
if (options().builtin_calls_as_table_load) {
call(EntryFromBuiltinAsOperand(builtin));
} else {
call(BuiltinEntry(builtin), RelocInfo::OFF_HEAP_TARGET);
switch (options().builtin_call_jump_mode) {
case BuiltinCallJumpMode::kAbsolute: {
call(BuiltinEntry(builtin), RelocInfo::OFF_HEAP_TARGET);
break;
}
case BuiltinCallJumpMode::kPCRelative:
UNREACHABLE();
case BuiltinCallJumpMode::kIndirect:
call(EntryFromBuiltinAsOperand(builtin));
break;
case BuiltinCallJumpMode::kForMksnapshot: {
Handle<CodeT> code = isolate()->builtins()->code_handle(builtin);
call(code, RelocInfo::CODE_TARGET);
break;
}
}
}
void TurboAssembler::TailCallBuiltin(Builtin builtin) {
ASM_CODE_COMMENT_STRING(this,
CommentForOffHeapTrampoline("tail call", builtin));
if (options().builtin_calls_as_table_load) {
jmp(EntryFromBuiltinAsOperand(builtin));
} else {
jmp(BuiltinEntry(builtin), RelocInfo::OFF_HEAP_TARGET);
switch (options().builtin_call_jump_mode) {
case BuiltinCallJumpMode::kAbsolute: {
jmp(BuiltinEntry(builtin), RelocInfo::OFF_HEAP_TARGET);
break;
}
case BuiltinCallJumpMode::kPCRelative:
UNREACHABLE();
case BuiltinCallJumpMode::kIndirect:
jmp(EntryFromBuiltinAsOperand(builtin));
break;
case BuiltinCallJumpMode::kForMksnapshot: {
Handle<CodeT> code = isolate()->builtins()->code_handle(builtin);
jmp(code, RelocInfo::CODE_TARGET);
break;
}
}
}
......@@ -2163,13 +2175,10 @@ void TurboAssembler::Jump(const ExternalReference& reference) {
void TurboAssembler::Jump(Handle<Code> code_object, RelocInfo::Mode rmode) {
DCHECK_IMPLIES(options().isolate_independent_code,
Builtins::IsIsolateIndependentBuiltin(*code_object));
if (options().inline_offheap_trampolines ||
options().builtin_calls_as_table_load) {
Builtin builtin = Builtin::kNoBuiltinId;
if (isolate()->builtins()->IsBuiltinHandle(code_object, &builtin)) {
TailCallBuiltin(builtin);
return;
}
Builtin builtin = Builtin::kNoBuiltinId;
if (isolate()->builtins()->IsBuiltinHandle(code_object, &builtin)) {
TailCallBuiltin(builtin);
return;
}
DCHECK(RelocInfo::IsCodeTarget(rmode));
jmp(code_object, rmode);
......
......@@ -110,13 +110,6 @@ class V8_EXPORT_PRIVATE TurboAssemblerBase : public Assembler {
return str.str();
}
V8_INLINE void RecordCommentForOffHeapTrampoline(Builtin builtin) {
if (!FLAG_code_comments) return;
std::ostringstream str;
str << "[ Inlined Trampoline to " << Builtins::name(builtin);
RecordComment(str.str().c_str());
}
enum class RecordWriteCallMode { kDefault, kWasm };
protected:
......
......@@ -528,13 +528,7 @@ void TurboAssembler::CallRecordWriteStub(Register object, Register slot_address,
#endif
} else {
Builtin builtin = Builtins::GetRecordWriteStub(fp_mode);
if (options().inline_offheap_trampolines ||
options().builtin_calls_as_table_load) {
CallBuiltin(builtin);
} else {
Handle<CodeT> code_target = isolate()->builtins()->code_handle(builtin);
Call(code_target, RelocInfo::CODE_TARGET);
}
CallBuiltin(builtin);
}
}
......@@ -2110,16 +2104,12 @@ void TurboAssembler::Jump(Address destination, RelocInfo::Mode rmode) {
void TurboAssembler::Jump(Handle<CodeT> code_object, RelocInfo::Mode rmode) {
DCHECK_IMPLIES(options().isolate_independent_code,
Builtins::IsIsolateIndependentBuiltin(*code_object));
if (options().inline_offheap_trampolines ||
options().builtin_calls_as_table_load) {
Builtin builtin = Builtin::kNoBuiltinId;
if (isolate()->builtins()->IsBuiltinHandle(code_object, &builtin)) {
TailCallBuiltin(builtin);
return;
}
Builtin builtin = Builtin::kNoBuiltinId;
if (isolate()->builtins()->IsBuiltinHandle(code_object, &builtin)) {
TailCallBuiltin(builtin);
return;
}
DCHECK(RelocInfo::IsCodeTarget(rmode));
DCHECK(!options().builtin_calls_as_table_load);
jmp(code_object, rmode);
}
......@@ -2127,19 +2117,15 @@ void TurboAssembler::Jump(Handle<CodeT> code_object, RelocInfo::Mode rmode,
Condition cc) {
DCHECK_IMPLIES(options().isolate_independent_code,
Builtins::IsIsolateIndependentBuiltin(*code_object));
if (options().inline_offheap_trampolines ||
options().builtin_calls_as_table_load) {
Builtin builtin = Builtin::kNoBuiltinId;
if (isolate()->builtins()->IsBuiltinHandle(code_object, &builtin)) {
Label skip;
j(NegateCondition(cc), &skip, Label::kNear);
TailCallBuiltin(builtin);
bind(&skip);
return;
}
Builtin builtin = Builtin::kNoBuiltinId;
if (isolate()->builtins()->IsBuiltinHandle(code_object, &builtin)) {
Label skip;
j(NegateCondition(cc), &skip, Label::kNear);
TailCallBuiltin(builtin);
bind(&skip);
return;
}
DCHECK(RelocInfo::IsCodeTarget(rmode));
DCHECK(!options().builtin_calls_as_table_load);
j(cc, code_object, rmode);
}
......@@ -2170,17 +2156,12 @@ void TurboAssembler::Call(Address destination, RelocInfo::Mode rmode) {
void TurboAssembler::Call(Handle<CodeT> code_object, RelocInfo::Mode rmode) {
DCHECK_IMPLIES(options().isolate_independent_code,
Builtins::IsIsolateIndependentBuiltin(*code_object));
if (options().inline_offheap_trampolines ||
options().builtin_calls_as_table_load) {
Builtin builtin = Builtin::kNoBuiltinId;
if (isolate()->builtins()->IsBuiltinHandle(code_object, &builtin)) {
// Inline the trampoline.
CallBuiltin(builtin);
return;
}
Builtin builtin = Builtin::kNoBuiltinId;
if (isolate()->builtins()->IsBuiltinHandle(code_object, &builtin)) {
CallBuiltin(builtin);
return;
}
DCHECK(RelocInfo::IsCodeTarget(rmode));
DCHECK(!options().builtin_calls_as_table_load);
call(code_object, rmode);
}
......@@ -2212,25 +2193,42 @@ void TurboAssembler::CallBuiltinByIndex(Register builtin_index) {
void TurboAssembler::CallBuiltin(Builtin builtin) {
ASM_CODE_COMMENT_STRING(this, CommentForOffHeapTrampoline("call", builtin));
if (options().short_builtin_calls) {
call(BuiltinEntry(builtin), RelocInfo::RUNTIME_ENTRY);
} else if (options().builtin_calls_as_table_load) {
Call(EntryFromBuiltinAsOperand(builtin));
} else {
Move(kScratchRegister, BuiltinEntry(builtin), RelocInfo::OFF_HEAP_TARGET);
call(kScratchRegister);
switch (options().builtin_call_jump_mode) {
case BuiltinCallJumpMode::kAbsolute:
Call(BuiltinEntry(builtin), RelocInfo::OFF_HEAP_TARGET);
break;
case BuiltinCallJumpMode::kPCRelative:
call(BuiltinEntry(builtin), RelocInfo::RUNTIME_ENTRY);
break;
case BuiltinCallJumpMode::kIndirect:
Call(EntryFromBuiltinAsOperand(builtin));
break;
case BuiltinCallJumpMode::kForMksnapshot: {
Handle<CodeT> code = isolate()->builtins()->code_handle(builtin);
call(code, RelocInfo::CODE_TARGET);
break;
}
}
}
void TurboAssembler::TailCallBuiltin(Builtin builtin) {
ASM_CODE_COMMENT_STRING(this,
CommentForOffHeapTrampoline("tail call", builtin));
if (options().short_builtin_calls) {
jmp(BuiltinEntry(builtin), RelocInfo::RUNTIME_ENTRY);
} else if (options().builtin_calls_as_table_load) {
Jump(EntryFromBuiltinAsOperand(builtin));
} else {
Jump(BuiltinEntry(builtin), RelocInfo::OFF_HEAP_TARGET);
switch (options().builtin_call_jump_mode) {
case BuiltinCallJumpMode::kAbsolute:
Jump(BuiltinEntry(builtin), RelocInfo::OFF_HEAP_TARGET);
break;
case BuiltinCallJumpMode::kPCRelative:
jmp(BuiltinEntry(builtin), RelocInfo::RUNTIME_ENTRY);
break;
case BuiltinCallJumpMode::kIndirect:
Jump(EntryFromBuiltinAsOperand(builtin));
break;
case BuiltinCallJumpMode::kForMksnapshot: {
Handle<CodeT> code = isolate()->builtins()->code_handle(builtin);
jmp(code, RelocInfo::CODE_TARGET);
break;
}
}
}
......
......@@ -283,10 +283,8 @@ class OutOfLineTruncateDoubleToI final : public OutOfLineCode {
// For balance.
if (false) {
#endif // V8_ENABLE_WEBASSEMBLY
} else if (tasm()->options().inline_offheap_trampolines) {
__ CallBuiltin(Builtin::kDoubleToI);
} else {
__ Call(BUILTIN_CODE(isolate_, DoubleToI), RelocInfo::CODE_TARGET);
__ CallBuiltin(Builtin::kDoubleToI);
}
__ mov(result_, MemOperand(esp, 0));
__ add(esp, Immediate(kDoubleSize));
......
......@@ -249,12 +249,10 @@ class OutOfLineTruncateDoubleToI final : public OutOfLineCode {
// For balance.
if (false) {
#endif // V8_ENABLE_WEBASSEMBLY
} else if (tasm()->options().inline_offheap_trampolines) {
} else {
// With embedded builtins we do not need the isolate here. This allows
// the call to be generated asynchronously.
__ CallBuiltin(Builtin::kDoubleToI);
} else {
__ Call(BUILTIN_CODE(isolate_, DoubleToI), RelocInfo::CODE_TARGET);
}
__ movl(result_, MemOperand(rsp, 0));
__ addq(rsp, Immediate(kDoubleSize));
......
......@@ -381,6 +381,8 @@ EmbeddedData EmbeddedData::FromIsolate(Isolate* isolate) {
std::memcpy(dst, reinterpret_cast<uint8_t*>(code.raw_metadata_start()),
code.raw_metadata_size());
}
CHECK_IMPLIES(kMaxPCRelativeCodeRangeInMB,
raw_code_size <= kMaxPCRelativeCodeRangeInMB * MB);
// .. and the variable-size code section.
uint8_t* const raw_code_start = blob_code + RawCodeOffset();
......
......@@ -3894,7 +3894,7 @@ TEST(IsDoubleElementsKind) {
0);
}
TEST(TestCallBuiltinInlineTrampoline) {
TEST(TestCallBuiltinAbsolute) {
Isolate* isolate(CcTest::InitIsolateOnce());
const int kNumParams = 1;
CodeAssemblerTester asm_tester(isolate, kNumParams + 1); // Include receiver.
......@@ -3909,8 +3909,32 @@ TEST(TestCallBuiltinInlineTrampoline) {
m.Return(m.CallStub(Builtins::CallableFor(isolate, Builtin::kStringRepeat),
context, str, index));
AssemblerOptions options = AssemblerOptions::Default(isolate);
options.inline_offheap_trampolines = true;
options.use_pc_relative_calls_and_jumps = false;
options.builtin_call_jump_mode = BuiltinCallJumpMode::kAbsolute;
options.isolate_independent_code = false;
FunctionTester ft(asm_tester.GenerateCode(options), kNumParams);
MaybeHandle<Object> result = ft.Call(CcTest::MakeString("abcdef"));
CHECK(String::Equals(isolate, CcTest::MakeString("abcdefabcdef"),
Handle<String>::cast(result.ToHandleChecked())));
}
DISABLED_TEST(TestCallBuiltinPCRelative) {
Isolate* isolate(CcTest::InitIsolateOnce());
if (!isolate->is_short_builtin_calls_enabled()) return;
const int kNumParams = 1;
CodeAssemblerTester asm_tester(isolate, kNumParams);
CodeStubAssembler m(asm_tester.state());
const int kContextOffset = 2;
auto str = m.Parameter<String>(0);
auto context = m.Parameter<Context>(kNumParams + kContextOffset);
TNode<Smi> index = m.SmiConstant(2);
m.Return(m.CallStub(Builtins::CallableFor(isolate, Builtin::kStringRepeat),
context, str, index));
AssemblerOptions options = AssemblerOptions::Default(isolate);
options.builtin_call_jump_mode = BuiltinCallJumpMode::kPCRelative;
options.isolate_independent_code = false;
FunctionTester ft(asm_tester.GenerateCode(options), kNumParams);
MaybeHandle<Object> result = ft.Call(CcTest::MakeString("abcdef"));
......@@ -3920,7 +3944,7 @@ TEST(TestCallBuiltinInlineTrampoline) {
// TODO(v8:9821): Remove the option to disable inlining off-heap trampolines
// along with this test.
DISABLED_TEST(TestCallBuiltinIndirectLoad) {
DISABLED_TEST(TestCallBuiltinIndirect) {
Isolate* isolate(CcTest::InitIsolateOnce());
const int kNumParams = 1;
CodeAssemblerTester asm_tester(isolate, kNumParams);
......@@ -3935,8 +3959,7 @@ DISABLED_TEST(TestCallBuiltinIndirectLoad) {
m.Return(m.CallStub(Builtins::CallableFor(isolate, Builtin::kStringRepeat),
context, str, index));
AssemblerOptions options = AssemblerOptions::Default(isolate);
options.inline_offheap_trampolines = false;
options.use_pc_relative_calls_and_jumps = false;
options.builtin_call_jump_mode = BuiltinCallJumpMode::kIndirect;
options.isolate_independent_code = true;
FunctionTester ft(asm_tester.GenerateCode(options), kNumParams);
MaybeHandle<Object> result = ft.Call(CcTest::MakeString("abcdef"));
......
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