/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
deps/v8/src/codegen/x64/macro-assembler-x64.cc
5 158 строк
175 KB
Michaël Zasso
deps: update V8 to 14.6.202.33
24 апр 2026, 19:01
Не верифицирован
24 апр 2026, 19:01
f1e0b83
Код
Авторство
О чём код?
// 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, ¬_code_handle, Label::kNear); ResolveCodePointerHandle(destination, handle); jmp(label, distance); bind(¬_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, 0)); 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) { AssertSignBitOfSmiIsZero(reg); 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); AssertSignBitOfSmiIsZero(dst); 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, ¬_zero_src, Label::kNear); Move(dst, 63); // 63^31 == 32 bind(¬_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, ¬_zero_src, Label::kNear); Move(dst, 63); // 63^31 == 32 bind(¬_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, ¬_zero_src, Label::kNear); Move(dst, 127); // 127^63 == 64 bind(¬_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, ¬_zero_src, Label::kNear); Move(dst, 127); // 127^63 == 64 bind(¬_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, ¬_zero_src, Label::kNear); // Define the result of tzcnt(0) separately, because bsf(0) is undefined. Move(dst, 64); bind(¬_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, ¬_zero_src, Label::kNear); // Define the result of tzcnt(0) separately, because bsf(0) is undefined. Move(dst, 64); bind(¬_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, ¬_zero_src, Label::kNear); Move(dst, 32); // The result of tzcnt is 32 if src = 0. bind(¬_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, ¬_zero_src, Label::kNear); Move(dst, 32); // The result of tzcnt is 32 if src = 0. bind(¬_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, ¬_supported, Label::kNear); incsspq(number_of_words); bind(¬_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, ®ular_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(©); 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, ©); 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(®ular_invoke); bind(&stack_overflow); { FrameScope frame( this, has_frame() ? StackFrame::NO_FRAME_TYPE : StackFrame::INTERNAL); CallRuntime(Runtime::kThrowStackOverflow); int3(); // This should be unreachable. } bind(®ular_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, ¬_ok); // Check last_young_allocation_ < (object address+1). cmpq(scratch, object); j(Condition::kUnsignedGreaterThanEqual, ¬_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(¬_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(¤t); int pc = pc_offset(); // Load effective address to get the address of the current instruction. leaq(dst, Operand(¤t, -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); } 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); } 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