blob: 12ed316a5212a32af73acac57389e00d1d119f1c [file] [log] [blame] [edit]
// Copyright 2023 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.
#ifndef V8_MAGLEV_MAGLEV_ASSEMBLER_INL_H_
#define V8_MAGLEV_MAGLEV_ASSEMBLER_INL_H_
#include "src/maglev/maglev-assembler.h"
// Include the non-inl header before the rest of the headers.
#include <algorithm>
#include <type_traits>
#include "src/base/iterator.h"
#include "src/base/template-utils.h"
#include "src/codegen/machine-type.h"
#ifdef V8_TARGET_ARCH_ARM
#include "src/maglev/arm/maglev-assembler-arm-inl.h"
#elif V8_TARGET_ARCH_ARM64
#include "src/maglev/arm64/maglev-assembler-arm64-inl.h"
#elif V8_TARGET_ARCH_RISCV64
#include "src/maglev/riscv/maglev-assembler-riscv-inl.h"
#elif V8_TARGET_ARCH_X64
#include "src/maglev/x64/maglev-assembler-x64-inl.h"
#elif V8_TARGET_ARCH_S390X
#include "src/maglev/s390/maglev-assembler-s390-inl.h"
#else
#error "Maglev does not supported this architecture."
#endif
namespace v8 {
namespace internal {
namespace maglev {
namespace detail {
// Base case provides an error.
template <typename T, typename Enable = void>
struct CopyForDeferredHelper {
template <typename U>
struct No_Copy_Helper_Implemented_For_Type;
static void Copy(MaglevCompilationInfo* compilation_info,
No_Copy_Helper_Implemented_For_Type<T>);
};
// Helper for copies by value.
template <typename T, typename Enable = void>
struct CopyForDeferredByValue {
static T Copy(MaglevCompilationInfo* compilation_info, T node) {
return node;
}
};
// Node pointers are copied by value.
template <typename T>
struct CopyForDeferredHelper<
T*, typename std::enable_if<std::is_base_of<NodeBase, T>::value>::type>
: public CopyForDeferredByValue<T*> {};
// Arithmetic values and enums are copied by value.
template <typename T>
struct CopyForDeferredHelper<
T, typename std::enable_if<std::is_arithmetic<T>::value>::type>
: public CopyForDeferredByValue<T> {};
template <typename T>
struct CopyForDeferredHelper<
T, typename std::enable_if<std::is_enum<T>::value>::type>
: public CopyForDeferredByValue<T> {};
// MaglevCompilationInfos are copied by value.
template <>
struct CopyForDeferredHelper<MaglevCompilationInfo*>
: public CopyForDeferredByValue<MaglevCompilationInfo*> {};
// Machine registers are copied by value.
template <>
struct CopyForDeferredHelper<Register>
: public CopyForDeferredByValue<Register> {};
template <>
struct CopyForDeferredHelper<std::optional<Register>>
: public CopyForDeferredByValue<std::optional<Register>> {};
template <>
struct CopyForDeferredHelper<DoubleRegister>
: public CopyForDeferredByValue<DoubleRegister> {};
// Bytecode offsets are copied by value.
template <>
struct CopyForDeferredHelper<BytecodeOffset>
: public CopyForDeferredByValue<BytecodeOffset> {};
// EagerDeoptInfo pointers are copied by value.
template <>
struct CopyForDeferredHelper<EagerDeoptInfo*>
: public CopyForDeferredByValue<EagerDeoptInfo*> {};
// LazyDeoptInfo pointers are copied by value.
template <>
struct CopyForDeferredHelper<LazyDeoptInfo*>
: public CopyForDeferredByValue<LazyDeoptInfo*> {};
// ZoneLabelRef is copied by value.
template <>
struct CopyForDeferredHelper<ZoneLabelRef>
: public CopyForDeferredByValue<ZoneLabelRef> {};
// MapCompare is copied by value.
template <>
struct CopyForDeferredHelper<MapCompare>
: public CopyForDeferredByValue<MapCompare> {};
// RegList are copied by value.
template <>
struct CopyForDeferredHelper<RegList> : public CopyForDeferredByValue<RegList> {
};
// Register snapshots are copied by value.
template <>
struct CopyForDeferredHelper<RegisterSnapshot>
: public CopyForDeferredByValue<RegisterSnapshot> {};
// Feedback slots are copied by value.
template <>
struct CopyForDeferredHelper<FeedbackSlot>
: public CopyForDeferredByValue<FeedbackSlot> {};
// Heap Refs are copied by value.
template <typename T>
struct CopyForDeferredHelper<T, typename std::enable_if<std::is_base_of<
compiler::ObjectRef, T>::value>::type>
: public CopyForDeferredByValue<T> {};
template <typename T>
T CopyForDeferred(MaglevCompilationInfo* compilation_info, T&& value) {
return CopyForDeferredHelper<T>::Copy(compilation_info,
std::forward<T>(value));
}
template <typename T>
T CopyForDeferred(MaglevCompilationInfo* compilation_info, T& value) {
return CopyForDeferredHelper<T>::Copy(compilation_info, value);
}
template <typename T>
T CopyForDeferred(MaglevCompilationInfo* compilation_info, const T& value) {
return CopyForDeferredHelper<T>::Copy(compilation_info, value);
}
template <typename Function>
struct FunctionArgumentsTupleHelper
: public FunctionArgumentsTupleHelper<decltype(&Function::operator())> {};
template <typename C, typename R, typename... A>
struct FunctionArgumentsTupleHelper<R (C::*)(A...) const> {
using FunctionPointer = R (*)(A...);
using Tuple = std::tuple<A...>;
static constexpr size_t kSize = sizeof...(A);
};
template <typename R, typename... A>
struct FunctionArgumentsTupleHelper<R (&)(A...)> {
using FunctionPointer = R (*)(A...);
using Tuple = std::tuple<A...>;
static constexpr size_t kSize = sizeof...(A);
};
template <typename T>
struct StripFirstTupleArg;
template <typename T1, typename... T>
struct StripFirstTupleArg<std::tuple<T1, T...>> {
using Stripped = std::tuple<T...>;
};
template <typename Function>
class DeferredCodeInfoImpl final : public DeferredCodeInfo {
public:
using FunctionPointer =
typename FunctionArgumentsTupleHelper<Function>::FunctionPointer;
using Tuple = typename StripFirstTupleArg<
typename FunctionArgumentsTupleHelper<Function>::Tuple>::Stripped;
template <typename... InArgs>
explicit DeferredCodeInfoImpl(
MaglevCompilationInfo* compilation_info,
MaglevAssembler::TemporaryRegisterScope::SavedData deferred_scratch,
FunctionPointer function, InArgs&&... args)
: function(function),
args(CopyForDeferred(compilation_info, std::forward<InArgs>(args))...),
deferred_scratch_(deferred_scratch) {}
DeferredCodeInfoImpl(DeferredCodeInfoImpl&&) = delete;
DeferredCodeInfoImpl(const DeferredCodeInfoImpl&) = delete;
void Generate(MaglevAssembler* masm) override {
MaglevAssembler::TemporaryRegisterScope scratch_scope(masm,
deferred_scratch_);
#ifdef DEBUG
masm->set_allow_call(allow_call_);
masm->set_allow_deferred_call(allow_call_);
masm->set_allow_allocate(allow_allocate_);
#endif // DEBUG
std::apply(function,
std::tuple_cat(std::make_tuple(masm), std::move(args)));
#ifdef DEBUG
masm->set_allow_call(false);
masm->set_allow_deferred_call(false);
masm->set_allow_allocate(false);
#endif // DEBUG
}
#ifdef DEBUG
void set_allow_call(bool value) { allow_call_ = value; }
void set_allow_allocate(bool value) { allow_allocate_ = value; }
#endif // DEBUG
private:
FunctionPointer function;
Tuple args;
MaglevAssembler::TemporaryRegisterScope::SavedData deferred_scratch_;
#ifdef DEBUG
bool allow_call_ = false;
bool allow_allocate_ = false;
#endif // DEBUG
};
} // namespace detail
template <typename Function, typename... Args>
inline Label* MaglevAssembler::MakeDeferredCode(Function&& deferred_code_gen,
Args&&... args) {
using FunctionPointer =
typename detail::FunctionArgumentsTupleHelper<Function>::FunctionPointer;
static_assert(
std::is_invocable_v<FunctionPointer, MaglevAssembler*,
decltype(detail::CopyForDeferred(
std::declval<MaglevCompilationInfo*>(),
std::declval<Args>()))...>,
"Parameters of deferred_code_gen function should match arguments into "
"MakeDeferredCode");
TemporaryRegisterScope scratch_scope(this);
using DeferredCodeInfoT = detail::DeferredCodeInfoImpl<Function>;
DeferredCodeInfoT* deferred_code =
compilation_info()->zone()->New<DeferredCodeInfoT>(
compilation_info(), scratch_scope.CopyForDefer(), deferred_code_gen,
std::forward<Args>(args)...);
#ifdef DEBUG
deferred_code->set_allow_call(allow_deferred_call_);
deferred_code->set_allow_allocate(allow_allocate_);
#endif // DEBUG
code_gen_state()->PushDeferredCode(deferred_code);
return &deferred_code->deferred_code_label;
}
// Note this doesn't take capturing lambdas by design, since state may
// change until `deferred_code_gen` is actually executed. Use either a
// non-capturing lambda, or a plain function pointer.
template <typename Function, typename... Args>
inline void MaglevAssembler::JumpToDeferredIf(Condition cond,
Function&& deferred_code_gen,
Args&&... args) {
if (v8_flags.code_comments) {
RecordComment("-- Jump to deferred code");
}
JumpIf(cond, MakeDeferredCode<Function, Args...>(
std::forward<Function>(deferred_code_gen),
std::forward<Args>(args)...));
}
template <typename T>
inline void AllocateSlow(MaglevAssembler* masm,
RegisterSnapshot register_snapshot, Register object,
Builtin builtin, T size_in_bytes, ZoneLabelRef done) {
// Remove {object} from snapshot, since it is the returned allocated
// HeapObject.
register_snapshot.live_registers.clear(object);
register_snapshot.live_tagged_registers.clear(object);
{
SaveRegisterStateForCall save_register_state(masm, register_snapshot);
using D = AllocateDescriptor;
masm->Move(D::GetRegisterParameter(D::kRequestedSize), size_in_bytes);
masm->CallBuiltin(builtin);
save_register_state.DefineSafepoint();
masm->Move(object, kReturnRegister0);
}
masm->Jump(*done);
}
inline void MaglevAssembler::SmiToDouble(DoubleRegister result, Register smi) {
AssertSmi(smi);
SmiUntag(smi);
Int32ToDouble(result, smi);
}
inline void MaglevAssembler::AssertContextCellState(Register cell,
ContextCell::State state,
Condition condition) {
if (!v8_flags.slow_debug_code) return;
TemporaryRegisterScope temps(this);
Register scratch = temps.AcquireScratch();
LoadContextCellState(scratch, cell);
CompareInt32AndAssert(scratch, static_cast<int>(state), condition,
AbortReason::kUnexpectedValue);
}
inline void MaglevAssembler::LoadContextCellTaggedValue(Register value,
Register cell) {
static_assert(ContextCell::kConst == 0);
static_assert(ContextCell::kSmi == 1);
AssertContextCellState(cell, ContextCell::kSmi, kLessThanEqual);
LoadTaggedField(value, cell, offsetof(ContextCell, tagged_value_));
}
inline void MaglevAssembler::StoreContextCellSmiValue(Register cell,
Register value) {
StoreTaggedFieldNoWriteBarrier(cell, offsetof(ContextCell, tagged_value_),
value);
}
#if !defined(V8_TARGET_ARCH_RISCV64)
inline void MaglevAssembler::CompareInstanceTypeAndJumpIf(
Register map, InstanceType type, Condition cond, Label* target,
Label::Distance distance) {
CompareInstanceType(map, type);
JumpIf(cond, target, distance);
}
template <typename NodeT>
inline void MaglevAssembler::CompareInstanceTypeRangeAndEagerDeoptIf(
Register map, Register instance_type_out, InstanceType lower_limit,
InstanceType higher_limit, Condition cond, DeoptimizeReason reason,
NodeT* node) {
CompareInstanceTypeRange(map, instance_type_out, lower_limit, higher_limit);
EmitEagerDeoptIf(cond, reason, node);
}
template <typename NodeT>
inline void MaglevAssembler::CompareRootAndEmitEagerDeoptIf(
Register reg, RootIndex index, Condition cond, DeoptimizeReason reason,
NodeT* node) {
CompareRoot(reg, index);
EmitEagerDeoptIf(cond, reason, node);
}
template <typename NodeT>
inline void MaglevAssembler::CompareMapWithRootAndEmitEagerDeoptIf(
Register reg, RootIndex index, Register scratch, Condition cond,
DeoptimizeReason reason, NodeT* node) {
CompareMapWithRoot(reg, index, scratch);
EmitEagerDeoptIf(cond, reason, node);
}
template <typename NodeT>
inline void MaglevAssembler::CompareTaggedRootAndEmitEagerDeoptIf(
Register reg, RootIndex index, Condition cond, DeoptimizeReason reason,
NodeT* node) {
CompareTaggedRoot(reg, index);
EmitEagerDeoptIf(cond, reason, node);
}
template <typename NodeT>
inline void MaglevAssembler::CompareUInt32AndEmitEagerDeoptIf(
Register reg, int imm, Condition cond, DeoptimizeReason reason,
NodeT* node) {
Cmp(reg, imm);
EmitEagerDeoptIf(cond, reason, node);
}
#endif
inline void MaglevAssembler::CompareInt32AndBranch(Register r1, int32_t value,
Condition cond,
BasicBlock* if_true,
BasicBlock* if_false,
BasicBlock* next_block) {
CompareInt32AndBranch(r1, value, cond, if_true->label(), Label::kFar,
if_true == next_block, if_false->label(), Label::kFar,
if_false == next_block);
}
inline void MaglevAssembler::CompareInt32AndBranch(Register r1, Register r2,
Condition cond,
BasicBlock* if_true,
BasicBlock* if_false,
BasicBlock* next_block) {
CompareInt32AndBranch(r1, r2, cond, if_true->label(), Label::kFar,
if_true == next_block, if_false->label(), Label::kFar,
if_false == next_block);
}
inline void MaglevAssembler::CompareIntPtrAndBranch(Register r1, int32_t value,
Condition cond,
BasicBlock* if_true,
BasicBlock* if_false,
BasicBlock* next_block) {
CompareIntPtrAndBranch(r1, value, cond, if_true->label(), Label::kFar,
if_true == next_block, if_false->label(), Label::kFar,
if_false == next_block);
}
inline void MaglevAssembler::Branch(Condition condition, BasicBlock* if_true,
BasicBlock* if_false,
BasicBlock* next_block) {
Branch(condition, if_true->label(), Label::kFar, if_true == next_block,
if_false->label(), Label::kFar, if_false == next_block);
}
inline void MaglevAssembler::Branch(Condition condition, Label* if_true,
Label::Distance true_distance,
bool fallthrough_when_true, Label* if_false,
Label::Distance false_distance,
bool fallthrough_when_false) {
if (fallthrough_when_false) {
if (fallthrough_when_true) {
// If both paths are a fallthrough, do nothing.
DCHECK_EQ(if_true, if_false);
return;
}
// Jump over the false block if true, otherwise fall through into it.
JumpIf(condition, if_true, true_distance);
} else {
// Jump to the false block if true.
JumpIf(NegateCondition(condition), if_false, false_distance);
// Jump to the true block if it's not the next block.
if (!fallthrough_when_true) {
Jump(if_true, true_distance);
}
}
}
inline void MaglevAssembler::LoadTaggedField(Register result,
MemOperand operand) {
MacroAssembler::LoadTaggedField(result, operand);
}
inline void MaglevAssembler::LoadTaggedField(Register result, Register object,
int offset) {
MacroAssembler::LoadTaggedField(result, FieldMemOperand(object, offset));
}
inline void MaglevAssembler::LoadTaggedSignedField(Register result,
MemOperand operand) {
MacroAssembler::LoadTaggedField(result, operand);
}
inline void MaglevAssembler::LoadTaggedSignedField(Register result,
Register object,
int offset) {
MacroAssembler::LoadTaggedField(result, FieldMemOperand(object, offset));
}
inline void MaglevAssembler::LoadAndUntagTaggedSignedField(Register result,
Register object,
int offset) {
MacroAssembler::SmiUntagField(result, FieldMemOperand(object, offset));
}
inline void MaglevAssembler::LoadHeapNumberOrOddballValue(DoubleRegister result,
Register object) {
static_assert(offsetof(HeapNumber, value_) ==
offsetof(Oddball, to_number_raw_));
LoadHeapNumberValue(result, object);
}
inline void MaglevAssembler::StoreHeapNumberValue(DoubleRegister value,
Register heap_number) {
StoreFloat64(FieldMemOperand(heap_number, offsetof(HeapNumber, value_)),
value);
}
namespace detail {
#ifdef DEBUG
inline bool ClobberedBy(RegList written_registers, Register reg) {
return written_registers.has(reg);
}
inline bool ClobberedBy(RegList written_registers, DoubleRegister reg) {
return false;
}
inline bool ClobberedBy(RegList written_registers,
DirectHandle<Object> handle) {
return false;
}
inline bool ClobberedBy(RegList written_registers, Tagged<Smi> smi) {
return false;
}
inline bool ClobberedBy(RegList written_registers, Tagged<TaggedIndex> index) {
return false;
}
inline bool ClobberedBy(RegList written_registers, int32_t imm) {
return false;
}
inline bool ClobberedBy(RegList written_registers, RootIndex index) {
return false;
}
inline bool ClobberedBy(RegList written_registers, const Input& input) {
if (!input.IsGeneralRegister()) return false;
return ClobberedBy(written_registers, input.AssignedGeneralRegister());
}
inline bool ClobberedBy(DoubleRegList written_registers, Register reg) {
return false;
}
inline bool ClobberedBy(DoubleRegList written_registers, DoubleRegister reg) {
return written_registers.has(reg);
}
inline bool ClobberedBy(DoubleRegList written_registers,
DirectHandle<Object> handle) {
return false;
}
inline bool ClobberedBy(DoubleRegList written_registers, Tagged<Smi> smi) {
return false;
}
inline bool ClobberedBy(DoubleRegList written_registers,
Tagged<TaggedIndex> index) {
return false;
}
inline bool ClobberedBy(DoubleRegList written_registers, int32_t imm) {
return false;
}
inline bool ClobberedBy(DoubleRegList written_registers, RootIndex index) {
return false;
}
inline bool ClobberedBy(DoubleRegList written_registers, const Input& input) {
if (!input.IsDoubleRegister()) return false;
return ClobberedBy(written_registers, input.AssignedDoubleRegister());
}
// We don't know what's inside machine registers or operands, so assume they
// match.
inline bool MachineTypeMatches(MachineType type, Register reg) {
return !IsFloatingPoint(type.representation());
}
inline bool MachineTypeMatches(MachineType type, DoubleRegister reg) {
return IsFloatingPoint(type.representation());
}
inline bool MachineTypeMatches(MachineType type, MemOperand reg) {
return true;
}
inline bool MachineTypeMatches(MachineType type,
DirectHandle<HeapObject> handle) {
return type.IsTagged() && !type.IsTaggedSigned();
}
inline bool MachineTypeMatches(MachineType type, Tagged<Smi> smi) {
return type.IsTagged() && !type.IsTaggedPointer();
}
inline bool MachineTypeMatches(MachineType type, Tagged<TaggedIndex> index) {
// TaggedIndex doesn't have a separate type, so check for the same type as for
// Smis.
return type.IsTagged() && !type.IsTaggedPointer();
}
inline bool MachineTypeMatches(MachineType type, int32_t imm) {
// 32-bit immediates can be used for 64-bit params -- they'll be
// zero-extended.
return type.representation() == MachineRepresentation::kWord32 ||
type.representation() == MachineRepresentation::kWord64;
}
inline bool MachineTypeMatches(MachineType type, RootIndex index) {
return type.IsTagged() && !type.IsTaggedSigned();
}
inline bool MachineTypeMatches(MachineType type, const Input& input) {
if (type.representation() == input.node()->GetMachineRepresentation()) {
return true;
}
if (type.IsTagged()) {
return input.node()->is_tagged();
}
return false;
}
template <typename Descriptor, typename Arg>
void CheckArg(MaglevAssembler* masm, Arg& arg, int& i) {
if (i >= Descriptor::GetParameterCount()) {
CHECK(Descriptor::AllowVarArgs());
}
CHECK(MachineTypeMatches(Descriptor::GetParameterType(i), arg));
++i;
}
template <typename Descriptor, typename Iterator>
void CheckArg(MaglevAssembler* masm,
const base::iterator_range<Iterator>& range, int& i) {
for (auto it = range.begin(), end = range.end(); it != end; ++it, ++i) {
if (i >= Descriptor::GetParameterCount()) {
CHECK(Descriptor::AllowVarArgs());
}
CHECK(MachineTypeMatches(Descriptor::GetParameterType(i), *it));
}
}
template <typename Descriptor, typename... Args>
void CheckArgs(MaglevAssembler* masm, const std::tuple<Args...>& args) {
int i = 0;
base::tuple_for_each(args,
[&](auto&& arg) { CheckArg<Descriptor>(masm, arg, i); });
if (Descriptor::AllowVarArgs()) {
CHECK_GE(i, Descriptor::GetParameterCount());
} else {
CHECK_EQ(i, Descriptor::GetParameterCount());
}
}
#else // DEBUG
template <typename Descriptor, typename... Args>
void CheckArgs(Args&&... args) {}
#endif // DEBUG
template <typename Descriptor, typename... Args>
void PushArgumentsForBuiltin(MaglevAssembler* masm, std::tuple<Args...> args) {
std::apply(
[&](auto&&... stack_args) {
if (Descriptor::kStackArgumentOrder == StackArgumentOrder::kDefault) {
masm->Push(std::forward<decltype(stack_args)>(stack_args)...);
} else {
masm->PushReverse(std::forward<decltype(stack_args)>(stack_args)...);
}
},
args);
}
template <typename Descriptor>
void PushArgumentsForBuiltin(MaglevAssembler* masm, std::tuple<> empty_args) {}
template <Builtin kBuiltin, typename... Args>
void MoveArgumentsForBuiltin(MaglevAssembler* masm, Args&&... args) {
using Descriptor = typename CallInterfaceDescriptorFor<kBuiltin>::type;
// Put the args into a tuple for easier manipulation.
std::tuple<Args&&...> args_tuple{std::forward<Args>(args)...};
// If there is a context, the first argument is the context parameter. Use
// the remaining args as the actual arguments. We pass the context first
// instead of last to avoid ambiguity around dealing with on-stack
// arguments.
constexpr size_t context_args = Descriptor::HasContextParameter() ? 1 : 0;
static_assert(context_args <= std::tuple_size_v<decltype(args_tuple)>,
"Not enough arguments passed in to builtin (are you missing a "
"context argument?)");
auto args_tuple_without_context = base::tuple_drop<context_args>(args_tuple);
CheckArgs<Descriptor>(masm, args_tuple_without_context);
// Split args into register and stack args.
static_assert(Descriptor::GetRegisterParameterCount() <=
std::tuple_size_v<decltype(args_tuple_without_context)>,
"Not enough arguments passed in to builtin (are you missing a "
"context argument?)");
auto register_args =
base::tuple_head<Descriptor::GetRegisterParameterCount()>(
args_tuple_without_context);
auto stack_args = base::tuple_drop<Descriptor::GetRegisterParameterCount()>(
args_tuple_without_context);
// Split stack args into fixed and variable.
static_assert(
Descriptor::GetStackParameterCount() <=
std::tuple_size_v<decltype(stack_args)>,
"Not enough stack arguments passed in to builtin (are you missing a "
"context argument?)");
auto fixed_stack_args =
base::tuple_head<Descriptor::GetStackParameterCount()>(stack_args);
auto vararg_stack_args =
base::tuple_drop<Descriptor::GetStackParameterCount()>(stack_args);
if constexpr (!Descriptor::AllowVarArgs()) {
static_assert(std::tuple_size_v<decltype(vararg_stack_args)> == 0,
"Too many arguments passed in to builtin that expects no "
"vararg stack arguments");
}
// First push stack arguments (if any), since some of these may be in
// registers and we don't want to clobber them. This supports any thing
// `masm->Push` supports, including iterator ranges, so the tuple size may be
// smaller than the number of arguments actually pushed. We push fixed and
// vararg stack arguments separately, so that there's an appropriate amount
// of padding between them.
if (Descriptor::kStackArgumentOrder == StackArgumentOrder::kDefault) {
PushArgumentsForBuiltin<Descriptor>(
masm, std::forward<decltype(fixed_stack_args)>(fixed_stack_args));
PushArgumentsForBuiltin<Descriptor>(
masm, std::forward<decltype(vararg_stack_args)>(vararg_stack_args));
} else {
PushArgumentsForBuiltin<Descriptor>(
masm, std::forward<decltype(vararg_stack_args)>(vararg_stack_args));
PushArgumentsForBuiltin<Descriptor>(
masm, std::forward<decltype(fixed_stack_args)>(fixed_stack_args));
}
// Then, set register arguments.
// TODO(leszeks): Use the parallel move helper to do register moves, instead
// of detecting clobbering.
#ifdef DEBUG
RegList written_registers = {};
DoubleRegList written_double_registers = {};
#endif // DEBUG
base::tuple_for_each_with_index(register_args, [&](auto&& arg, auto index) {
using Arg = decltype(arg);
static_assert(index < Descriptor::GetRegisterParameterCount());
// Make sure the argument wasn't clobbered by any previous write.
DCHECK(!ClobberedBy(written_registers, arg));
DCHECK(!ClobberedBy(written_double_registers, arg));
static constexpr bool use_double_register =
IsFloatingPoint(Descriptor::GetParameterType(index).representation());
if constexpr (use_double_register) {
DoubleRegister target = Descriptor::GetDoubleRegisterParameter(index);
if constexpr (std::is_same_v<Input, std::decay_t<Arg>>) {
DCHECK_EQ(target, arg.AssignedDoubleRegister());
USE(target);
} else {
masm->Move(target, std::forward<Arg>(arg));
}
#ifdef DEBUG
written_double_registers.set(target);
#endif // DEBUG
} else {
Register target = Descriptor::GetRegisterParameter(index);
if constexpr (std::is_same_v<Input, std::decay_t<Arg>>) {
DCHECK_EQ(target, arg.AssignedGeneralRegister());
USE(target);
} else {
masm->Move(target, std::forward<Arg>(arg));
}
#ifdef DEBUG
written_registers.set(target);
#endif // DEBUG
}
// TODO(leszeks): Support iterator range for register args.
});
// Set the context last (to avoid clobbering).
if constexpr (Descriptor::HasContextParameter()) {
auto&& context = std::get<0>(args_tuple);
DCHECK(!ClobberedBy(written_registers, context));
DCHECK(!ClobberedBy(written_double_registers, context));
DCHECK(MachineTypeMatches(MachineType::AnyTagged(), context));
if constexpr (std::is_same_v<Input, std::decay_t<decltype(context)>>) {
DCHECK_EQ(Descriptor::ContextRegister(),
context.AssignedGeneralRegister());
} else {
// Don't allow raw Register here, force materialisation from a constant.
// This is because setting parameters could have clobbered the register.
// TODO(leszeks): Include the context register in the parallel moves
// described above.
static_assert(!std::is_same_v<Register, std::decay_t<decltype(context)>>);
masm->Move(Descriptor::ContextRegister(), context);
}
}
}
} // namespace detail
inline void MaglevAssembler::CallBuiltin(Builtin builtin) {
// Special case allowing calls to DoubleToI, which takes care to preserve all
// registers and therefore doesn't require special spill handling.
DCHECK(allow_call() || builtin == Builtin::kDoubleToI);
// Temporaries have to be reset before calling CallBuiltin, in case it uses
// temporaries that alias register parameters.
TemporaryRegisterScope reset_temps(this);
reset_temps.ResetToDefault();
// Make sure that none of the register parameters alias the default
// temporaries.
#ifdef DEBUG
CallInterfaceDescriptor descriptor =
Builtins::CallInterfaceDescriptorFor(builtin);
for (int i = 0; i < descriptor.GetRegisterParameterCount(); ++i) {
DCHECK(!reset_temps.Available().has(descriptor.GetRegisterParameter(i)));
}
#endif
MacroAssembler::CallBuiltin(builtin);
}
template <Builtin kBuiltin, typename... Args>
inline void MaglevAssembler::CallBuiltin(Args&&... args) {
ASM_CODE_COMMENT(this);
detail::MoveArgumentsForBuiltin<kBuiltin>(this, std::forward<Args>(args)...);
CallBuiltin(kBuiltin);
}
inline void MaglevAssembler::CallRuntime(Runtime::FunctionId fid) {
DCHECK(allow_call());
// Temporaries have to be reset before calling CallRuntime, in case it uses
// temporaries that alias register parameters.
TemporaryRegisterScope reset_temps(this);
reset_temps.ResetToDefault();
MacroAssembler::CallRuntime(fid);
}
inline void MaglevAssembler::CallRuntime(Runtime::FunctionId fid,
int num_args) {
DCHECK(allow_call());
// Temporaries have to be reset before calling CallRuntime, in case it uses
// temporaries that alias register parameters.
TemporaryRegisterScope reset_temps(this);
reset_temps.ResetToDefault();
MacroAssembler::CallRuntime(fid, num_args);
}
inline void MaglevAssembler::SetMapAsRoot(Register object, RootIndex map) {
TemporaryRegisterScope temps(this);
Register scratch = temps.AcquireScratch();
LoadTaggedRoot(scratch, map);
StoreTaggedFieldNoWriteBarrier(object, HeapObject::kMapOffset, scratch);
}
inline void MaglevAssembler::SmiTagInt32AndJumpIfFail(
Register dst, Register src, Label* fail, Label::Distance distance) {
SmiTagInt32AndSetFlags(dst, src);
if (!SmiValuesAre32Bits()) {
JumpIf(kOverflow, fail, distance);
}
}
inline void MaglevAssembler::SmiTagInt32AndJumpIfFail(
Register reg, Label* fail, Label::Distance distance) {
SmiTagInt32AndJumpIfFail(reg, reg, fail, distance);
}
inline void MaglevAssembler::SmiTagInt32AndJumpIfSuccess(
Register dst, Register src, Label* success, Label::Distance distance) {
SmiTagInt32AndSetFlags(dst, src);
if (!SmiValuesAre32Bits()) {
JumpIf(kNoOverflow, success, distance);
} else {
jmp(success);
}
}
inline void MaglevAssembler::SmiTagInt32AndJumpIfSuccess(
Register reg, Label* success, Label::Distance distance) {
SmiTagInt32AndJumpIfSuccess(reg, reg, success, distance);
}
inline void MaglevAssembler::UncheckedSmiTagInt32(Register dst, Register src) {
SmiTagInt32AndSetFlags(dst, src);
if (!SmiValuesAre32Bits()) {
Assert(kNoOverflow, AbortReason::kInputDoesNotFitSmi);
}
}
inline void MaglevAssembler::UncheckedSmiTagInt32(Register reg) {
UncheckedSmiTagInt32(reg, reg);
}
inline void MaglevAssembler::SmiTagUint32AndJumpIfFail(
Register dst, Register src, Label* fail, Label::Distance distance) {
// Perform an unsigned comparison against Smi::kMaxValue.
CompareInt32AndJumpIf(src, Smi::kMaxValue, kUnsignedGreaterThan, fail,
distance);
SmiTagInt32AndSetFlags(dst, src);
if (!SmiValuesAre32Bits()) {
Assert(kNoOverflow, AbortReason::kInputDoesNotFitSmi);
}
}
inline void MaglevAssembler::SmiTagUint32AndJumpIfFail(
Register reg, Label* fail, Label::Distance distance) {
SmiTagUint32AndJumpIfFail(reg, reg, fail, distance);
}
inline void MaglevAssembler::SmiTagIntPtrAndJumpIfFail(
Register dst, Register src, Label* fail, Label::Distance distance) {
CheckIntPtrIsSmi(src, fail, distance);
// If the IntPtr is in the Smi range, we can treat it as Int32.
SmiTagInt32AndSetFlags(dst, src);
if (!SmiValuesAre32Bits()) {
Assert(kNoOverflow, AbortReason::kInputDoesNotFitSmi);
}
}
inline void MaglevAssembler::SmiTagIntPtrAndJumpIfSuccess(
Register dst, Register src, Label* success, Label::Distance distance) {
Label done;
SmiTagIntPtrAndJumpIfFail(dst, src, &done);
Jump(success, distance);
bind(&done);
}
inline void MaglevAssembler::SmiTagUint32AndJumpIfSuccess(
Register dst, Register src, Label* success, Label::Distance distance) {
Label fail;
SmiTagUint32AndJumpIfFail(dst, src, &fail, Label::Distance::kNear);
Jump(success, distance);
bind(&fail);
}
inline void MaglevAssembler::SmiTagUint32AndJumpIfSuccess(
Register reg, Label* success, Label::Distance distance) {
SmiTagUint32AndJumpIfSuccess(reg, reg, success, distance);
}
inline void MaglevAssembler::UncheckedSmiTagUint32(Register dst, Register src) {
if (v8_flags.debug_code) {
// Perform an unsigned comparison against Smi::kMaxValue.
CompareInt32AndAssert(src, Smi::kMaxValue, kUnsignedLessThanEqual,
AbortReason::kInputDoesNotFitSmi);
}
SmiTagInt32AndSetFlags(dst, src);
if (!SmiValuesAre32Bits()) {
Assert(kNoOverflow, AbortReason::kInputDoesNotFitSmi);
}
}
inline void MaglevAssembler::UncheckedSmiTagUint32(Register reg) {
UncheckedSmiTagUint32(reg, reg);
}
inline void MaglevAssembler::CheckIntPtrIsSmi(Register obj, Label* fail,
Label::Distance distance) {
// TODO(388844115): Optimize this per platform.
int32_t kSmiMaxValueInt32 = static_cast<int32_t>(Smi::kMaxValue);
int32_t kSmiMinValueInt32 = static_cast<int32_t>(Smi::kMinValue);
CompareIntPtrAndJumpIf(obj, kSmiMaxValueInt32, kGreaterThan, fail, distance);
CompareIntPtrAndJumpIf(obj, kSmiMinValueInt32, kLessThan, fail, distance);
}
inline void MaglevAssembler::SmiAddConstant(Register reg, int value,
Label* fail,
Label::Distance distance) {
return SmiAddConstant(reg, reg, value, fail, distance);
}
inline void MaglevAssembler::SmiSubConstant(Register reg, int value,
Label* fail,
Label::Distance distance) {
return SmiSubConstant(reg, reg, value, fail, distance);
}
inline void MaglevAssembler::JumpIfStringMap(Register map, Label* target,
Label::Distance distance,
bool jump_if_true) {
#if V8_STATIC_ROOTS_BOOL
// All string maps are allocated at the start of the read only heap. Thus,
// non-strings must have maps with larger (compressed) addresses.
CompareInt32AndJumpIf(
map, InstanceTypeChecker::kStringMapUpperBound,
jump_if_true ? kUnsignedLessThanEqual : kUnsignedGreaterThan, target,
distance);
#else
#ifdef V8_COMPRESS_POINTERS
DecompressTagged(map, map);
#endif
static_assert(FIRST_STRING_TYPE == FIRST_TYPE);
CompareInstanceTypeAndJumpIf(
map, LAST_STRING_TYPE,
jump_if_true ? kUnsignedLessThanEqual : kUnsignedGreaterThan, target,
distance);
#endif
}
inline void MaglevAssembler::JumpIfString(Register heap_object, Label* target,
Label::Distance distance) {
TemporaryRegisterScope temps(this);
Register scratch = temps.AcquireScratch();
#ifdef V8_COMPRESS_POINTERS
LoadCompressedMap(scratch, heap_object);
#else
LoadMap(scratch, heap_object);
#endif
JumpIfStringMap(scratch, target, distance, true);
}
inline void MaglevAssembler::JumpIfNotString(Register heap_object,
Label* target,
Label::Distance distance) {
TemporaryRegisterScope temps(this);
Register scratch = temps.AcquireScratch();
#ifdef V8_COMPRESS_POINTERS
LoadCompressedMap(scratch, heap_object);
#else
LoadMap(scratch, heap_object);
#endif
JumpIfStringMap(scratch, target, distance, false);
}
inline void MaglevAssembler::CheckJSAnyIsStringAndBranch(
Register heap_object, Label* if_true, Label::Distance true_distance,
bool fallthrough_when_true, Label* if_false, Label::Distance false_distance,
bool fallthrough_when_false) {
BranchOnObjectTypeInRange(heap_object, FIRST_STRING_TYPE, LAST_STRING_TYPE,
if_true, true_distance, fallthrough_when_true,
if_false, false_distance, fallthrough_when_false);
}
inline void MaglevAssembler::StringLength(Register result, Register string) {
if (v8_flags.debug_code) {
// Check if {string} is a string.
AssertObjectTypeInRange(string, FIRST_STRING_TYPE, LAST_STRING_TYPE,
AbortReason::kUnexpectedValue);
}
LoadSignedField(result, FieldMemOperand(string, offsetof(String, length_)),
sizeof(int32_t));
}
inline void MaglevAssembler::LoadThinStringValue(Register result,
Register string) {
if (v8_flags.slow_debug_code) {
TemporaryRegisterScope temps(this);
Register scratch = temps.AcquireScratch();
LoadInstanceType(scratch, string);
Label ok;
static_assert(base::bits::CountPopulation(kThinStringTagBit) == 1);
TestInt32AndJumpIfAnySet(scratch, kThinStringTagBit, &ok);
Abort(AbortReason::kUnexpectedValue);
bind(&ok);
}
LoadTaggedField(result, string, offsetof(ThinString, actual_));
}
void MaglevAssembler::LoadMapForCompare(Register dst, Register obj) {
#ifdef V8_COMPRESS_POINTERS
MacroAssembler::LoadCompressedMap(dst, obj);
#else
MacroAssembler::LoadMap(dst, obj);
#endif
}
inline void MaglevAssembler::DefineLazyDeoptPoint(LazyDeoptInfo* info) {
info->set_deopting_call_return_pc(pc_offset_for_safepoint());
code_gen_state()->PushLazyDeopt(info);
safepoint_table_builder()->DefineSafepoint(this);
MaybeEmitPlaceHolderForDeopt();
}
inline void MaglevAssembler::DefineExceptionHandlerPoint(NodeBase* node) {
ExceptionHandlerInfo* info = node->exception_handler_info();
if (!info->HasExceptionHandler()) return;
info->set_pc_offset(pc_offset_for_safepoint());
code_gen_state()->PushHandlerInfo(node);
}
inline void MaglevAssembler::DefineExceptionHandlerAndLazyDeoptPoint(
NodeBase* node) {
DefineExceptionHandlerPoint(node);
DefineLazyDeoptPoint(node->lazy_deopt_info());
}
inline void SaveRegisterStateForCall::DefineSafepointWithLazyDeopt(
LazyDeoptInfo* lazy_deopt_info) {
lazy_deopt_info->set_deopting_call_return_pc(masm->pc_offset_for_safepoint());
masm->code_gen_state()->PushLazyDeopt(lazy_deopt_info);
DefineSafepoint();
masm->MaybeEmitPlaceHolderForDeopt();
}
inline void MaglevAssembler::AssertElidedWriteBarrier(
Register object, Register value, RegisterSnapshot snapshot) {
#if defined(V8_ENABLE_DEBUG_CODE) && !V8_DISABLE_WRITE_BARRIERS_BOOL
if (!v8_flags.slow_debug_code) return;
ZoneLabelRef ok(this);
Label* deferred_write_barrier_check = MakeDeferredCode(
[](MaglevAssembler* masm, ZoneLabelRef ok, Register object,
Register value, RegisterSnapshot snapshot) {
masm->set_allow_call(true);
{
SaveRegisterStateForCall save_register_state(masm, snapshot);
#ifdef V8_COMPRESS_POINTERS
masm->DecompressTagged(object, object);
masm->DecompressTagged(value, value);
#endif
masm->Push(object, value);
masm->Move(kContextRegister, masm->native_context().object());
masm->CallRuntime(Runtime::kCheckNoWriteBarrierNeeded, 2);
}
masm->set_allow_call(false);
masm->Jump(*ok);
},
ok, object, value, snapshot);
JumpIfNotSmi(value, deferred_write_barrier_check);
bind(*ok);
#endif // V8_ENABLE_DEBUG_CODE && !V8_DISABLE_WRITE_BARRIERS
}
} // namespace maglev
} // namespace internal
} // namespace v8
#endif // V8_MAGLEV_MAGLEV_ASSEMBLER_INL_H_