blob: c812533dd82132850158c9eca5f6db0e30e2fd51 [file]
// Copyright 2012 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <climits>
#include <cstdint>
#include "src/sandbox/js-dispatch-table.h"
#if V8_TARGET_ARCH_X64
#include <optional>
#include "src/base/bits.h"
#include "src/base/division-by-constant.h"
#include "src/base/utils/random-number-generator.h"
#include "src/builtins/builtins-inl.h"
#include "src/codegen/callable.h"
#include "src/codegen/code-factory.h"
#include "src/codegen/cpu-features.h"
#include "src/codegen/external-reference-table.h"
#include "src/codegen/interface-descriptors-inl.h"
#include "src/codegen/macro-assembler.h"
#include "src/codegen/register-configuration.h"
#include "src/codegen/register.h"
#include "src/codegen/x64/assembler-x64.h"
#include "src/codegen/x64/register-x64.h"
#include "src/common/globals.h"
#include "src/debug/debug.h"
#include "src/deoptimizer/deoptimizer.h"
#include "src/execution/frames-inl.h"
#include "src/heap/mutable-page.h"
#include "src/init/bootstrapper.h"
#include "src/logging/counters.h"
#include "src/objects/instance-type-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/smi.h"
#include "src/sandbox/external-pointer.h"
#include "src/snapshot/snapshot.h"
// Satisfy cpplint check, but don't include platform-specific header. It is
// included recursively via macro-assembler.h.
#if 0
#include "src/codegen/x64/macro-assembler-x64.h"
#endif
#define __ ACCESS_MASM(masm)
namespace v8 {
namespace internal {
Operand StackArgumentsAccessor::GetArgumentOperand(int index) const {
DCHECK_GE(index, 0);
// arg[0] = rsp + kPCOnStackSize;
// arg[i] = arg[0] + i * kSystemPointerSize;
return Operand(rsp, kPCOnStackSize + index * kSystemPointerSize);
}
void MacroAssembler::CodeEntry() {
endbr64();
}
void MacroAssembler::ExceptionHandler() {
CodeEntry();
// Exception handlers are always invoked in sandboxed execution mode.
AssertInSandboxedExecutionMode();
// In case we're currently assembling the code of an unsandboxed builtin
// (e.g. RunMicrotasks), we now need to exit sandboxed execution mode.
if (sandboxing_mode() == CodeSandboxingMode::kUnsandboxed) {
ExitSandbox();
}
}
void MacroAssembler::Load(Register destination, ExternalReference source) {
if (root_array_available_ && options().enable_root_relative_access) {
intptr_t delta = RootRegisterOffsetForExternalReference(isolate(), source);
if (is_int32(delta)) {
movq(destination, Operand(kRootRegister, static_cast<int32_t>(delta)));
return;
}
}
// Safe code.
if (destination == rax && !options().isolate_independent_code) {
load_rax(source);
} else {
movq(destination, ExternalReferenceAsOperand(source));
}
}
void MacroAssembler::Store(ExternalReference destination, Register source) {
if (root_array_available_ && options().enable_root_relative_access) {
intptr_t delta =
RootRegisterOffsetForExternalReference(isolate(), destination);
if (is_int32(delta)) {
movq(Operand(kRootRegister, static_cast<int32_t>(delta)), source);
return;
}
}
// Safe code.
if (source == rax && !options().isolate_independent_code) {
store_rax(destination);
} else {
movq(ExternalReferenceAsOperand(destination), source);
}
}
void MacroAssembler::LoadFromConstantsTable(Register destination,
int constant_index) {
DCHECK(RootsTable::IsImmortalImmovable(RootIndex::kBuiltinsConstantsTable));
LoadRoot(destination, RootIndex::kBuiltinsConstantsTable);
LoadTaggedField(
destination,
FieldOperand(destination, FixedArray::OffsetOfElementAt(constant_index)));
}
void MacroAssembler::LoadRootRegisterOffset(Register destination,
intptr_t offset) {
DCHECK(is_int32(offset));
if (offset == 0) {
Move(destination, kRootRegister);
} else {
leaq(destination, Operand(kRootRegister, static_cast<int32_t>(offset)));
}
}
void MacroAssembler::LoadRootRelative(Register destination, int32_t offset) {
movq(destination, Operand(kRootRegister, offset));
}
void MacroAssembler::StoreRootRelative(int32_t offset, Register value) {
movq(Operand(kRootRegister, offset), value);
}
void MacroAssembler::LoadAddress(Register destination,
ExternalReference source) {
if (root_array_available()) {
if (source.IsIsolateFieldId()) {
leaq(destination,
Operand(kRootRegister, source.offset_from_root_register()));
return;
}
if (options().enable_root_relative_access) {
intptr_t delta =
RootRegisterOffsetForExternalReference(isolate(), source);
if (is_int32(delta)) {
leaq(destination, Operand(kRootRegister, static_cast<int32_t>(delta)));
return;
}
} else if (options().isolate_independent_code) {
IndirectLoadExternalReference(destination, source);
return;
}
}
Move(destination, source);
}
Operand MacroAssembler::ExternalReferenceAsOperand(ExternalReference reference,
Register scratch) {
if (root_array_available()) {
if (reference.IsIsolateFieldId()) {
return Operand(kRootRegister, reference.offset_from_root_register());
}
if (options().enable_root_relative_access) {
int64_t delta =
RootRegisterOffsetForExternalReference(isolate(), reference);
if (is_int32(delta)) {
return Operand(kRootRegister, static_cast<int32_t>(delta));
}
}
if (options().isolate_independent_code) {
if (IsAddressableThroughRootRegister(isolate(), reference)) {
// Some external references can be efficiently loaded as an offset from
// kRootRegister.
intptr_t offset =
RootRegisterOffsetForExternalReference(isolate(), reference);
CHECK(is_int32(offset));
return Operand(kRootRegister, static_cast<int32_t>(offset));
} else {
// Otherwise, do a memory load from the external reference table.
movq(scratch, Operand(kRootRegister,
RootRegisterOffsetForExternalReferenceTableEntry(
isolate(), reference)));
return Operand(scratch, 0);
}
}
}
Move(scratch, reference);
return Operand(scratch, 0);
}
void MacroAssembler::PushAddress(ExternalReference source, Register scratch) {
LoadAddress(scratch, source);
Push(scratch);
}
Operand MacroAssembler::RootAsOperand(RootIndex index) {
DCHECK(root_array_available());
return Operand(kRootRegister, RootRegisterOffsetForRootIndex(index));
}
void MacroAssembler::LoadTaggedRoot(Register destination, RootIndex index) {
static_assert(!CanBeImmediate(RootIndex::kUndefinedValue) ||
std::is_same_v<Tagged_t, uint32_t>);
if (CanBeImmediate(index)) {
mov_tagged(destination,
Immediate(static_cast<uint32_t>(ReadOnlyRootPtr(index))));
return;
}
DCHECK(root_array_available_);
movq(destination, RootAsOperand(index));
}
void MacroAssembler::LoadRoot(Register destination, RootIndex index) {
if (CanBeImmediate(index)) {
DecompressTagged(destination,
static_cast<uint32_t>(ReadOnlyRootPtr(index)));
return;
}
DCHECK(root_array_available_);
movq(destination, RootAsOperand(index));
}
void MacroAssembler::PushRoot(RootIndex index) {
DCHECK(root_array_available_);
Push(RootAsOperand(index));
}
void MacroAssembler::CompareRoot(Register with, RootIndex index,
ComparisonMode mode) {
if (mode == ComparisonMode::kFullPointer ||
!base::IsInRange(index, RootIndex::kFirstStrongOrReadOnlyRoot,
RootIndex::kLastStrongOrReadOnlyRoot)) {
// Some smi roots contain system pointer size values like stack limits.
cmpq(with, RootAsOperand(index));
return;
}
CompareTaggedRoot(with, index);
}
void MacroAssembler::CompareTaggedRoot(Register with, RootIndex index) {
AssertSmiOrHeapObjectInMainCompressionCage(with);
if (CanBeImmediate(index)) {
cmp_tagged(with, Immediate(static_cast<uint32_t>(ReadOnlyRootPtr(index))));
return;
}
DCHECK(root_array_available_);
// Some smi roots contain system pointer size values like stack limits.
DCHECK(base::IsInRange(index, RootIndex::kFirstStrongOrReadOnlyRoot,
RootIndex::kLastStrongOrReadOnlyRoot));
cmp_tagged(with, RootAsOperand(index));
}
void MacroAssembler::CompareRoot(Operand with, RootIndex index) {
if (CanBeImmediate(index)) {
cmp_tagged(with, Immediate(static_cast<uint32_t>(ReadOnlyRootPtr(index))));
return;
}
DCHECK(root_array_available_);
DCHECK(!with.AddressUsesRegister(kScratchRegister));
if (base::IsInRange(index, RootIndex::kFirstStrongOrReadOnlyRoot,
RootIndex::kLastStrongOrReadOnlyRoot)) {
mov_tagged(kScratchRegister, RootAsOperand(index));
cmp_tagged(with, kScratchRegister);
} else {
// Some smi roots contain system pointer size values like stack limits.
movq(kScratchRegister, RootAsOperand(index));
cmpq(with, kScratchRegister);
}
}
void MacroAssembler::LoadCompressedMap(Register destination, Register object) {
CHECK(COMPRESS_POINTERS_BOOL);
mov_tagged(destination, FieldOperand(object, HeapObject::kMapOffset));
}
void MacroAssembler::LoadMap(Register destination, Register object) {
LoadTaggedField(destination, FieldOperand(object, HeapObject::kMapOffset));
#ifdef V8_MAP_PACKING
UnpackMapWord(destination);
#endif
}
void MacroAssembler::LoadFeedbackVector(Register dst, Register closure,
Label* fbv_undef,
Label::Distance distance) {
Label done;
// Load the feedback vector from the closure.
TaggedRegister feedback_cell(dst);
LoadTaggedField(feedback_cell,
FieldOperand(closure, JSFunction::kFeedbackCellOffset));
LoadTaggedField(dst, FieldOperand(feedback_cell, FeedbackCell::kValueOffset));
// Check if feedback vector is valid.
IsObjectType(dst, FEEDBACK_VECTOR_TYPE, rcx);
j(equal, &done, Label::kNear);
// Not valid, load undefined.
LoadRoot(dst, RootIndex::kUndefinedValue);
jmp(fbv_undef, distance);
bind(&done);
}
void MacroAssembler::LoadInterpreterDataBytecodeArray(
Register destination, Register interpreter_data) {
LoadProtectedPointerField(
destination, FieldOperand(interpreter_data,
offsetof(InterpreterData, bytecode_array_)));
}
void MacroAssembler::LoadInterpreterDataInterpreterTrampoline(
Register destination, Register interpreter_data) {
LoadProtectedPointerField(
destination,
FieldOperand(interpreter_data,
offsetof(InterpreterData, interpreter_trampoline_)));
}
void MacroAssembler::LoadTaggedField(Register destination,
Operand field_operand) {
if (COMPRESS_POINTERS_BOOL) {
DecompressTagged(destination, field_operand);
} else {
mov_tagged(destination, field_operand);
}
}
void MacroAssembler::LoadTaggedField(TaggedRegister destination,
Operand field_operand) {
LoadTaggedFieldWithoutDecompressing(destination.reg(), field_operand);
}
void MacroAssembler::LoadTaggedFieldWithoutDecompressing(
Register destination, Operand field_operand) {
mov_tagged(destination, field_operand);
}
#ifdef V8_MAP_PACKING
void MacroAssembler::UnpackMapWord(Register r) {
// Clear the top two bytes (which may include metadata). Must be in sync with
// MapWord::Unpack, and vice versa.
shlq(r, Immediate(16));
shrq(r, Immediate(16));
xorq(r, Immediate(Internals::kMapWordXorMask));
}
#endif
void MacroAssembler::LoadTaggedSignedField(Register destination,
Operand field_operand) {
if (COMPRESS_POINTERS_BOOL) {
DecompressTaggedSigned(destination, field_operand);
} else {
mov_tagged(destination, field_operand);
}
}
void MacroAssembler::PushTaggedField(Operand field_operand, Register scratch) {
if (COMPRESS_POINTERS_BOOL) {
DCHECK(!field_operand.AddressUsesRegister(scratch));
DecompressTagged(scratch, field_operand);
Push(scratch);
} else {
Push(field_operand);
}
}
void MacroAssembler::SmiUntagField(Register dst, Operand src) {
SmiUntag(dst, src);
}
void MacroAssembler::SmiUntagFieldUnsigned(Register dst, Operand src) {
SmiUntagUnsigned(dst, src);
}
void MacroAssembler::StoreTaggedField(Operand dst_field_operand,
Immediate value) {
if (COMPRESS_POINTERS_BOOL) {
movl(dst_field_operand, value);
} else {
movq(dst_field_operand, value);
}
}
void MacroAssembler::StoreTaggedField(Operand dst_field_operand,
Register value) {
if (COMPRESS_POINTERS_BOOL) {
movl(dst_field_operand, value);
} else {
movq(dst_field_operand, value);
}
}
void MacroAssembler::StoreTaggedSignedField(Operand dst_field_operand,
Tagged<Smi> value) {
if (SmiValuesAre32Bits()) {
Move(kScratchRegister, value);
movq(dst_field_operand, kScratchRegister);
} else {
StoreTaggedField(dst_field_operand, Immediate(value));
}
}
void MacroAssembler::AtomicStoreTaggedField(Operand dst_field_operand,
Register value) {
if (COMPRESS_POINTERS_BOOL) {
movl(kScratchRegister, value);
xchgl(kScratchRegister, dst_field_operand);
} else {
movq(kScratchRegister, value);
xchgq(kScratchRegister, dst_field_operand);
}
}
void MacroAssembler::DecompressTaggedSigned(Register destination,
Operand field_operand) {
ASM_CODE_COMMENT(this);
movl(destination, field_operand);
}
void MacroAssembler::DecompressTagged(Register destination,
Operand field_operand) {
ASM_CODE_COMMENT(this);
movl(destination, field_operand);
addq(destination, kPtrComprCageBaseRegister);
}
void MacroAssembler::DecompressTagged(Register destination, Register source) {
ASM_CODE_COMMENT(this);
movl(destination, source);
addq(destination, kPtrComprCageBaseRegister);
}
void MacroAssembler::DecompressTagged(Register destination,
Tagged_t immediate) {
ASM_CODE_COMMENT(this);
leaq(destination,
Operand(kPtrComprCageBaseRegister, static_cast<int32_t>(immediate)));
}
void MacroAssembler::DecompressProtected(Register destination,
Operand field_operand) {
#if V8_ENABLE_SANDBOX
ASM_CODE_COMMENT(this);
movl(destination, field_operand);
DCHECK(root_array_available_);
orq(destination,
Operand{kRootRegister, IsolateData::trusted_cage_base_offset()});
#else
UNREACHABLE();
#endif // V8_ENABLE_SANDBOX
}
void MacroAssembler::RecordWriteField(Register object, int offset,
Register value, Register slot_address,
SaveFPRegsMode save_fp,
SmiCheck smi_check,
ReadOnlyCheck ro_check,
SlotDescriptor slot) {
ASM_CODE_COMMENT(this);
DCHECK(!AreAliased(object, value, slot_address));
// First, check if a write barrier is even needed. The tests below
// catch stores of Smis and read-only objects.
Label done;
if (ro_check == ReadOnlyCheck::kInline) {
MaybeJumpIfReadOnlyOrSmallSmi(value, &done);
}
// Skip barrier if writing a smi.
if (smi_check == SmiCheck::kInline) {
JumpIfSmi(value, &done);
}
// Although the object register is tagged, the offset is relative to the start
// of the object, so the offset must be a multiple of kTaggedSize.
DCHECK(IsAligned(offset, kTaggedSize));
leaq(slot_address, FieldOperand(object, offset));
if (v8_flags.slow_debug_code) {
ASM_CODE_COMMENT_STRING(this, "Debug check slot_address");
Label ok;
testb(slot_address, Immediate(kTaggedSize - 1));
j(zero, &ok, Label::kNear);
int3();
bind(&ok);
}
RecordWrite(object, slot_address, value, save_fp, SmiCheck::kOmit,
ReadOnlyCheck::kOmit, slot);
bind(&done);
// Clobber clobbered input registers when running with the debug-code flag
// turned on to provoke errors.
if (v8_flags.slow_debug_code) {
ASM_CODE_COMMENT_STRING(this, "Zap scratch registers");
Move(value, kZapValue, RelocInfo::NO_INFO);
Move(slot_address, kZapValue, RelocInfo::NO_INFO);
}
}
void MacroAssembler::EnterSandbox() {
#ifdef V8_ENABLE_SANDBOX_HARDWARE_SUPPORT
pushq(rax);
pushq(rbx);
pushq(rcx);
pushq(rdx);
xorq(rcx, rcx);
xorq(rdx, rdx);
// TODO(350324877): it would be nicer if we could use an IsolateFieldId here.
// However, that isn't currently possible since these routines are also used
// in code that doesn't have the root register available. In the future, we
// might anyway want to refactor this mechanism to instead use dedicated
// trampoline builtins for entering/exiting sandboxed execution mode. Then we
// could consider generating the code for that trampoline at runtime, at
// which point we would no longer need an external reference at all.
LoadAddress(rbx, ExternalReference::sandboxed_mode_pkey_mask_address());
movl(rbx, Operand(rbx, 0));
if (v8_flags.debug_code) {
// Check that we are not in sandboxed mode.
// Avoid calling the Abort builtin here as that would again Assert that the
// sandboxing mode is as expected, leading to recursive aborts.
HardAbortScope hard_abort(this);
// If sandbox hardware support is not active, the mask will be all zeroes
// and so this test will also pass.
rdpkru();
andl(rax, rbx);
Assert(zero, AbortReason::kUnexpectedSandboxMode);
}
// PKEY permissions bits:
// 00: kNoRestrictions
// 01: kDisableAccess
// 10: kDisableWrite
rdpkru();
// Set the bit from the pkey mask, thereby restricting access to the
// out-of-sandbox pkey.
orl(rax, rbx);
wrpkru();
popq(rdx);
popq(rcx);
popq(rbx);
popq(rax);
#endif // V8_ENABLE_SANDBOX_HARDWARE_SUPPORT
}
void MacroAssembler::ExitSandbox() {
#ifdef V8_ENABLE_SANDBOX_HARDWARE_SUPPORT
pushq(rax);
pushq(rbx);
pushq(rcx);
pushq(rdx);
xorq(rcx, rcx);
xorq(rdx, rdx);
LoadAddress(rbx, ExternalReference::sandboxed_mode_pkey_mask_address());
movl(rbx, Operand(rbx, 0));
if (v8_flags.debug_code) {
// Check that we are in sandboxed mode.
// Avoid calling the Abort builtin here as that would again Assert that the
// sandboxing mode is as expected, leading to recursive aborts.
HardAbortScope hard_abort(this);
// If sandbox hardware support is not active, the mask will be all zeroes
// and so we need to handle this here.
Label hardware_support_not_active;
testl(rbx, rbx);
j(zero, &hardware_support_not_active);
rdpkru();
andl(rax, rbx);
Assert(not_zero, AbortReason::kUnexpectedSandboxMode);
bind(&hardware_support_not_active);
}
// PKEY permissions bits:
// 00: kNoRestrictions
// 01: kDisableAccess
// 10: kDisableWrite
rdpkru();
// Clear the bit from the pkey mask, thereby restoring full access to the
// out-of-sandbox pkey.
notl(rbx);
andl(rax, rbx);
wrpkru();
popq(rdx);
popq(rcx);
popq(rbx);
popq(rax);
#endif // V8_ENABLE_SANDBOX_HARDWARE_SUPPORT
}
void MacroAssembler::AssertInSandboxedExecutionMode() {
#ifdef V8_ENABLE_SANDBOX_HARDWARE_SUPPORT
if (v8_flags.debug_code) {
// Avoid calling the Abort builtin here as that would again Assert that the
// sandboxing mode is as expected, leading to recursive aborts.
HardAbortScope hard_abort(this);
pushq(rax);
pushq(rbx);
pushq(rcx);
pushq(rdx);
xorq(rcx, rcx);
xorq(rdx, rdx);
LoadAddress(rbx, ExternalReference::sandboxed_mode_pkey_mask_address());
movl(rbx, Operand(rbx, 0));
// If sandbox hardware support is not active, the mask will be all zeroes
// and so we need to handle this here.
Label hardware_support_not_active;
testl(rbx, rbx);
j(zero, &hardware_support_not_active);
rdpkru();
andl(rax, rbx);
Assert(not_zero, AbortReason::kUnexpectedSandboxMode);
bind(&hardware_support_not_active);
popq(rdx);
popq(rcx);
popq(rbx);
popq(rax);
}
#endif // V8_ENABLE_SANDBOX_HARDWARE_SUPPORT
}
void MacroAssembler::SwitchSandboxingModeTo(CodeSandboxingMode mode) {
switch (mode) {
case CodeSandboxingMode::kSandboxed:
return EnterSandbox();
case CodeSandboxingMode::kUnsandboxed:
return ExitSandbox();
}
}
CodeSandboxingMode MacroAssembler::SwitchSandboxingModeBeforeCallIfNeeded(
CodeSandboxingMode target_sandboxing_mode) {
CodeSandboxingMode previous_sandboxing_mode = sandboxing_mode();
if (sandboxing_mode() != target_sandboxing_mode) {
SwitchSandboxingModeTo(target_sandboxing_mode);
sandboxing_mode_ = target_sandboxing_mode;
}
return previous_sandboxing_mode;
}
void MacroAssembler::SwitchSandboxingModeAfterCallIfNeeded(
CodeSandboxingMode previous_sandboxing_mode) {
if (sandboxing_mode() != previous_sandboxing_mode) {
SwitchSandboxingModeTo(previous_sandboxing_mode);
sandboxing_mode_ = previous_sandboxing_mode;
}
}
void MacroAssembler::EncodeSandboxedPointer(Register value) {
ASM_CODE_COMMENT(this);
#ifdef V8_ENABLE_SANDBOX
subq(value, kPtrComprCageBaseRegister);
shlq(value, Immediate(kSandboxedPointerShift));
#else
UNREACHABLE();
#endif
}
void MacroAssembler::DecodeSandboxedPointer(Register value) {
ASM_CODE_COMMENT(this);
#ifdef V8_ENABLE_SANDBOX
shrq(value, Immediate(kSandboxedPointerShift));
addq(value, kPtrComprCageBaseRegister);
#else
UNREACHABLE();
#endif
}
void MacroAssembler::LoadSandboxedPointerField(Register destination,
Operand field_operand) {
ASM_CODE_COMMENT(this);
movq(destination, field_operand);
DecodeSandboxedPointer(destination);
}
void MacroAssembler::StoreSandboxedPointerField(Operand dst_field_operand,
Register value) {
ASM_CODE_COMMENT(this);
DCHECK(!AreAliased(value, kScratchRegister));
DCHECK(!dst_field_operand.AddressUsesRegister(kScratchRegister));
movq(kScratchRegister, value);
EncodeSandboxedPointer(kScratchRegister);
movq(dst_field_operand, kScratchRegister);
}
void MacroAssembler::LoadExternalPointerField(
Register destination, Operand field_operand,
ExternalPointerTagRange tag_range, Register scratch,
IsolateRootLocation isolateRootLocation) {
DCHECK(!AreAliased(destination, scratch));
#ifdef V8_ENABLE_SANDBOX
DCHECK(!tag_range.IsEmpty());
DCHECK(!IsSharedExternalPointerType(tag_range));
DCHECK(!field_operand.AddressUsesRegister(scratch));
if (isolateRootLocation == IsolateRootLocation::kInRootRegister) {
DCHECK(root_array_available_);
// TODO(saelo): consider using an ExternalReference here.
movq(scratch,
Operand(kRootRegister,
IsolateData::external_pointer_table_offset() +
Internals::kExternalEntityTableBasePointerOffset));
} else {
DCHECK(isolateRootLocation == IsolateRootLocation::kInScratchRegister);
movq(
scratch,
Operand(scratch, IsolateData::external_pointer_table_offset() +
Internals::kExternalEntityTableBasePointerOffset));
}
movl(destination, field_operand);
shrq(destination, Immediate(kExternalPointerIndexShift));
static_assert(kExternalPointerTableEntrySize == 8);
movq(destination, Operand(scratch, destination, times_8, 0));
// We don't expect to see empty fields here. If this is ever needed, consider
// using an dedicated empty value entry for those tags instead (i.e. an entry
// with the right tag and nullptr payload).
// Although interceptor callbacks can be empty in general, once we decide
// to generate a code loading a callback value it's guaranteed that the
// external pointer handle is not empty.
DCHECK(!ExternalPointerCanBeEmpty(tag_range) ||
kAnyInterceptorInfoExternalPointerTagRange.Contains(tag_range));
if (tag_range.Size() == 1) {
// The common and simple case: we expect exactly one tag.
movq(scratch, destination);
shrq(scratch, Immediate(kExternalPointerTagShift));
andl(scratch, Immediate(kExternalPointerShiftedTagMask));
cmpl(scratch, Immediate(tag_range.first));
SbxCheck(equal, AbortReason::kExternalPointerTagMismatch);
movq(scratch, Immediate64(kExternalPointerPayloadMask));
andq(destination, scratch);
} else {
// Not currently supported. Implement once needed.
DCHECK_NE(tag_range, kAnyExternalPointerTagRange);
UNREACHABLE();
}
#else
movq(destination, field_operand);
#endif // V8_ENABLE_SANDBOX
}
void MacroAssembler::LoadTrustedPointerField(Register destination,
Operand field_operand,
IndirectPointerTagRange tag_range,
Register scratch) {
#ifdef V8_ENABLE_SANDBOX
LoadIndirectPointerField(destination, field_operand, tag_range, scratch);
#else
LoadTaggedField(destination, field_operand);
#endif // V8_ENABLE_SANDBOX
}
void MacroAssembler::LoadTrustedUnknownPointerField(
Register destination, Operand field_operand, Register scratch,
const std::initializer_list<
std::tuple<InstanceType, Label*, Label::Distance>>& cases) {
DCHECK(!AreAliased(destination, scratch));
Label done;
#ifdef V8_ENABLE_SANDBOX
{
Register handle = scratch;
movl(handle, field_operand);
bool handles_code_case = false;
for (auto& [type, label, distance] : cases) {
if (type == CODE_TYPE) {
handles_code_case = true;
Label not_code_handle;
testl(handle, Immediate(kCodePointerHandleMarker));
j(zero, &not_code_handle, Label::kNear);
ResolveCodePointerHandle(destination, handle);
jmp(label, distance);
bind(&not_code_handle);
break;
}
}
if (!handles_code_case) {
testl(handle, Immediate(kCodePointerHandleMarker));
j(not_zero, &done, Label::kNear);
}
ResolveTrustedPointerHandle(destination, handle, kAllTrustedPointerTags);
}
#else
LoadTaggedField(destination, field_operand);
#endif // V8_ENABLE_SANDBOX
#if V8_STATIC_ROOTS_BOOL
LoadCompressedMap(scratch, destination);
for (auto& [type, label, distance] : cases) {
if (V8_ENABLE_SANDBOX_BOOL && type == CODE_TYPE) {
continue;
}
CompareInstanceTypeWithUniqueCompressedMap(scratch, type);
j(equal, label, distance);
}
#else
LoadMap(scratch, destination);
for (auto& [type, label, distance] : cases) {
if (V8_ENABLE_SANDBOX_BOOL && type == CODE_TYPE) {
continue;
}
CmpInstanceType(scratch, type);
j(equal, label, distance);
}
#endif // V8_STATIC_ROOTS_BOOL
bind(&done);
xorq(destination, destination);
}
void MacroAssembler::StoreTrustedPointerField(Operand dst_field_operand,
Register value) {
#ifdef V8_ENABLE_SANDBOX
StoreIndirectPointerField(dst_field_operand, value);
#else
StoreTaggedField(dst_field_operand, value);
#endif // V8_ENABLE_SANDBOX
}
void MacroAssembler::LoadIndirectPointerField(Register destination,
Operand field_operand,
IndirectPointerTagRange tag_range,
Register scratch) {
#ifdef V8_ENABLE_SANDBOX
DCHECK(!AreAliased(destination, scratch));
Register handle = scratch;
movl(handle, field_operand);
ResolveIndirectPointerHandle(destination, handle, tag_range);
#else
UNREACHABLE();
#endif // V8_ENABLE_SANDBOX
}
void MacroAssembler::StoreIndirectPointerField(Operand dst_field_operand,
Register value) {
#ifdef V8_ENABLE_SANDBOX
movl(kScratchRegister,
FieldOperand(value, ExposedTrustedObject::kSelfIndirectPointerOffset));
movl(dst_field_operand, kScratchRegister);
#else
UNREACHABLE();
#endif // V8_ENABLE_SANDBOX
}
#ifdef V8_ENABLE_SANDBOX
void MacroAssembler::ResolveIndirectPointerHandle(
Register destination, Register handle, IndirectPointerTagRange tag_range) {
// This function must not be used to resolve kAllIndirectPointerTags. Use
// LoadTrustedUnknownPointerField for that instead.
CHECK_NE(tag_range, kAllIndirectPointerTags);
// The tag implies which pointer table to use.
if (tag_range == kCodeIndirectPointerTag) {
ResolveCodePointerHandle(destination, handle);
} else {
DCHECK(!tag_range.Contains(kCodeIndirectPointerTag));
ResolveTrustedPointerHandle(destination, handle, tag_range);
}
}
void MacroAssembler::ResolveTrustedPointerHandle(
Register destination, Register handle, IndirectPointerTagRange tag_range) {
DCHECK(!tag_range.Contains(kCodeIndirectPointerTag));
DCHECK(!AreAliased(handle, destination));
shrl(handle, Immediate(kTrustedPointerHandleShift));
static_assert(kTrustedPointerTableEntrySize == 8);
DCHECK(root_array_available_);
movq(destination,
Operand{kRootRegister, IsolateData::trusted_pointer_table_offset()});
movq(destination, Operand{destination, handle, times_8, 0});
if (IsFastIndirectPointerTagRange(tag_range)) {
uint64_t mask = ComputeUntaggingMaskForFastIndirectPointerTag(tag_range);
movq(kScratchRegister, Immediate64(mask));
andq(destination, kScratchRegister);
} else {
Register scratch = kScratchRegister;
movq(scratch, destination);
shrq(scratch, Immediate(kTrustedPointerTableTagShift));
Label done;
if (tag_range.Size() == 1) {
cmpl(scratch, Immediate(tag_range.first));
j(equal, &done, Label::kNear);
} else {
subl(scratch, Immediate(tag_range.first));
cmpl(scratch, Immediate(tag_range.last - tag_range.first));
j(below_equal, &done, Label::kNear);
}
xorq(destination, destination);
bind(&done);
shlq(destination, Immediate(16));
shrq(destination, Immediate(16));
}
}
void MacroAssembler::ResolveCodePointerHandle(Register destination,
Register handle) {
DCHECK(!AreAliased(handle, destination));
Register table = destination;
LoadCodePointerTableBase(table);
shrl(handle, Immediate(kCodePointerHandleShift));
// The code pointer table entry size is 16 bytes, so we have to do an
// explicit shift first (times_16 doesn't exist).
shll(handle, Immediate(kCodePointerTableEntrySizeLog2));
movq(destination,
Operand(table, handle, times_1, kCodePointerTableEntryCodeObjectOffset));
// The LSB is used as marking bit by the code pointer table, so here we have
// to set it using a bitwise OR as it may or may not be set.
orq(destination, Immediate(kHeapObjectTag));
}
void MacroAssembler::LoadCodeEntrypointViaCodePointer(Register destination,
Operand field_operand,
CodeEntrypointTag tag) {
DCHECK(!AreAliased(destination, kScratchRegister));
DCHECK(!field_operand.AddressUsesRegister(kScratchRegister));
DCHECK_NE(tag, kInvalidEntrypointTag);
LoadCodePointerTableBase(kScratchRegister);
movl(destination, field_operand);
shrl(destination, Immediate(kCodePointerHandleShift));
shll(destination, Immediate(kCodePointerTableEntrySizeLog2));
movq(destination, Operand(kScratchRegister, destination, times_1,
kCodePointerTableEntryEntrypointOffset));
if (tag != 0) {
// Can this be improved?
movq(kScratchRegister, Immediate64(tag));
xorq(destination, kScratchRegister);
}
}
void MacroAssembler::LoadCodePointerTableBase(Register destination) {
#ifdef V8_COMPRESS_POINTERS_IN_MULTIPLE_CAGES
if (!options().isolate_independent_code && isolate()) {
// Embed the code pointer table address into the code.
LoadAddress(destination,
ExternalReference::code_pointer_table_base_address(isolate()));
} else {
// Force indirect load via root register as a workaround for
// isolate-independent code (for example, for Wasm).
Load(destination,
ExternalReference::address_of_code_pointer_table_base_address());
}
#else
// Embed the code pointer table address into the code.
LoadAddress(destination,
ExternalReference::global_code_pointer_table_base_address());
#endif // V8_COMPRESS_POINTERS_IN_MULTIPLE_CAGES
}
#endif // V8_ENABLE_SANDBOX
void MacroAssembler::LoadEntrypointFromJSDispatchTable(
Register destination, Register dispatch_handle) {
DCHECK(!AreAliased(destination, dispatch_handle, kScratchRegister));
CHECK(root_array_available());
movq(kScratchRegister,
ExternalReferenceAsOperand(IsolateFieldId::kJSDispatchTable));
movq(destination, dispatch_handle);
shrl(destination, Immediate(kJSDispatchHandleShift));
shll(destination, Immediate(kJSDispatchTableEntrySizeLog2));
movq(destination, Operand(kScratchRegister, destination, times_1,
JSDispatchEntry::kEntrypointOffset));
}
void MacroAssembler::LoadParameterCountFromJSDispatchTable(
Register destination, Register dispatch_handle) {
DCHECK(!AreAliased(destination, dispatch_handle, kScratchRegister));
CHECK(root_array_available());
movq(kScratchRegister,
ExternalReferenceAsOperand(IsolateFieldId::kJSDispatchTable));
movq(destination, dispatch_handle);
shrl(destination, Immediate(kJSDispatchHandleShift));
shll(destination, Immediate(kJSDispatchTableEntrySizeLog2));
static_assert(JSDispatchEntry::kParameterCountMask == 0xffff);
movzxwq(destination, Operand(kScratchRegister, destination, times_1,
JSDispatchEntry::kCodeObjectOffset));
}
void MacroAssembler::LoadEntrypointAndParameterCountFromJSDispatchTable(
Register entrypoint, Register parameter_count, Register dispatch_handle) {
DCHECK(!AreAliased(entrypoint, parameter_count, dispatch_handle,
kScratchRegister));
CHECK(root_array_available());
movq(kScratchRegister,
ExternalReferenceAsOperand(IsolateFieldId::kJSDispatchTable));
Register offset = parameter_count;
movq(offset, dispatch_handle);
shrl(offset, Immediate(kJSDispatchHandleShift));
shll(offset, Immediate(kJSDispatchTableEntrySizeLog2));
movq(entrypoint, Operand(kScratchRegister, offset, times_1,
JSDispatchEntry::kEntrypointOffset));
static_assert(JSDispatchEntry::kParameterCountMask == 0xffff);
movzxwq(parameter_count, Operand(kScratchRegister, offset, times_1,
JSDispatchEntry::kCodeObjectOffset));
}
void MacroAssembler::LoadProtectedPointerField(Register destination,
Operand field_operand) {
DCHECK(root_array_available());
#ifdef V8_ENABLE_SANDBOX
DecompressProtected(destination, field_operand);
#else
LoadTaggedField(destination, field_operand);
#endif
}
void MacroAssembler::CallEphemeronKeyBarrier(Register object,
Register slot_address,
SaveFPRegsMode fp_mode) {
ASM_CODE_COMMENT(this);
DCHECK(!AreAliased(object, slot_address));
RegList registers =
WriteBarrierDescriptor::ComputeSavedRegisters(object, slot_address);
PushAll(registers);
Register object_parameter = WriteBarrierDescriptor::ObjectRegister();
Register slot_address_parameter =
WriteBarrierDescriptor::SlotAddressRegister();
MovePair(slot_address_parameter, slot_address, object_parameter, object);
CallBuiltin(Builtins::EphemeronKeyBarrier(fp_mode));
PopAll(registers);
}
void MacroAssembler::CallIndirectPointerBarrier(Register object,
Register slot_address,
SaveFPRegsMode fp_mode,
IndirectPointerTag tag) {
ASM_CODE_COMMENT(this);
DCHECK(!AreAliased(object, slot_address));
// TODO(saelo) if necessary, we could introduce a "SaveRegisters version of
// this function and make this code not save clobbered registers. It's
// probably not currently worth the effort though since stores to indirect
// pointer fields are fairly rare.
RegList registers =
IndirectPointerWriteBarrierDescriptor::ComputeSavedRegisters(
object, slot_address);
PushAll(registers);
Register object_parameter =
IndirectPointerWriteBarrierDescriptor::ObjectRegister();
Register slot_address_parameter =
IndirectPointerWriteBarrierDescriptor::SlotAddressRegister();
MovePair(slot_address_parameter, slot_address, object_parameter, object);
Register tag_parameter =
IndirectPointerWriteBarrierDescriptor::IndirectPointerTagRegister();
Move(tag_parameter, tag);
CallBuiltin(Builtins::IndirectPointerBarrier(fp_mode));
PopAll(registers);
}
void MacroAssembler::CallRecordWriteStubSaveRegisters(Register object,
Register slot_address,
SaveFPRegsMode fp_mode,
StubCallMode mode) {
ASM_CODE_COMMENT(this);
DCHECK(!AreAliased(object, slot_address));
RegList registers =
WriteBarrierDescriptor::ComputeSavedRegisters(object, slot_address);
PushAll(registers);
Register object_parameter = WriteBarrierDescriptor::ObjectRegister();
Register slot_address_parameter =
WriteBarrierDescriptor::SlotAddressRegister();
MovePair(object_parameter, object, slot_address_parameter, slot_address);
CallRecordWriteStub(object_parameter, slot_address_parameter, fp_mode, mode);
PopAll(registers);
}
void MacroAssembler::CallRecordWriteStub(Register object, Register slot_address,
SaveFPRegsMode fp_mode,
StubCallMode mode) {
ASM_CODE_COMMENT(this);
// Use CallRecordWriteStubSaveRegisters if the object and slot registers
// need to be caller saved.
DCHECK_EQ(WriteBarrierDescriptor::ObjectRegister(), object);
DCHECK_EQ(WriteBarrierDescriptor::SlotAddressRegister(), slot_address);
#if V8_ENABLE_WEBASSEMBLY
if (mode == StubCallMode::kCallWasmRuntimeStub) {
// Use {near_call} for direct Wasm call within a module.
Builtin wasm_target = wasm::WasmCode::GetRecordWriteBuiltin(fp_mode);
// TODO(429142815): replace with a DCHECK_EQ(sandboxing_mode(),
// target_sandboxing_mode()) once write barrier builtins can run in
// sandboxed execution mode.
CodeSandboxingMode previous_mode = SwitchSandboxingModeBeforeCallIfNeeded(
Builtins::SandboxingModeOf(wasm_target));
near_call(static_cast<intptr_t>(wasm_target), RelocInfo::WASM_STUB_CALL);
SwitchSandboxingModeAfterCallIfNeeded(previous_mode);
#else
if (false) {
#endif
} else {
CallBuiltin(Builtins::RecordWrite(fp_mode));
}
}
void MacroAssembler::CallVerifySkippedWriteBarrierStubSaveRegisters(
Register object, Register value, SaveFPRegsMode fp_mode) {
ASM_CODE_COMMENT(this);
PushCallerSaved(fp_mode);
CallVerifySkippedWriteBarrierStub(object, value);
PopCallerSaved(fp_mode);
}
void MacroAssembler::CallVerifySkippedWriteBarrierStub(Register object,
Register value) {
ASM_CODE_COMMENT(this);
MovePair(kCArgRegs[0], object, kCArgRegs[1], value);
PrepareCallCFunction(2);
CallCFunction(ExternalReference::verify_skipped_write_barrier(), 2);
}
void MacroAssembler::CallVerifySkippedIndirectWriteBarrierStubSaveRegisters(
Register object, Register value, SaveFPRegsMode fp_mode) {
ASM_CODE_COMMENT(this);
PushCallerSaved(fp_mode);
CallVerifySkippedIndirectWriteBarrierStub(object, value);
PopCallerSaved(fp_mode);
}
void MacroAssembler::CallVerifySkippedIndirectWriteBarrierStub(Register object,
Register value) {
ASM_CODE_COMMENT(this);
MovePair(kCArgRegs[0], object, kCArgRegs[1], value);
PrepareCallCFunction(2);
CallCFunction(ExternalReference::verify_skipped_indirect_write_barrier(), 2);
}
#ifdef V8_IS_TSAN
void MacroAssembler::CallTSANStoreStub(Register address, Register value,
SaveFPRegsMode fp_mode, int size,
StubCallMode mode,
std::memory_order order) {
ASM_CODE_COMMENT(this);
DCHECK(!AreAliased(address, value));
TSANStoreDescriptor descriptor;
RegList registers = descriptor.allocatable_registers();
PushAll(registers);
Register address_parameter(
descriptor.GetRegisterParameter(TSANStoreDescriptor::kAddress));
Register value_parameter(
descriptor.GetRegisterParameter(TSANStoreDescriptor::kValue));
// Prepare argument registers for calling GetTSANStoreStub.
MovePair(address_parameter, address, value_parameter, value);
#if V8_ENABLE_WEBASSEMBLY
if (mode != StubCallMode::kCallWasmRuntimeStub) {
// JS functions and Wasm wrappers.
CallBuiltin(CodeFactory::GetTSANStoreStub(fp_mode, size, order));
} else {
// Wasm functions should call builtins through their far jump table.
auto wasm_target = static_cast<intptr_t>(
wasm::WasmCode::GetTSANStoreBuiltin(fp_mode, size, order));
near_call(wasm_target, RelocInfo::WASM_STUB_CALL);
}
#else
CallBuiltin(CodeFactory::GetTSANStoreStub(fp_mode, size, order));
#endif // V8_ENABLE_WEBASSEMBLY
PopAll(registers);
}
void MacroAssembler::CallTSANRelaxedLoadStub(Register address,
SaveFPRegsMode fp_mode, int size,
StubCallMode mode) {
TSANLoadDescriptor descriptor;
RegList registers = descriptor.allocatable_registers();
PushAll(registers);
Register address_parameter(
descriptor.GetRegisterParameter(TSANLoadDescriptor::kAddress));
// Prepare argument registers for calling TSANRelaxedLoad.
Move(address_parameter, address);
#if V8_ENABLE_WEBASSEMBLY
if (mode != StubCallMode::kCallWasmRuntimeStub) {
// JS functions and Wasm wrappers.
CallBuiltin(CodeFactory::GetTSANRelaxedLoadStub(fp_mode, size));
} else {
// Wasm functions should call builtins through their far jump table.
auto wasm_target = static_cast<intptr_t>(
wasm::WasmCode::GetTSANRelaxedLoadBuiltin(fp_mode, size));
near_call(wasm_target, RelocInfo::WASM_STUB_CALL);
}
#else
CallBuiltin(CodeFactory::GetTSANRelaxedLoadStub(fp_mode, size));
#endif // V8_ENABLE_WEBASSEMBLY
PopAll(registers);
}
#endif // V8_IS_TSAN
void MacroAssembler::MaybeJumpIfReadOnlyOrSmallSmi(Register value,
Label* dest) {
#if V8_STATIC_ROOTS_BOOL && CONTIGUOUS_COMPRESSED_READ_ONLY_SPACE_BOOL
// Quick check for Read-only and small Smi values.
// This optimization requires contiguous compressed RO space to ensure RO
// space is at the beginning of the cage; otherwise, objects from other spaces
// could alias with low addresses.
constexpr int kLastStaticRootPage =
RoundUp<kRegularPageSize>(StaticReadOnlyRoot::kLastAllocatedRoot);
static_assert(kLastStaticRootPage <= kContiguousReadOnlyReservationSize);
JumpIfUnsignedLessThan(value, kContiguousReadOnlyReservationSize, dest);
#endif // V8_STATIC_ROOTS_BOOL && CONTIGUOUS_COMPRESSED_READ_ONLY_SPACE_BOOL
}
void MacroAssembler::RecordWrite(Register object, Register slot_address,
Register value, SaveFPRegsMode fp_mode,
SmiCheck smi_check, ReadOnlyCheck ro_check,
SlotDescriptor slot) {
ASM_CODE_COMMENT(this);
DCHECK(!AreAliased(object, slot_address, value));
AssertNotSmi(object);
if (v8_flags.disable_write_barriers) {
return;
}
if (v8_flags.slow_debug_code) {
ASM_CODE_COMMENT_STRING(this, "Debug check slot_address");
Label ok;
if (slot.contains_indirect_pointer()) {
Push(object); // Use object register as scratch
Register scratch = object;
Push(slot_address); // Use slot address register to load the value into
Register value_in_slot = slot_address;
LoadIndirectPointerField(value_in_slot, Operand(slot_address, 0),
slot.indirect_pointer_tag(), scratch);
cmp_tagged(value, value_in_slot);
// These pops don't affect the flag registers, so we can do them before
// the conditional jump below.
Pop(slot_address);
Pop(object);
} else {
cmp_tagged(value, Operand(slot_address, 0));
}
j(equal, &ok, Label::kNear);
int3();
bind(&ok);
}
// First, check if a write barrier is even needed. The tests below
// catch stores of smis and read-only objects, as well as stores into the
// young generation.
Label done;
if (ro_check == ReadOnlyCheck::kInline) {
MaybeJumpIfReadOnlyOrSmallSmi(value, &done);
}
if (smi_check == SmiCheck::kInline) {
// Skip barrier if writing a smi.
JumpIfSmi(value, &done);
}
if (slot.contains_indirect_pointer()) {
// The indirect pointer write barrier is only enabled during marking.
JumpIfNotMarking(&done);
} else {
#if V8_ENABLE_STICKY_MARK_BITS_BOOL
DCHECK(!AreAliased(kScratchRegister, object, slot_address, value));
Label stub_call;
JumpIfMarking(&stub_call);
// Save the slot_address in the xmm scratch register.
movq(kScratchDoubleReg, slot_address);
Register scratch0 = slot_address;
CheckMarkBit(object, kScratchRegister, scratch0, carry, &done);
#if CONTIGUOUS_COMPRESSED_READ_ONLY_SPACE_BOOL
JumpIfUnsignedLessThan(value, kContiguousReadOnlyReservationSize, &done);
#else // !CONTIGUOUS_COMPRESSED_READ_ONLY_SPACE_BOOL
CheckPageFlag(value, kScratchRegister, MemoryChunk::kIsInReadOnlyHeapMask,
not_zero, &done, Label::kFar);
#endif // !CONTIGUOUS_COMPRESSED_READ_ONLY_SPACE_BOOL
CheckMarkBit(value, kScratchRegister, scratch0, carry, &done);
movq(slot_address, kScratchDoubleReg);
bind(&stub_call);
#else // !V8_ENABLE_STICKY_MARK_BITS_BOOL
CheckPageFlag(value,
value, // Used as scratch.
MemoryChunk::kPointersToHereAreInterestingMask, zero, &done,
Label::kFar);
CheckPageFlag(object,
value, // Used as scratch.
MemoryChunk::kPointersFromHereAreInterestingMask, zero, &done,
Label::kFar);
#endif // !V8_ENABLE_STICKY_MARK_BITS_BOOL
}
if (slot.contains_direct_pointer()) {
CallRecordWriteStub(object, slot_address, fp_mode,
StubCallMode::kCallBuiltinPointer);
} else {
DCHECK(slot.contains_indirect_pointer());
CallIndirectPointerBarrier(object, slot_address, fp_mode,
slot.indirect_pointer_tag());
}
bind(&done);
// Clobber clobbered registers when running with the debug-code flag
// turned on to provoke errors.
if (v8_flags.slow_debug_code) {
ASM_CODE_COMMENT_STRING(this, "Zap scratch registers");
Move(slot_address, kZapValue, RelocInfo::NO_INFO);
Move(value, kZapValue, RelocInfo::NO_INFO);
}
}
void MacroAssembler::Check(Condition cc, AbortReason reason) {
Label L;
j(cc, &L, Label::kNear);
Abort(reason);
// Control will not return here.
bind(&L);
}
void MacroAssembler::SbxCheck(Condition cc, AbortReason reason) {
Check(cc, reason);
}
void MacroAssembler::CheckStackAlignment() {
int frame_alignment = base::OS::ActivationFrameAlignment();
int frame_alignment_mask = frame_alignment - 1;
if (frame_alignment > kSystemPointerSize) {
ASM_CODE_COMMENT(this);
DCHECK(base::bits::IsPowerOfTwo(frame_alignment));
Label alignment_as_expected;
testq(rsp, Immediate(frame_alignment_mask));
j(zero, &alignment_as_expected, Label::kNear);
// Abort if stack is not aligned.
int3();
bind(&alignment_as_expected);
}
}
void MacroAssembler::AlignStackPointer() {
const int kFrameAlignment = base::OS::ActivationFrameAlignment();
if (kFrameAlignment > 0) {
DCHECK(base::bits::IsPowerOfTwo(kFrameAlignment));
DCHECK(is_int8(kFrameAlignment));
andq(rsp, Immediate(-kFrameAlignment));
}
}
void MacroAssembler::Abort(AbortReason reason) {
ASM_CODE_COMMENT(this);
if (v8_flags.code_comments) {
RecordComment("Abort message:", SourceLocation{});
RecordComment(GetAbortReason(reason), SourceLocation{});
}
// Without debug code, save the code size and just trap.
if (!v8_flags.debug_code || v8_flags.trap_on_abort) {
int3();
return;
}
if (should_abort_hard()) {
// We don't care if we constructed a frame. Just pretend we did.
FrameScope assume_frame(this, StackFrame::NO_FRAME_TYPE);
Move(kCArgRegs[0], static_cast<int>(reason));
PrepareCallCFunction(1);
LoadAddress(rax, ExternalReference::abort_with_reason());
call(rax);
return;
}
Move(rdx, Smi::FromInt(static_cast<int>(reason)));
{
// We don't actually want to generate a pile of code for this, so just
// claim there is a stack frame, without generating one.
FrameScope scope(this, StackFrame::NO_FRAME_TYPE);
if (root_array_available()) {
// Generate an indirect call via builtins entry table here in order to
// ensure that the interpreter_entry_return_pc_offset is the same for
// InterpreterEntryTrampoline and InterpreterEntryTrampolineForProfiling
// when v8_flags.debug_code is enabled.
Call(EntryFromBuiltinAsOperand(Builtin::kAbort));
} else {
CallBuiltin(Builtin::kAbort);
}
}
// Control will not return here.
int3();
}
void MacroAssembler::CallRuntime(const Runtime::Function* f,
int num_arguments) {
ASM_CODE_COMMENT(this);
// If the expected number of arguments of the runtime function is
// constant, we check that the actual number of arguments match the
// expectation.
CHECK(f->nargs < 0 || f->nargs == num_arguments);
// TODO(1236192): Most runtime routines don't need the number of
// arguments passed in because it is constant. At some point we
// should remove this need and make the runtime routine entry code
// smarter.
Move(rax, num_arguments);
LoadAddress(rbx, ExternalReference::Create(f));
bool switch_to_central = options().is_wasm;
CallBuiltin(Builtins::RuntimeCEntry(f->result_size, switch_to_central));
}
void MacroAssembler::TailCallRuntime(Runtime::FunctionId fid) {
// ----------- S t a t e -------------
// -- rsp[0] : return address
// -- rsp[8] : argument num_arguments - 1
// ...
// -- rsp[8 * num_arguments] : argument 0 (receiver)
//
// For runtime functions with variable arguments:
// -- rax : number of arguments
// -----------------------------------
ASM_CODE_COMMENT(this);
const Runtime::Function* function = Runtime::FunctionForId(fid);
DCHECK_EQ(1, function->result_size);
if (function->nargs >= 0) {
Move(rax, function->nargs);
}
JumpToExternalReference(ExternalReference::Create(fid));
}
void MacroAssembler::JumpToExternalReference(const ExternalReference& ext,
bool builtin_exit_frame) {
ASM_CODE_COMMENT(this);
// Set the entry point and jump to the C entry runtime stub.
LoadAddress(rbx, ext);
TailCallBuiltin(Builtins::CEntry(1, ArgvMode::kStack, builtin_exit_frame));
}
#ifdef V8_ENABLE_DEBUG_CODE
void MacroAssembler::AssertFeedbackCell(Register object, Register scratch) {
if (v8_flags.debug_code) {
IsObjectType(object, FEEDBACK_CELL_TYPE, scratch);
Assert(equal, AbortReason::kExpectedFeedbackCell);
}
}
void MacroAssembler::AssertFeedbackVector(Register object, Register scratch) {
if (v8_flags.debug_code) {
IsObjectType(object, FEEDBACK_VECTOR_TYPE, scratch);
Assert(equal, AbortReason::kExpectedFeedbackVector);
}
}
#endif // V8_ENABLE_DEBUG_CODE
void MacroAssembler::GenerateTailCallToReturnedCode(
Runtime::FunctionId function_id, JumpMode jump_mode) {
// ----------- S t a t e -------------
// -- rax : actual argument count (preserved for callee)
// -- rdx : new target (preserved for callee)
// -- rdi : target function (preserved for callee)
// -- r15 : dispatch handle (preserved for callee)
// -----------------------------------
ASM_CODE_COMMENT(this);
{
FrameScope scope(this, StackFrame::INTERNAL);
// Push a copy of the target function, the new target, the actual argument
// count, and the dispatch handle.
Push(kJavaScriptCallTargetRegister);
Push(kJavaScriptCallNewTargetRegister);
SmiTag(kJavaScriptCallArgCountRegister);
Push(kJavaScriptCallArgCountRegister);
#ifdef V8_JS_LINKAGE_INCLUDES_DISPATCH_HANDLE
// No need to SmiTag since dispatch handles always look like Smis.
static_assert(kJSDispatchHandleShift > 0);
AssertSmi(kJavaScriptCallDispatchHandleRegister);
Push(kJavaScriptCallDispatchHandleRegister);
#endif
// Function is also the parameter to the runtime call.
Push(kJavaScriptCallTargetRegister);
CallRuntime(function_id, 1);
// Restore target function, new target, actual argument count, and dispatch
// handle.
#ifdef V8_JS_LINKAGE_INCLUDES_DISPATCH_HANDLE
Pop(kJavaScriptCallDispatchHandleRegister);
#endif
Pop(kJavaScriptCallArgCountRegister);
SmiUntagUnsigned(kJavaScriptCallArgCountRegister);
Pop(kJavaScriptCallNewTargetRegister);
Pop(kJavaScriptCallTargetRegister);
}
static_assert(kJavaScriptCallCodeStartRegister == rcx, "ABI mismatch");
#ifndef V8_JS_LINKAGE_INCLUDES_DISPATCH_HANDLE
movl(kJavaScriptCallDispatchHandleRegister,
FieldOperand(kJavaScriptCallTargetRegister,
JSFunction::kDispatchHandleOffset));
#endif
LoadEntrypointFromJSDispatchTable(rcx, kJavaScriptCallDispatchHandleRegister);
DCHECK_EQ(jump_mode, JumpMode::kJump);
jmp(rcx);
}
int MacroAssembler::RequiredStackSizeForCallerSaved(SaveFPRegsMode fp_mode,
Register exclusion) const {
int bytes = 0;
RegList saved_regs = kCallerSaved - exclusion;
bytes += kSystemPointerSize * saved_regs.Count();
// R12 to r15 are callee save on all platforms.
if (fp_mode == SaveFPRegsMode::kSave) {
bytes += kStackSavedSavedFPSize * kAllocatableDoubleRegisters.Count();
}
return bytes;
}
int MacroAssembler::PushCallerSaved(SaveFPRegsMode fp_mode,
Register exclusion) {
ASM_CODE_COMMENT(this);
int bytes = 0;
bytes += PushAll(kCallerSaved - exclusion);
if (fp_mode == SaveFPRegsMode::kSave) {
bytes += PushAll(kAllocatableDoubleRegisters);
}
return bytes;
}
int MacroAssembler::PopCallerSaved(SaveFPRegsMode fp_mode, Register exclusion) {
ASM_CODE_COMMENT(this);
int bytes = 0;
if (fp_mode == SaveFPRegsMode::kSave) {
bytes += PopAll(kAllocatableDoubleRegisters);
}
bytes += PopAll(kCallerSaved - exclusion);
return bytes;
}
int MacroAssembler::PushAll(RegList registers) {
int bytes = 0;
for (Register reg : registers) {
pushq(reg);
bytes += kSystemPointerSize;
}
return bytes;
}
int MacroAssembler::PopAll(RegList registers) {
int bytes = 0;
for (Register reg : base::Reversed(registers)) {
popq(reg);
bytes += kSystemPointerSize;
}
return bytes;
}
int MacroAssembler::PushAll(DoubleRegList registers, int stack_slot_size) {
if (registers.is_empty()) return 0;
const int delta = stack_slot_size * registers.Count();
AllocateStackSpace(delta);
int slot = 0;
for (XMMRegister reg : registers) {
if (stack_slot_size == kDoubleSize) {
Movsd(Operand(rsp, slot), reg);
} else {
DCHECK_EQ(stack_slot_size, 2 * kDoubleSize);
Movdqu(Operand(rsp, slot), reg);
}
slot += stack_slot_size;
}
DCHECK_EQ(slot, delta);
return delta;
}
int MacroAssembler::PopAll(DoubleRegList registers, int stack_slot_size) {
if (registers.is_empty()) return 0;
int slot = 0;
for (XMMRegister reg : registers) {
if (stack_slot_size == kDoubleSize) {
Movsd(reg, Operand(rsp, slot));
} else {
DCHECK_EQ(stack_slot_size, 2 * kDoubleSize);
Movdqu(reg, Operand(rsp, slot));
}
slot += stack_slot_size;
}
DCHECK_EQ(slot, stack_slot_size * registers.Count());
addq(rsp, Immediate(slot));
return slot;
}
void MacroAssembler::Movq(XMMRegister dst, Register src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope avx_scope(this, AVX);
vmovq(dst, src);
} else {
movq(dst, src);
}
}
void MacroAssembler::Movq(Register dst, XMMRegister src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope avx_scope(this, AVX);
vmovq(dst, src);
} else {
movq(dst, src);
}
}
void MacroAssembler::Pextrq(Register dst, XMMRegister src, int8_t imm8) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope avx_scope(this, AVX);
vpextrq(dst, src, imm8);
} else {
CpuFeatureScope sse_scope(this, SSE4_1);
pextrq(dst, src, imm8);
}
}
void MacroAssembler::Cvtss2sd(XMMRegister dst, XMMRegister src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope scope(this, AVX);
vcvtss2sd(dst, src, src);
} else {
cvtss2sd(dst, src);
}
}
void MacroAssembler::Cvtss2sd(XMMRegister dst, Operand src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope scope(this, AVX);
vcvtss2sd(dst, dst, src);
} else {
cvtss2sd(dst, src);
}
}
void MacroAssembler::Cvtsd2ss(XMMRegister dst, XMMRegister src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope scope(this, AVX);
vcvtsd2ss(dst, src, src);
} else {
cvtsd2ss(dst, src);
}
}
void MacroAssembler::Cvtsd2ss(XMMRegister dst, Operand src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope scope(this, AVX);
vcvtsd2ss(dst, dst, src);
} else {
cvtsd2ss(dst, src);
}
}
void MacroAssembler::Cvtlsi2sd(XMMRegister dst, Register src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope scope(this, AVX);
vcvtlsi2sd(dst, kScratchDoubleReg, src);
} else {
xorpd(dst, dst);
cvtlsi2sd(dst, src);
}
}
void MacroAssembler::Cvtlsi2sd(XMMRegister dst, Operand src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope scope(this, AVX);
vcvtlsi2sd(dst, kScratchDoubleReg, src);
} else {
xorpd(dst, dst);
cvtlsi2sd(dst, src);
}
}
void MacroAssembler::Cvtlsi2ss(XMMRegister dst, Register src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope scope(this, AVX);
vcvtlsi2ss(dst, kScratchDoubleReg, src);
} else {
xorps(dst, dst);
cvtlsi2ss(dst, src);
}
}
void MacroAssembler::Cvtlsi2ss(XMMRegister dst, Operand src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope scope(this, AVX);
vcvtlsi2ss(dst, kScratchDoubleReg, src);
} else {
xorps(dst, dst);
cvtlsi2ss(dst, src);
}
}
void MacroAssembler::Cvtqsi2ss(XMMRegister dst, Register src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope scope(this, AVX);
vcvtqsi2ss(dst, kScratchDoubleReg, src);
} else {
xorps(dst, dst);
cvtqsi2ss(dst, src);
}
}
void MacroAssembler::Cvtqsi2ss(XMMRegister dst, Operand src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope scope(this, AVX);
vcvtqsi2ss(dst, kScratchDoubleReg, src);
} else {
xorps(dst, dst);
cvtqsi2ss(dst, src);
}
}
void MacroAssembler::Cvtqsi2sd(XMMRegister dst, Register src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope scope(this, AVX);
vcvtqsi2sd(dst, kScratchDoubleReg, src);
} else {
xorpd(dst, dst);
cvtqsi2sd(dst, src);
}
}
void MacroAssembler::Cvtqsi2sd(XMMRegister dst, Operand src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope scope(this, AVX);
vcvtqsi2sd(dst, kScratchDoubleReg, src);
} else {
xorpd(dst, dst);
cvtqsi2sd(dst, src);
}
}
void MacroAssembler::Cvtlui2ss(XMMRegister dst, Register src) {
// Zero-extend the 32 bit value to 64 bit.
movl(kScratchRegister, src);
Cvtqsi2ss(dst, kScratchRegister);
}
void MacroAssembler::Cvtlui2ss(XMMRegister dst, Operand src) {
// Zero-extend the 32 bit value to 64 bit.
movl(kScratchRegister, src);
Cvtqsi2ss(dst, kScratchRegister);
}
void MacroAssembler::Cvtlui2sd(XMMRegister dst, Register src) {
// Zero-extend the 32 bit value to 64 bit.
movl(kScratchRegister, src);
Cvtqsi2sd(dst, kScratchRegister);
}
void MacroAssembler::Cvtlui2sd(XMMRegister dst, Operand src) {
// Zero-extend the 32 bit value to 64 bit.
movl(kScratchRegister, src);
Cvtqsi2sd(dst, kScratchRegister);
}
void MacroAssembler::Cvtqui2ss(XMMRegister dst, Register src) {
Label done;
Cvtqsi2ss(dst, src);
testq(src, src);
j(positive, &done, Label::kNear);
// Compute {src/2 | (src&1)} (retain the LSB to avoid rounding errors).
if (src != kScratchRegister) movq(kScratchRegister, src);
shrq(kScratchRegister, Immediate(1));
// The LSB is shifted into CF. If it is set, set the LSB in {tmp}.
Label msb_not_set;
j(not_carry, &msb_not_set, Label::kNear);
orq(kScratchRegister, Immediate(1));
bind(&msb_not_set);
Cvtqsi2ss(dst, kScratchRegister);
Addss(dst, dst);
bind(&done);
}
void MacroAssembler::Cvtqui2ss(XMMRegister dst, Operand src) {
movq(kScratchRegister, src);
Cvtqui2ss(dst, kScratchRegister);
}
void MacroAssembler::Cvtqui2sd(XMMRegister dst, Register src) {
Label done;
Cvtqsi2sd(dst, src);
testq(src, src);
j(positive, &done, Label::kNear);
// Compute {src/2 | (src&1)} (retain the LSB to avoid rounding errors).
if (src != kScratchRegister) movq(kScratchRegister, src);
shrq(kScratchRegister, Immediate(1));
// The LSB is shifted into CF. If it is set, set the LSB in {tmp}.
Label msb_not_set;
j(not_carry, &msb_not_set, Label::kNear);
orq(kScratchRegister, Immediate(1));
bind(&msb_not_set);
Cvtqsi2sd(dst, kScratchRegister);
Addsd(dst, dst);
bind(&done);
}
void MacroAssembler::Cvtqui2sd(XMMRegister dst, Operand src) {
movq(kScratchRegister, src);
Cvtqui2sd(dst, kScratchRegister);
}
void MacroAssembler::Cvttss2si(Register dst, XMMRegister src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope scope(this, AVX);
vcvttss2si(dst, src);
} else {
cvttss2si(dst, src);
}
}
void MacroAssembler::Cvttss2si(Register dst, Operand src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope scope(this, AVX);
vcvttss2si(dst, src);
} else {
cvttss2si(dst, src);
}
}
void MacroAssembler::Cvttsd2si(Register dst, XMMRegister src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope scope(this, AVX);
vcvttsd2si(dst, src);
} else {
cvttsd2si(dst, src);
}
}
void MacroAssembler::Cvttsd2si(Register dst, Operand src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope scope(this, AVX);
vcvttsd2si(dst, src);
} else {
cvttsd2si(dst, src);
}
}
void MacroAssembler::Cvttss2siq(Register dst, XMMRegister src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope scope(this, AVX);
vcvttss2siq(dst, src);
} else {
cvttss2siq(dst, src);
}
}
void MacroAssembler::Cvttss2siq(Register dst, Operand src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope scope(this, AVX);
vcvttss2siq(dst, src);
} else {
cvttss2siq(dst, src);
}
}
void MacroAssembler::Cvttsd2siq(Register dst, XMMRegister src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope scope(this, AVX);
vcvttsd2siq(dst, src);
} else {
cvttsd2siq(dst, src);
}
}
void MacroAssembler::Cvttsd2siq(Register dst, Operand src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope scope(this, AVX);
vcvttsd2siq(dst, src);
} else {
cvttsd2siq(dst, src);
}
}
void MacroAssembler::Cvtph2pd(XMMRegister dst, XMMRegister src) {
ASM_CODE_COMMENT(this);
CpuFeatureScope f16c_scope(this, F16C);
CpuFeatureScope avx_scope(this, AVX);
vcvtph2ps(dst, src);
Cvtss2sd(dst, dst);
}
void MacroAssembler::Cvtpd2ph(XMMRegister dst, XMMRegister src, Register tmp) {
ASM_CODE_COMMENT(this);
CpuFeatureScope f16c_scope(this, F16C);
CpuFeatureScope avx_scope(this, AVX);
Register tmp2 = kScratchRegister;
DCHECK_NE(tmp, tmp2);
DCHECK_NE(dst, src);
// Conversion algo from
// https://github.com/tc39/proposal-float16array/issues/12#issuecomment-2256642971
Label f32tof16;
// Convert Float64 -> Float32.
Cvtsd2ss(dst, src);
vmovd(tmp, dst);
// Mask off sign bit.
andl(tmp, Immediate(kFP32WithoutSignMask));
// Underflow to zero.
cmpl(tmp, Immediate(kFP32MinFP16ZeroRepresentable));
j(below, &f32tof16);
// Overflow to infinity.
cmpl(tmp, Immediate(kFP32MaxFP16Representable));
j(above_equal, &f32tof16);
// Detection of subnormal numbers.
cmpl(tmp, Immediate(kFP32SubnormalThresholdOfFP16));
setcc(above_equal, tmp2);
movzxbl(tmp2, tmp2);
// Compute 0x1000 for normal and 0x0000 for denormal numbers.
shll(tmp2, Immediate(12));
// Look at the last thirteen bits of the mantissa which will be shifted out
// when converting from float32 to float16. (The round and sticky bits.)
// Normal numbers: If the round bit is set and sticky bits are zero, then
// adjust the float32 mantissa.
// Denormal numbers: If all bits are zero, then adjust the mantissa.
andl(tmp, Immediate(0x1fff));
// Check round and sticky bits.
cmpl(tmp, tmp2);
j(not_equal, &f32tof16);
// Adjust mantissa by -1/0/+1.
Move(kScratchDoubleReg, static_cast<uint32_t>(1));
psignd(kScratchDoubleReg, src);
paddd(dst, kScratchDoubleReg);
bind(&f32tof16);
// Convert Float32 -> Float16.
vcvtps2ph(dst, dst, 4);
}
namespace {
template <typename OperandOrXMMRegister, bool is_double>
void ConvertFloatToUint64(MacroAssembler* masm, Register dst,
OperandOrXMMRegister src, Label* fail) {
Label success;
// There does not exist a native float-to-uint instruction, so we have to use
// a float-to-int, and postprocess the result.
if (is_double) {
masm->Cvttsd2siq(dst, src);
} else {
masm->Cvttss2siq(dst, src);
}
// If the result of the conversion is positive, we are already done.
masm->testq(dst, dst);
masm->j(positive, &success);
// The result of the first conversion was negative, which means that the
// input value was not within the positive int64 range. We subtract 2^63
// and convert it again to see if it is within the uint64 range.
if (is_double) {
masm->Move(kScratchDoubleReg, -9223372036854775808.0);
masm->Addsd(kScratchDoubleReg, src);
masm->Cvttsd2siq(dst, kScratchDoubleReg);
} else {
masm->Move(kScratchDoubleReg, -9223372036854775808.0f);
masm->Addss(kScratchDoubleReg, src);
masm->Cvttss2siq(dst, kScratchDoubleReg);
}
masm->testq(dst, dst);
// The only possible negative value here is 0x8000000000000000, which is
// used on x64 to indicate an integer overflow.
masm->j(negative, fail ? fail : &success);
// The input value is within uint64 range and the second conversion worked
// successfully, but we still have to undo the subtraction we did
// earlier.
masm->Move(kScratchRegister, 0x8000000000000000);
masm->orq(dst, kScratchRegister);
masm->bind(&success);
}
template <typename OperandOrXMMRegister, bool is_double>
void ConvertFloatToUint32(MacroAssembler* masm, Register dst,
OperandOrXMMRegister src, Label* fail) {
Label success;
// There does not exist a native float-to-uint instruction, so we have to use
// a float-to-int, and postprocess the result.
if (is_double) {
masm->Cvttsd2si(dst, src);
} else {
masm->Cvttss2si(dst, src);
}
// If the result of the conversion is positive, we are already done.
masm->testl(dst, dst);
masm->j(positive, &success);
// The result of the first conversion was negative, which means that the
// input value was not within the positive int32 range. We subtract 2^31
// and convert it again to see if it is within the uint32 range.
if (is_double) {
masm->Move(kScratchDoubleReg, -2147483648.0);
masm->Addsd(kScratchDoubleReg, src);
masm->Cvttsd2si(dst, kScratchDoubleReg);
} else {
masm->Move(kScratchDoubleReg, -2147483648.0f);
masm->Addss(kScratchDoubleReg, src);
masm->Cvttss2si(dst, kScratchDoubleReg);
}
masm->testl(dst, dst);
// The only possible negative value here is 0x80000000, which is
// used on x64 to indicate an integer overflow.
masm->j(negative, fail ? fail : &success);
// The input value is within uint32 range and the second conversion worked
// successfully, but we still have to undo the subtraction we did
// earlier.
masm->Move(kScratchRegister, 0x80000000);
masm->orl(dst, kScratchRegister);
masm->bind(&success);
}
} // namespace
void MacroAssembler::Cvttsd2uiq(Register dst, Operand src, Label* fail) {
ConvertFloatToUint64<Operand, true>(this, dst, src, fail);
}
void MacroAssembler::Cvttsd2uiq(Register dst, XMMRegister src, Label* fail) {
ConvertFloatToUint64<XMMRegister, true>(this, dst, src, fail);
}
void MacroAssembler::Cvttsd2ui(Register dst, Operand src, Label* fail) {
ConvertFloatToUint32<Operand, true>(this, dst, src, fail);
}
void MacroAssembler::Cvttsd2ui(Register dst, XMMRegister src, Label* fail) {
ConvertFloatToUint32<XMMRegister, true>(this, dst, src, fail);
}
void MacroAssembler::Cvttss2uiq(Register dst, Operand src, Label* fail) {
ConvertFloatToUint64<Operand, false>(this, dst, src, fail);
}
void MacroAssembler::Cvttss2uiq(Register dst, XMMRegister src, Label* fail) {
ConvertFloatToUint64<XMMRegister, false>(this, dst, src, fail);
}
void MacroAssembler::Cvttss2ui(Register dst, Operand src, Label* fail) {
ConvertFloatToUint32<Operand, false>(this, dst, src, fail);
}
void MacroAssembler::Cvttss2ui(Register dst, XMMRegister src, Label* fail) {
ConvertFloatToUint32<XMMRegister, false>(this, dst, src, fail);
}
void MacroAssembler::Cmpeqss(XMMRegister dst, XMMRegister src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope avx_scope(this, AVX);
vcmpeqss(dst, src);
} else {
cmpeqss(dst, src);
}
}
void MacroAssembler::Cmpeqsd(XMMRegister dst, XMMRegister src) {
if (CpuFeatures::IsSupported(AVX)) {
CpuFeatureScope avx_scope(this, AVX);
vcmpeqsd(dst, src);
} else {
cmpeqsd(dst, src);
}
}
void MacroAssembler::S256Not(YMMRegister dst, YMMRegister src,
YMMRegister scratch) {
ASM_CODE_COMMENT(this);
CpuFeatureScope avx2_scope(this, AVX2);
if (dst == src) {
vpcmpeqd(scratch, scratch, scratch);
vpxor(dst, dst, scratch);
} else {
vpcmpeqd(dst, dst, dst);
vpxor(dst, dst, src);
}
}
void MacroAssembler::S256Select(YMMRegister dst, YMMRegister mask,
YMMRegister src1, YMMRegister src2,
YMMRegister scratch) {
ASM_CODE_COMMENT(this);
CpuFeatureScope avx2_scope(this, AVX2);
// v256.select = v256.or(v256.and(v1, c), v256.andnot(v2, c)).
// pandn(x, y) = !x & y, so we have to flip the mask and input.
vpandn(scratch, mask, src2);
vpand(dst, src1, mask);
vpor(dst, dst, scratch);
}
// ----------------------------------------------------------------------------
// Smi tagging, untagging and tag detection.
Register MacroAssembler::GetSmiConstant(Tagged<Smi> source) {
Move(kScratchRegister, source);
return kScratchRegister;
}
void MacroAssembler::Cmp(Register dst, int32_t src) {
if (src == 0) {
testl(dst, dst);
} else {
cmpl(dst, Immediate(src));
}
}
void MacroAssembler::I64x4Mul(YMMRegister dst, YMMRegister lhs, YMMRegister rhs,
YMMRegister tmp1, YMMRegister tmp2) {
ASM_CODE_COMMENT(this);
DCHECK(!AreAliased(dst, tmp1, tmp2));
DCHECK(!AreAliased(lhs, tmp1, tmp2));
DCHECK(!AreAliased(rhs, tmp1, tmp2));
DCHECK(CpuFeatures::IsSupported(AVX2));
CpuFeatureScope avx_scope(this, AVX2);
// 1. Multiply high dword of each qword of left with right.
vpsrlq(tmp1, lhs, uint8_t{32});
vpmuludq(tmp1, tmp1, rhs);
// 2. Multiply high dword of each qword of right with left.
vpsrlq(tmp2, rhs, uint8_t{32});
vpmuludq(tmp2, tmp2, lhs);
// 3. Add 1 and 2, then shift left by 32 (this is the high dword of result).
vpaddq(tmp2, tmp2, tmp1);
vpsllq(tmp2, tmp2, uint8_t{32});
// 4. Multiply low dwords (this is the low dword of result).
vpmuludq(dst, lhs, rhs);
// 5. Add 3 and 4.
vpaddq(dst, dst, tmp2);
}
#define DEFINE_ISPLAT(name, suffix, instr_mov) \
void MacroAssembler::name(YMMRegister dst, Register src) { \
ASM_CODE_COMMENT(this); \
DCHECK(CpuFeatures::IsSupported(AVX) && CpuFeatures::IsSupported(AVX2)); \
CpuFeatureScope avx_scope(this, AVX); \
CpuFeatureScope avx2_scope(this, AVX2); \
instr_mov(dst, src); \
vpbroadcast##suffix(dst, dst); \
} \
\
void MacroAssembler::name(YMMRegister dst, Operand src) { \
ASM_CODE_COMMENT(this); \
DCHECK(CpuFeatures::IsSupported(AVX2)); \
CpuFeatureScope avx2_scope(this, AVX2); \
vpbroadcast##suffix(dst, src); \
}
MACRO_ASM_X64_ISPLAT_LIST(DEFINE_ISPLAT)
#undef DEFINE_ISPLAT
void MacroAssembler::F64x4Splat(YMMRegister dst, XMMRegister src) {
ASM_CODE_COMMENT(this);
DCHECK(CpuFeatures::IsSupported(AVX2));
CpuFeatureScope avx2_scope(this, AVX2);
vbroadcastsd(dst, src);
}
void MacroAssembler::F32x8Splat(YMMRegister dst, XMMRegister src) {
ASM_CODE_COMMENT(this);
DCHECK(CpuFeatures::IsSupported(AVX2));
CpuFeatureScope avx2_scope(this, AVX2);
vbroadcastss(dst, src);
}
void MacroAssembler::F64x4Min(YMMRegister dst, YMMRegister lhs, YMMRegister rhs,
YMMRegister scratch) {
ASM_CODE_COMMENT(this);
DCHECK(CpuFeatures::IsSupported(AVX) && CpuFeatures::IsSupported(AVX2));
CpuFeatureScope avx_scope(this, AVX);
CpuFeatureScope avx2_scope(this, AVX2);
vminpd(scratch, lhs, rhs);
vminpd(dst, rhs, lhs);
vorpd(scratch, scratch, dst);
vcmpunordpd(dst, dst, scratch);
vorpd(scratch, scratch, dst);
vpsrlq(dst, dst, uint8_t{13});
vandnpd(dst, dst, scratch);
}
void MacroAssembler::F64x4Max(YMMRegister dst, YMMRegister lhs, YMMRegister rhs,
YMMRegister scratch) {
ASM_CODE_COMMENT(this);
DCHECK(CpuFeatures::IsSupported(AVX) && CpuFeatures::IsSupported(AVX2));
CpuFeatureScope avx_scope(this, AVX);
CpuFeatureScope avx2_scope(this, AVX2);
vmaxpd(scratch, lhs, rhs);
vmaxpd(dst, rhs, lhs);
vxorpd(dst, dst, scratch);
vorpd(scratch, scratch, dst);
vsubpd(scratch, scratch, dst);
vcmpunordpd(dst, dst, scratch);
vpsrlq(dst, dst, uint8_t{13});
vandnpd(dst, dst, scratch);
}
void MacroAssembler::F32x8Min(YMMRegister dst, YMMRegister lhs, YMMRegister rhs,
YMMRegister scratch) {
ASM_CODE_COMMENT(this);
DCHECK(CpuFeatures::IsSupported(AVX) && CpuFeatures::IsSupported(AVX2));
CpuFeatureScope avx_scope(this, AVX);
CpuFeatureScope avx2_scope(this, AVX2);
vminps(scratch, lhs, rhs);
vminps(dst, rhs, lhs);
vorps(scratch, scratch, dst);
vcmpunordps(dst, dst, scratch);
vorps(scratch, scratch, dst);
vpsrld(dst, dst, uint8_t{10});
vandnps(dst, dst, scratch);
}
void MacroAssembler::F32x8Max(YMMRegister dst, YMMRegister lhs, YMMRegister rhs,
YMMRegister scratch) {
ASM_CODE_COMMENT(this);
DCHECK(CpuFeatures::IsSupported(AVX) && CpuFeatures::IsSupported(AVX2));
CpuFeatureScope avx_scope(this, AVX);
CpuFeatureScope avx2_scope(this, AVX2);
vmaxps(scratch, lhs, rhs);
vmaxps(dst, rhs, lhs);
vxorps(dst, dst, scratch);
vorps(scratch, scratch, dst);
vsubps(scratch, scratch, dst);
vcmpunordps(dst, dst, scratch);
vpsrld(dst, dst, uint8_t{10});
vandnps(dst, dst, scratch);
}
void MacroAssembler::F16x8Min(YMMRegister dst, XMMRegister lhs, XMMRegister rhs,
YMMRegister scratch, YMMRegister scratch2) {
ASM_CODE_COMMENT(this);
CpuFeatureScope f16c_scope(this, F16C);
CpuFeatureScope avx_scope(this, AVX);
CpuFeatureScope avx2_scope(this, AVX2);
vcvtph2ps(scratch, lhs);
vcvtph2ps(scratch2, rhs);
// The minps instruction doesn't propagate NaNs and +0's in its first
// operand. Perform minps in both orders, merge the results, and adjust.
vminps(dst, scratch, scratch2);
vminps(scratch, scratch2, scratch);
// Propagate -0's and NaNs, which may be non-canonical.
vorps(scratch, scratch, dst);
// Canonicalize NaNs by quieting and clearing the payload.
vcmpunordps(dst, dst, scratch);
vorps(scratch, scratch, dst);
vpsrld(dst, dst, uint8_t{10});
vandnps(dst, dst, scratch);
vcvtps2ph(dst, dst, 0);
}
void MacroAssembler::F16x8Max(YMMRegister dst, XMMRegister lhs, XMMRegister rhs,
YMMRegister scratch, YMMRegister scratch2) {
ASM_CODE_COMMENT(this);
CpuFeatureScope f16c_scope(this, F16C);
CpuFeatureScope avx_scope(this, AVX);
CpuFeatureScope avx2_scope(this, AVX2);
vcvtph2ps(scratch, lhs);
vcvtph2ps(scratch2, rhs);
// The maxps instruction doesn't propagate NaNs and +0's in its first
// operand. Perform maxps in both orders, merge the results, and adjust.
vmaxps(dst, scratch, scratch2);
vmaxps(scratch, scratch2, scratch);
// Find discrepancies.
vxorps(dst, dst, scratch);
// Propagate NaNs, which may be non-canonical.
vorps(scratch, scratch, dst);
// Propagate sign discrepancy and (subtle) quiet NaNs.
vsubps(scratch, scratch, dst);
// Canonicalize NaNs by clearing the payload. Sign is non-deterministic.
vcmpunordps(dst, dst, scratch);
vpsrld(dst, dst, uint8_t{10});
vandnps(dst, dst, scratch);
vcvtps2ph(dst, dst, 0);
}
// 1. Zero extend 4 packed 32-bit integers in src1 to 4 packed 64-bit integers
// in scratch
// 2. Zero extend 4 packed 32-bit integers in src2 to 4 packed 64-bit integers
// in dst
// 3. Multiply packed doubleword integers in scratch with dst, the extended zero
// are ignored
void MacroAssembler::I64x4ExtMul(YMMRegister dst, XMMRegister src1,
XMMRegister src2, YMMRegister scratch,
bool is_signed) {
ASM_CODE_COMMENT(this);
DCHECK(CpuFeatures::IsSupported(AVX2));
CpuFeatureScope avx_scope(this, AVX2);
vpmovzxdq(scratch, src1);
vpmovzxdq(dst, src2);
if (is_signed) {
vpmuldq(dst, scratch, dst);
} else {
vpmuludq(dst, scratch, dst);
}
}
// 1. Extend 8 packed 16-bit integers in src1 to 8 packed 32-bit integers in
// scratch
// 2. Extend 8 packed 16-bit integers in src2 to 8 packed 32-bit integers in dst
// 3. Multiply the packed doubleword integers in scratch and dst and store the
// low 32 bits of each product in dst.
void MacroAssembler::I32x8ExtMul(YMMRegister dst, XMMRegister src1,
XMMRegister src2, YMMRegister scratch,
bool is_signed) {
ASM_CODE_COMMENT(this);
DCHECK(CpuFeatures::IsSupported(AVX2));
CpuFeatureScope avx_scope(this, AVX2);
is_signed ? vpmovsxwd(scratch, src1) : vpmovzxwd(scratch, src1);
is_signed ? vpmovsxwd(dst, src2) : vpmovzxwd(dst, src2);
vpmulld(dst, dst, scratch);
}
void MacroAssembler::I16x16ExtMul(YMMRegister dst, XMMRegister src1,
XMMRegister src2, YMMRegister scratch,
bool is_signed) {
ASM_CODE_COMMENT(this);
DCHECK(CpuFeatures::IsSupported(AVX2));
CpuFeatureScope avx_scope(this, AVX2);
is_signed ? vpmovsxbw(scratch, src1) : vpmovzxbw(scratch, src1);
is_signed ? vpmovsxbw(dst, src2) : vpmovzxbw(dst, src2);
vpmullw(dst, dst, scratch);
}
void MacroAssembler::I32x8ExtAddPairwiseI16x16S(YMMRegister dst,
YMMRegister src,
YMMRegister scratch) {
ASM_CODE_COMMENT(this);
DCHECK(CpuFeatures::IsSupported(AVX2));
CpuFeatureScope avx2_scope(this, AVX2);
Move(scratch, uint32_t{1});
vpbroadcastw(scratch, scratch);
// vpmaddwd multiplies signed words in src and op, producing
// signed doublewords, then adds pairwise.
// src = |l0|l1|...|l14|l15|
// dst = |l0*1+l1*1|l2*1+l3*1|...|l14*1+l15*1|
vpmaddwd(dst, src, scratch);
}
void MacroAssembler::I32x8ExtAddPairwiseI16x16U(YMMRegister dst,
YMMRegister src,
YMMRegister scratch) {
ASM_CODE_COMMENT(this);
DCHECK(CpuFeatures::IsSupported(AVX2));
CpuFeatureScope avx2_scope(this, AVX2);
// src = |l0|l1|...l14|l15|
// scratch = |0|l0|0|l2|...|0|l14|
vpsrld(scratch, src, 16);
// dst = |0|l1|0|l3|...|0|l15|
vpblendw(dst, src, scratch, 0xAA);
vpaddd(dst, dst, scratch);
}
void MacroAssembler::I16x16ExtAddPairwiseI8x32S(YMMRegister dst,
YMMRegister src,
YMMRegister scratch) {
ASM_CODE_COMMENT(this);
DCHECK(CpuFeatures::IsSupported(AVX2));
CpuFeatureScope avx2_scope(this, AVX2);
Move(scratch, uint32_t{1});
vpbroadcastb(scratch, scratch);
// pmaddubsw treats the first operand as unsigned, so scratch here should
// be first operand
// src = |l0|l1|...|l34|l35|
// dst = |l0*1+l1*1|l2*1+l3*1|...|l34*1+l35*1|
vpmaddubsw(dst, scratch, src);
}
void MacroAssembler::I16x16ExtAddPairwiseI8x32U(YMMRegister dst,
YMMRegister src,
YMMRegister scratch) {
ASM_CODE_COMMENT(this);
DCHECK(CpuFeatures::IsSupported(AVX2));
CpuFeatureScope avx2_scope(this, AVX2);
Move(scratch, uint32_t{1});
vpbroadcastb(scratch, scratch);
vpmaddubsw(dst, src, scratch);
}
void MacroAssembler::I32x8SConvertF32x8(YMMRegister dst, YMMRegister src,
YMMRegister tmp, Register scratch) {
ASM_CODE_COMMENT(this);
DCHECK(CpuFeatures::IsSupported(AVX) && CpuFeatures::IsSupported(AVX2));
CpuFeatureScope avx_scope(this, AVX);
CpuFeatureScope avx2_scope(this, AVX2);
Operand int32_overflow_as_float = ExternalReferenceAsOperand(
ExternalReference::address_of_wasm_i32x8_int32_overflow_as_float(),
scratch);
// This algorithm works by:
// 1. lanes with NaNs are zero-ed
// 2. lanes ge than 2147483648.0f (MAX_INT32+1) set to 0xffff'ffff
// 3. cvttps2dq sets all out of range lanes to 0x8000'0000
// a. correct for underflows (< MIN_INT32)
// b. wrong for overflow, and we know which lanes overflow from 2.
// 4. adjust for 3b by xor-ing 2 and 3
// a. 0x8000'0000 xor 0xffff'ffff = 0x7fff'ffff (MAX_INT32)
vcmpeqps(tmp, src, src);
vandps(dst, src, tmp);
vcmpgeps(tmp, src, int32_overflow_as_float);
vcvttps2dq(dst, dst);
vpxor(dst, dst, tmp);
}
void MacroAssembler::I16x8SConvertF16x8(YMMRegister dst, XMMRegister src,
YMMRegister tmp, Register scratch) {
ASM_CODE_COMMENT(this);
DCHECK(CpuFeatures::IsSupported(AVX) && CpuFeatures::IsSupported(AVX2) &&
CpuFeatures::IsSupported(F16C));
CpuFeatureScope f16c_scope(this, F16C);
CpuFeatureScope avx_scope(this, AVX);
CpuFeatureScope avx2_scope(this, AVX2);
Operand op = ExternalReferenceAsOperand(
ExternalReference::address_of_wasm_i32x8_int32_overflow_as_float(),
scratch);
// Convert source f16 to f32.
vcvtph2ps(dst, src);
// Compare it to itself, NaNs are turn to 0s because don't equal to itself.
vcmpeqps(tmp, dst, dst);
// Reset NaNs.
vandps(dst, dst, tmp);
// Detect positive Infinity as an overflow above MAX_INT32.
vcmpgeps(tmp, dst, op);
// Convert f32 to i32.
vcvttps2dq(dst, dst);
// cvttps2dq sets all out of range lanes to 0x8000'0000,
// but as soon as source values are result of conversion from f16,
// and so less than MAX_INT32, only +Infinity is an issue.
// Convert all infinities to MAX_INT32 and let vpackssdw
// clamp it to MAX_INT16 later.
// 0x8000'0000 xor 0xffff'ffff(from 2 steps before) = 0x7fff'ffff (MAX_INT32)
vpxor(dst, dst, tmp);
// We now have 8 i32 values. Using one character per 16 bits:
// dst: [AABBCCDDEEFFGGHH]
// Create a copy of the upper four values in the lower half of {tmp}
// (so the upper half of the immediate doesn't matter):
vpermq(tmp, dst, 0x4E); // 0b01001110
// tmp: [EEFFGGHHAABBCCDD]
// Now pack them together as i16s. Note that {vpackssdw} interleaves
// 128-bit chunks from each input, and takes care of saturating each
// value to kMinInt16 and kMaxInt16. We will then ignore the upper half
// of {dst}.
vpackssdw(dst, dst, tmp);
// dst: [EFGHABCDABCDEFGH]
// <--><--><--><-->
// ↑ ↑ ↑ └── from lower half of {dst}
// │ │ └────── from lower half of {tmp}
// │ └────────── from upper half of {dst} (ignored)
// └────────────── from upper half of {tmp} (ignored)
}
void MacroAssembler::I16x8TruncF16x8U(YMMRegister dst, XMMRegister src,
YMMRegister tmp) {
ASM_CODE_COMMENT(this);
DCHECK(CpuFeatures::IsSupported(AVX) && CpuFeatures::IsSupported(AVX2) &&
CpuFeatures::IsSupported(F16C));
CpuFeatureScope f16c_scope(this, F16C);
CpuFeatureScope avx_scope(this, AVX);
CpuFeatureScope avx2_scope(this, AVX2);
Operand op = ExternalReferenceAsOperand(
ExternalReference::address_of_wasm_i32x8_int32_overflow_as_float(),
kScratchRegister);
vcvtph2ps(dst, src);
// NAN->0, negative->0.
vpxor(tmp, tmp, tmp);
vmaxps(dst, dst, tmp);
// Detect positive Infinity as an overflow above MAX_INT32.
vcmpgeps(tmp, dst, op);
// Convert to int.
vcvttps2dq(dst, dst);
// cvttps2dq sets all out of range lanes to 0x8000'0000,
// but as soon as source values are result of conversion from f16,
// and so less than MAX_INT32, only +Infinity is an issue.
// Convert all infinities to MAX_INT32 and let vpackusdw
// clamp it to MAX_INT16 later.
// 0x8000'0000 xor 0xffff'ffff(from 2 steps before) = 0x7fff'ffff (MAX_INT32)
vpxor(dst, dst, tmp);
// Move high part to a spare register.
// See detailed comment in {I16x8SConvertF16x8} for how this works.
vpermq(tmp, dst, 0x4E); // 0b01001110
vpackusdw(dst, dst, tmp);
}
void MacroAssembler::F16x8Qfma(YMMRegister dst, XMMRegister src1,
XMMRegister src2, XMMRegister src3,
YMMRegister tmp, YMMRegister tmp2) {
CpuFeatureScope fma3_scope(this, FMA3);
CpuFeatureScope f16c_scope(this, F16C);
if (dst.code() == src2.code()) {
vcvtph2ps(dst, dst);
vcvtph2ps(tmp, src1);
vcvtph2ps(tmp2, src3);
vfmadd213ps(dst, tmp, tmp2);
} else if (dst.code() == src3.code()) {
vcvtph2ps(dst, dst);
vcvtph2ps(tmp, src2);
vcvtph2ps(tmp2, src1);
vfmadd231ps(dst, tmp, tmp2);
} else {
vcvtph2ps(dst, src1);
vcvtph2ps(tmp, src2);
vcvtph2ps(tmp2, src3);
vfmadd213ps(dst, tmp, tmp2);
}
vcvtps2ph(dst, dst, 0);
}
void MacroAssembler::F16x8Qfms(YMMRegister dst, XMMRegister src1,
XMMRegister src2, XMMRegister src3,
YMMRegister tmp, YMMRegister tmp2) {
CpuFeatureScope fma3_scope(this, FMA3);
CpuFeatureScope f16c_scope(this, F16C);
if (dst.code() == src2.code()) {
vcvtph2ps(dst, dst);
vcvtph2ps(tmp, src1);
vcvtph2ps(tmp2, src3);
vfnmadd213ps(dst, tmp, tmp2);
} else if (dst.code() == src3.code()) {
vcvtph2ps(dst, dst);
vcvtph2ps(tmp, src2);
vcvtph2ps(tmp2, src1);
vfnmadd231ps(dst, tmp, tmp2);
} else {
vcvtph2ps(dst, src1);
vcvtph2ps(tmp, src2);
vcvtph2ps(tmp2, src3);
vfnmadd213ps(dst, tmp, tmp2);
}
vcvtps2ph(dst, dst, 0);
}
void MacroAssembler::F32x8Qfma(YMMRegister dst, YMMRegister src1,
YMMRegister src2, YMMRegister src3,
YMMRegister tmp) {
QFMA(ps);
}
void MacroAssembler::F32x8Qfms(YMMRegister dst, YMMRegister src1,
YMMRegister src2, YMMRegister src3,
YMMRegister tmp) {
QFMS(ps);
}
void MacroAssembler::F64x4Qfma(YMMRegister dst, YMMRegister src1,
YMMRegister src2, YMMRegister src3,
YMMRegister tmp) {
QFMA(pd);
}
void MacroAssembler::F64x4Qfms(YMMRegister dst, YMMRegister src1,
YMMRegister src2, YMMRegister src3,
YMMRegister tmp) {
QFMS(pd);
}
void MacroAssembler::I32x8DotI8x32I7x32AddS(YMMRegister dst, YMMRegister src1,
YMMRegister src2, YMMRegister src3,
YMMRegister scratch,
YMMRegister splat_reg) {
ASM_CODE_COMMENT(this);
DCHECK(CpuFeatures::IsSupported(AVX) && CpuFeatures::IsSupported(AVX2));
// It's guaranteed in instruction selector
DCHECK_EQ(dst, src3);
if (CpuFeatures::IsSupported(AVX_VNNI_INT8)) {
CpuFeatureScope avx_vnni_int8_scope(this, AVX_VNNI_INT8);
vpdpbssd(dst, src2, src1);
return;
} else if (CpuFeatures::IsSupported(AVX_VNNI)) {
CpuFeatureScope avx_scope(this, AVX_VNNI);
vpdpbusd(dst, src2, src1);
return;
}
DCHECK_NE(scratch, splat_reg);
CpuFeatureScope avx_scope(this, AVX);
CpuFeatureScope avx2_scope(this, AVX2);
// splat_reg = i16x16.splat(1)
vpcmpeqd(splat_reg, splat_reg, splat_reg);
vpsrlw(splat_reg, splat_reg, uint8_t{15});
vpmaddubsw(scratch, src2, src1);
vpmaddwd(scratch, splat_reg, scratch);
vpaddd(dst, src3, scratch);
}
void MacroAssembler::I32x8TruncF32x8U(YMMRegister dst, YMMRegister src,
YMMRegister scratch1,
YMMRegister scratch2) {
ASM_CODE_COMMENT(this);
DCHECK(CpuFeatures::IsSupported(AVX) && CpuFeatures::IsSupported(AVX2));
CpuFeatureScope avx_scope(this, AVX);
CpuFeatureScope avx2_scope(this, AVX2);
// NAN->0, negative->0.
vpxor(scratch1, scratch1, scratch1);
vmaxps(dst, src, scratch1);
// scratch1: float representation of max_signed.
vpcmpeqd(scratch1, scratch1, scratch1);
vpsrld(scratch1, scratch1, uint8_t{1}); // 0x7fffffff
vcvtdq2ps(scratch1, scratch1); // 0x4f000000
// scratch2: convert (src-max_signed).
// Set positive overflow lanes to 0x7FFFFFFF.
// Set negative lanes to 0.
vsubps(scratch2, dst, scratch1);
vcmpleps(scratch1, scratch1, scratch2);
vcvttps2dq(scratch2, scratch2);
vpxor(scratch2, scratch2, scratch1);
vpxor(scratch1, scratch1, scratch1);
vpmaxsd(scratch2, scratch2, scratch1);
// Convert to int. Overflow lanes above max_signed will be 0x80000000.
vcvttps2dq(dst, dst);
// Add (src-max_signed) for overflow lanes.
vpaddd(dst, dst, scratch2);
}
void MacroAssembler::Negpd(YMMRegister dst, YMMRegister src,
YMMRegister scratch) {
ASM_CODE_COMMENT(this);
DCHECK(CpuFeatures::IsSupported(AVX) && CpuFeatures::IsSupported(AVX2));
CpuFeatureScope avx2_scope(this, AVX2);
if (dst == src) {
vpcmpeqq(scratch, scratch, scratch);
vpsllq(scratch, scratch, uint8_t{63});
vpxor(dst, dst, scratch);
} else {
vpcmpeqq(dst, dst, dst);
vpsllq(dst, dst, uint8_t{63});
vpxor(dst, dst, src);
}
}
void MacroAssembler::Negps(YMMRegister dst, YMMRegister src,
YMMRegister scratch) {
ASM_CODE_COMMENT(this);
DCHECK(CpuFeatures::IsSupported(AVX) && CpuFeatures::IsSupported(AVX2));
CpuFeatureScope avx2_scope(this, AVX2);
if (dst == src) {
vpcmpeqd(scratch, scratch, scratch);
vpslld(scratch, scratch, uint8_t{31});
vpxor(dst, dst, scratch);
} else {
vpcmpeqd(dst, dst, dst);
vpslld(dst, dst, uint8_t{31});
vpxor(dst, dst, src);
}
}
void MacroAssembler::SmiTag(Register reg) {
static_assert(kSmiTag == 0);
DCHECK(SmiValuesAre32Bits() || SmiValuesAre31Bits());
if (COMPRESS_POINTERS_BOOL) {
DCHECK_EQ(kSmiShift, 1);
addl(reg, reg);
} else {
shlq(reg, Immediate(kSmiShift));
}
#ifdef ENABLE_SLOW_DCHECKS
ClobberDecompressedSmiBits(reg);
#endif
}
void MacroAssembler::SmiTag(Register dst, Register src) {
if (dst != src) {
if (COMPRESS_POINTERS_BOOL) {
movl(dst, src);
} else {
movq(dst, src);
}
}
SmiTag(dst);
}
void MacroAssembler::SmiUntag(Register reg) {
static_assert(kSmiTag == 0);
DCHECK(SmiValuesAre32Bits() || SmiValuesAre31Bits());
// TODO(v8:7703): Is there a way to avoid this sign extension when pointer
// compression is enabled?
if (COMPRESS_POINTERS_BOOL) {
sarl(reg, Immediate(kSmiShift));
movsxlq(reg, reg);
} else {
sarq(reg, Immediate(kSmiShift));
}
}
void MacroAssembler::SmiUntagUnsigned(Register reg) {
static_assert(kSmiTag == 0);
DCHECK(SmiValuesAre32Bits() || SmiValuesAre31Bits());
if (COMPRESS_POINTERS_BOOL) {
#ifndef V8_ENABLE_MEMORY_CORRUPTION_API
// This check doesn't make sense for sandbox testing since this value
// might be legitimately corrupted.
AssertSignBitOfSmiIsZero(reg);
#endif
shrl(reg, Immediate(kSmiShift));
} else {
shrq(reg, Immediate(kSmiShift));
}
}
void MacroAssembler::SmiUntag(Register dst, Register src) {
DCHECK(dst != src);
if (COMPRESS_POINTERS_BOOL) {
movsxlq(dst, src);
} else {
movq(dst, src);
}
// TODO(v8:7703): Call SmiUntag(reg) if we can find a way to avoid the extra
// mov when pointer compression is enabled.
static_assert(kSmiTag == 0);
DCHECK(SmiValuesAre32Bits() || SmiValuesAre31Bits());
sarq(dst, Immediate(kSmiShift));
}
void MacroAssembler::SmiUntag(Register dst, Operand src) {
if (SmiValuesAre32Bits()) {
// Sign extend to 64-bit.
movsxlq(dst, Operand(src, kSmiShift / kBitsPerByte));
} else {
DCHECK(SmiValuesAre31Bits());
if (COMPRESS_POINTERS_BOOL) {
movsxlq(dst, src);
} else {
movq(dst, src);
}
sarq(dst, Immediate(kSmiShift));
}
}
void MacroAssembler::SmiUntagUnsigned(Register dst, Operand src) {
if (SmiValuesAre32Bits()) {
// Zero extend to 64-bit.
movl(dst, Operand(src, kSmiShift / kBitsPerByte));
} else {
DCHECK(SmiValuesAre31Bits());
if (COMPRESS_POINTERS_BOOL) {
movl(dst, src);
#ifndef V8_ENABLE_MEMORY_CORRUPTION_API
// This check doesn't make sense for sandbox testing since this value
// might be legitimately corrupted.
AssertSignBitOfSmiIsZero(dst);
#endif
shrl(dst, Immediate(kSmiShift));
} else {
movq(dst, src);
shrq(dst, Immediate(kSmiShift));
}
}
}
void MacroAssembler::SmiToInt32(Register reg) {
AssertSmi(reg);
static_assert(kSmiTag == 0);
DCHECK(SmiValuesAre32Bits() || SmiValuesAre31Bits());
if (COMPRESS_POINTERS_BOOL) {
sarl(reg, Immediate(kSmiShift));
} else {
shrq(reg, Immediate(kSmiShift));
}
}
void MacroAssembler::SmiToInt32(Register dst, Register src) {
if (dst != src) {
mov_tagged(dst, src);
}
SmiToInt32(dst);
}
void MacroAssembler::SmiCompare(Register smi1, Register smi2) {
AssertSmi(smi1);
AssertSmi(smi2);
cmp_tagged(smi1, smi2);
}
void MacroAssembler::SmiCompare(Register dst, Tagged<Smi> src) {
AssertSmi(dst);
Cmp(dst, src);
}
void MacroAssembler::Cmp(Register dst, Tagged<Smi> src) {
if (src.value() == 0) {
test_tagged(dst, dst);
} else if (COMPRESS_POINTERS_BOOL) {
cmp_tagged(dst, Immediate(src));
} else {
DCHECK_NE(dst, kScratchRegister);
Register constant_reg = GetSmiConstant(src);
cmp_tagged(dst, constant_reg);
}
}
void MacroAssembler::SmiCompare(Register dst, Operand src) {
AssertSmi(dst);
AssertSmi(src);
cmp_tagged(dst, src);
}
void MacroAssembler::SmiCompare(Operand dst, Register src) {
AssertSmi(dst);
AssertSmi(src);
cmp_tagged(dst, src);
}
void MacroAssembler::SmiCompare(Operand dst, Tagged<Smi> src) {
AssertSmi(dst);
if (SmiValuesAre32Bits()) {
cmpl(Operand(dst, kSmiShift / kBitsPerByte), Immediate(src.value()));
} else {
DCHECK(SmiValuesAre31Bits());
cmpl(dst, Immediate(src));
}
}
void MacroAssembler::Cmp(Operand dst, Tagged<Smi> src) {
// The Operand cannot use the smi register.
Register smi_reg = GetSmiConstant(src);
DCHECK(!dst.AddressUsesRegister(smi_reg));
cmp_tagged(dst, smi_reg);
}
void MacroAssembler::ClobberDecompressedSmiBits(Register src) {
#ifdef V8_COMPRESS_POINTERS
ASM_CODE_COMMENT(this);
static constexpr unsigned int clobber_mask = 0x515151;
static constexpr int rot_to_unused =
64 - kSmiShiftSize - kSmiTagSize - kSmiValueSize;
rolq(src, Immediate(rot_to_unused));
xorq(src, Immediate(clobber_mask));
rorq(src, Immediate(rot_to_unused));
#endif
}
Condition MacroAssembler::CheckSmi(Register src) {
static_assert(kSmiTag == 0);
testb(src, Immediate(kSmiTagMask));
return zero;
}
Condition MacroAssembler::CheckSmi(Operand src) {
static_assert(kSmiTag == 0);
testb(src, Immediate(kSmiTagMask));
return zero;
}
void MacroAssembler::JumpIfSmi(Register src, Label* on_smi,
Label::Distance near_jump) {
Condition smi = CheckSmi(src);
j(smi, on_smi, near_jump);
}
void MacroAssembler::JumpIfNotSmi(Register src, Label* on_not_smi,
Label::Distance near_jump) {
Condition smi = CheckSmi(src);
j(NegateCondition(smi), on_not_smi, near_jump);
}
void MacroAssembler::JumpIfNotSmi(Operand src, Label* on_not_smi,
Label::Distance near_jump) {
Condition smi = CheckSmi(src);
j(NegateCondition(smi), on_not_smi, near_jump);
}
void MacroAssembler::SmiAddConstant(Operand dst, Tagged<Smi> constant) {
if (constant.value() != 0) {
if (SmiValuesAre32Bits()) {
addl(Operand(dst, kSmiShift / kBitsPerByte), Immediate(constant.value()));
} else {
DCHECK(SmiValuesAre31Bits());
if (kTaggedSize == kInt64Size) {
// Sign-extend value after addition
movl(kScratchRegister, dst);
addl(kScratchRegister, Immediate(constant));
movsxlq(kScratchRegister, kScratchRegister);
movq(dst, kScratchRegister);
} else {
DCHECK_EQ(kTaggedSize, kInt32Size);
addl(dst, Immediate(constant));
}
}
}
}
SmiIndex MacroAssembler::SmiToIndex(Register dst, Register src, int shift) {
if (SmiValuesAre32Bits()) {
DCHECK(is_uint6(shift));
// There is a possible optimization if shift is in the range 60-63, but that
// will (and must) never happen.
if (dst != src) {
movq(dst, src);
}
if (shift < kSmiShift) {
sarq(dst, Immediate(kSmiShift - shift));
} else {
shlq(dst, Immediate(shift - kSmiShift));
}
return SmiIndex(dst, times_1);
} else {
DCHECK(SmiValuesAre31Bits());
// We have to sign extend the index register to 64-bit as the SMI might
// be negative.
movsxlq(dst, src);
if (shift < kSmiShift) {
sarq(dst, Immediate(kSmiShift - shift));
} else if (shift != kSmiShift) {
if (shift - kSmiShift <= static_cast<int>(times_8)) {
return SmiIndex(dst, static_cast<ScaleFactor>(shift - kSmiShift));
}
shlq(dst, Immediate(shift - kSmiShift));
}
return SmiIndex(dst, times_1);
}
}
void MacroAssembler::Switch(Register scratch, Register reg, int case_value_base,
Label** labels, int num_labels) {
Register table = scratch;
Label fallthrough, jump_table;
if (case_value_base != 0) {
subq(reg, Immediate(case_value_base));
}
cmpq(reg, Immediate(num_labels));
j(above_equal, &fallthrough);
leaq(table, MemOperand(&jump_table));
#ifdef V8_ENABLE_CET_IBT
// Add the notrack prefix to disable landing pad enforcement.
jmp(MemOperand(table, reg, times_8, 0), /*notrack=*/true);
#else
jmp(MemOperand(table, reg, times_8, 0));
#endif
// Emit the jump table inline, under the assumption that it's not too big.
Align(kSystemPointerSize);
bind(&jump_table);
for (int i = 0; i < num_labels; ++i) {
dq(labels[i]);
}
bind(&fallthrough);
}
void MacroAssembler::Push(Tagged<Smi> source) {
intptr_t smi = static_cast<intptr_t>(source.ptr());
if (is_int32(smi)) {
Push(Immediate(static_cast<int32_t>(smi)));
return;
}
int first_byte_set = base::bits::CountTrailingZeros64(smi) / 8;
int last_byte_set = (63 - base::bits::CountLeadingZeros64(smi)) / 8;
if (first_byte_set == last_byte_set) {
// This sequence has only 7 bytes, compared to the 12 bytes below.
Push(Immediate(0));
movb(Operand(rsp, first_byte_set),
Immediate(static_cast<int8_t>(smi >> (8 * first_byte_set))));
return;
}
Register constant = GetSmiConstant(source);
Push(constant);
}
// ----------------------------------------------------------------------------
void MacroAssembler::Move(Register dst, Tagged<Smi> source) {
static_assert(kSmiTag == 0);
int value = source.value();
if (value == 0) {
xorl(dst, dst);
} else if (SmiValuesAre32Bits()) {
Move(dst, source.ptr(), RelocInfo::NO_INFO);
} else {
uint32_t uvalue = static_cast<uint32_t>(source.ptr());
Move(dst, uvalue);
}
}
void MacroAssembler::Move(Operand dst, intptr_t x) {
if (is_int32(x)) {
movq(dst, Immediate(static_cast<int32_t>(x)));
} else {
Move(kScratchRegister, x);
movq(dst, kScratchRegister);
}
}
void MacroAssembler::Move(Register dst, ExternalReference ext) {
// TODO(jgruber,v8:8887): Also consider a root-relative load when generating
// non-isolate-independent code. In many cases it might be cheaper than
// embedding the relocatable value.
if (root_array_available()) {
if (ext.IsIsolateFieldId()) {
leaq(dst, Operand(kRootRegister, ext.offset_from_root_register()));
return;
} else if (options().isolate_independent_code) {
IndirectLoadExternalReference(dst, ext);
return;
}
}
// External references should not get created with IDs if
// `!root_array_available()`.
CHECK(!ext.IsIsolateFieldId());
movq(dst, Immediate64(ext.address(), RelocInfo::EXTERNAL_REFERENCE));
}
void MacroAssembler::Move(Register dst, Register src) {
if (dst != src) {
movq(dst, src);
}
}
void MacroAssembler::Move(Register dst, Operand src) { movq(dst, src); }
void MacroAssembler::Move(Register dst, Immediate src) {
if (src.rmode() == RelocInfo::Mode::NO_INFO) {
Move(dst, src.value());
} else {
movl(dst, src);
}
}
void MacroAssembler::Move(XMMRegister dst, XMMRegister src) {
if (dst != src) {
Movaps(dst, src);
}
}
void MacroAssembler::MovePair(Register dst0, Register src0, Register dst1,
Register src1) {
if (dst0 != src1) {
// Normal case: Writing to dst0 does not destroy src1.
Move(dst0, src0);
Move(dst1, src1);
} else if (dst1 != src0) {
// Only dst0 and src1 are the same register,
// but writing to dst1 does not destroy src0.
Move(dst1, src1);
Move(dst0, src0);
} else {
// dst0 == src1, and dst1 == src0, a swap is required:
// dst0 \/ src0
// dst1 /\ src1
xchgq(dst0, dst1);
}
}
void MacroAssembler::MoveNumber(Register dst, double value) {
int32_t smi;
if (DoubleToSmiInteger(value, &smi)) {
Move(dst, Smi::FromInt(smi));
} else {
movq_heap_number(dst, value);
}
}
void MacroAssembler::Move(XMMRegister dst, uint32_t src) {
if (src == 0) {
Xorps(dst, dst);
} else {
unsigned nlz = base::bits::CountLeadingZeros(src);
unsigned ntz = base::bits::CountTrailingZeros(src);
unsigned pop = base::bits::CountPopulation(src);
DCHECK_NE(0u, pop);
if (pop + ntz + nlz == 32) {
Pcmpeqd(dst, dst);
if (ntz) Pslld(dst, static_cast<uint8_t>(ntz + nlz));
if (nlz) Psrld(dst, static_cast<uint8_t>(nlz));
} else {
movl(kScratchRegister, Immediate(src));
Movd(dst, kScratchRegister);
}
}
}
void MacroAssembler::Move(XMMRegister dst, uint64_t src) {
if (src == 0) {
Xorpd(dst, dst);
} else {
unsigned nlz = base::bits::CountLeadingZeros(src);
unsigned ntz = base::bits::CountTrailingZeros(src);
unsigned pop = base::bits::CountPopulation(src);
DCHECK_NE(0u, pop);
if (pop + ntz + nlz == 64) {
Pcmpeqd(dst, dst);
if (ntz) Psllq(dst, static_cast<uint8_t>(ntz + nlz));
if (nlz) Psrlq(dst, static_cast<uint8_t>(nlz));
} else {
uint32_t lower = static_cast<uint32_t>(src);
uint32_t upper = static_cast<uint32_t>(src >> 32);
if (upper == 0) {
Move(dst, lower);
} else {
movq(kScratchRegister, src);
Movq(dst, kScratchRegister);
}
}
}
}
void MacroAssembler::Move(XMMRegister dst, uint64_t high, uint64_t low) {
if (high == low) {
Move(dst, low);
Punpcklqdq(dst, dst);
return;
}
Move(dst, low);
movq(kScratchRegister, high);
Pinsrq(dst, dst, kScratchRegister, uint8_t{1});
}
// ----------------------------------------------------------------------------
void MacroAssembler::Cmp(Register dst, Handle<Object> source) {
if (IsSmi(*source)) {
Cmp(dst, Cast<Smi>(*source));
} else if (root_array_available_ && options().isolate_independent_code) {
// TODO(jgruber,v8:8887): Also consider a root-relative load when generating
// non-isolate-independent code. In many cases it might be cheaper than
// embedding the relocatable value.
// TODO(v8:9706): Fix-it! This load will always uncompress the value
// even when we are loading a compressed embedded object.
IndirectLoadConstant(kScratchRegister, Cast<HeapObject>(source));
cmp_tagged(dst, kScratchRegister);
} else if (COMPRESS_POINTERS_BOOL) {
EmbeddedObjectIndex index = AddEmbeddedObject(Cast<HeapObject>(source));
DCHECK(is_uint32(index));
cmpl(dst, Immediate(static_cast<int>(index),
RelocInfo::COMPRESSED_EMBEDDED_OBJECT));
} else {
movq(kScratchRegister,
Immediate64(source.address(), RelocInfo::FULL_EMBEDDED_OBJECT));
cmpq(dst, kScratchRegister);
}
}
void MacroAssembler::Cmp(Operand dst, Handle<Object> source) {
if (IsSmi(*source)) {
Cmp(dst, Cast<Smi>(*source));
} else if (root_array_available_ && options().isolate_independent_code) {
// TODO(jgruber,v8:8887): Also consider a root-relative load when generating
// non-isolate-independent code. In many cases it might be cheaper than
// embedding the relocatable value.
// TODO(v8:9706): Fix-it! This load will always uncompress the value
// even when we are loading a compressed embedded object.
IndirectLoadConstant(kScratchRegister, Cast<HeapObject>(source));
cmp_tagged(dst, kScratchRegister);
} else if (COMPRESS_POINTERS_BOOL) {
EmbeddedObjectIndex index = AddEmbeddedObject(Cast<HeapObject>(source));
DCHECK(is_uint32(index));
cmpl(dst, Immediate(static_cast<int>(index),
RelocInfo::COMPRESSED_EMBEDDED_OBJECT));
} else {
Move(kScratchRegister, Cast<HeapObject>(source),
RelocInfo::FULL_EMBEDDED_OBJECT);
cmp_tagged(dst, kScratchRegister);
}
}
void MacroAssembler::CompareRange(Register value, unsigned lower_limit,
unsigned higher_limit) {
ASM_CODE_COMMENT(this);
DCHECK_LT(lower_limit, higher_limit);
if (lower_limit != 0) {
leal(kScratchRegister, Operand(value, 0u - lower_limit));
cmpl(kScratchRegister, Immediate(higher_limit - lower_limit));
} else {
cmpl(value, Immediate(higher_limit));
}
}
void MacroAssembler::JumpIfIsInRange(Register value, unsigned lower_limit,
unsigned higher_limit, Label* on_in_range,
Label::Distance near_jump) {
CompareRange(value, lower_limit, higher_limit);
j(below_equal, on_in_range, near_jump);
}
void MacroAssembler::Push(Handle<HeapObject> source) {
Move(kScratchRegister, source);
Push(kScratchRegister);
}
void MacroAssembler::PushArray(Register array, Register size, Register scratch,
PushArrayOrder order) {
DCHECK(!AreAliased(array, size, scratch));
Register counter = scratch;
Label loop, entry;
if (order == PushArrayOrder::kReverse) {
Move(counter, 0);
jmp(&entry);
bind(&loop);
Push(Operand(array, counter, times_system_pointer_size, 0));
incq(counter);
bind(&entry);
cmpq(counter, size);
j(less, &loop, Label::kNear);
} else {
movq(counter, size);
jmp(&entry);
bind(&loop);
Push(Operand(array, counter, times_system_pointer_size, 0));
bind(&entry);
decq(counter);
j(greater_equal, &loop, Label::kNear);
}
}
void MacroAssembler::Move(Register result, Handle<HeapObject> object,
RelocInfo::Mode rmode) {
// TODO(jgruber,v8:8887): Also consider a root-relative load when generating
// non-isolate-independent code. In many cases it might be cheaper than
// embedding the relocatable value.
if (root_array_available_ && options().isolate_independent_code) {
// TODO(v8:9706): Fix-it! This load will always uncompress the value
// even when we are loading a compressed embedded object.
IndirectLoadConstant(result, object);
} else if (RelocInfo::IsCompressedEmbeddedObject(rmode)) {
EmbeddedObjectIndex index = AddEmbeddedObject(object);
DCHECK(is_uint32(index));
movl(result, Immediate(static_cast<int>(index), rmode));
} else {
DCHECK(RelocInfo::IsFullEmbeddedObject(rmode));
movq(result, Immediate64(object.address(), rmode));
}
}
void MacroAssembler::Move(Operand dst, Handle<HeapObject> object,
RelocInfo::Mode rmode) {
Move(kScratchRegister, object, rmode);
movq(dst, kScratchRegister);
}
void MacroAssembler::Drop(int stack_elements) {
if (stack_elements > 0) {
addq(rsp, Immediate(stack_elements * kSystemPointerSize));
}
}
void MacroAssembler::DropUnderReturnAddress(int stack_elements,
Register scratch) {
DCHECK_GT(stack_elements, 0);
if (stack_elements == 1) {
popq(MemOperand(rsp, 0));
return;
}
PopReturnAddressTo(scratch);
Drop(stack_elements);
PushReturnAddressFrom(scratch);
}
void MacroAssembler::DropArguments(Register count) {
leaq(rsp, Operand(rsp, count, times_system_pointer_size, 0));
}
void MacroAssembler::DropArguments(Register count, Register scratch) {
DCHECK(!AreAliased(count, scratch));
PopReturnAddressTo(scratch);
DropArguments(count);
PushReturnAddressFrom(scratch);
}
void MacroAssembler::DropArgumentsAndPushNewReceiver(Register argc,
Register receiver,
Register scratch) {
DCHECK(!AreAliased(argc, receiver, scratch));
PopReturnAddressTo(scratch);
DropArguments(argc);
Push(receiver);
PushReturnAddressFrom(scratch);
}
void MacroAssembler::DropArgumentsAndPushNewReceiver(Register argc,
Operand receiver,
Register scratch) {
DCHECK(!AreAliased(argc, scratch));
DCHECK(!receiver.AddressUsesRegister(scratch));
PopReturnAddressTo(scratch);
DropArguments(argc);
Push(receiver);
PushReturnAddressFrom(scratch);
}
void MacroAssembler::Push(Register src) { pushq(src); }
void MacroAssembler::Push(Operand src) { pushq(src); }
void MacroAssembler::PushQuad(Operand src) { pushq(src); }
void MacroAssembler::Push(Immediate value) { pushq(value); }
void MacroAssembler::PushImm32(int32_t imm32) { pushq_imm32(imm32); }
void MacroAssembler::Pop(Register dst) { popq(dst); }
void MacroAssembler::Pop(Operand dst) { popq(dst); }
void MacroAssembler::PopQuad(Operand dst) { popq(dst); }
void MacroAssembler::Jump(const ExternalReference& reference) {
DCHECK(root_array_available());
jmp(Operand(kRootRegister, RootRegisterOffsetForExternalReferenceTableEntry(
isolate(), reference)));
}
void MacroAssembler::Jump(Operand op) { jmp(op); }
void MacroAssembler::Jump(Operand op, Condition cc) {
Label skip;
j(NegateCondition(cc), &skip, Label::kNear);
Jump(op);
bind(&skip);
}
void MacroAssembler::Jump(Address destination, RelocInfo::Mode rmode) {
Move(kScratchRegister, destination, rmode);
jmp(kScratchRegister);
}
void MacroAssembler::Jump(Address destination, RelocInfo::Mode rmode,
Condition cc) {
Label skip;
j(NegateCondition(cc), &skip, Label::kNear);
Jump(destination, rmode);
bind(&skip);
}
void MacroAssembler::Jump(Handle<Code> code_object, RelocInfo::Mode rmode) {
DCHECK_EQ(sandboxing_mode(), code_object->sandboxing_mode());
DCHECK_IMPLIES(options().isolate_independent_code,
Builtins::IsIsolateIndependentBuiltin(*code_object));
Builtin builtin = Builtin::kNoBuiltinId;
if (isolate()->builtins()->IsBuiltinHandle(code_object, &builtin)) {
TailCallBuiltin(builtin);
return;
}
DCHECK(RelocInfo::IsCodeTarget(rmode));
jmp(code_object, rmode);
}
void MacroAssembler::Jump(Handle<Code> code_object, RelocInfo::Mode rmode,
Condition cc) {
DCHECK_EQ(sandboxing_mode(), code_object->sandboxing_mode());
DCHECK_IMPLIES(options().isolate_independent_code,
Builtins::IsIsolateIndependentBuiltin(*code_object));
Builtin builtin = Builtin::kNoBuiltinId;
if (isolate()->builtins()->IsBuiltinHandle(code_object, &builtin)) {
TailCallBuiltin(builtin, cc);
return;
}
DCHECK(RelocInfo::IsCodeTarget(rmode));
j(cc, code_object, rmode);
}
void MacroAssembler::Call(ExternalReference ext) {
// TODO(350324877): can we DCHECK that the sandboxing mode is correct here?
LoadAddress(kScratchRegister, ext);
call(kScratchRegister);
}
void MacroAssembler::Call(Operand op) {
// TODO(350324877): can we DCHECK that the sandboxing mode is correct here?
if (!CpuFeatures::IsSupported(INTEL_ATOM)) {
call(op);
} else {
movq(kScratchRegister, op);
call(kScratchRegister);
}
}
void MacroAssembler::Call(Address destination, RelocInfo::Mode rmode) {
Move(kScratchRegister, destination, rmode);
call(kScratchRegister);
}
void MacroAssembler::Call(Handle<Code> code_object, RelocInfo::Mode rmode) {
DCHECK_IMPLIES(options().isolate_independent_code,
Builtins::IsIsolateIndependentBuiltin(*code_object));
Builtin builtin = Builtin::kNoBuiltinId;
if (isolate()->builtins()->IsBuiltinHandle(code_object, &builtin)) {
CallBuiltin(builtin);
return;
}
DCHECK_EQ(sandboxing_mode(), code_object->sandboxing_mode());
DCHECK(RelocInfo::IsCodeTarget(rmode));
call(code_object, rmode);
}
Operand MacroAssembler::EntryFromBuiltinAsOperand(Builtin builtin) {
DCHECK(root_array_available());
return Operand(kRootRegister, IsolateData::BuiltinEntrySlotOffset(builtin));
}
Operand MacroAssembler::EntryFromBuiltinIndexAsOperand(Register builtin_index) {
if (SmiValuesAre32Bits()) {
// The builtin_index register contains the builtin index as a Smi.
Move(kScratchRegister, builtin_index); // Callee checks for equality.
SmiUntagUnsigned(kScratchRegister);
return Operand(kRootRegister, kScratchRegister, times_system_pointer_size,
IsolateData::builtin_entry_table_offset());
} else {
DCHECK(SmiValuesAre31Bits());
// The builtin_index register contains the builtin index as a Smi.
// Untagging is folded into the indexing operand below (we use
// times_half_system_pointer_size since smis are already shifted by one).
return Operand(kRootRegister, builtin_index, times_half_system_pointer_size,
IsolateData::builtin_entry_table_offset());
}
}
void MacroAssembler::CallBuiltinByIndex(Register builtin_index) {
// TODO(350324877): can we DCHECK that the sandboxing mode is correct here?
Call(EntryFromBuiltinIndexAsOperand(builtin_index));
}
void MacroAssembler::CallBuiltin(Builtin builtin) {
ASM_CODE_COMMENT_STRING(this, CommentForOffHeapTrampoline("call", builtin));
// Check if this builtin call transitions out of sandboxed execution mode.
CodeSandboxingMode previous_mode = SwitchSandboxingModeBeforeCallIfNeeded(
Builtins::SandboxingModeOf(builtin));
switch (options().builtin_call_jump_mode) {
case BuiltinCallJumpMode::kAbsolute:
Call(BuiltinEntry(builtin), RelocInfo::OFF_HEAP_TARGET);
break;
case BuiltinCallJumpMode::kPCRelative:
near_call(static_cast<intptr_t>(builtin), RelocInfo::NEAR_BUILTIN_ENTRY);
break;
case BuiltinCallJumpMode::kIndirect:
Call(EntryFromBuiltinAsOperand(builtin));
break;
case BuiltinCallJumpMode::kForMksnapshot: {
Handle<Code> code = isolate()->builtins()->code_handle(builtin);
call(code, RelocInfo::CODE_TARGET);
break;
}
}
SwitchSandboxingModeAfterCallIfNeeded(previous_mode);
}
void MacroAssembler::TailCallBuiltin(Builtin builtin) {
// We cannot (currently) switch the sandboxing mode on tail calls.
DCHECK_EQ(sandboxing_mode(), Builtins::SandboxingModeOf(builtin));
ASM_CODE_COMMENT_STRING(this,
CommentForOffHeapTrampoline("tail call", builtin));
switch (options().builtin_call_jump_mode) {
case BuiltinCallJumpMode::kAbsolute:
Jump(BuiltinEntry(builtin), RelocInfo::OFF_HEAP_TARGET);
break;
case BuiltinCallJumpMode::kPCRelative:
near_jmp(static_cast<intptr_t>(builtin), RelocInfo::NEAR_BUILTIN_ENTRY);
break;
case BuiltinCallJumpMode::kIndirect:
Jump(EntryFromBuiltinAsOperand(builtin));
break;
case BuiltinCallJumpMode::kForMksnapshot: {
Handle<Code> code = isolate()->builtins()->code_handle(builtin);
jmp(code, RelocInfo::CODE_TARGET);
break;
}
}
}
void MacroAssembler::TailCallBuiltin(Builtin builtin, Condition cc) {
// We cannot (currently) switch the sandboxing mode on tail calls.
DCHECK_EQ(sandboxing_mode(), Builtins::SandboxingModeOf(builtin));
ASM_CODE_COMMENT_STRING(this,
CommentForOffHeapTrampoline("tail call", builtin));
switch (options().builtin_call_jump_mode) {
case BuiltinCallJumpMode::kAbsolute:
Jump(BuiltinEntry(builtin), RelocInfo::OFF_HEAP_TARGET, cc);
break;
case BuiltinCallJumpMode::kPCRelative:
near_j(cc, static_cast<intptr_t>(builtin), RelocInfo::NEAR_BUILTIN_ENTRY);
break;
case BuiltinCallJumpMode::kIndirect:
Jump(EntryFromBuiltinAsOperand(builtin), cc);
break;
case BuiltinCallJumpMode::kForMksnapshot: {
Handle<Code> code = isolate()->builtins()->code_handle(builtin);
j(cc, code, RelocInfo::CODE_TARGET);
break;
}
}
}
void MacroAssembler::LoadCodeInstructionStart(Register destination,
Register code_object,
CodeEntrypointTag tag) {
ASM_CODE_COMMENT(this);
#ifdef V8_ENABLE_SANDBOX
LoadCodeEntrypointViaCodePointer(
destination, FieldOperand(code_object, Code::kSelfIndirectPointerOffset),
tag);
#else
movq(destination, FieldOperand(code_object, Code::kInstructionStartOffset));
#endif
}
void MacroAssembler::CallCodeObject(Register code_object,
CodeEntrypointTag tag) {
LoadCodeInstructionStart(code_object, code_object, tag);
call(code_object);
}
void MacroAssembler::JumpCodeObject(Register code_object, CodeEntrypointTag tag,
JumpMode jump_mode) {
// TODO(saelo): can we avoid using this for JavaScript functions
// (kJSEntrypointTag) and instead use a variant that ensures that the caller
// and callee agree on the signature (i.e. parameter count)?
LoadCodeInstructionStart(code_object, code_object, tag);
switch (jump_mode) {
case JumpMode::kJump:
jmp(code_object);
return;
case JumpMode::kPushAndReturn:
pushq(code_object);
Ret();
return;
}
}
void MacroAssembler::CallJSFunction(Register function_object,
uint16_t argument_count) {
static_assert(kJavaScriptCallCodeStartRegister == rcx, "ABI mismatch");
static_assert(kJavaScriptCallDispatchHandleRegister == r15, "ABI mismatch");
movl(r15, FieldOperand(function_object, JSFunction::kDispatchHandleOffset));
LoadEntrypointAndParameterCountFromJSDispatchTable(rcx, rbx, r15);
// Force a safe crash if the parameter count doesn't match.
// TODO(412398354): to avoid this runtime check, we should switch all
// remaining users to call the function via its dispatch handle instead. See
// CallJSDispatchEntry below and crbug.com/412398354 for more details.
cmpl(rbx, Immediate(argument_count));
SbxCheck(less_equal, AbortReason::kJSSignatureMismatch);
call(rcx);
}
void MacroAssembler::CallJSDispatchEntry(JSDispatchHandle dispatch_handle,
uint16_t argument_count) {
static_assert(kJavaScriptCallCodeStartRegister == rcx, "ABI mismatch");
static_assert(kJavaScriptCallDispatchHandleRegister == r15, "ABI mismatch");
movl(kJavaScriptCallDispatchHandleRegister,
Immediate(dispatch_handle.value(), RelocInfo::JS_DISPATCH_HANDLE));
LoadEntrypointFromJSDispatchTable(rcx, kJavaScriptCallDispatchHandleRegister);
CHECK_EQ(argument_count,
isolate()->js_dispatch_table().GetParameterCount(dispatch_handle));
call(rcx);
}
void MacroAssembler::JumpJSFunction(Register function_object,
JumpMode jump_mode) {
CHECK(!V8_ENABLE_SANDBOX_BOOL);
// This implementation is not currently used because callers usually need
// to load both entry point and parameter count and then do something with
// the latter before the actual call.
UNREACHABLE();
}
#ifdef V8_ENABLE_WEBASSEMBLY
void MacroAssembler::CallWasmCodePointer(Register target,
uint64_t signature_hash,
CallJumpMode call_jump_mode) {
ASM_CODE_COMMENT(this);
Move(kScratchRegister, ExternalReference::wasm_code_pointer_table());
#ifdef V8_ENABLE_SANDBOX
// Execute a left shift followed by right shift to achieve two things:
// - Only keep `kNumRelevantBits` bits (to avoid OOB access to the table),
// - shift by `kLeftShift` to translate from index to offset into the table.
static constexpr int kLeftShift =
base::bits::WhichPowerOfTwo(sizeof(wasm::WasmCodePointerTableEntry));
static constexpr int kNumRelevantBits =
base::bits::WhichPowerOfTwo(WasmCodePointer::kIndexSpaceSize);
static constexpr int kNumClearedHighBits = 32 - kNumRelevantBits;
shll(target, Immediate(kNumClearedHighBits));
shrl(target, Immediate(kNumClearedHighBits - kLeftShift));
// Add `target` and `kScratchRegister` early to free `kScratchRegister` again.
addq(target, kScratchRegister);
Operand signature_hash_op{target,
wasm::WasmCodePointerTable::kOffsetOfSignatureHash};
if (is_int32(signature_hash)) {
// cmpq sign-extends the 32-bit immediate.
cmpq(signature_hash_op, Immediate(static_cast<int32_t>(signature_hash)));
} else {
Move(kScratchRegister, signature_hash);
cmpq(kScratchRegister, signature_hash_op);
}
Label fail, ok;
j(Condition::kNotEqual, &fail, Label::Distance::kNear);
jmp(&ok, Label::Distance::kNear);
bind(&fail);
Abort(AbortReason::kWasmSignatureMismatch);
bind(&ok);
Operand target_op{target, 0};
#else
static_assert(sizeof(wasm::WasmCodePointerTableEntry) == 8);
Operand target_op{kScratchRegister, target, ScaleFactor::times_8, 0};
#endif
if (call_jump_mode == CallJumpMode::kTailCall) {
jmp(target_op);
} else {
call(target_op);
}
}
void MacroAssembler::CallWasmCodePointerNoSignatureCheck(Register target) {
Move(kScratchRegister, ExternalReference::wasm_code_pointer_table());
#ifdef V8_ENABLE_SANDBOX
// Execute a left shift followed by right shift to achieve two things:
// - Only keep `kNumRelevantBits` bits (to avoid OOB access to the table),
// - shift by `kLeftShift` to translate from index to offset into the table.
static constexpr int kLeftShift =
base::bits::WhichPowerOfTwo(sizeof(wasm::WasmCodePointerTableEntry));
static constexpr int kNumRelevantBits =
base::bits::WhichPowerOfTwo(WasmCodePointer::kIndexSpaceSize);
static constexpr int kNumClearedHighBits = 32 - kNumRelevantBits;
static_assert(kNumClearedHighBits == 9);
shll(target, Immediate(kNumClearedHighBits));
shrl(target, Immediate(kNumClearedHighBits - kLeftShift));
call(Operand(kScratchRegister, target, ScaleFactor::times_1, 0));
#else
static_assert(sizeof(wasm::WasmCodePointerTableEntry) == 8);
call(Operand(kScratchRegister, target, ScaleFactor::times_8, 0));
#endif
}
void MacroAssembler::LoadWasmCodePointer(Register dst, Operand src) {
static_assert(sizeof(WasmCodePointer) == 4);
movl(dst, src);
}
#endif
void MacroAssembler::PextrdPreSse41(Register dst, XMMRegister src,
uint8_t imm8) {
if (imm8 == 0) {
Movd(dst, src);
return;
}
DCHECK_EQ(1, imm8);
movq(dst, src);
shrq(dst, Immediate(32));
}
namespace {
template <typename Op>
void PinsrdPreSse41Helper(MacroAssembler* masm, XMMRegister dst, Op src,
uint8_t imm8, uint32_t* load_pc_offset) {
masm->Movd(kScratchDoubleReg, src);
if (load_pc_offset) *load_pc_offset = masm->pc_offset();
if (imm8 == 1) {
masm->punpckldq(dst, kScratchDoubleReg);
} else {
DCHECK_EQ(0, imm8);
masm->Movss(dst, kScratchDoubleReg);
}
}
} // namespace
void MacroAssembler::PinsrdPreSse41(XMMRegister dst, Register src, uint8_t imm8,
uint32_t* load_pc_offset) {
PinsrdPreSse41Helper(this, dst, src, imm8, load_pc_offset);
}
void MacroAssembler::PinsrdPreSse41(XMMRegister dst, Operand src, uint8_t imm8,
uint32_t* load_pc_offset) {
PinsrdPreSse41Helper(this, dst, src, imm8, load_pc_offset);
}
void MacroAssembler::Pinsrq(XMMRegister dst, XMMRegister src1, Register src2,
uint8_t imm8, uint32_t* load_pc_offset) {
PinsrHelper(this, &Assembler::vpinsrq, &Assembler::pinsrq, dst, src1, src2,
imm8, load_pc_offset, {SSE4_1});
}
void MacroAssembler::Pinsrq(XMMRegister dst, XMMRegister src1, Operand src2,
uint8_t imm8, uint32_t* load_pc_offset) {
PinsrHelper(this, &Assembler::vpinsrq, &Assembler::pinsrq, dst, src1, src2,
imm8, load_pc_offset, {SSE4_1});
}
void MacroAssembler::Lzcntl(Register dst, Register src) {
if (CpuFeatures::IsSupported(LZCNT)) {
CpuFeatureScope scope(this, LZCNT);
lzcntl(dst, src);
return;
}
Label not_zero_src;
bsrl(dst, src);
j(not_zero, &not_zero_src, Label::kNear);
Move(dst, 63); // 63^31 == 32
bind(&not_zero_src);
xorl(dst, Immediate(31)); // for x in [0..31], 31^x == 31 - x
}
void MacroAssembler::Lzcntl(Register dst, Operand src) {
if (CpuFeatures::IsSupported(LZCNT)) {
CpuFeatureScope scope(this, LZCNT);
lzcntl(dst, src);
return;
}
Label not_zero_src;
bsrl(dst, src);
j(not_zero, &not_zero_src, Label::kNear);
Move(dst, 63); // 63^31 == 32
bind(&not_zero_src);
xorl(dst, Immediate(31)); // for x in [0..31], 31^x == 31 - x
}
void MacroAssembler::Lzcntq(Register dst, Register src) {
if (CpuFeatures::IsSupported(LZCNT)) {
CpuFeatureScope scope(this, LZCNT);
lzcntq(dst, src);
return;
}
Label not_zero_src;
bsrq(dst, src);
j(not_zero, &not_zero_src, Label::kNear);
Move(dst, 127); // 127^63 == 64
bind(&not_zero_src);
xorl(dst, Immediate(63)); // for x in [0..63], 63^x == 63 - x
}
void MacroAssembler::Lzcntq(Register dst, Operand src) {
if (CpuFeatures::IsSupported(LZCNT)) {
CpuFeatureScope scope(this, LZCNT);
lzcntq(dst, src);
return;
}
Label not_zero_src;
bsrq(dst, src);
j(not_zero, &not_zero_src, Label::kNear);
Move(dst, 127); // 127^63 == 64
bind(&not_zero_src);
xorl(dst, Immediate(63)); // for x in [0..63], 63^x == 63 - x
}
void MacroAssembler::Tzcntq(Register dst, Register src) {
if (CpuFeatures::IsSupported(BMI1)) {
CpuFeatureScope scope(this, BMI1);
tzcntq(dst, src);
return;
}
Label not_zero_src;
bsfq(dst, src);
j(not_zero, &not_zero_src, Label::kNear);
// Define the result of tzcnt(0) separately, because bsf(0) is undefined.
Move(dst, 64);
bind(&not_zero_src);
}
void MacroAssembler::Tzcntq(Register dst, Operand src) {
if (CpuFeatures::IsSupported(BMI1)) {
CpuFeatureScope scope(this, BMI1);
tzcntq(dst, src);
return;
}
Label not_zero_src;
bsfq(dst, src);
j(not_zero, &not_zero_src, Label::kNear);
// Define the result of tzcnt(0) separately, because bsf(0) is undefined.
Move(dst, 64);
bind(&not_zero_src);
}
void MacroAssembler::Tzcntl(Register dst, Register src) {
if (CpuFeatures::IsSupported(BMI1)) {
CpuFeatureScope scope(this, BMI1);
tzcntl(dst, src);
return;
}
Label not_zero_src;
bsfl(dst, src);
j(not_zero, &not_zero_src, Label::kNear);
Move(dst, 32); // The result of tzcnt is 32 if src = 0.
bind(&not_zero_src);
}
void MacroAssembler::Tzcntl(Register dst, Operand src) {
if (CpuFeatures::IsSupported(BMI1)) {
CpuFeatureScope scope(this, BMI1);
tzcntl(dst, src);
return;
}
Label not_zero_src;
bsfl(dst, src);
j(not_zero, &not_zero_src, Label::kNear);
Move(dst, 32); // The result of tzcnt is 32 if src = 0.
bind(&not_zero_src);
}
void MacroAssembler::Popcntl(Register dst, Register src) {
if (CpuFeatures::IsSupported(POPCNT)) {
CpuFeatureScope scope(this, POPCNT);
popcntl(dst, src);
return;
}
UNREACHABLE();
}
void MacroAssembler::Popcntl(Register dst, Operand src) {
if (CpuFeatures::IsSupported(POPCNT)) {
CpuFeatureScope scope(this, POPCNT);
popcntl(dst, src);
return;
}
UNREACHABLE();
}
void MacroAssembler::Popcntq(Register dst, Register src) {
if (CpuFeatures::IsSupported(POPCNT)) {
CpuFeatureScope scope(this, POPCNT);
popcntq(dst, src);
return;
}
UNREACHABLE();
}
void MacroAssembler::Popcntq(Register dst, Operand src) {
if (CpuFeatures::IsSupported(POPCNT)) {
CpuFeatureScope scope(this, POPCNT);
popcntq(dst, src);
return;
}
UNREACHABLE();
}
void MacroAssembler::PushStackHandler() {
// Adjust this code if not the case.
static_assert(StackHandlerConstants::kSize == 2 * kSystemPointerSize);
static_assert(StackHandlerConstants::kNextOffset == 0);
Push(Immediate(0)); // Padding.
// Link the current handler as the next handler.
Push(AsMemOperand(IsolateFieldId::kHandler));
// Set this new handler as the current one.
movq(AsMemOperand(IsolateFieldId::kHandler), rsp);
}
void MacroAssembler::PopStackHandler() {
static_assert(StackHandlerConstants::kNextOffset == 0);
Pop(AsMemOperand(IsolateFieldId::kHandler));
addq(rsp, Immediate(StackHandlerConstants::kSize - kSystemPointerSize));
}
void MacroAssembler::Ret() { ret(0); }
void MacroAssembler::Ret(int bytes_dropped, Register scratch) {
if (is_uint16(bytes_dropped)) {
ret(bytes_dropped);
} else {
PopReturnAddressTo(scratch);
addq(rsp, Immediate(bytes_dropped));
// Push and ret (instead of jmp) to keep the RSB and the CET shadow stack
// balanced.
PushReturnAddressFrom(scratch);
ret(0);
}
}
void MacroAssembler::IncsspqIfSupported(Register number_of_words,
Register scratch) {
// Optimized code can validate at runtime whether the cpu supports the
// incsspq instruction, so it shouldn't use this method.
CHECK(isolate()->IsGeneratingEmbeddedBuiltins());
DCHECK_NE(number_of_words, scratch);
Label not_supported;
ExternalReference supports_cetss =
ExternalReference::supports_cetss_address();
Operand supports_cetss_operand =
ExternalReferenceAsOperand(supports_cetss, scratch);
cmpb(supports_cetss_operand, Immediate(0));
j(equal, &not_supported, Label::kNear);
incsspq(number_of_words);
bind(&not_supported);
}
#if V8_STATIC_ROOTS_BOOL
void MacroAssembler::CompareInstanceTypeWithUniqueCompressedMap(
Register map, InstanceType type) {
std::optional<RootIndex> expected =
InstanceTypeChecker::UniqueMapOfInstanceType(type);
CHECK(expected);
Tagged_t expected_ptr = ReadOnlyRootPtr(*expected);
cmp_tagged(map, Immediate(expected_ptr));
}
void MacroAssembler::IsObjectTypeFast(Register object, InstanceType type,
Register compressed_map_scratch) {
ASM_CODE_COMMENT(this);
CHECK(InstanceTypeChecker::UniqueMapOfInstanceType(type));
LoadCompressedMap(compressed_map_scratch, object);
CompareInstanceTypeWithUniqueCompressedMap(compressed_map_scratch, type);
}
#endif // V8_STATIC_ROOTS_BOOL
void MacroAssembler::IsObjectType(Register heap_object, InstanceType type,
Register map) {
#if V8_STATIC_ROOTS_BOOL
if (InstanceTypeChecker::UniqueMapOfInstanceType(type)) {
LoadCompressedMap(map, heap_object);
CompareInstanceTypeWithUniqueCompressedMap(map, type);
return;
}
#endif // V8_STATIC_ROOTS_BOOL
CmpObjectType(heap_object, type, map);
}
void MacroAssembler::IsObjectTypeInRange(Register heap_object,
InstanceType lower_limit,
InstanceType higher_limit,
Register scratch) {
DCHECK_LT(lower_limit, higher_limit);
#if V8_STATIC_ROOTS_BOOL
if (auto range = InstanceTypeChecker::UniqueMapRangeOfInstanceTypeRange(
lower_limit, higher_limit)) {
LoadCompressedMap(scratch, heap_object);
CompareRange(scratch, range->first, range->second);
return;
}
#endif // V8_STATIC_ROOTS_BOOL
LoadMap(scratch, heap_object);
CmpInstanceTypeRange(scratch, scratch, lower_limit, higher_limit);
}
void MacroAssembler::JumpIfJSAnyIsNotPrimitive(Register heap_object,
Register scratch, Label* target,
Label::Distance distance,
Condition cc) {
CHECK(cc == Condition::kUnsignedLessThan ||
cc == Condition::kUnsignedGreaterThanEqual);
if (V8_STATIC_ROOTS_BOOL) {
#ifdef DEBUG
Label ok;
LoadMap(scratch, heap_object);
CmpInstanceTypeRange(scratch, scratch, FIRST_JS_RECEIVER_TYPE,
LAST_JS_RECEIVER_TYPE);
j(Condition::kUnsignedLessThanEqual, &ok, Label::Distance::kNear);
LoadMap(scratch, heap_object);
CmpInstanceTypeRange(scratch, scratch, FIRST_PRIMITIVE_HEAP_OBJECT_TYPE,
LAST_PRIMITIVE_HEAP_OBJECT_TYPE);
j(Condition::kUnsignedLessThanEqual, &ok, Label::Distance::kNear);
Abort(AbortReason::kInvalidReceiver);
bind(&ok);
#endif // DEBUG
// All primitive object's maps are allocated at the start of the read only
// heap. Thus JS_RECEIVER's must have maps with larger (compressed)
// addresses.
LoadCompressedMap(scratch, heap_object);
cmp_tagged(scratch, Immediate(InstanceTypeChecker::kNonJsReceiverMapLimit));
} else {
static_assert(LAST_JS_RECEIVER_TYPE == LAST_TYPE);
CmpObjectType(heap_object, FIRST_JS_RECEIVER_TYPE, scratch);
}
j(cc, target, distance);
}
void MacroAssembler::CmpObjectType(Register heap_object, InstanceType type,
Register map) {
LoadMap(map, heap_object);
CmpInstanceType(map, type);
}
void MacroAssembler::CmpInstanceType(Register map, InstanceType type) {
cmpw(FieldOperand(map, Map::kInstanceTypeOffset), Immediate(type));
}
void MacroAssembler::CmpInstanceTypeRange(Register map,
Register instance_type_out,
InstanceType lower_limit,
InstanceType higher_limit) {
DCHECK_LT(lower_limit, higher_limit);
movzxwl(instance_type_out, FieldOperand(map, Map::kInstanceTypeOffset));
CompareRange(instance_type_out, lower_limit, higher_limit);
}
void MacroAssembler::TestCodeIsMarkedForDeoptimization(Register code) {
const int kByteWithDeoptBitOffset = 0 * kByteSize;
const int kByteWithDeoptBitOffsetInBits = kByteWithDeoptBitOffset * 8;
static_assert(V8_TARGET_LITTLE_ENDIAN == 1);
static_assert(FIELD_SIZE(Code::kFlagsOffset) * kBitsPerByte == 32);
static_assert(Code::kMarkedForDeoptimizationBit >
kByteWithDeoptBitOffsetInBits);
testb(FieldOperand(code, Code::kFlagsOffset + kByteWithDeoptBitOffset),
Immediate(1 << (Code::kMarkedForDeoptimizationBit -
kByteWithDeoptBitOffsetInBits)));
}
void MacroAssembler::TestCodeIsTurbofanned(Register code) {
testl(FieldOperand(code, Code::kFlagsOffset),
Immediate(1 << Code::kIsTurbofannedBit));
}
Immediate MacroAssembler::ClearedValue() const {
return Immediate(static_cast<int32_t>(i::kClearedWeakValue.ptr()));
}
#ifdef V8_ENABLE_DEBUG_CODE
void MacroAssembler::AssertNotSmi(Register object) {
if (!v8_flags.debug_code) return;
ASM_CODE_COMMENT(this);
Condition is_smi = CheckSmi(object);
Check(NegateCondition(is_smi), AbortReason::kOperandIsASmi);
}
void MacroAssembler::AssertSmi(Register object) {
if (!v8_flags.debug_code) return;
ASM_CODE_COMMENT(this);
Condition is_smi = CheckSmi(object);
Check(is_smi, AbortReason::kOperandIsNotASmi);
#ifdef ENABLE_SLOW_DCHECKS
ClobberDecompressedSmiBits(object);
#endif
}
void MacroAssembler::AssertSmi(Operand object) {
if (!v8_flags.debug_code) return;
ASM_CODE_COMMENT(this);
Condition is_smi = CheckSmi(object);
Check(is_smi, AbortReason::kOperandIsNotASmi);
}
void MacroAssembler::AssertZeroExtended(Register int32_register) {
if (!v8_flags.slow_debug_code) return;
ASM_CODE_COMMENT(this);
DCHECK_NE(int32_register, kScratchRegister);
movl(kScratchRegister, Immediate(kMaxUInt32)); // zero-extended
cmpq(int32_register, kScratchRegister);
Check(below_equal, AbortReason::k32BitValueInRegisterIsNotZeroExtended);
}
void MacroAssembler::AssertSignBitOfSmiIsZero(Register smi_register) {
if (!v8_flags.slow_debug_code) return;
ASM_CODE_COMMENT(this);
DCHECK(COMPRESS_POINTERS_BOOL);
constexpr int kSmiShiftBits = kSmiTagSize + kSmiShiftSize;
constexpr Tagged_t kSmiSignBit = Tagged_t{1}
<< (kSmiShiftBits + kSmiValueSize - 1);
testl(smi_register, Immediate(static_cast<uint32_t>(kSmiSignBit)));
Check(zero, AbortReason::kSignBitOfSmiIsNotZero);
}
void MacroAssembler::AssertMap(Register object) {
if (!v8_flags.debug_code) return;
ASM_CODE_COMMENT(this);
testb(object, Immediate(kSmiTagMask));
Check(not_equal, AbortReason::kOperandIsNotAMap);
Push(object);
LoadMap(object, object);
CmpInstanceType(object, MAP_TYPE);
popq(object);
Check(equal, AbortReason::kOperandIsNotAMap);
}
void MacroAssembler::AssertCode(Register object) {
if (!v8_flags.debug_code) return;
ASM_CODE_COMMENT(this);
testb(object, Immediate(kSmiTagMask));
Check(not_equal, AbortReason::kOperandIsNotACode);
Push(object);
LoadMap(object, object);
CmpInstanceType(object, CODE_TYPE);
popq(object);
Check(equal, AbortReason::kOperandIsNotACode);
}
void MacroAssembler::AssertSmiOrHeapObjectInMainCompressionCage(
Register object) {
if (!PointerCompressionIsEnabled()) return;
if (!v8_flags.debug_code) return;
ASM_CODE_COMMENT(this);
Label ok;
// We may not have any scratch registers so we preserve our input register.
pushq(object);
j(CheckSmi(object), &ok);
// Clear the lower 32 bits.
shrq(object, Immediate(32));
shlq(object, Immediate(32));
// Either the value is now equal to the pointer compression cage base or it's
// zero if we got a compressed pointer register as input.
j(zero, &ok);
cmpq(object, kPtrComprCageBaseRegister);
Check(equal, AbortReason::kObjectNotTagged);
bind(&ok);
popq(object);
}
void MacroAssembler::AssertConstructor(Register object) {
if (!v8_flags.debug_code) return;
ASM_CODE_COMMENT(this);
testb(object, Immediate(kSmiTagMask));
Check(not_equal, AbortReason::kOperandIsASmiAndNotAConstructor);
Push(object);
LoadMap(object, object);
testb(FieldOperand(object, Map::kBitFieldOffset),
Immediate(Map::Bits1::IsConstructorBit::kMask));
Pop(object);
Check(not_zero, AbortReason::kOperandIsNotAConstructor);
}
void MacroAssembler::AssertFunction(Register object) {
if (!v8_flags.debug_code) return;
ASM_CODE_COMMENT(this);
testb(object, Immediate(kSmiTagMask));
Check(not_equal, AbortReason::kOperandIsASmiAndNotAFunction);
Push(object);
LoadMap(object, object);
CmpInstanceTypeRange(object, object, FIRST_JS_FUNCTION_TYPE,
LAST_JS_FUNCTION_TYPE);
Pop(object);
Check(below_equal, AbortReason::kOperandIsNotAFunction);
}
void MacroAssembler::AssertCallableFunction(Register object) {
if (!v8_flags.debug_code) return;
ASM_CODE_COMMENT(this);
testb(object, Immediate(kSmiTagMask));
Check(not_equal, AbortReason::kOperandIsASmiAndNotAFunction);
Push(object);
LoadMap(object, object);
CmpInstanceTypeRange(object, object, FIRST_CALLABLE_JS_FUNCTION_TYPE,
LAST_CALLABLE_JS_FUNCTION_TYPE);
Pop(object);
Check(below_equal, AbortReason::kOperandIsNotACallableFunction);
}
void MacroAssembler::AssertBoundFunction(Register object) {
if (!v8_flags.debug_code) return;
ASM_CODE_COMMENT(this);
testb(object, Immediate(kSmiTagMask));
Check(not_equal, AbortReason::kOperandIsASmiAndNotABoundFunction);
Push(object);
IsObjectType(object, JS_BOUND_FUNCTION_TYPE, object);
Pop(object);
Check(equal, AbortReason::kOperandIsNotABoundFunction);
}
void MacroAssembler::AssertGeneratorObject(Register object) {
if (!v8_flags.debug_code) return;
ASM_CODE_COMMENT(this);
testb(object, Immediate(kSmiTagMask));
Check(not_equal, AbortReason::kOperandIsASmiAndNotAGeneratorObject);
// Load map
Register map = object;
Push(object);
LoadMap(map, object);
// Check if JSGeneratorObject
CmpInstanceTypeRange(map, kScratchRegister, FIRST_JS_GENERATOR_OBJECT_TYPE,
LAST_JS_GENERATOR_OBJECT_TYPE);
// Restore generator object to register and perform assertion
Pop(object);
Check(below_equal, AbortReason::kOperandIsNotAGeneratorObject);
}
void MacroAssembler::AssertUndefinedOrAllocationSite(Register object) {
if (!v8_flags.debug_code) return;
ASM_CODE_COMMENT(this);
Label done_checking;
AssertNotSmi(object);
Cmp(object, isolate()->factory()->undefined_value());
j(equal, &done_checking);
Register map = object;
Push(object);
LoadMap(map, object);
Cmp(map, isolate()->factory()->allocation_site_map());
Pop(object);
Assert(equal, AbortReason::kExpectedUndefinedOrCell);
bind(&done_checking);
}
void MacroAssembler::AssertJSAny(Register object, Register map_tmp,
AbortReason abort_reason) {
if (!v8_flags.debug_code) return;
ASM_CODE_COMMENT(this);
DCHECK(!AreAliased(object, map_tmp));
Label ok;
Label::Distance dist = DEBUG_BOOL ? Label::kFar : Label::kNear;
JumpIfSmi(object, &ok, dist);
LoadMap(map_tmp, object);
CmpInstanceType(map_tmp, LAST_NAME_TYPE);
j(below_equal, &ok, dist);
CmpInstanceType(map_tmp, FIRST_JS_RECEIVER_TYPE);
j(above_equal, &ok, dist);
CompareRoot(map_tmp, RootIndex::kHeapNumberMap);
j(equal, &ok, dist);
CompareRoot(map_tmp, RootIndex::kBigIntMap);
j(equal, &ok, dist);
CompareRoot(object, RootIndex::kUndefinedValue);
j(equal, &ok, dist);
CompareRoot(object, RootIndex::kTrueValue);
j(equal, &ok, dist);
CompareRoot(object, RootIndex::kFalseValue);
j(equal, &ok, dist);
CompareRoot(object, RootIndex::kNullValue);
j(equal, &ok, dist);
Abort(abort_reason);
bind(&ok);
}
void MacroAssembler::Assert(Condition cc, AbortReason reason) {
if (v8_flags.debug_code) Check(cc, reason);
}
void MacroAssembler::AssertUnreachable(AbortReason reason) {
if (v8_flags.debug_code) Abort(reason);
}
#endif // V8_ENABLE_DEBUG_CODE
void MacroAssembler::LoadWeakValue(Register in_out, Label* target_if_cleared) {
cmpl(in_out, Immediate(kClearedWeakHeapObjectLower32));
j(equal, target_if_cleared);
andq(in_out, Immediate(~static_cast<int32_t>(kWeakHeapObjectMask)));
}
void MacroAssembler::EmitIncrementCounter(StatsCounter* counter, int value) {
DCHECK_GT(value, 0);
if (v8_flags.native_code_counters && counter->Enabled()) {
ASM_CODE_COMMENT(this);
Operand counter_operand =
ExternalReferenceAsOperand(ExternalReference::Create(counter));
// This operation has to be exactly 32-bit wide in case the external
// reference table redirects the counter to a uint32_t dummy_stats_counter_
// field.
if (value == 1) {
incl(counter_operand);
} else {
addl(counter_operand, Immediate(value));
}
}
}
void MacroAssembler::EmitDecrementCounter(StatsCounter* counter, int value) {
DCHECK_GT(value, 0);
if (v8_flags.native_code_counters && counter->Enabled()) {
ASM_CODE_COMMENT(this);
Operand counter_operand =
ExternalReferenceAsOperand(ExternalReference::Create(counter));
// This operation has to be exactly 32-bit wide in case the external
// reference table redirects the counter to a uint32_t dummy_stats_counter_
// field.
if (value == 1) {
decl(counter_operand);
} else {
subl(counter_operand, Immediate(value));
}
}
}
void MacroAssembler::InvokeFunction(
Register function, Register new_target, Register actual_parameter_count,
InvokeType type, ArgumentAdaptionMode argument_adaption_mode) {
ASM_CODE_COMMENT(this);
DCHECK_EQ(function, rdi);
LoadTaggedField(rsi, FieldOperand(function, JSFunction::kContextOffset));
InvokeFunctionCode(rdi, new_target, actual_parameter_count, type,
argument_adaption_mode);
}
void MacroAssembler::InvokeFunctionCode(
Register function, Register new_target, Register actual_parameter_count,
InvokeType type, ArgumentAdaptionMode argument_adaption_mode) {
ASM_CODE_COMMENT(this);
// You can't call a function without a valid frame.
DCHECK_IMPLIES(type == InvokeType::kCall, has_frame());
DCHECK_EQ(function, rdi);
DCHECK_IMPLIES(new_target.is_valid(), new_target == rdx);
Register dispatch_handle = kJavaScriptCallDispatchHandleRegister;
movl(dispatch_handle,
FieldOperand(function, JSFunction::kDispatchHandleOffset));
AssertFunction(function);
// On function call, call into the debugger if necessary.
Label debug_hook, continue_after_hook;
{
ExternalReference debug_hook_active =
ExternalReference::debug_hook_on_function_call_address(isolate());
Operand debug_hook_active_operand =
ExternalReferenceAsOperand(debug_hook_active);
cmpb(debug_hook_active_operand, Immediate(0));
j(not_equal, &debug_hook);
}
bind(&continue_after_hook);
// Clear the new.target register if not given.
if (!new_target.is_valid()) {
LoadRoot(rdx, RootIndex::kUndefinedValue);
}
if (argument_adaption_mode == ArgumentAdaptionMode::kAdapt) {
Register expected_parameter_count = rbx;
LoadParameterCountFromJSDispatchTable(expected_parameter_count,
dispatch_handle);
InvokePrologue(expected_parameter_count, actual_parameter_count, type);
}
// We call indirectly through the code field in the function to
// allow recompilation to take effect without changing any of the
// call sites.
static_assert(kJavaScriptCallCodeStartRegister == rcx, "ABI mismatch");
LoadEntrypointFromJSDispatchTable(rcx, dispatch_handle);
switch (type) {
case InvokeType::kCall:
call(rcx);
break;
case InvokeType::kJump:
jmp(rcx);
break;
}
Label done;
jmp(&done, Label::kNear);
// Deferred debug hook.
bind(&debug_hook);
CallDebugOnFunctionCall(function, new_target, dispatch_handle,
actual_parameter_count);
jmp(&continue_after_hook);
bind(&done);
}
Operand MacroAssembler::StackLimitAsOperand(StackLimitKind kind) {
DCHECK(root_array_available());
intptr_t offset = kind == StackLimitKind::kRealStackLimit
? IsolateData::real_jslimit_offset()
: IsolateData::jslimit_offset();
CHECK(is_int32(offset));
return Operand(kRootRegister, static_cast<int32_t>(offset));
}
void MacroAssembler::StackOverflowCheck(
Register num_args, Label* stack_overflow,
Label::Distance stack_overflow_distance) {
ASM_CODE_COMMENT(this);
DCHECK_NE(num_args, kScratchRegister);
// Check the stack for overflow. We are not trying to catch
// interruptions (e.g. debug break and preemption) here, so the "real stack
// limit" is checked.
movq(kScratchRegister, rsp);
// Make kScratchRegister the space we have left. The stack might already be
// overflowed here which will cause kScratchRegister to become negative.
subq(kScratchRegister, StackLimitAsOperand(StackLimitKind::kRealStackLimit));
// TODO(victorgomes): Use ia32 approach with leaq, since it requires less
// instructions.
sarq(kScratchRegister, Immediate(kSystemPointerSizeLog2));
// Check if the arguments will overflow the stack.
cmpq(kScratchRegister, num_args);
// Signed comparison.
// TODO(victorgomes): Save some bytes in the builtins that use stack checks
// by jumping to a builtin that throws the exception.
j(less_equal, stack_overflow, stack_overflow_distance);
}
void MacroAssembler::InvokePrologue(Register expected_parameter_count,
Register actual_parameter_count,
InvokeType type) {
ASM_CODE_COMMENT(this);
if (expected_parameter_count == actual_parameter_count) {
Move(rax, actual_parameter_count);
return;
}
Label regular_invoke;
// If overapplication or if the actual argument count is equal to the
// formal parameter count, no need to push extra undefined values.
subq(expected_parameter_count, actual_parameter_count);
j(less_equal, &regular_invoke, Label::kFar);
Label stack_overflow;
StackOverflowCheck(expected_parameter_count, &stack_overflow);
// Underapplication. Move the arguments already in the stack, including the
// receiver and the return address.
{
Label copy, check;
Register src = r8, dest = rsp, num = r9, current = r11;
movq(src, rsp);
leaq(kScratchRegister,
Operand(expected_parameter_count, times_system_pointer_size, 0));
AllocateStackSpace(kScratchRegister);
// Extra words are for the return address (if a jump).
int extra_words =
type == InvokeType::kCall ? 0 : kReturnAddressStackSlotCount;
leaq(num, Operand(rax, extra_words)); // Number of words to copy.
Move(current, 0);
// Fall-through to the loop body because there are non-zero words to copy.
bind(&copy);
movq(kScratchRegister,
Operand(src, current, times_system_pointer_size, 0));
movq(Operand(dest, current, times_system_pointer_size, 0),
kScratchRegister);
incq(current);
bind(&check);
cmpq(current, num);
j(less, &copy);
leaq(r8, Operand(rsp, num, times_system_pointer_size, 0));
}
// Fill remaining expected arguments with undefined values.
LoadRoot(kScratchRegister, RootIndex::kUndefinedValue);
{
Label loop;
bind(&loop);
decq(expected_parameter_count);
movq(Operand(r8, expected_parameter_count, times_system_pointer_size, 0),
kScratchRegister);
j(greater, &loop, Label::kNear);
}
jmp(&regular_invoke);
bind(&stack_overflow);
{
FrameScope frame(
this, has_frame() ? StackFrame::NO_FRAME_TYPE : StackFrame::INTERNAL);
CallRuntime(Runtime::kThrowStackOverflow);
int3(); // This should be unreachable.
}
bind(&regular_invoke);
}
void MacroAssembler::CallDebugOnFunctionCall(Register fun, Register new_target,
Register dispatch_handle,
Register actual_parameter_count) {
ASM_CODE_COMMENT(this);
// Load receiver to pass it later to DebugOnFunctionCall hook.
// Receiver is located on top of the stack if we have a frame (usually a
// construct frame), or after the return address if we do not yet have a
// frame.
movq(kScratchRegister, Operand(rsp, has_frame() ? 0 : kSystemPointerSize));
FrameScope frame(
this, has_frame() ? StackFrame::NO_FRAME_TYPE : StackFrame::INTERNAL);
// We must not Smi-tag the dispatch handle, because its top bits are
// meaningful; and we also don't need to, because its low bits are zero.
static_assert(kJSDispatchHandleShift >= 1);
Push(dispatch_handle);
SmiTag(actual_parameter_count);
Push(actual_parameter_count);
SmiUntag(actual_parameter_count);
if (new_target.is_valid()) {
Push(new_target);
}
Push(fun);
Push(fun);
Push(kScratchRegister);
CallRuntime(Runtime::kDebugOnFunctionCall);
Pop(fun);
if (new_target.is_valid()) {
Pop(new_target);
}
Pop(actual_parameter_count);
SmiUntag(actual_parameter_count);
Pop(dispatch_handle);
}
void MacroAssembler::StubPrologue(StackFrame::Type type) {
ASM_CODE_COMMENT(this);
pushq(rbp); // Caller's frame pointer.
movq(rbp, rsp);
Push(Immediate(StackFrame::TypeToMarker(type)));
}
void MacroAssembler::Prologue() {
ASM_CODE_COMMENT(this);
pushq(rbp); // Caller's frame pointer.
movq(rbp, rsp);
Push(kContextRegister); // Callee's context.
Push(kJSFunctionRegister); // Callee's JS function.
Push(kJavaScriptCallArgCountRegister); // Actual argument count.
}
void MacroAssembler::EnterFrame(StackFrame::Type type) {
ASM_CODE_COMMENT(this);
pushq(rbp);
movq(rbp, rsp);
if (!StackFrame::IsJavaScript(type)) {
static_assert(CommonFrameConstants::kContextOrFrameTypeOffset ==
-kSystemPointerSize);
Push(Immediate(StackFrame::TypeToMarker(type)));
}
#if V8_ENABLE_WEBASSEMBLY
if (type == StackFrame::WASM) Push(kWasmImplicitArgRegister);
#endif // V8_ENABLE_WEBASSEMBLY
}
void MacroAssembler::LeaveFrame(StackFrame::Type type) {
ASM_CODE_COMMENT(this);
// TODO(v8:11429): Consider passing BASELINE instead, and checking for
// IsJSFrame or similar. Could then unify with manual frame leaves in the
// interpreter too.
if (v8_flags.debug_code && !StackFrame::IsJavaScript(type)) {
cmpq(Operand(rbp, CommonFrameConstants::kContextOrFrameTypeOffset),
Immediate(StackFrame::TypeToMarker(type)));
Check(equal, AbortReason::kStackFrameTypesMustMatch);
}
movq(rsp, rbp);
popq(rbp);
}
#if defined(V8_TARGET_OS_WIN) || defined(V8_TARGET_OS_MACOS)
void MacroAssembler::AllocateStackSpace(Register bytes_scratch) {
ASM_CODE_COMMENT(this);
// On Windows and on macOS, we cannot increment the stack size by more than
// one page (minimum page size is 4KB) without accessing at least one byte on
// the page. Check this:
// https://msdn.microsoft.com/en-us/library/aa227153(v=vs.60).aspx.
Label check_offset;
Label touch_next_page;
jmp(&check_offset);
bind(&touch_next_page);
subq(rsp, Immediate(kStackPageSize));
// Just to touch the page, before we increment further.
movb(Operand(rsp, 0), Immediate(0));
subq(bytes_scratch, Immediate(kStackPageSize));
bind(&check_offset);
cmpq(bytes_scratch, Immediate(kStackPageSize));
j(greater_equal, &touch_next_page);
subq(rsp, bytes_scratch);
}
void MacroAssembler::AllocateStackSpace(int bytes) {
ASM_CODE_COMMENT(this);
DCHECK_GE(bytes, 0);
while (bytes >= kStackPageSize) {
subq(rsp, Immediate(kStackPageSize));
movb(Operand(rsp, 0), Immediate(0));
bytes -= kStackPageSize;
}
if (bytes == 0) return;
subq(rsp, Immediate(bytes));
}
#endif
void MacroAssembler::EnterExitFrame(int extra_slots,
StackFrame::Type frame_type) {
ASM_CODE_COMMENT(this);
DCHECK(frame_type == StackFrame::EXIT ||
frame_type == StackFrame::BUILTIN_EXIT ||
frame_type == StackFrame::API_NAMED_ACCESSOR_EXIT ||
frame_type == StackFrame::API_CALLBACK_EXIT);
// Set up the frame structure on the stack.
// All constants are relative to the frame pointer of the exit frame.
DCHECK_EQ(kFPOnStackSize + kPCOnStackSize,
ExitFrameConstants::kCallerSPDisplacement);
DCHECK_EQ(kFPOnStackSize, ExitFrameConstants::kCallerPCOffset);
DCHECK_EQ(0 * kSystemPointerSize, ExitFrameConstants::kCallerFPOffset);
pushq(rbp);
movq(rbp, rsp);
Push(Immediate(StackFrame::TypeToMarker(frame_type)));
DCHECK_EQ(-2 * kSystemPointerSize, ExitFrameConstants::kSPOffset);
Push(Immediate(0)); // Saved entry sp, patched below.
DCHECK(!AreAliased(rbp, kContextRegister));
movq(AsMemOperand(IsolateFieldId::kCEntryFP), rbp);
movq(AsMemOperand(IsolateFieldId::kContext), kContextRegister);
#ifdef V8_TARGET_OS_WIN
// Note this is only correct under the assumption that the caller hasn't
// considered home stack slots already.
// TODO(jgruber): This is a bit hacky since the caller in most cases still
// needs to know about the home stack slots in order to address reserved
// slots. Consider moving this fully into caller code.
extra_slots += kWindowsHomeStackSlots;
#endif
AllocateStackSpace(extra_slots * kSystemPointerSize);
AlignStackPointer();
// Patch the saved entry sp.
movq(Operand(rbp, ExitFrameConstants::kSPOffset), rsp);
}
void MacroAssembler::LeaveExitFrame() {
ASM_CODE_COMMENT(this);
leave();
// Restore the current context from top and clear it in debug mode.
movq(rsi, AsMemOperand(IsolateFieldId::kContext));
#ifdef DEBUG
Move(AsMemOperand(IsolateFieldId::kContext), Context::kNoContext);
#endif
// Clear the top frame.
Move(AsMemOperand(IsolateFieldId::kCEntryFP), 0);
}
void MacroAssembler::LoadNativeContextSlot(Register dst, int index) {
ASM_CODE_COMMENT(this);
// Load native context.
LoadMap(dst, rsi);
LoadTaggedField(
dst,
FieldOperand(dst, Map::kConstructorOrBackPointerOrNativeContextOffset));
// Load value from native context.
LoadTaggedField(dst, Operand(dst, Context::SlotOffset(index)));
}
void MacroAssembler::TryLoadOptimizedOsrCode(Register scratch_and_result,
CodeKind min_opt_level,
Register feedback_vector,
FeedbackSlot slot,
Label* on_result,
Label::Distance distance) {
ASM_CODE_COMMENT(this);
Label fallthrough, on_mark_deopt;
LoadTaggedField(
scratch_and_result,
FieldOperand(feedback_vector,
FeedbackVector::OffsetOfElementAt(slot.ToInt())));
LoadWeakValue(scratch_and_result, &fallthrough);
// Is it marked_for_deoptimization? If yes, clear the slot.
{
// The entry references a CodeWrapper object. Unwrap it now.
LoadCodePointerField(
scratch_and_result,
FieldOperand(scratch_and_result, CodeWrapper::kCodeOffset),
kScratchRegister);
TestCodeIsMarkedForDeoptimization(scratch_and_result);
if (min_opt_level == CodeKind::TURBOFAN_JS) {
j(not_zero, &on_mark_deopt, Label::Distance::kNear);
TestCodeIsTurbofanned(scratch_and_result);
j(not_zero, on_result, distance);
jmp(&fallthrough);
} else {
DCHECK_EQ(min_opt_level, CodeKind::MAGLEV);
j(equal, on_result, distance);
}
bind(&on_mark_deopt);
StoreTaggedField(
FieldOperand(feedback_vector,
FeedbackVector::OffsetOfElementAt(slot.ToInt())),
ClearedValue());
}
bind(&fallthrough);
Move(scratch_and_result, 0);
}
int MacroAssembler::ArgumentStackSlotsForCFunctionCall(int num_arguments) {
DCHECK_GE(num_arguments, 0);
#ifdef V8_TARGET_OS_WIN
return std::max(num_arguments, kWindowsHomeStackSlots);
#else
return std::max(num_arguments - kRegisterPassedArguments, 0);
#endif
}
void MacroAssembler::PrepareCallCFunction(int num_arguments) {
ASM_CODE_COMMENT(this);
int frame_alignment = base::OS::ActivationFrameAlignment();
DCHECK_NE(frame_alignment, 0);
DCHECK_GE(num_arguments, 0);
// Make stack end at alignment and allocate space for arguments and old rsp.
movq(kScratchRegister, rsp);
DCHECK(base::bits::IsPowerOfTwo(frame_alignment));
int argument_slots_on_stack =
ArgumentStackSlotsForCFunctionCall(num_arguments);
AllocateStackSpace((argument_slots_on_stack + 1) * kSystemPointerSize);
andq(rsp, Immediate(-frame_alignment));
movq(Operand(rsp, argument_slots_on_stack * kSystemPointerSize),
kScratchRegister);
}
int MacroAssembler::CallCFunction(ExternalReference function, int num_arguments,
SetIsolateDataSlots set_isolate_data_slots,
Label* return_location) {
// Note: The "CallCFunction" code comment will be generated by the other
// CallCFunction method called below.
LoadAddress(rax, function);
return CallCFunction(rax, num_arguments, set_isolate_data_slots,
return_location, CodeSandboxingMode::kUnsandboxed);
}
int MacroAssembler::CallCFunction(Register function, int num_arguments,
SetIsolateDataSlots set_isolate_data_slots,
Label* return_location,
CodeSandboxingMode target_sandboxing_mode) {
ASM_CODE_COMMENT(this);
DCHECK_LE(num_arguments, kMaxCParameters);
DCHECK(has_frame());
// Check stack alignment.
if (v8_flags.debug_code) {
CheckStackAlignment();
}
CodeSandboxingMode previous_mode =
SwitchSandboxingModeBeforeCallIfNeeded(target_sandboxing_mode);
// Save the frame pointer and PC so that the stack layout remains iterable,
// even without an ExitFrame which normally exists between JS and C frames.
Label get_pc;
if (set_isolate_data_slots == SetIsolateDataSlots::kYes) {
DCHECK(!AreAliased(kScratchRegister, function));
leaq(kScratchRegister, Operand(&get_pc, 0));
CHECK(root_array_available());
movq(ExternalReferenceAsOperand(IsolateFieldId::kFastCCallCallerPC),
kScratchRegister);
movq(ExternalReferenceAsOperand(IsolateFieldId::kFastCCallCallerFP), rbp);
}
call(function);
int call_pc_offset = pc_offset();
bind(&get_pc);
if (return_location) bind(return_location);
DCHECK_NE(base::OS::ActivationFrameAlignment(), 0);
DCHECK_GE(num_arguments, 0);
int argument_slots_on_stack =
ArgumentStackSlotsForCFunctionCall(num_arguments);
// Restoring the stack pointer has to happen right after the call. The
// deoptimizer may overwrite everything after restoring the SP.
movq(rsp, Operand(rsp, argument_slots_on_stack * kSystemPointerSize));
if (set_isolate_data_slots == SetIsolateDataSlots::kYes) {
// We don't unset the PC; the FP is the source of truth.
movq(ExternalReferenceAsOperand(IsolateFieldId::kFastCCallCallerFP),
Immediate(0));
}
SwitchSandboxingModeAfterCallIfNeeded(previous_mode);
return call_pc_offset;
}
void MacroAssembler::MemoryChunkHeaderFromObject(Register object,
Register header) {
constexpr intptr_t alignment_mask =
MemoryChunk::GetAlignmentMaskForAssembler();
if (header == object) {
andq(header, Immediate(~alignment_mask));
} else {
movq(header, Immediate(~alignment_mask));
andq(header, object);
}
}
void MacroAssembler::CheckPageFlag(Register object, Register scratch, int mask,
Condition cc, Label* condition_met,
Label::Distance condition_met_distance) {
ASM_CODE_COMMENT(this);
DCHECK(cc == zero || cc == not_zero);
MemoryChunkHeaderFromObject(object, scratch);
if (mask < (1 << kBitsPerByte)) {
testb(Operand(scratch, MemoryChunk::FlagsOffset()),
Immediate(static_cast<uint8_t>(mask)));
} else {
testl(Operand(scratch, MemoryChunk::FlagsOffset()), Immediate(mask));
}
j(cc, condition_met, condition_met_distance);
}
void MacroAssembler::JumpIfMarking(Label* is_marking,
Label::Distance condition_met_distance) {
testb(Operand(kRootRegister, IsolateData::is_marking_flag_offset()),
Immediate(static_cast<uint8_t>(1)));
j(not_zero, is_marking, condition_met_distance);
}
void MacroAssembler::JumpIfNotMarking(Label* not_marking,
Label::Distance condition_met_distance) {
testb(Operand(kRootRegister, IsolateData::is_marking_flag_offset()),
Immediate(static_cast<uint8_t>(1)));
j(zero, not_marking, condition_met_distance);
}
void MacroAssembler::PreCheckSkippedWriteBarrier(Register object,
Register value,
Register scratch, Label* ok) {
ASM_CODE_COMMENT(this);
DCHECK(!AreAliased(object, scratch));
DCHECK(!AreAliased(value, scratch));
// The most common case: Static write barrier elimination is allowed on the
// last young allocation.
leaq(scratch, Operand(object, -kHeapObjectTag));
cmpq(scratch,
Operand(kRootRegister, IsolateData::last_young_allocation_offset()));
j(Condition::equal, ok);
#if CONTIGUOUS_COMPRESSED_READ_ONLY_SPACE_BOOL
JumpIfUnsignedLessThan(value, kContiguousReadOnlyReservationSize, ok);
#else // !CONTIGUOUS_COMPRESSED_READ_ONLY_SPACE_BOOL
// Write barier can also be removed if value is in read-only space.
CheckPageFlag(value, scratch, MemoryChunk::kIsInReadOnlyHeapMask, not_zero,
ok);
#endif // !CONTIGUOUS_COMPRESSED_READ_ONLY_SPACE_BOOL
Label not_ok;
// Handle allocation folding, allow WB removal if:
// LAB start <= last_young_allocation_ < (object address+1) < LAB top
// Note that object has tag bit set, so object == object address+1.
// Check LAB start <= last_young_allocation_.
movq(scratch,
Operand(kRootRegister, IsolateData::last_young_allocation_offset()));
cmpq(scratch,
Operand(kRootRegister, IsolateData::new_allocation_info_start_offset()));
j(Condition::kUnsignedLessThan, &not_ok);
// Check last_young_allocation_ < (object address+1).
cmpq(scratch, object);
j(Condition::kUnsignedGreaterThanEqual, &not_ok);
// Check (object address+1) < LAB top.
cmpq(object,
Operand(kRootRegister, IsolateData::new_allocation_info_top_offset()));
j(Condition::kUnsignedLessThan, ok);
// Slow path: Potentially check more cases in C++.
bind(&not_ok);
}
void MacroAssembler::CheckMarkBit(Register object, Register scratch0,
Register scratch1, Condition cc,
Label* condition_met,
Label::Distance condition_met_distance) {
ASM_CODE_COMMENT(this);
DCHECK(cc == carry || cc == not_carry);
DCHECK(!AreAliased(object, scratch0, scratch1));
// Computing cell.
MemoryChunkHeaderFromObject(object, scratch0);
#ifdef V8_ENABLE_SANDBOX
movl(scratch0, Operand(scratch0, MemoryChunk::MetadataIndexOffset()));
andl(scratch0,
Immediate(MemoryChunkConstants::kMetadataPointerTableSizeMask));
shll(scratch0, Immediate(kSystemPointerSizeLog2));
LoadAddress(scratch1,
ExternalReference::memory_chunk_metadata_table_address());
movq(scratch0, Operand(scratch1, scratch0, times_1, 0));
#else // !V8_ENABLE_SANDBOX
movq(scratch0, Operand(scratch0, MemoryChunk::MetadataOffset()));
#endif // !V8_ENABLE_SANDBOX
if (v8_flags.slow_debug_code) {
Push(object);
movq(scratch1, Operand(scratch0, BasePage::AreaStartOffset()));
MemoryChunkHeaderFromObject(scratch1, scratch1);
MemoryChunkHeaderFromObject(object, object);
cmpq(object, scratch1);
Check(equal, AbortReason::kMetadataAreaStartDoesNotMatch);
Pop(object);
}
addq(scratch0, Immediate(MutablePage::MarkingBitmapOffset()));
movq(scratch1, object);
andq(scratch1, Immediate(MemoryChunk::GetAlignmentMaskForAssembler()));
// It's important not to fold the next two shifts.
shrq(scratch1, Immediate(kTaggedSizeLog2 + MarkingBitmap::kBitsPerCellLog2));
shlq(scratch1, Immediate(kBitsPerByteLog2));
addq(scratch0, scratch1);
// Computing mask.
movq(scratch1, object);
andq(scratch1, Immediate(MemoryChunk::GetAlignmentMaskForAssembler()));
shrq(scratch1, Immediate(kTaggedSizeLog2));
andq(scratch1, Immediate(MarkingBitmap::kBitIndexMask));
btq(Operand(scratch0, 0), scratch1);
j(cc, condition_met, condition_met_distance);
}
void MacroAssembler::ComputeCodeStartAddress(Register dst) {
Label current;
bind(&current);
int pc = pc_offset();
// Load effective address to get the address of the current instruction.
leaq(dst, Operand(&current, -pc));
}
void MacroAssembler::AssertNotDeoptimized(Register scratch) {
int offset = InstructionStream::kCodeOffset - InstructionStream::kHeaderSize;
LoadProtectedPointerField(scratch,
Operand(kJavaScriptCallCodeStartRegister, offset));
TestCodeIsMarkedForDeoptimization(scratch);
Assert(zero, AbortReason::kInvalidDeoptimizedCode);
}
void MacroAssembler::CallForDeoptimization(Builtin target, int, Label* exit,
DeoptimizeKind kind, Label* ret,
Label*) {
ASM_CODE_COMMENT(this);
// Note: Assembler::call is used here on purpose to guarantee fixed-size
// exits even on Atom CPUs; see MacroAssembler::Call for Atom-specific
// performance tuning which emits a different instruction sequence.
call(EntryFromBuiltinAsOperand(target));
DCHECK_EQ(SizeOfCodeGeneratedSince(exit),
(kind == DeoptimizeKind::kLazy ||
kind == DeoptimizeKind::kLazyAfterFastCall)
? Deoptimizer::kLazyDeoptExitSize
: Deoptimizer::kEagerDeoptExitSize);
}
void MacroAssembler::Trap() { int3(); }
void MacroAssembler::DebugBreak() { int3(); }
// Calls an API function. Allocates HandleScope, extracts returned value
// from handle and propagates exceptions. Clobbers C argument registers
// and C caller-saved registers. Restores context. On return removes
// (*argc_operand + slots_to_drop_on_return) * kSystemPointerSize
// (GCed, includes the call JS arguments space and the additional space
// allocated for the fast call).
void CallApiFunctionAndReturn(MacroAssembler* masm, bool with_profiling,
Register function_address,
ExternalReference thunk_ref, Register thunk_arg,
int slots_to_drop_on_return,
MemOperand* argc_operand,
MemOperand return_value_operand,
bool handle_interceptor_result) {
ASM_CODE_COMMENT(masm);
Label propagate_exception;
Label delete_allocated_handles;
Label leave_exit_frame;
using ER = ExternalReference;
Isolate* isolate = masm->isolate();
MemOperand next_mem_op = __ ExternalReferenceAsOperand(
ER::handle_scope_next_address(isolate), no_reg);
MemOperand limit_mem_op = __ ExternalReferenceAsOperand(
ER::handle_scope_limit_address(isolate), no_reg);
MemOperand level_mem_op = __ ExternalReferenceAsOperand(
ER::handle_scope_level_address(isolate), no_reg);
Register return_value = rax;
Register scratch = kCArgRegs[3];
// Allocate HandleScope in callee-saved registers.
// We will need to restore the HandleScope after the call to the API function,
// by allocating it in callee-saved registers it'll be preserved by C code.
Register prev_next_address_reg = r12;
Register prev_limit_reg = r15;
// C arguments (kCArgRegs[0/1/2]) are expected to be initialized outside, so
// this function must not corrupt them. kScratchRegister might be used
// implicitly by the macro assembler.
DCHECK(!AreAliased(kCArgRegs[0], kCArgRegs[1], kCArgRegs[2], // C args
return_value, scratch, kScratchRegister,
prev_next_address_reg, prev_limit_reg));
// function_address and thunk_arg might overlap but this function must not
// corrupt them until the call is made (i.e. overlap with return_value is
// fine).
DCHECK(!AreAliased(function_address, // incoming parameters
scratch, kScratchRegister, prev_next_address_reg,
prev_limit_reg));
DCHECK(!AreAliased(thunk_arg, // incoming parameters
scratch, kScratchRegister, prev_next_address_reg,
prev_limit_reg));
{
ASM_CODE_COMMENT_STRING(masm,
"Allocate HandleScope in callee-save registers.");
__ movq(prev_next_address_reg, next_mem_op);
__ movq(prev_limit_reg, limit_mem_op);
__ addl(level_mem_op, Immediate(1));
}
DCHECK_EQ(__ sandboxing_mode(), CodeSandboxingMode::kSandboxed);
__ ExitSandbox();
Label profiler_or_side_effects_check_enabled, done_api_call,
done_reading_result;
if (with_profiling) {
__ RecordComment("Check if profiler or side effects check is enabled");
__ cmpb(__ ExternalReferenceAsOperand(IsolateFieldId::kExecutionMode),
Immediate(0));
__ j(not_zero, &profiler_or_side_effects_check_enabled);
#ifdef V8_RUNTIME_CALL_STATS
__ RecordComment("Check if RCS is enabled");
__ Move(scratch, ER::address_of_runtime_stats_flag());
__ cmpl(Operand(scratch, 0), Immediate(0));
__ j(not_zero, &profiler_or_side_effects_check_enabled);
#endif // V8_RUNTIME_CALL_STATS
}
__ RecordComment("Call the api function directly.");
__ call(function_address);
__ bind(&done_api_call);
if (handle_interceptor_result) {
// Skip reading return value if the callback returned kInterceptedNo,
// this would make the builtin return kNotInterceptedSentinel value.
// Size is important here, otherwise the C++ function could have returned
// one- or two-byte value with junk in the upper part.
static_assert(kInterceptedNo == 1 && kInterceptedSize == 4);
static_assert(kInterceptedNo == kNotInterceptedSentinel);
static_assert(kInterceptedYes == 0);
__ testb(return_value, return_value);
__ j(not_zero, &done_reading_result);
}
__ RecordComment("Load the value from ReturnValue");
__ movq(return_value, return_value_operand);
__ bind(&done_reading_result);
{
ASM_CODE_COMMENT_STRING(
masm,
"No more valid handles (the result handle was the last one)."
"Restore previous handle scope.");
__ subl(level_mem_op, Immediate(1));
__ Assert(above_equal, AbortReason::kInvalidHandleScopeLevel);
__ movq(next_mem_op, prev_next_address_reg);
__ cmpq(prev_limit_reg, limit_mem_op);
__ j(not_equal, &delete_allocated_handles);
}
__ RecordComment("Leave the API exit frame.");
__ bind(&leave_exit_frame);
Register argc_reg = prev_limit_reg;
if (argc_operand != nullptr) {
__ movq(argc_reg, *argc_operand);
}
__ LeaveExitFrame();
__ EnterSandbox();
{
ASM_CODE_COMMENT_STRING(masm,
"Check if the function scheduled an exception.");
__ CompareRoot(
__ ExternalReferenceAsOperand(ER::exception_address(isolate), no_reg),
RootIndex::kTheHoleValue);
__ j(not_equal, &propagate_exception);
}
#ifndef V8_ENABLE_MEMORY_CORRUPTION_API
// This check doesn't make sense for sandbox testing since
// Sandbox.getObjectAt(..) might legitimately return non-JSAny values
// and this check just hinders debugging.
if (v8_flags.debug_code) {
Label ok;
if (handle_interceptor_result) {
__ Cmp(return_value, kNotInterceptedSentinel);
__ j(equal, &ok);
}
__ AssertJSAny(return_value, scratch,
AbortReason::kAPICallReturnedInvalidObject);
__ bind(&ok);
}
#endif // V8_ENABLE_MEMORY_CORRUPTION_API
if (argc_operand == nullptr) {
DCHECK_NE(slots_to_drop_on_return, 0);
__ Ret(slots_to_drop_on_return * kSystemPointerSize, scratch);
} else {
__ PopReturnAddressTo(scratch);
// {argc_operand} was loaded into {argc_reg} above.
__ leaq(rsp, Operand(rsp, argc_reg, times_system_pointer_size,
slots_to_drop_on_return * kSystemPointerSize));
// Push and ret (instead of jmp) to keep the RSB and the CET shadow stack
// balanced.
__ PushReturnAddressFrom(scratch);
__ ret(0);
}
if (with_profiling) {
ASM_CODE_COMMENT_STRING(masm, "Call the api function via thunk wrapper.");
// Call the api function via thunk wrapper.
__ bind(&profiler_or_side_effects_check_enabled);
// Additional parameter if provided.
if (thunk_arg.is_valid()) {
MemOperand thunk_arg_mem_op = __ ExternalReferenceAsOperand(
IsolateFieldId::kApiCallbackThunkArgument);
__ movq(thunk_arg_mem_op, thunk_arg);
}
__ Call(thunk_ref);
__ jmp(&done_api_call);
}
__ RecordComment("An exception was thrown. Propagate it.");
__ bind(&propagate_exception);
__ TailCallRuntime(Runtime::kPropagateException);
{
ASM_CODE_COMMENT_STRING(
masm, "HandleScope limit has changed. Delete allocated extensions.");
__ bind(&delete_allocated_handles);
__ movq(limit_mem_op, prev_limit_reg);
// Save the return value in a callee-save register.
Register saved_result = prev_limit_reg;
__ movq(saved_result, return_value);
__ LoadAddress(kCArgRegs[0], ER::isolate_address());
__ Call(ER::delete_handle_scope_extensions());
__ movq(return_value, saved_result);
__ jmp(&leave_exit_frame);
}
}
} // namespace internal
} // namespace v8
#undef __
#endif // V8_TARGET_ARCH_X64