diff options
Diffstat (limited to '3rdparty/asmjit/src/asmjit/core/func.h')
-rw-r--r-- | 3rdparty/asmjit/src/asmjit/core/func.h | 1575 |
1 files changed, 1097 insertions, 478 deletions
diff --git a/3rdparty/asmjit/src/asmjit/core/func.h b/3rdparty/asmjit/src/asmjit/core/func.h index 9c45c1f8b3d..695a23bbcf0 100644 --- a/3rdparty/asmjit/src/asmjit/core/func.h +++ b/3rdparty/asmjit/src/asmjit/core/func.h @@ -1,31 +1,12 @@ -// AsmJit - Machine code generation for C++ +// This file is part of AsmJit project <https://asmjit.com> // -// * Official AsmJit Home Page: https://asmjit.com -// * Official Github Repository: https://github.com/asmjit/asmjit -// -// Copyright (c) 2008-2020 The AsmJit Authors -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. +// See asmjit.h or LICENSE.md for license and copyright information +// SPDX-License-Identifier: Zlib #ifndef ASMJIT_CORE_FUNC_H_INCLUDED #define ASMJIT_CORE_FUNC_H_INCLUDED -#include "../core/arch.h" -#include "../core/callconv.h" +#include "../core/archtraits.h" #include "../core/environment.h" #include "../core/operand.h" #include "../core/type.h" @@ -36,138 +17,452 @@ ASMJIT_BEGIN_NAMESPACE //! \addtogroup asmjit_function //! \{ -// ============================================================================ -// [asmjit::FuncArgIndex] -// ============================================================================ +//! Calling convention id. +//! +//! Calling conventions can be divided into the following groups: +//! +//! - Universal - calling conventions are applicable to any target. They will be converted to a target dependent +//! calling convention at runtime by \ref CallConv::init() with some help from \ref Environment. The purpose of +//! these calling conventions is to make using functions less target dependent and closer to C and C++. +//! +//! - Target specific - calling conventions that are used by a particular architecture and ABI. For example +//! Windows 64-bit calling convention and AMD64 SystemV calling convention. +enum class CallConvId : uint8_t { + // Universal Calling Conventions + // ----------------------------- + + //! Standard function call or explicit `__cdecl` where it can be specified. + //! + //! This is a universal calling convention, which is used to initialize specific calling conventions based on + //! architecture, platform, and its ABI. + kCDecl = 0, -//! Function argument index (lo/hi). -enum FuncArgIndex : uint32_t { - //! Maximum number of function arguments supported by AsmJit. - kFuncArgCount = Globals::kMaxFuncArgs, - //! Extended maximum number of arguments (used internally). - kFuncArgCountLoHi = kFuncArgCount * 2, + //! `__stdcall` on targets that support this calling convention (X86). + //! + //! \note This calling convention is only supported on 32-bit X86. If used on environment that doesn't support + //! this calling convention it will be replaced by \ref CallConvId::kCDecl. + kStdCall = 1, - //! Index to the LO part of function argument (default). + //! `__fastcall` on targets that support this calling convention (X86). //! - //! This value is typically omitted and added only if there is HI argument - //! accessed. - kFuncArgLo = 0, + //! \note This calling convention is only supported on 32-bit X86. If used on environment that doesn't support + //! this calling convention it will be replaced by \ref CallConvId::kCDecl. + kFastCall = 2, - //! Index to the HI part of function argument. + //! `__vectorcall` on targets that support this calling convention (X86/X64). //! - //! HI part of function argument depends on target architecture. On x86 it's - //! typically used to transfer 64-bit integers (they form a pair of 32-bit - //! integers). - kFuncArgHi = kFuncArgCount + //! \note This calling convention is only supported on 32-bit and 64-bit X86 architecture on Windows platform. + //! If used on environment that doesn't support this calling it will be replaced by \ref CallConvId::kCDecl. + kVectorCall = 3, + + //! `__thiscall` on targets that support this calling convention (X86). + //! + //! \note This calling convention is only supported on 32-bit X86 Windows platform. If used on environment that + //! doesn't support this calling convention it will be replaced by \ref CallConvId::kCDecl. + kThisCall = 4, + + //! `__attribute__((regparm(1)))` convention (GCC and Clang). + kRegParm1 = 5, + //! `__attribute__((regparm(2)))` convention (GCC and Clang). + kRegParm2 = 6, + //! `__attribute__((regparm(3)))` convention (GCC and Clang). + kRegParm3 = 7, + + //! AsmJit specific calling convention designed for calling functions inside a multimedia code that don't use many + //! registers internally, but are long enough to be called and not inlined. These functions are usually used to + //! calculate trigonometric functions, logarithms, etc... + kLightCall2 = 16, + kLightCall3 = 17, + kLightCall4 = 18, + + // ABI-Specific Calling Conventions + // -------------------------------- + + //! Soft-float calling convention (AArch32). + //! + //! Floating point arguments are passed via general purpose registers. + kSoftFloat = 30, + + //! Hard-float calling convention (AArch32). + //! + //! Floating point arguments are passed via SIMD registers. + kHardFloat = 31, + + //! X64 System-V calling convention. + kX64SystemV = 32, + //! X64 Windows calling convention. + kX64Windows = 33, + + //! Maximum value of `CallConvId`. + kMaxValue = kX64Windows + + // Deprecated Aliases + // ------------------ + +#if !defined(ASMJIT_NO_DEPRECATED) + , + kNone = kCDecl, + kHost = kCDecl +#endif // !ASMJIT_NO_DEPRECATED }; -// ============================================================================ -// [asmjit::FuncSignature] -// ============================================================================ +//! Strategy used by calling conventions to assign registers to function arguments. +//! +//! Calling convention strategy describes how AsmJit should convert function arguments used by \ref FuncSignature +//! into register identifiers and stack offsets. The \ref CallConvStrategy::kDefault strategy assigns registers +//! and then stack whereas \ref CallConvStrategy::kX64Windows strategy does register shadowing as defined by WIN64 +//! calling convention, which is only used by 64-bit Windows. +enum class CallConvStrategy : uint8_t { + //! Default register assignment strategy. + kDefault = 0, + //! Windows 64-bit ABI register assignment strategy. + kX64Windows = 1, + //! Windows 64-bit __vectorcall register assignment strategy. + kX64VectorCall = 2, + //! Apple's AArch64 calling convention (differs compared to AArch64 calling convention used by Linux). + kAArch64Apple = 3, + + //! Maximum value of `CallConvStrategy`. + kMaxValue = kX64VectorCall +}; -//! Function signature. +//! Calling convention flags. +enum class CallConvFlags : uint32_t { + //! No flags. + kNone = 0, + //! Callee is responsible for cleaning up the stack. + kCalleePopsStack = 0x0001u, + //! Pass vector arguments indirectly (as a pointer). + kIndirectVecArgs = 0x0002u, + //! Pass F32 and F64 arguments via VEC128 register. + kPassFloatsByVec = 0x0004u, + //! Pass MMX and vector arguments via stack if the function has variable arguments. + kPassVecByStackIfVA = 0x0008u, + //! MMX registers are passed and returned via GP registers. + kPassMmxByGp = 0x0010u, + //! MMX registers are passed and returned via XMM registers. + kPassMmxByXmm = 0x0020u, + //! Calling convention can be used with variable arguments. + kVarArgCompatible = 0x0080u +}; +ASMJIT_DEFINE_ENUM_FLAGS(CallConvFlags) + +//! Function calling convention. //! -//! Contains information about function return type, count of arguments and -//! their TypeIds. Function signature is a low level structure which doesn't -//! contain platform specific or calling convention specific information. -struct FuncSignature { +//! Function calling convention is a scheme that defines how function parameters are passed and how function +//! returns its result. AsmJit defines a variety of architecture and OS specific calling conventions and also +//! provides a compile time detection to make the code-generation easier. +struct CallConv { + //! \name Constants + //! \{ + + //! Maximum number of register arguments per register group. + //! + //! \note This is not really AsmJit's limitation, it's just the number that makes sense considering all common + //! calling conventions. Usually even conventions that use registers to pass function arguments are limited to 8 + //! and less arguments passed via registers per group. + static constexpr uint32_t kMaxRegArgsPerGroup = 16; + + //! \} + + //! \name Members + //! \{ + + //! Target architecture. + Arch _arch; //! Calling convention id. - uint8_t _callConv; - //! Count of arguments. - uint8_t _argCount; - //! Index of a first VA or `kNoVarArgs`. - uint8_t _vaIndex; - //! Return value TypeId. - uint8_t _ret; - //! Function arguments TypeIds. - const uint8_t* _args; + CallConvId _id; + //! Register assignment strategy. + CallConvStrategy _strategy; - enum : uint8_t { - //! Doesn't have variable number of arguments (`...`). - kNoVarArgs = 0xFF + //! Red zone size (AMD64 == 128 bytes). + uint8_t _redZoneSize; + //! Spill zone size (WIN-X64 == 32 bytes). + uint8_t _spillZoneSize; + //! Natural stack alignment as defined by OS/ABI. + uint8_t _naturalStackAlignment; + + //! \cond INTERNAL + //! Reserved for future use. + uint8_t _reserved[2]; + //! \endcond + + //! Calling convention flags. + CallConvFlags _flags; + + //! Size to save/restore per register group. + Support::Array<uint8_t, Globals::kNumVirtGroups> _saveRestoreRegSize; + //! Alignment of save/restore groups. + Support::Array<uint8_t, Globals::kNumVirtGroups> _saveRestoreAlignment; + + //! Mask of all passed registers, per group. + Support::Array<RegMask, Globals::kNumVirtGroups> _passedRegs; + //! Mask of all preserved registers, per group. + Support::Array<RegMask, Globals::kNumVirtGroups> _preservedRegs; + + //! Passed registers' order. + union RegOrder { + //! Passed registers, ordered. + uint8_t id[kMaxRegArgsPerGroup]; + //! Packed IDs in `uint32_t` array. + uint32_t packed[(kMaxRegArgsPerGroup + 3) / 4]; }; - //! \name Initializtion & Reset + //! Passed registers' order, per register group. + Support::Array<RegOrder, Globals::kNumVirtGroups> _passedOrder; + + //! \} + + //! \name Construction & Destruction //! \{ - //! Initializes the function signature. - inline void init(uint32_t ccId, uint32_t vaIndex, uint32_t ret, const uint8_t* args, uint32_t argCount) noexcept { - ASMJIT_ASSERT(ccId <= 0xFF); - ASMJIT_ASSERT(argCount <= 0xFF); + //! Initializes this calling convention to the given `ccId` based on the `environment`. + //! + //! See \ref CallConvId and \ref Environment for more details. + ASMJIT_API Error init(CallConvId ccId, const Environment& environment) noexcept; - _callConv = uint8_t(ccId); - _argCount = uint8_t(argCount); - _vaIndex = uint8_t(vaIndex); - _ret = uint8_t(ret); - _args = args; + //! Resets this CallConv struct into a defined state. + //! + //! It's recommended to reset the \ref CallConv struct in case you would like create a custom calling convention + //! as it prevents from using an uninitialized data (CallConv doesn't have a constructor that would initialize it, + //! it's just a struct). + ASMJIT_INLINE_NODEBUG void reset() noexcept { + *this = CallConv{}; + memset(_passedOrder.data(), 0xFF, sizeof(_passedOrder)); } - inline void reset() noexcept { memset(this, 0, sizeof(*this)); } - //! \} //! \name Accessors //! \{ - //! Returns the calling convention. - inline uint32_t callConv() const noexcept { return _callConv; } - //! Sets the calling convention to `ccId`; - inline void setCallConv(uint32_t ccId) noexcept { _callConv = uint8_t(ccId); } + //! Returns the target architecture of this calling convention. + ASMJIT_INLINE_NODEBUG Arch arch() const noexcept { return _arch; } + //! Sets the target architecture of this calling convention. + ASMJIT_INLINE_NODEBUG void setArch(Arch arch) noexcept { _arch = arch; } + + //! Returns the calling convention id. + ASMJIT_INLINE_NODEBUG CallConvId id() const noexcept { return _id; } + //! Sets the calling convention id. + ASMJIT_INLINE_NODEBUG void setId(CallConvId ccId) noexcept { _id = ccId; } + + //! Returns the strategy used to assign registers to arguments. + ASMJIT_INLINE_NODEBUG CallConvStrategy strategy() const noexcept { return _strategy; } + //! Sets the strategy used to assign registers to arguments. + ASMJIT_INLINE_NODEBUG void setStrategy(CallConvStrategy ccStrategy) noexcept { _strategy = ccStrategy; } + + //! Tests whether the calling convention has the given `flag` set. + ASMJIT_INLINE_NODEBUG bool hasFlag(CallConvFlags flag) const noexcept { return Support::test(_flags, flag); } + //! Returns the calling convention flags, see `Flags`. + ASMJIT_INLINE_NODEBUG CallConvFlags flags() const noexcept { return _flags; } + //! Adds the calling convention flags, see `Flags`. + ASMJIT_INLINE_NODEBUG void setFlags(CallConvFlags flag) noexcept { _flags = flag; }; + //! Adds the calling convention flags, see `Flags`. + ASMJIT_INLINE_NODEBUG void addFlags(CallConvFlags flags) noexcept { _flags |= flags; }; + + //! Tests whether this calling convention specifies 'RedZone'. + ASMJIT_INLINE_NODEBUG bool hasRedZone() const noexcept { return _redZoneSize != 0; } + //! Tests whether this calling convention specifies 'SpillZone'. + ASMJIT_INLINE_NODEBUG bool hasSpillZone() const noexcept { return _spillZoneSize != 0; } + + //! Returns size of 'RedZone'. + ASMJIT_INLINE_NODEBUG uint32_t redZoneSize() const noexcept { return _redZoneSize; } + //! Returns size of 'SpillZone'. + ASMJIT_INLINE_NODEBUG uint32_t spillZoneSize() const noexcept { return _spillZoneSize; } + + //! Sets size of 'RedZone'. + ASMJIT_INLINE_NODEBUG void setRedZoneSize(uint32_t size) noexcept { _redZoneSize = uint8_t(size); } + //! Sets size of 'SpillZone'. + ASMJIT_INLINE_NODEBUG void setSpillZoneSize(uint32_t size) noexcept { _spillZoneSize = uint8_t(size); } + + //! Returns a natural stack alignment. + ASMJIT_INLINE_NODEBUG uint32_t naturalStackAlignment() const noexcept { return _naturalStackAlignment; } + //! Sets a natural stack alignment. + //! + //! This function can be used to override the default stack alignment in case that you know that it's alignment is + //! different. For example it allows to implement custom calling conventions that guarantee higher stack alignment. + ASMJIT_INLINE_NODEBUG void setNaturalStackAlignment(uint32_t value) noexcept { _naturalStackAlignment = uint8_t(value); } + + //! Returns the size of a register (or its part) to be saved and restored of the given `group`. + ASMJIT_INLINE_NODEBUG uint32_t saveRestoreRegSize(RegGroup group) const noexcept { return _saveRestoreRegSize[group]; } + //! Sets the size of a vector register (or its part) to be saved and restored. + ASMJIT_INLINE_NODEBUG void setSaveRestoreRegSize(RegGroup group, uint32_t size) noexcept { _saveRestoreRegSize[group] = uint8_t(size); } + + //! Returns the alignment of a save-restore area of the given `group`. + ASMJIT_INLINE_NODEBUG uint32_t saveRestoreAlignment(RegGroup group) const noexcept { return _saveRestoreAlignment[group]; } + //! Sets the alignment of a save-restore area of the given `group`. + ASMJIT_INLINE_NODEBUG void setSaveRestoreAlignment(RegGroup group, uint32_t alignment) noexcept { _saveRestoreAlignment[group] = uint8_t(alignment); } + + //! Returns the order of passed registers of the given `group`. + inline const uint8_t* passedOrder(RegGroup group) const noexcept { + ASMJIT_ASSERT(group <= RegGroup::kMaxVirt); + return _passedOrder[size_t(group)].id; + } - //! Tests whether the function has variable number of arguments (...). - inline bool hasVarArgs() const noexcept { return _vaIndex != kNoVarArgs; } - //! Returns the variable arguments (...) index, `kNoVarArgs` if none. - inline uint32_t vaIndex() const noexcept { return _vaIndex; } - //! Sets the variable arguments (...) index to `index`. - inline void setVaIndex(uint32_t index) noexcept { _vaIndex = uint8_t(index); } - //! Resets the variable arguments index (making it a non-va function). - inline void resetVaIndex() noexcept { _vaIndex = kNoVarArgs; } + //! Returns the mask of passed registers of the given `group`. + inline RegMask passedRegs(RegGroup group) const noexcept { + ASMJIT_ASSERT(group <= RegGroup::kMaxVirt); + return _passedRegs[size_t(group)]; + } - //! Returns the number of function arguments. - inline uint32_t argCount() const noexcept { return _argCount; } + inline void _setPassedPacked(RegGroup group, uint32_t p0, uint32_t p1, uint32_t p2, uint32_t p3) noexcept { + ASMJIT_ASSERT(group <= RegGroup::kMaxVirt); - inline bool hasRet() const noexcept { return _ret != Type::kIdVoid; } - //! Returns the return value type. - inline uint32_t ret() const noexcept { return _ret; } + _passedOrder[group].packed[0] = p0; + _passedOrder[group].packed[1] = p1; + _passedOrder[group].packed[2] = p2; + _passedOrder[group].packed[3] = p3; + } - //! Returns the type of the argument at index `i`. - inline uint32_t arg(uint32_t i) const noexcept { - ASMJIT_ASSERT(i < _argCount); - return _args[i]; + //! Resets the order and mask of passed registers. + inline void setPassedToNone(RegGroup group) noexcept { + ASMJIT_ASSERT(group <= RegGroup::kMaxVirt); + + _setPassedPacked(group, 0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu); + _passedRegs[size_t(group)] = 0u; + } + + //! Sets the order and mask of passed registers. + inline void setPassedOrder(RegGroup group, uint32_t a0, uint32_t a1 = 0xFF, uint32_t a2 = 0xFF, uint32_t a3 = 0xFF, uint32_t a4 = 0xFF, uint32_t a5 = 0xFF, uint32_t a6 = 0xFF, uint32_t a7 = 0xFF) noexcept { + ASMJIT_ASSERT(group <= RegGroup::kMaxVirt); + + // NOTE: This should always be called with all arguments known at compile time, so even if it looks scary it + // should be translated into few instructions. + _setPassedPacked(group, Support::bytepack32_4x8(a0, a1, a2, a3), + Support::bytepack32_4x8(a4, a5, a6, a7), + 0xFFFFFFFFu, + 0xFFFFFFFFu); + + _passedRegs[group] = (a0 != 0xFF ? 1u << a0 : 0u) | + (a1 != 0xFF ? 1u << a1 : 0u) | + (a2 != 0xFF ? 1u << a2 : 0u) | + (a3 != 0xFF ? 1u << a3 : 0u) | + (a4 != 0xFF ? 1u << a4 : 0u) | + (a5 != 0xFF ? 1u << a5 : 0u) | + (a6 != 0xFF ? 1u << a6 : 0u) | + (a7 != 0xFF ? 1u << a7 : 0u) ; + } + + //! Returns preserved register mask of the given `group`. + inline RegMask preservedRegs(RegGroup group) const noexcept { + ASMJIT_ASSERT(group <= RegGroup::kMaxVirt); + return _preservedRegs[group]; + } + + //! Sets preserved register mask of the given `group`. + inline void setPreservedRegs(RegGroup group, RegMask regs) noexcept { + ASMJIT_ASSERT(group <= RegGroup::kMaxVirt); + _preservedRegs[group] = regs; } - //! Returns the array of function arguments' types. - inline const uint8_t* args() const noexcept { return _args; } //! \} }; -// ============================================================================ -// [asmjit::FuncSignatureT] -// ============================================================================ +//! Function signature. +//! +//! Contains information about a function return type, count of arguments, and their TypeIds. Function signature +//! is a low level structure which doesn't contain platform specific or calling convention specific information. +//! It's typically used to describe function arguments in a C-API like form, which is then used to calculate a +//! \ref FuncDetail instance, which then maps function signature into a platform and calling convention specific +//! format. +//! +//! Function signature can be built either dynamically by using \ref addArg() and \ref addArgT() functionality, +//! or dynamically by using a template-based \ref FuncSignature::build() function, which maps template types +//! into a function signature. +struct FuncSignature { + //! \name Constants + //! \{ + + //! Doesn't have variable number of arguments (`...`). + static constexpr uint8_t kNoVarArgs = 0xFFu; -template<typename... RET_ARGS> -class FuncSignatureT : public FuncSignature { -public: - inline FuncSignatureT(uint32_t ccId = CallConv::kIdHost, uint32_t vaIndex = kNoVarArgs) noexcept { - static const uint8_t ret_args[] = { (uint8_t(Type::IdOfT<RET_ARGS>::kTypeId))... }; - init(ccId, vaIndex, ret_args[0], ret_args + 1, uint32_t(ASMJIT_ARRAY_SIZE(ret_args) - 1)); + //! \} + + //! \name Members + //! \{ + + //! Calling convention id. + CallConvId _ccId = CallConvId::kCDecl; + //! Count of arguments. + uint8_t _argCount = 0; + //! Index of a first VA or `kNoVarArgs`. + uint8_t _vaIndex = kNoVarArgs; + //! Return value TypeId. + TypeId _ret = TypeId::kVoid; + //! Reserved for future use. + uint8_t _reserved[4] {}; + //! Function argument TypeIds. + TypeId _args[Globals::kMaxFuncArgs] {}; + + //! \} + + //! \name Construction & Destruction + //! \{ + + //! Default constructed function signature, initialized to \ref CallConvId::kCDecl, having no return value and no arguments. + ASMJIT_FORCE_INLINE constexpr FuncSignature() = default; + + //! Copy constructor, which is initialized to the same function signature as `other`. + ASMJIT_FORCE_INLINE constexpr FuncSignature(const FuncSignature& other) = default; + + //! Initializes the function signature with calling convention id `ccId` and variable argument's index `vaIndex`. + ASMJIT_FORCE_INLINE constexpr FuncSignature(CallConvId ccId, uint32_t vaIndex = kNoVarArgs) noexcept + : _ccId(ccId), + _vaIndex(uint8_t(vaIndex)) {} + + //! Initializes the function signature with calling convention id `ccId`, `vaIndex`, return value, and function arguments. + template<typename... Args> + ASMJIT_FORCE_INLINE constexpr FuncSignature(CallConvId ccId, uint32_t vaIndex, TypeId ret, Args&&...args) noexcept + : _ccId(ccId), + _argCount(uint8_t(sizeof...(args))), + _vaIndex(uint8_t(vaIndex)), + _ret(ret), + _args{std::forward<Args>(args)...} {} + + //! Builds a function signature based on `RetValueAndArgs`. The first template argument is a function return type, + //! and function arguments follow. + //! + //! \note This function returns a new function signature, which can be passed to functions where it's required. It's + //! a convenience function that allows to build function signature statically based on types known at compile time, + //! which is common in JIT code generation. + template<typename... RetValueAndArgs> + static ASMJIT_INLINE_NODEBUG constexpr FuncSignature build(CallConvId ccId = CallConvId::kCDecl, uint32_t vaIndex = kNoVarArgs) noexcept { + return FuncSignature(ccId, vaIndex, (TypeId(TypeUtils::TypeIdOfT<RetValueAndArgs>::kTypeId))... ); } -}; -// ============================================================================ -// [asmjit::FuncSignatureBuilder] -// ============================================================================ + //! \} + + //! \name Overloaded Operators + //! \{ -//! Function signature builder. -class FuncSignatureBuilder : public FuncSignature { -public: - uint8_t _builderArgList[kFuncArgCount]; + //! Copy assignment - function signature can be copied by value. + ASMJIT_FORCE_INLINE FuncSignature& operator=(const FuncSignature& other) noexcept = default; - //! \name Initializtion & Reset + //! Compares this function signature with `other` for equality.. + ASMJIT_FORCE_INLINE bool operator==(const FuncSignature& other) const noexcept { return equals(other); } + //! Compares this function signature with `other` for inequality.. + ASMJIT_FORCE_INLINE bool operator!=(const FuncSignature& other) const noexcept { return !equals(other); } + + //! \} + + //! \name Initialization & Reset //! \{ - inline FuncSignatureBuilder(uint32_t ccId = CallConv::kIdHost, uint32_t vaIndex = kNoVarArgs) noexcept { - init(ccId, vaIndex, Type::kIdVoid, _builderArgList, 0); + //! Resets this function signature to a default constructed state. + ASMJIT_INLINE_NODEBUG void reset() noexcept { *this = FuncSignature{}; } + + //! \} + + //! \name Equality & Comparison + //! \{ + + //! Compares this function signature with `other` for equality.. + ASMJIT_INLINE_NODEBUG bool equals(const FuncSignature& other) const noexcept { + return _ccId == other._ccId && + _argCount == other._argCount && + _vaIndex == other._vaIndex && + _ret == other._ret && + memcmp(_args, other._args, sizeof(_args)) == 0; } //! \} @@ -175,43 +470,92 @@ public: //! \name Accessors //! \{ + //! Returns the calling convention. + ASMJIT_INLINE_NODEBUG CallConvId callConvId() const noexcept { return _ccId; } + //! Sets the calling convention to `ccId`; + ASMJIT_INLINE_NODEBUG void setCallConvId(CallConvId ccId) noexcept { _ccId = ccId; } + + //! Tests whether the function signature has a return value. + ASMJIT_INLINE_NODEBUG bool hasRet() const noexcept { return _ret != TypeId::kVoid; } + //! Returns the type of the return value. + ASMJIT_INLINE_NODEBUG TypeId ret() const noexcept { return _ret; } //! Sets the return type to `retType`. - inline void setRet(uint32_t retType) noexcept { _ret = uint8_t(retType); } + ASMJIT_INLINE_NODEBUG void setRet(TypeId retType) noexcept { _ret = retType; } //! Sets the return type based on `T`. template<typename T> - inline void setRetT() noexcept { setRet(Type::IdOfT<T>::kTypeId); } + ASMJIT_INLINE_NODEBUG void setRetT() noexcept { setRet(TypeId(TypeUtils::TypeIdOfT<T>::kTypeId)); } + + + //! Returns the array of function arguments' types. + ASMJIT_INLINE_NODEBUG const TypeId* args() const noexcept { return _args; } + //! Returns the number of function arguments. + ASMJIT_INLINE_NODEBUG uint32_t argCount() const noexcept { return _argCount; } + + //! Returns the type of the argument at index `i`. + inline TypeId arg(uint32_t i) const noexcept { + ASMJIT_ASSERT(i < _argCount); + return _args[i]; + } //! Sets the argument at index `index` to `argType`. - inline void setArg(uint32_t index, uint32_t argType) noexcept { + inline void setArg(uint32_t index, TypeId argType) noexcept { ASMJIT_ASSERT(index < _argCount); - _builderArgList[index] = uint8_t(argType); + _args[index] = argType; } //! Sets the argument at index `i` to the type based on `T`. template<typename T> - inline void setArgT(uint32_t index) noexcept { setArg(index, Type::IdOfT<T>::kTypeId); } + inline void setArgT(uint32_t index) noexcept { setArg(index, TypeId(TypeUtils::TypeIdOfT<T>::kTypeId)); } + + //! Tests whether an argument can be added to the signature, use before calling \ref addArg() and \ref addArgT(). + //! + //! \note If you know that you are not adding more arguments than \ref Globals::kMaxFuncArgs then it's not necessary + //! to use this function. However, if you are adding arguments based on user input, for example, then either check + //! the number of arguments before using function signature or use \ref canAddArg() before actually adding them to + //! the function signature. + inline bool canAddArg() const noexcept { return _argCount < Globals::kMaxFuncArgs; } //! Appends an argument of `type` to the function prototype. - inline void addArg(uint32_t type) noexcept { - ASMJIT_ASSERT(_argCount < kFuncArgCount); - _builderArgList[_argCount++] = uint8_t(type); + inline void addArg(TypeId type) noexcept { + ASMJIT_ASSERT(_argCount < Globals::kMaxFuncArgs); + _args[_argCount++] = type; } + //! Appends an argument of type based on `T` to the function prototype. template<typename T> - inline void addArgT() noexcept { addArg(Type::IdOfT<T>::kTypeId); } + inline void addArgT() noexcept { addArg(TypeId(TypeUtils::TypeIdOfT<T>::kTypeId)); } + + //! Tests whether the function has variable number of arguments (...). + ASMJIT_INLINE_NODEBUG bool hasVarArgs() const noexcept { return _vaIndex != kNoVarArgs; } + //! Returns the variable arguments (...) index, `kNoVarArgs` if none. + ASMJIT_INLINE_NODEBUG uint32_t vaIndex() const noexcept { return _vaIndex; } + //! Sets the variable arguments (...) index to `index`. + ASMJIT_INLINE_NODEBUG void setVaIndex(uint32_t index) noexcept { _vaIndex = uint8_t(index); } + //! Resets the variable arguments index (making it a non-va function). + ASMJIT_INLINE_NODEBUG void resetVaIndex() noexcept { _vaIndex = kNoVarArgs; } //! \} }; -// ============================================================================ -// [asmjit::FuncValue] -// ============================================================================ +#if !defined(ASMJIT_NO_DEPRECATED) +template<typename... RetValueAndArgs> +class FuncSignatureT : public FuncSignature { +public: + ASMJIT_DEPRECATED("Use FuncSignature::build<RetValueAndArgs>() instead") + ASMJIT_INLINE_NODEBUG constexpr FuncSignatureT(CallConvId ccId = CallConvId::kCDecl, uint32_t vaIndex = kNoVarArgs) noexcept + : FuncSignature(ccId, vaIndex, (TypeId(TypeUtils::TypeIdOfT<RetValueAndArgs>::kTypeId))... ) {} +}; -//! Argument or return value as defined by `FuncSignature`, but with register -//! or stack address (and other metadata) assigned to it. +ASMJIT_DEPRECATED("Use FuncSignature instead of FuncSignatureBuilder") +typedef FuncSignature FuncSignatureBuilder; +#endif // !ASMJIT_NO_DEPRECATED + +//! Argument or return value (or its part) as defined by `FuncSignature`, but with register or stack address +//! (and other metadata) assigned. struct FuncValue { - uint32_t _data; + //! \name Constants + //! \{ - enum Parts : uint32_t { + enum Bits : uint32_t { kTypeIdShift = 0, //!< TypeId shift. kTypeIdMask = 0x000000FFu, //!< TypeId mask. @@ -230,42 +574,56 @@ struct FuncValue { kRegTypeMask = 0xFF000000u //!< RegType mask. }; - //! \name Initializtion & Reset + //! \} + + //! \name Members //! \{ - // These initialize the whole `FuncValue` to either register or stack. Useful - // when you know all of these properties and wanna just set it up. + uint32_t _data; + + //! \} + + //! \name Initialization & Reset + //! + //! These initialize the whole `FuncValue` to either register or stack. Useful when you know all of these + //! properties and wanna just set it up. + //! + //! \{ - //! Initializes the `typeId` of this `FuncValue`. - inline void initTypeId(uint32_t typeId) noexcept { - _data = typeId << kTypeIdShift; + //! Initializes this `FuncValue` only to the `typeId` provided - the rest of the values will be cleared. + ASMJIT_INLINE_NODEBUG void initTypeId(TypeId typeId) noexcept { + _data = uint32_t(typeId) << kTypeIdShift; } - inline void initReg(uint32_t regType, uint32_t regId, uint32_t typeId, uint32_t flags = 0) noexcept { - _data = (regType << kRegTypeShift) | (regId << kRegIdShift) | (typeId << kTypeIdShift) | kFlagIsReg | flags; + //! Initializes this `FuncValue` to a register of `regType`, `regId`, and assigns its `typeId` and `flags`. + ASMJIT_INLINE_NODEBUG void initReg(RegType regType, uint32_t regId, TypeId typeId, uint32_t flags = 0) noexcept { + _data = (uint32_t(regType) << kRegTypeShift) | (regId << kRegIdShift) | (uint32_t(typeId) << kTypeIdShift) | kFlagIsReg | flags; } - inline void initStack(int32_t offset, uint32_t typeId) noexcept { - _data = (uint32_t(offset) << kStackOffsetShift) | (typeId << kTypeIdShift) | kFlagIsStack; + //! Initializes this `FuncValue` to a stack at the given `offset` and assigns its `typeId`. + ASMJIT_INLINE_NODEBUG void initStack(int32_t offset, TypeId typeId) noexcept { + _data = (uint32_t(offset) << kStackOffsetShift) | (uint32_t(typeId) << kTypeIdShift) | kFlagIsStack; } //! Resets the value to its unassigned state. - inline void reset() noexcept { _data = 0; } + ASMJIT_INLINE_NODEBUG void reset() noexcept { _data = 0; } //! \} //! \name Assign + //! + //! These initialize only part of `FuncValue`, useful when building `FuncValue` incrementally. The caller + //! should first init the type-id by calling `initTypeId` and then continue building either register or stack. + //! //! \{ - // These initialize only part of `FuncValue`, useful when building `FuncValue` - // incrementally. The caller should first init the type-id by caliing `initTypeId` - // and then continue building either register or stack. - - inline void assignRegData(uint32_t regType, uint32_t regId) noexcept { + //! Assigns a register of `regType` and `regId`. + inline void assignRegData(RegType regType, uint32_t regId) noexcept { ASMJIT_ASSERT((_data & (kRegTypeMask | kRegIdMask)) == 0); - _data |= (regType << kRegTypeShift) | (regId << kRegIdShift) | kFlagIsReg; + _data |= (uint32_t(regType) << kRegTypeShift) | (regId << kRegIdShift) | kFlagIsReg; } + //! Assigns a stack location at `offset`. inline void assignStackOffset(int32_t offset) noexcept { ASMJIT_ASSERT((_data & kStackOffsetMask) == 0); _data |= (uint32_t(offset) << kStackOffsetShift) | kFlagIsStack; @@ -276,98 +634,258 @@ struct FuncValue { //! \name Accessors //! \{ - inline void _replaceValue(uint32_t mask, uint32_t value) noexcept { _data = (_data & ~mask) | value; } + //! Returns true if the value is initialized (explicit bool cast). + ASMJIT_INLINE_NODEBUG explicit operator bool() const noexcept { return _data != 0; } + + //! \cond INTERNAL + ASMJIT_INLINE_NODEBUG void _replaceValue(uint32_t mask, uint32_t value) noexcept { _data = (_data & ~mask) | value; } + //! \endcond //! Tests whether the `FuncValue` has a flag `flag` set. - inline bool hasFlag(uint32_t flag) const noexcept { return (_data & flag) != 0; } + ASMJIT_INLINE_NODEBUG bool hasFlag(uint32_t flag) const noexcept { return Support::test(_data, flag); } //! Adds `flags` to `FuncValue`. - inline void addFlags(uint32_t flags) noexcept { _data |= flags; } + ASMJIT_INLINE_NODEBUG void addFlags(uint32_t flags) noexcept { _data |= flags; } //! Clears `flags` of `FuncValue`. - inline void clearFlags(uint32_t flags) noexcept { _data &= ~flags; } + ASMJIT_INLINE_NODEBUG void clearFlags(uint32_t flags) noexcept { _data &= ~flags; } //! Tests whether the value is initialized (i.e. contains a valid data). - inline bool isInitialized() const noexcept { return _data != 0; } + ASMJIT_INLINE_NODEBUG bool isInitialized() const noexcept { return _data != 0; } //! Tests whether the argument is passed by register. - inline bool isReg() const noexcept { return hasFlag(kFlagIsReg); } + ASMJIT_INLINE_NODEBUG bool isReg() const noexcept { return hasFlag(kFlagIsReg); } //! Tests whether the argument is passed by stack. - inline bool isStack() const noexcept { return hasFlag(kFlagIsStack); } + ASMJIT_INLINE_NODEBUG bool isStack() const noexcept { return hasFlag(kFlagIsStack); } //! Tests whether the argument is passed by register. - inline bool isAssigned() const noexcept { return hasFlag(kFlagIsReg | kFlagIsStack); } + ASMJIT_INLINE_NODEBUG bool isAssigned() const noexcept { return hasFlag(kFlagIsReg | kFlagIsStack); } //! Tests whether the argument is passed through a pointer (used by WIN64 to pass XMM|YMM|ZMM). - inline bool isIndirect() const noexcept { return hasFlag(kFlagIsIndirect); } + ASMJIT_INLINE_NODEBUG bool isIndirect() const noexcept { return hasFlag(kFlagIsIndirect); } //! Tests whether the argument was already processed (used internally). - inline bool isDone() const noexcept { return hasFlag(kFlagIsDone); } + ASMJIT_INLINE_NODEBUG bool isDone() const noexcept { return hasFlag(kFlagIsDone); } //! Returns a register type of the register used to pass function argument or return value. - inline uint32_t regType() const noexcept { return (_data & kRegTypeMask) >> kRegTypeShift; } + ASMJIT_INLINE_NODEBUG RegType regType() const noexcept { return RegType((_data & kRegTypeMask) >> kRegTypeShift); } //! Sets a register type of the register used to pass function argument or return value. - inline void setRegType(uint32_t regType) noexcept { _replaceValue(kRegTypeMask, regType << kRegTypeShift); } + ASMJIT_INLINE_NODEBUG void setRegType(RegType regType) noexcept { _replaceValue(kRegTypeMask, uint32_t(regType) << kRegTypeShift); } //! Returns a physical id of the register used to pass function argument or return value. - inline uint32_t regId() const noexcept { return (_data & kRegIdMask) >> kRegIdShift; } + ASMJIT_INLINE_NODEBUG uint32_t regId() const noexcept { return (_data & kRegIdMask) >> kRegIdShift; } //! Sets a physical id of the register used to pass function argument or return value. - inline void setRegId(uint32_t regId) noexcept { _replaceValue(kRegIdMask, regId << kRegIdShift); } + ASMJIT_INLINE_NODEBUG void setRegId(uint32_t regId) noexcept { _replaceValue(kRegIdMask, regId << kRegIdShift); } //! Returns a stack offset of this argument. - inline int32_t stackOffset() const noexcept { return int32_t(_data & kStackOffsetMask) >> kStackOffsetShift; } + ASMJIT_INLINE_NODEBUG int32_t stackOffset() const noexcept { return int32_t(_data & kStackOffsetMask) >> kStackOffsetShift; } //! Sets a stack offset of this argument. - inline void setStackOffset(int32_t offset) noexcept { _replaceValue(kStackOffsetMask, uint32_t(offset) << kStackOffsetShift); } + ASMJIT_INLINE_NODEBUG void setStackOffset(int32_t offset) noexcept { _replaceValue(kStackOffsetMask, uint32_t(offset) << kStackOffsetShift); } - //! Tests whether the argument or return value has associated `Type::Id`. - inline bool hasTypeId() const noexcept { return (_data & kTypeIdMask) != 0; } + //! Tests whether the argument or return value has associated `TypeId`. + ASMJIT_INLINE_NODEBUG bool hasTypeId() const noexcept { return Support::test(_data, kTypeIdMask); } //! Returns a TypeId of this argument or return value. - inline uint32_t typeId() const noexcept { return (_data & kTypeIdMask) >> kTypeIdShift; } + ASMJIT_INLINE_NODEBUG TypeId typeId() const noexcept { return TypeId((_data & kTypeIdMask) >> kTypeIdShift); } //! Sets a TypeId of this argument or return value. - inline void setTypeId(uint32_t typeId) noexcept { _replaceValue(kTypeIdMask, typeId << kTypeIdShift); } + ASMJIT_INLINE_NODEBUG void setTypeId(TypeId typeId) noexcept { _replaceValue(kTypeIdMask, uint32_t(typeId) << kTypeIdShift); } //! \} }; -// ============================================================================ -// [asmjit::FuncDetail] -// ============================================================================ +//! Contains multiple `FuncValue` instances in an array so functions that use multiple registers for arguments or +//! return values can represent all inputs and outputs. +struct FuncValuePack { +public: + //! \name Members + //! \{ + + //! Values of the pack. + FuncValue _values[Globals::kMaxValuePack]; + + //! \} -//! Function detail - CallConv and expanded FuncSignature. + //! \name Initialization & Reset + //! \{ + + //! Resets all values in the pack. + inline void reset() noexcept { + for (size_t i = 0; i < Globals::kMaxValuePack; i++) + _values[i].reset(); + } + + //! \} + + //! \name Accessors + //! \{ + + //! Calculates how many values are in the pack, checking for non-values from the end. + inline uint32_t count() const noexcept { + uint32_t n = Globals::kMaxValuePack; + while (n && !_values[n - 1]) + n--; + return n; + } + + //! Returns values in this value in the pack. + //! + //! \note The returned array has exactly \ref Globals::kMaxValuePack elements. + ASMJIT_INLINE_NODEBUG FuncValue* values() noexcept { return _values; } + //! \overload + ASMJIT_INLINE_NODEBUG const FuncValue* values() const noexcept { return _values; } + + //! Resets a value at the given `index` in the pack, which makes it unassigned. + inline void resetValue(size_t index) noexcept { + ASMJIT_ASSERT(index < Globals::kMaxValuePack); + _values[index].reset(); + } + + //! Tests whether the value at the given `index` in the pack is assigned. + inline bool hasValue(size_t index) noexcept { + ASMJIT_ASSERT(index < Globals::kMaxValuePack); + return _values[index].isInitialized(); + } + + //! Assigns a register at the given `index` to `reg` and an optional `typeId`. + inline void assignReg(size_t index, const BaseReg& reg, TypeId typeId = TypeId::kVoid) noexcept { + ASMJIT_ASSERT(index < Globals::kMaxValuePack); + ASMJIT_ASSERT(reg.isPhysReg()); + _values[index].initReg(reg.type(), reg.id(), typeId); + } + + //! Assigns a register at the given `index` to `regType`, `regId`, and an optional `typeId`. + inline void assignReg(size_t index, RegType regType, uint32_t regId, TypeId typeId = TypeId::kVoid) noexcept { + ASMJIT_ASSERT(index < Globals::kMaxValuePack); + _values[index].initReg(regType, regId, typeId); + } + + //! Assigns a stack location at the given `index` to `offset` and an optional `typeId`. + inline void assignStack(size_t index, int32_t offset, TypeId typeId = TypeId::kVoid) noexcept { + ASMJIT_ASSERT(index < Globals::kMaxValuePack); + _values[index].initStack(offset, typeId); + } + + //! Accesses the value in the pack at the given `index`. + //! + //! \note The maximum index value is `Globals::kMaxValuePack - 1`. + inline FuncValue& operator[](size_t index) { + ASMJIT_ASSERT(index < Globals::kMaxValuePack); + return _values[index]; + } + //! \overload + inline const FuncValue& operator[](size_t index) const { + ASMJIT_ASSERT(index < Globals::kMaxValuePack); + return _values[index]; + } + + //! \} +}; + +//! Attributes are designed in a way that all are initially false, and user or \ref FuncFrame finalizer adds +//! them when necessary. +enum class FuncAttributes : uint32_t { + //! No attributes. + kNoAttributes = 0, + + //! Function has variable number of arguments. + kHasVarArgs = 0x00000001u, + //! Preserve frame pointer (don't omit FP). + kHasPreservedFP = 0x00000010u, + //! Function calls other functions (is not leaf). + kHasFuncCalls = 0x00000020u, + //! Function has aligned save/restore of vector registers. + kAlignedVecSR = 0x00000040u, + //! Function must begin with an instruction that marks a start of a branch or function. + //! + //! * `ENDBR32/ENDBR64` instruction is inserted at the beginning of the function (X86, X86_64). + //! * `BTI` instruction is inserted at the beginning of the function (AArch64) + kIndirectBranchProtection = 0x00000080u, + //! FuncFrame is finalized and can be used by prolog/epilog inserter (PEI). + kIsFinalized = 0x00000800u, + + // X86 Specific Attributes + // ----------------------- + + //! Enables the use of AVX within the function's body, prolog, and epilog (X86). + //! + //! This flag instructs prolog and epilog emitter to use AVX instead of SSE for manipulating XMM registers. + kX86_AVXEnabled = 0x00010000u, + + //! Enables the use of AVX-512 within the function's body, prolog, and epilog (X86). + //! + //! This flag instructs Compiler register allocator to use additional 16 registers introduced by AVX-512. + //! Additionally, if the functions saves full width of ZMM registers (custom calling conventions only) then + //! the prolog/epilog inserter would use AVX-512 move instructions to emit the save and restore sequence. + kX86_AVX512Enabled = 0x00020000u, + + //! This flag instructs the epilog writer to emit EMMS instruction before RET (X86). + kX86_MMXCleanup = 0x00040000u, + + //! This flag instructs the epilog writer to emit VZEROUPPER instruction before RET (X86). + kX86_AVXCleanup = 0x00080000u +}; +ASMJIT_DEFINE_ENUM_FLAGS(FuncAttributes) + +//! Function detail - \ref CallConv and expanded \ref FuncSignature. //! -//! Function detail is architecture and OS dependent representation of a function. -//! It contains calling convention and expanded function signature so all -//! arguments have assigned either register type & id or stack address. +//! Function detail is architecture and OS dependent representation of a function. It contains a materialized +//! calling convention and expanded function signature so all arguments have assigned either register type/id +//! or stack address. class FuncDetail { public: + //! \name Constants + //! \{ + + //! Function doesn't have a variable number of arguments (`...`). + static constexpr uint8_t kNoVarArgs = 0xFFu; + + //! \} + + //! \name Members + //! \{ + //! Calling convention. - CallConv _callConv; + CallConv _callConv {}; //! Number of function arguments. - uint8_t _argCount; - //! Number of function return values. - uint8_t _retCount; + uint8_t _argCount = 0; //! Variable arguments index of `kNoVarArgs`. - uint8_t _vaIndex; + uint8_t _vaIndex = 0; //! Reserved for future use. - uint8_t _reserved; - //! Registers that contains arguments. - uint32_t _usedRegs[BaseReg::kGroupVirt]; + uint16_t _reserved = 0; + //! Registers that contain arguments. + Support::Array<RegMask, Globals::kNumVirtGroups> _usedRegs {}; //! Size of arguments passed by stack. - uint32_t _argStackSize; - //! Function return values. - FuncValue _rets[2]; + uint32_t _argStackSize = 0; + //! Function return value(s). + FuncValuePack _rets {}; //! Function arguments. - FuncValue _args[kFuncArgCountLoHi]; + FuncValuePack _args[Globals::kMaxFuncArgs] {}; - enum : uint8_t { - //! Doesn't have variable number of arguments (`...`). - kNoVarArgs = 0xFF - }; + //! \} //! \name Construction & Destruction //! \{ - inline FuncDetail() noexcept { reset(); } - inline FuncDetail(const FuncDetail& other) noexcept = default; + //! Creates a default constructed \ref FuncDetail. + ASMJIT_INLINE_NODEBUG FuncDetail() noexcept {} + + //! Copy constructor. + //! + //! Function details are copyable. + ASMJIT_INLINE_NODEBUG FuncDetail(const FuncDetail& other) noexcept = default; //! Initializes this `FuncDetail` to the given signature. ASMJIT_API Error init(const FuncSignature& signature, const Environment& environment) noexcept; - inline void reset() noexcept { memset(this, 0, sizeof(*this)); } + + //! \} + + //! \name Overloaded Operators + //! \{ + + //! Assignment operator, copies `other` to this \ref FuncDetail. + ASMJIT_INLINE_NODEBUG FuncDetail& operator=(const FuncDetail& other) noexcept = default; + + //! \} + + //! \name Reset + //! \{ + + //! Resets the function detail to its default constructed state. + ASMJIT_INLINE_NODEBUG void reset() noexcept { *this = FuncDetail{}; } //! \} @@ -375,103 +893,124 @@ public: //! \{ //! Returns the function's calling convention, see `CallConv`. - inline const CallConv& callConv() const noexcept { return _callConv; } + ASMJIT_INLINE_NODEBUG const CallConv& callConv() const noexcept { return _callConv; } //! Returns the associated calling convention flags, see `CallConv::Flags`. - inline uint32_t flags() const noexcept { return _callConv.flags(); } + ASMJIT_INLINE_NODEBUG CallConvFlags flags() const noexcept { return _callConv.flags(); } //! Checks whether a CallConv `flag` is set, see `CallConv::Flags`. - inline bool hasFlag(uint32_t ccFlag) const noexcept { return _callConv.hasFlag(ccFlag); } - - //! Returns count of function return values. - inline uint32_t retCount() const noexcept { return _retCount; } - //! Returns the number of function arguments. - inline uint32_t argCount() const noexcept { return _argCount; } + ASMJIT_INLINE_NODEBUG bool hasFlag(CallConvFlags ccFlag) const noexcept { return _callConv.hasFlag(ccFlag); } //! Tests whether the function has a return value. - inline bool hasRet() const noexcept { return _retCount != 0; } - //! Returns function return value associated with the given `index`. - inline FuncValue& ret(uint32_t index = 0) noexcept { - ASMJIT_ASSERT(index < ASMJIT_ARRAY_SIZE(_rets)); - return _rets[index]; - } - //! Returns function return value associated with the given `index` (const). - inline const FuncValue& ret(uint32_t index = 0) const noexcept { - ASMJIT_ASSERT(index < ASMJIT_ARRAY_SIZE(_rets)); - return _rets[index]; + ASMJIT_INLINE_NODEBUG bool hasRet() const noexcept { return bool(_rets[0]); } + //! Returns the number of function arguments. + ASMJIT_INLINE_NODEBUG uint32_t argCount() const noexcept { return _argCount; } + + //! Returns function return values. + ASMJIT_INLINE_NODEBUG FuncValuePack& retPack() noexcept { return _rets; } + //! Returns function return values. + ASMJIT_INLINE_NODEBUG const FuncValuePack& retPack() const noexcept { return _rets; } + + //! Returns a function return value associated with the given `valueIndex`. + ASMJIT_INLINE_NODEBUG FuncValue& ret(size_t valueIndex = 0) noexcept { return _rets[valueIndex]; } + //! Returns a function return value associated with the given `valueIndex` (const). + ASMJIT_INLINE_NODEBUG const FuncValue& ret(size_t valueIndex = 0) const noexcept { return _rets[valueIndex]; } + + //! Returns function argument packs array. + ASMJIT_INLINE_NODEBUG FuncValuePack* argPacks() noexcept { return _args; } + //! Returns function argument packs array (const). + ASMJIT_INLINE_NODEBUG const FuncValuePack* argPacks() const noexcept { return _args; } + + //! Returns function argument pack at the given `argIndex`. + inline FuncValuePack& argPack(size_t argIndex) noexcept { + ASMJIT_ASSERT(argIndex < Globals::kMaxFuncArgs); + return _args[argIndex]; } - //! Returns function arguments array. - inline FuncValue* args() noexcept { return _args; } - //! Returns function arguments array (const). - inline const FuncValue* args() const noexcept { return _args; } - - inline bool hasArg(uint32_t index) const noexcept { - ASMJIT_ASSERT(index < kFuncArgCountLoHi); - return _args[index].isInitialized(); + //! Returns function argument pack at the given `argIndex` (const). + inline const FuncValuePack& argPack(size_t argIndex) const noexcept { + ASMJIT_ASSERT(argIndex < Globals::kMaxFuncArgs); + return _args[argIndex]; } - //! Returns function argument at the given `index`. - inline FuncValue& arg(uint32_t index) noexcept { - ASMJIT_ASSERT(index < kFuncArgCountLoHi); - return _args[index]; + //! Returns an argument at `valueIndex` from the argument pack at the given `argIndex`. + inline FuncValue& arg(size_t argIndex, size_t valueIndex = 0) noexcept { + ASMJIT_ASSERT(argIndex < Globals::kMaxFuncArgs); + return _args[argIndex][valueIndex]; } - //! Returnsfunction argument at the given index `index` (const). - inline const FuncValue& arg(uint32_t index) const noexcept { - ASMJIT_ASSERT(index < kFuncArgCountLoHi); - return _args[index]; + //! Returns an argument at `valueIndex` from the argument pack at the given `argIndex` (const). + inline const FuncValue& arg(size_t argIndex, size_t valueIndex = 0) const noexcept { + ASMJIT_ASSERT(argIndex < Globals::kMaxFuncArgs); + return _args[argIndex][valueIndex]; } - inline void resetArg(uint32_t index) noexcept { - ASMJIT_ASSERT(index < kFuncArgCountLoHi); - _args[index].reset(); + //! Resets an argument at the given `argIndex`. + //! + //! If the argument is a parameter pack (has multiple values) all values are reset. + inline void resetArg(size_t argIndex) noexcept { + ASMJIT_ASSERT(argIndex < Globals::kMaxFuncArgs); + _args[argIndex].reset(); } - inline bool hasVarArgs() const noexcept { return _vaIndex != kNoVarArgs; } - inline uint32_t vaIndex() const noexcept { return _vaIndex; } + //! Tests whether the function has variable arguments. + ASMJIT_INLINE_NODEBUG bool hasVarArgs() const noexcept { return _vaIndex != kNoVarArgs; } + //! Returns an index of a first variable argument. + ASMJIT_INLINE_NODEBUG uint32_t vaIndex() const noexcept { return _vaIndex; } //! Tests whether the function passes one or more argument by stack. - inline bool hasStackArgs() const noexcept { return _argStackSize != 0; } + ASMJIT_INLINE_NODEBUG bool hasStackArgs() const noexcept { return _argStackSize != 0; } //! Returns stack size needed for function arguments passed on the stack. - inline uint32_t argStackSize() const noexcept { return _argStackSize; } - - inline uint32_t redZoneSize() const noexcept { return _callConv.redZoneSize(); } - inline uint32_t spillZoneSize() const noexcept { return _callConv.spillZoneSize(); } - inline uint32_t naturalStackAlignment() const noexcept { return _callConv.naturalStackAlignment(); } - - inline uint32_t passedRegs(uint32_t group) const noexcept { return _callConv.passedRegs(group); } - inline uint32_t preservedRegs(uint32_t group) const noexcept { return _callConv.preservedRegs(group); } - - inline uint32_t usedRegs(uint32_t group) const noexcept { - ASMJIT_ASSERT(group < BaseReg::kGroupVirt); - return _usedRegs[group]; + ASMJIT_INLINE_NODEBUG uint32_t argStackSize() const noexcept { return _argStackSize; } + + //! Returns red zone size. + ASMJIT_INLINE_NODEBUG uint32_t redZoneSize() const noexcept { return _callConv.redZoneSize(); } + //! Returns spill zone size. + ASMJIT_INLINE_NODEBUG uint32_t spillZoneSize() const noexcept { return _callConv.spillZoneSize(); } + //! Returns natural stack alignment. + ASMJIT_INLINE_NODEBUG uint32_t naturalStackAlignment() const noexcept { return _callConv.naturalStackAlignment(); } + + //! Returns a mask of all passed registers of the given register `group`. + ASMJIT_INLINE_NODEBUG RegMask passedRegs(RegGroup group) const noexcept { return _callConv.passedRegs(group); } + //! Returns a mask of all preserved registers of the given register `group`. + ASMJIT_INLINE_NODEBUG RegMask preservedRegs(RegGroup group) const noexcept { return _callConv.preservedRegs(group); } + + //! Returns a mask of all used registers of the given register `group`. + inline RegMask usedRegs(RegGroup group) const noexcept { + ASMJIT_ASSERT(group <= RegGroup::kMaxVirt); + return _usedRegs[size_t(group)]; } - inline void addUsedRegs(uint32_t group, uint32_t regs) noexcept { - ASMJIT_ASSERT(group < BaseReg::kGroupVirt); - _usedRegs[group] |= regs; + //! Adds `regs` to the mask of used registers of the given register `group`. + inline void addUsedRegs(RegGroup group, RegMask regs) noexcept { + ASMJIT_ASSERT(group <= RegGroup::kMaxVirt); + _usedRegs[size_t(group)] |= regs; } //! \} }; -// ============================================================================ -// [asmjit::FuncFrame] -// ============================================================================ - //! Function frame. //! -//! Function frame is used directly by prolog and epilog insertion (PEI) utils. -//! It provides information necessary to insert a proper and ABI comforming -//! prolog and epilog. Function frame calculation is based on `CallConv` and +//! Function frame is used directly by prolog and epilog insertion (PEI) utils. It provides information necessary to +//! insert a proper and ABI conforming prolog and epilog. Function frame calculation is based on `CallConv` and //! other function attributes. //! +//! SSE vs AVX vs AVX-512 +//! --------------------- +//! +//! Function frame provides a way to tell prolog/epilog inserter to use AVX instructions instead of SSE. Use +//! `setAvxEnabled()` and `setAvx512Enabled()` to enable AVX and/or AVX-512, respectively. Enabling AVX-512 +//! is mostly for Compiler as it would use 32 SIMD registers instead of 16 when enabled. +//! +//! \note If your code uses AVX instructions and AVX is not enabled there would be a performance hit in case that +//! some registers had to be saved/restored in function's prolog/epilog, respectively. Thus, it's recommended to +//! always let the function frame know about the use of AVX. +//! //! Function Frame Structure //! ------------------------ //! -//! Various properties can contribute to the size and structure of the function -//! frame. The function frame in most cases won't use all of the properties -//! illustrated (for example Spill Zone and Red Zone are never used together). +//! Various properties can contribute to the size and structure of the function frame. The function frame in most +//! cases won't use all of the properties illustrated (for example Spill Zone and Red Zone are never used together). //! //! ``` //! +-----------------------------+ @@ -492,110 +1031,112 @@ public: //! ``` class FuncFrame { public: - enum Tag : uint32_t { + //! \name Constants + //! \{ + + enum : uint32_t { //! Tag used to inform that some offset is invalid. kTagInvalidOffset = 0xFFFFFFFFu }; - //! Attributes are designed in a way that all are initially false, and user - //! or FuncFrame finalizer adds them when necessary. - enum Attributes : uint32_t { - //! Function has variable number of arguments. - kAttrHasVarArgs = 0x00000001u, - //! Preserve frame pointer (don't omit FP). - kAttrHasPreservedFP = 0x00000010u, - //! Function calls other functions (is not leaf). - kAttrHasFuncCalls = 0x00000020u, - - //! Use AVX instead of SSE for all operations (X86). - kAttrX86AvxEnabled = 0x00010000u, - //! Emit VZEROUPPER instruction in epilog (X86). - kAttrX86AvxCleanup = 0x00020000u, - //! Emit EMMS instruction in epilog (X86). - kAttrX86MmxCleanup = 0x00040000u, - - //! Function has aligned save/restore of vector registers. - kAttrAlignedVecSR = 0x40000000u, - //! FuncFrame is finalized and can be used by PEI. - kAttrIsFinalized = 0x80000000u - }; + //! \} + + //! \name Members + //! \{ //! Function attributes. - uint32_t _attributes; + FuncAttributes _attributes {}; - //! Architecture, see \ref Environment::Arch. - uint8_t _arch; + //! Target architecture. + Arch _arch {}; //! SP register ID (to access call stack and local stack). - uint8_t _spRegId; + uint8_t _spRegId = uint8_t(BaseReg::kIdBad); //! SA register ID (to access stack arguments). - uint8_t _saRegId; + uint8_t _saRegId = uint8_t(BaseReg::kIdBad); //! Red zone size (copied from CallConv). - uint8_t _redZoneSize; + uint8_t _redZoneSize = 0; //! Spill zone size (copied from CallConv). - uint8_t _spillZoneSize; + uint8_t _spillZoneSize = 0; //! Natural stack alignment (copied from CallConv). - uint8_t _naturalStackAlignment; + uint8_t _naturalStackAlignment = 0; //! Minimum stack alignment to turn on dynamic alignment. - uint8_t _minDynamicAlignment; + uint8_t _minDynamicAlignment = 0; //! Call stack alignment. - uint8_t _callStackAlignment; + uint8_t _callStackAlignment = 0; //! Local stack alignment. - uint8_t _localStackAlignment; + uint8_t _localStackAlignment = 0; //! Final stack alignment. - uint8_t _finalStackAlignment; + uint8_t _finalStackAlignment = 0; //! Adjustment of the stack before returning (X86-STDCALL). - uint16_t _calleeStackCleanup; + uint16_t _calleeStackCleanup = 0; //! Call stack size. - uint32_t _callStackSize; + uint32_t _callStackSize = 0; //! Local stack size. - uint32_t _localStackSize; + uint32_t _localStackSize = 0; //! Final stack size (sum of call stack and local stack). - uint32_t _finalStackSize; + uint32_t _finalStackSize = 0; //! Local stack offset (non-zero only if call stack is used). - uint32_t _localStackOffset; + uint32_t _localStackOffset = 0; //! Offset relative to SP that contains previous SP (before alignment). - uint32_t _daOffset; + uint32_t _daOffset = 0; //! Offset of the first stack argument relative to SP. - uint32_t _saOffsetFromSP; + uint32_t _saOffsetFromSP = 0; //! Offset of the first stack argument relative to SA (_saRegId or FP). - uint32_t _saOffsetFromSA; + uint32_t _saOffsetFromSA = 0; //! Local stack adjustment in prolog/epilog. - uint32_t _stackAdjustment; + uint32_t _stackAdjustment = 0; //! Registers that are dirty. - uint32_t _dirtyRegs[BaseReg::kGroupVirt]; + Support::Array<RegMask, Globals::kNumVirtGroups> _dirtyRegs {}; //! Registers that must be preserved (copied from CallConv). - uint32_t _preservedRegs[BaseReg::kGroupVirt]; + Support::Array<RegMask, Globals::kNumVirtGroups> _preservedRegs {}; + //! Size to save/restore per register group. + Support::Array<uint8_t, Globals::kNumVirtGroups> _saveRestoreRegSize {}; + //! Alignment of save/restore area per register group. + Support::Array<uint8_t, Globals::kNumVirtGroups> _saveRestoreAlignment {}; + + //! Stack size required to save registers with push/pop. + uint16_t _pushPopSaveSize = 0; + //! Stack size required to save extra registers that cannot use push/pop. + uint16_t _extraRegSaveSize = 0; + //! Offset where registers saved/restored via push/pop are stored + uint32_t _pushPopSaveOffset = 0; + //! Offset where extra registers that cannot use push/pop are stored. + uint32_t _extraRegSaveOffset = 0; - //! Final stack size required to save GP regs. - uint16_t _gpSaveSize; - //! Final Stack size required to save other than GP regs. - uint16_t _nonGpSaveSize; - //! Final offset where saved GP regs are stored. - uint32_t _gpSaveOffset; - //! Final offset where saved other than GP regs are stored. - uint32_t _nonGpSaveOffset; + //! \} //! \name Construction & Destruction //! \{ - inline FuncFrame() noexcept { reset(); } - inline FuncFrame(const FuncFrame& other) noexcept = default; + //! Creates a default constructed function frame, which has initialized all members to their default values. + ASMJIT_INLINE_NODEBUG FuncFrame() noexcept = default; + //! Creates a copy of `other` function frame. + ASMJIT_INLINE_NODEBUG FuncFrame(const FuncFrame& other) noexcept = default; + + //! \} + + //! \name Initialization & Reset + //! \{ + //! Initializes the function frame based on `func` detail. ASMJIT_API Error init(const FuncDetail& func) noexcept; + //! Resets the function frame into its default constructed state. + ASMJIT_INLINE_NODEBUG void reset() noexcept { *this = FuncFrame{}; } - inline void reset() noexcept { - memset(this, 0, sizeof(FuncFrame)); - _spRegId = BaseReg::kIdBad; - _saRegId = BaseReg::kIdBad; - _daOffset = kTagInvalidOffset; - } + //! \} + + //! \name Overloaded Operators + //! \{ + + //! Copy assignment - function frame is copy assignable. + ASMJIT_INLINE_NODEBUG FuncFrame& operator=(const FuncFrame& other) noexcept = default; //! \} @@ -603,93 +1144,116 @@ public: //! \{ //! Returns the target architecture of the function frame. - inline uint32_t arch() const noexcept { return _arch; } + ASMJIT_INLINE_NODEBUG Arch arch() const noexcept { return _arch; } //! Returns function frame attributes, see `Attributes`. - inline uint32_t attributes() const noexcept { return _attributes; } + ASMJIT_INLINE_NODEBUG FuncAttributes attributes() const noexcept { return _attributes; } //! Checks whether the FuncFame contains an attribute `attr`. - inline bool hasAttribute(uint32_t attr) const noexcept { return (_attributes & attr) != 0; } + ASMJIT_INLINE_NODEBUG bool hasAttribute(FuncAttributes attr) const noexcept { return Support::test(_attributes, attr); } //! Adds attributes `attrs` to the FuncFrame. - inline void addAttributes(uint32_t attrs) noexcept { _attributes |= attrs; } + ASMJIT_INLINE_NODEBUG void addAttributes(FuncAttributes attrs) noexcept { _attributes |= attrs; } //! Clears attributes `attrs` from the FrameFrame. - inline void clearAttributes(uint32_t attrs) noexcept { _attributes &= ~attrs; } + ASMJIT_INLINE_NODEBUG void clearAttributes(FuncAttributes attrs) noexcept { _attributes &= ~attrs; } //! Tests whether the function has variable number of arguments. - inline bool hasVarArgs() const noexcept { return hasAttribute(kAttrHasVarArgs); } + ASMJIT_INLINE_NODEBUG bool hasVarArgs() const noexcept { return hasAttribute(FuncAttributes::kHasVarArgs); } //! Sets the variable arguments flag. - inline void setVarArgs() noexcept { addAttributes(kAttrHasVarArgs); } + ASMJIT_INLINE_NODEBUG void setVarArgs() noexcept { addAttributes(FuncAttributes::kHasVarArgs); } //! Resets variable arguments flag. - inline void resetVarArgs() noexcept { clearAttributes(kAttrHasVarArgs); } + ASMJIT_INLINE_NODEBUG void resetVarArgs() noexcept { clearAttributes(FuncAttributes::kHasVarArgs); } //! Tests whether the function preserves frame pointer (EBP|ESP on X86). - inline bool hasPreservedFP() const noexcept { return hasAttribute(kAttrHasPreservedFP); } + ASMJIT_INLINE_NODEBUG bool hasPreservedFP() const noexcept { return hasAttribute(FuncAttributes::kHasPreservedFP); } //! Enables preserved frame pointer. - inline void setPreservedFP() noexcept { addAttributes(kAttrHasPreservedFP); } + ASMJIT_INLINE_NODEBUG void setPreservedFP() noexcept { addAttributes(FuncAttributes::kHasPreservedFP); } //! Disables preserved frame pointer. - inline void resetPreservedFP() noexcept { clearAttributes(kAttrHasPreservedFP); } + ASMJIT_INLINE_NODEBUG void resetPreservedFP() noexcept { clearAttributes(FuncAttributes::kHasPreservedFP); } //! Tests whether the function calls other functions. - inline bool hasFuncCalls() const noexcept { return hasAttribute(kAttrHasFuncCalls); } - //! Sets `kFlagHasCalls` to true. - inline void setFuncCalls() noexcept { addAttributes(kAttrHasFuncCalls); } - //! Sets `kFlagHasCalls` to false. - inline void resetFuncCalls() noexcept { clearAttributes(kAttrHasFuncCalls); } - - //! Tests whether the function contains AVX cleanup - 'vzeroupper' instruction in epilog. - inline bool hasAvxCleanup() const noexcept { return hasAttribute(kAttrX86AvxCleanup); } - //! Enables AVX cleanup. - inline void setAvxCleanup() noexcept { addAttributes(kAttrX86AvxCleanup); } - //! Disables AVX cleanup. - inline void resetAvxCleanup() noexcept { clearAttributes(kAttrX86AvxCleanup); } + ASMJIT_INLINE_NODEBUG bool hasFuncCalls() const noexcept { return hasAttribute(FuncAttributes::kHasFuncCalls); } + //! Sets `FuncAttributes::kHasFuncCalls` to true. + ASMJIT_INLINE_NODEBUG void setFuncCalls() noexcept { addAttributes(FuncAttributes::kHasFuncCalls); } + //! Sets `FuncAttributes::kHasFuncCalls` to false. + ASMJIT_INLINE_NODEBUG void resetFuncCalls() noexcept { clearAttributes(FuncAttributes::kHasFuncCalls); } + + //! Tests whether the function uses indirect branch protection, see \ref FuncAttributes::kIndirectBranchProtection. + ASMJIT_INLINE_NODEBUG bool hasIndirectBranchProtection() const noexcept { return hasAttribute(FuncAttributes::kIndirectBranchProtection); } + //! Enabled indirect branch protection (sets `FuncAttributes::kIndirectBranchProtection` attribute to true). + ASMJIT_INLINE_NODEBUG void setIndirectBranchProtection() noexcept { addAttributes(FuncAttributes::kIndirectBranchProtection); } + //! Disables indirect branch protection (sets `FuncAttributes::kIndirectBranchProtection` attribute to false). + ASMJIT_INLINE_NODEBUG void resetIndirectBranchProtection() noexcept { clearAttributes(FuncAttributes::kIndirectBranchProtection); } + + //! Tests whether the function has AVX enabled. + ASMJIT_INLINE_NODEBUG bool isAvxEnabled() const noexcept { return hasAttribute(FuncAttributes::kX86_AVXEnabled); } + //! Enables AVX use. + ASMJIT_INLINE_NODEBUG void setAvxEnabled() noexcept { addAttributes(FuncAttributes::kX86_AVXEnabled); } + //! Disables AVX use. + ASMJIT_INLINE_NODEBUG void resetAvxEnabled() noexcept { clearAttributes(FuncAttributes::kX86_AVXEnabled); } + + //! Tests whether the function has AVX-512 enabled. + ASMJIT_INLINE_NODEBUG bool isAvx512Enabled() const noexcept { return hasAttribute(FuncAttributes::kX86_AVX512Enabled); } + //! Enables AVX-512 use. + ASMJIT_INLINE_NODEBUG void setAvx512Enabled() noexcept { addAttributes(FuncAttributes::kX86_AVX512Enabled); } + //! Disables AVX-512 use. + ASMJIT_INLINE_NODEBUG void resetAvx512Enabled() noexcept { clearAttributes(FuncAttributes::kX86_AVX512Enabled); } + + //! Tests whether the function has MMX cleanup - 'emms' instruction in epilog. + ASMJIT_INLINE_NODEBUG bool hasMmxCleanup() const noexcept { return hasAttribute(FuncAttributes::kX86_MMXCleanup); } + //! Enables MMX cleanup. + ASMJIT_INLINE_NODEBUG void setMmxCleanup() noexcept { addAttributes(FuncAttributes::kX86_MMXCleanup); } + //! Disables MMX cleanup. + ASMJIT_INLINE_NODEBUG void resetMmxCleanup() noexcept { clearAttributes(FuncAttributes::kX86_MMXCleanup); } - //! Tests whether the function contains AVX cleanup - 'vzeroupper' instruction in epilog. - inline bool isAvxEnabled() const noexcept { return hasAttribute(kAttrX86AvxEnabled); } + //! Tests whether the function has AVX cleanup - 'vzeroupper' instruction in epilog. + ASMJIT_INLINE_NODEBUG bool hasAvxCleanup() const noexcept { return hasAttribute(FuncAttributes::kX86_AVXCleanup); } //! Enables AVX cleanup. - inline void setAvxEnabled() noexcept { addAttributes(kAttrX86AvxEnabled); } + ASMJIT_INLINE_NODEBUG void setAvxCleanup() noexcept { addAttributes(FuncAttributes::kX86_AVXCleanup); } //! Disables AVX cleanup. - inline void resetAvxEnabled() noexcept { clearAttributes(kAttrX86AvxEnabled); } - - //! Tests whether the function contains MMX cleanup - 'emms' instruction in epilog. - inline bool hasMmxCleanup() const noexcept { return hasAttribute(kAttrX86MmxCleanup); } - //! Enables MMX cleanup. - inline void setMmxCleanup() noexcept { addAttributes(kAttrX86MmxCleanup); } - //! Disables MMX cleanup. - inline void resetMmxCleanup() noexcept { clearAttributes(kAttrX86MmxCleanup); } + ASMJIT_INLINE_NODEBUG void resetAvxCleanup() noexcept { clearAttributes(FuncAttributes::kX86_AVXCleanup); } //! Tests whether the function uses call stack. - inline bool hasCallStack() const noexcept { return _callStackSize != 0; } + ASMJIT_INLINE_NODEBUG bool hasCallStack() const noexcept { return _callStackSize != 0; } //! Tests whether the function uses local stack. - inline bool hasLocalStack() const noexcept { return _localStackSize != 0; } + ASMJIT_INLINE_NODEBUG bool hasLocalStack() const noexcept { return _localStackSize != 0; } //! Tests whether vector registers can be saved and restored by using aligned reads and writes. - inline bool hasAlignedVecSR() const noexcept { return hasAttribute(kAttrAlignedVecSR); } + ASMJIT_INLINE_NODEBUG bool hasAlignedVecSR() const noexcept { return hasAttribute(FuncAttributes::kAlignedVecSR); } //! Tests whether the function has to align stack dynamically. - inline bool hasDynamicAlignment() const noexcept { return _finalStackAlignment >= _minDynamicAlignment; } + ASMJIT_INLINE_NODEBUG bool hasDynamicAlignment() const noexcept { return _finalStackAlignment >= _minDynamicAlignment; } //! Tests whether the calling convention specifies 'RedZone'. - inline bool hasRedZone() const noexcept { return _redZoneSize != 0; } + ASMJIT_INLINE_NODEBUG bool hasRedZone() const noexcept { return _redZoneSize != 0; } //! Tests whether the calling convention specifies 'SpillZone'. - inline bool hasSpillZone() const noexcept { return _spillZoneSize != 0; } + ASMJIT_INLINE_NODEBUG bool hasSpillZone() const noexcept { return _spillZoneSize != 0; } //! Returns the size of 'RedZone'. - inline uint32_t redZoneSize() const noexcept { return _redZoneSize; } + ASMJIT_INLINE_NODEBUG uint32_t redZoneSize() const noexcept { return _redZoneSize; } //! Returns the size of 'SpillZone'. - inline uint32_t spillZoneSize() const noexcept { return _spillZoneSize; } + ASMJIT_INLINE_NODEBUG uint32_t spillZoneSize() const noexcept { return _spillZoneSize; } + + //! Resets the size of red zone, which would disable it entirely. + //! + //! \note Red zone is currently only used by an AMD64 SystemV calling convention, which expects 128 + //! bytes of stack to be accessible below stack pointer. These bytes are then accessible within the + //! function and Compiler can use this space as a spill area. However, sometimes it's better to + //! disallow the use of red zone in case that a user wants to use this stack for a custom purpose. + ASMJIT_INLINE_NODEBUG void resetRedZone() noexcept { _redZoneSize = 0; } + //! Returns natural stack alignment (guaranteed stack alignment upon entry). - inline uint32_t naturalStackAlignment() const noexcept { return _naturalStackAlignment; } + ASMJIT_INLINE_NODEBUG uint32_t naturalStackAlignment() const noexcept { return _naturalStackAlignment; } //! Returns natural stack alignment (guaranteed stack alignment upon entry). - inline uint32_t minDynamicAlignment() const noexcept { return _minDynamicAlignment; } + ASMJIT_INLINE_NODEBUG uint32_t minDynamicAlignment() const noexcept { return _minDynamicAlignment; } //! Tests whether the callee must adjust SP before returning (X86-STDCALL only) - inline bool hasCalleeStackCleanup() const noexcept { return _calleeStackCleanup != 0; } - //! Returns home many bytes of the stack the the callee must adjust before returning (X86-STDCALL only) - inline uint32_t calleeStackCleanup() const noexcept { return _calleeStackCleanup; } + ASMJIT_INLINE_NODEBUG bool hasCalleeStackCleanup() const noexcept { return _calleeStackCleanup != 0; } + //! Returns home many bytes of the stack the callee must adjust before returning (X86-STDCALL only) + ASMJIT_INLINE_NODEBUG uint32_t calleeStackCleanup() const noexcept { return _calleeStackCleanup; } //! Returns call stack alignment. - inline uint32_t callStackAlignment() const noexcept { return _callStackAlignment; } + ASMJIT_INLINE_NODEBUG uint32_t callStackAlignment() const noexcept { return _callStackAlignment; } //! Returns local stack alignment. - inline uint32_t localStackAlignment() const noexcept { return _localStackAlignment; } + ASMJIT_INLINE_NODEBUG uint32_t localStackAlignment() const noexcept { return _localStackAlignment; } //! Returns final stack alignment (the maximum value of call, local, and natural stack alignments). - inline uint32_t finalStackAlignment() const noexcept { return _finalStackAlignment; } + ASMJIT_INLINE_NODEBUG uint32_t finalStackAlignment() const noexcept { return _finalStackAlignment; } //! Sets call stack alignment. //! @@ -724,61 +1288,59 @@ public: } //! Returns call stack size. - inline uint32_t callStackSize() const noexcept { return _callStackSize; } + ASMJIT_INLINE_NODEBUG uint32_t callStackSize() const noexcept { return _callStackSize; } //! Returns local stack size. - inline uint32_t localStackSize() const noexcept { return _localStackSize; } + ASMJIT_INLINE_NODEBUG uint32_t localStackSize() const noexcept { return _localStackSize; } //! Sets call stack size. - inline void setCallStackSize(uint32_t size) noexcept { _callStackSize = size; } + ASMJIT_INLINE_NODEBUG void setCallStackSize(uint32_t size) noexcept { _callStackSize = size; } //! Sets local stack size. - inline void setLocalStackSize(uint32_t size) noexcept { _localStackSize = size; } + ASMJIT_INLINE_NODEBUG void setLocalStackSize(uint32_t size) noexcept { _localStackSize = size; } //! Combines call stack size with `size`, updating it to the greater value. - inline void updateCallStackSize(uint32_t size) noexcept { _callStackSize = Support::max(_callStackSize, size); } + ASMJIT_INLINE_NODEBUG void updateCallStackSize(uint32_t size) noexcept { _callStackSize = Support::max(_callStackSize, size); } //! Combines local stack size with `size`, updating it to the greater value. - inline void updateLocalStackSize(uint32_t size) noexcept { _localStackSize = Support::max(_localStackSize, size); } + ASMJIT_INLINE_NODEBUG void updateLocalStackSize(uint32_t size) noexcept { _localStackSize = Support::max(_localStackSize, size); } //! Returns final stack size (only valid after the FuncFrame is finalized). - inline uint32_t finalStackSize() const noexcept { return _finalStackSize; } + ASMJIT_INLINE_NODEBUG uint32_t finalStackSize() const noexcept { return _finalStackSize; } //! Returns an offset to access the local stack (non-zero only if call stack is used). - inline uint32_t localStackOffset() const noexcept { return _localStackOffset; } + ASMJIT_INLINE_NODEBUG uint32_t localStackOffset() const noexcept { return _localStackOffset; } //! Tests whether the function prolog/epilog requires a memory slot for storing unaligned SP. - inline bool hasDAOffset() const noexcept { return _daOffset != kTagInvalidOffset; } + ASMJIT_INLINE_NODEBUG bool hasDAOffset() const noexcept { return _daOffset != kTagInvalidOffset; } //! Returns a memory offset used to store DA (dynamic alignment) slot (relative to SP). - inline uint32_t daOffset() const noexcept { return _daOffset; } + ASMJIT_INLINE_NODEBUG uint32_t daOffset() const noexcept { return _daOffset; } - inline uint32_t saOffset(uint32_t regId) const noexcept { + ASMJIT_INLINE_NODEBUG uint32_t saOffset(uint32_t regId) const noexcept { return regId == _spRegId ? saOffsetFromSP() : saOffsetFromSA(); } - inline uint32_t saOffsetFromSP() const noexcept { return _saOffsetFromSP; } - inline uint32_t saOffsetFromSA() const noexcept { return _saOffsetFromSA; } + ASMJIT_INLINE_NODEBUG uint32_t saOffsetFromSP() const noexcept { return _saOffsetFromSP; } + ASMJIT_INLINE_NODEBUG uint32_t saOffsetFromSA() const noexcept { return _saOffsetFromSA; } - //! Returns mask of registers of the given register `group` that are modified - //! by the function. The engine would then calculate which registers must be - //! saved & restored by the function by using the data provided by the calling - //! convention. - inline uint32_t dirtyRegs(uint32_t group) const noexcept { - ASMJIT_ASSERT(group < BaseReg::kGroupVirt); + //! Returns mask of registers of the given register `group` that are modified by the function. The engine would + //! then calculate which registers must be saved & restored by the function by using the data provided by the + //! calling convention. + inline RegMask dirtyRegs(RegGroup group) const noexcept { + ASMJIT_ASSERT(group <= RegGroup::kMaxVirt); return _dirtyRegs[group]; } //! Sets which registers (as a mask) are modified by the function. //! - //! \remarks Please note that this will completely overwrite the existing - //! register mask, use `addDirtyRegs()` to modify the existing register - //! mask. - inline void setDirtyRegs(uint32_t group, uint32_t regs) noexcept { - ASMJIT_ASSERT(group < BaseReg::kGroupVirt); + //! \remarks Please note that this will completely overwrite the existing register mask, use `addDirtyRegs()` + //! to modify the existing register mask. + inline void setDirtyRegs(RegGroup group, RegMask regs) noexcept { + ASMJIT_ASSERT(group <= RegGroup::kMaxVirt); _dirtyRegs[group] = regs; } //! Adds which registers (as a mask) are modified by the function. - inline void addDirtyRegs(uint32_t group, uint32_t regs) noexcept { - ASMJIT_ASSERT(group < BaseReg::kGroupVirt); + inline void addDirtyRegs(RegGroup group, RegMask regs) noexcept { + ASMJIT_ASSERT(group <= RegGroup::kMaxVirt); _dirtyRegs[group] |= regs; } @@ -790,67 +1352,83 @@ public: //! \overload template<typename... Args> - ASMJIT_INLINE void addDirtyRegs(const BaseReg& reg, Args&&... args) noexcept { + inline void addDirtyRegs(const BaseReg& reg, Args&&... args) noexcept { addDirtyRegs(reg); addDirtyRegs(std::forward<Args>(args)...); } - inline void setAllDirty() noexcept { + //! A helper function to set all registers from all register groups dirty. + //! + //! \note This should not be used in general as it's the most pessimistic case. However, it can be used for testing + //! or in cases in which all registers are considered clobbered. + ASMJIT_INLINE_NODEBUG void setAllDirty() noexcept { for (size_t i = 0; i < ASMJIT_ARRAY_SIZE(_dirtyRegs); i++) _dirtyRegs[i] = 0xFFFFFFFFu; } - inline void setAllDirty(uint32_t group) noexcept { - ASMJIT_ASSERT(group < BaseReg::kGroupVirt); + //! A helper function to set all registers from the given register `group` dirty. + inline void setAllDirty(RegGroup group) noexcept { + ASMJIT_ASSERT(group <= RegGroup::kMaxVirt); _dirtyRegs[group] = 0xFFFFFFFFu; } - //! Returns a calculated mask of registers of the given `group` that will be - //! saved and restored in the function's prolog and epilog, respectively. The - //! register mask is calculated from both `dirtyRegs` (provided by user) and + //! Returns a calculated mask of registers of the given `group` that will be saved and restored in the function's + //! prolog and epilog, respectively. The register mask is calculated from both `dirtyRegs` (provided by user) and //! `preservedMask` (provided by the calling convention). - inline uint32_t savedRegs(uint32_t group) const noexcept { - ASMJIT_ASSERT(group < BaseReg::kGroupVirt); + inline RegMask savedRegs(RegGroup group) const noexcept { + ASMJIT_ASSERT(group <= RegGroup::kMaxVirt); return _dirtyRegs[group] & _preservedRegs[group]; } //! Returns the mask of preserved registers of the given register `group`. //! - //! Preserved registers are those that must survive the function call - //! unmodified. The function can only modify preserved registers it they - //! are saved and restored in funciton's prolog and epilog, respectively. - inline uint32_t preservedRegs(uint32_t group) const noexcept { - ASMJIT_ASSERT(group < BaseReg::kGroupVirt); + //! Preserved registers are those that must survive the function call unmodified. The function can only modify + //! preserved registers it they are saved and restored in function's prolog and epilog, respectively. + inline RegMask preservedRegs(RegGroup group) const noexcept { + ASMJIT_ASSERT(group <= RegGroup::kMaxVirt); return _preservedRegs[group]; } - inline bool hasSARegId() const noexcept { return _saRegId != BaseReg::kIdBad; } - inline uint32_t saRegId() const noexcept { return _saRegId; } - inline void setSARegId(uint32_t regId) { _saRegId = uint8_t(regId); } - inline void resetSARegId() { setSARegId(BaseReg::kIdBad); } + //! Returns the size of a save-restore are for the required register `group`. + inline uint32_t saveRestoreRegSize(RegGroup group) const noexcept { + ASMJIT_ASSERT(group <= RegGroup::kMaxVirt); + return _saveRestoreRegSize[group]; + } + + inline uint32_t saveRestoreAlignment(RegGroup group) const noexcept { + ASMJIT_ASSERT(group <= RegGroup::kMaxVirt); + return _saveRestoreAlignment[group]; + } - //! Returns stack size required to save GP registers. - inline uint32_t gpSaveSize() const noexcept { return _gpSaveSize; } - //! Returns stack size required to save other than GP registers (MM, XMM|YMM|ZMM, K, VFP, etc...). - inline uint32_t nonGpSaveSize() const noexcept { return _nonGpSaveSize; } + ASMJIT_INLINE_NODEBUG bool hasSARegId() const noexcept { return _saRegId != BaseReg::kIdBad; } + ASMJIT_INLINE_NODEBUG uint32_t saRegId() const noexcept { return _saRegId; } + ASMJIT_INLINE_NODEBUG void setSARegId(uint32_t regId) { _saRegId = uint8_t(regId); } + ASMJIT_INLINE_NODEBUG void resetSARegId() { setSARegId(BaseReg::kIdBad); } - //! Returns an offset to the stack where general purpose registers are saved. - inline uint32_t gpSaveOffset() const noexcept { return _gpSaveOffset; } - //! Returns an offset to the stack where other than GP registers are saved. - inline uint32_t nonGpSaveOffset() const noexcept { return _nonGpSaveOffset; } + //! Returns stack size required to save/restore registers via push/pop. + ASMJIT_INLINE_NODEBUG uint32_t pushPopSaveSize() const noexcept { return _pushPopSaveSize; } + //! Returns an offset to the stack where registers are saved via push/pop. + ASMJIT_INLINE_NODEBUG uint32_t pushPopSaveOffset() const noexcept { return _pushPopSaveOffset; } + + //! Returns stack size required to save/restore extra registers that don't use push/pop/ + //! + //! \note On X86 this covers all registers except GP registers, on other architectures it can be always + //! zero (for example AArch64 saves all registers via push/pop like instructions, so this would be zero). + ASMJIT_INLINE_NODEBUG uint32_t extraRegSaveSize() const noexcept { return _extraRegSaveSize; } + //! Returns an offset to the stack where extra registers are saved. + ASMJIT_INLINE_NODEBUG uint32_t extraRegSaveOffset() const noexcept { return _extraRegSaveOffset; } //! Tests whether the functions contains stack adjustment. - inline bool hasStackAdjustment() const noexcept { return _stackAdjustment != 0; } + ASMJIT_INLINE_NODEBUG bool hasStackAdjustment() const noexcept { return _stackAdjustment != 0; } //! Returns function's stack adjustment used in function's prolog and epilog. //! - //! If the returned value is zero it means that the stack is not adjusted. - //! This can mean both that the stack is not used and/or the stack is only - //! adjusted by instructions that pust/pop registers into/from stack. - inline uint32_t stackAdjustment() const noexcept { return _stackAdjustment; } + //! If the returned value is zero it means that the stack is not adjusted. This can mean both that the stack + //! is not used and/or the stack is only adjusted by instructions that pust/pop registers into/from stack. + ASMJIT_INLINE_NODEBUG uint32_t stackAdjustment() const noexcept { return _stackAdjustment; } //! \} - //! \name Finaliztion + //! \name Finalization //! \{ ASMJIT_API Error finalize() noexcept; @@ -858,96 +1436,139 @@ public: //! \} }; -// ============================================================================ -// [asmjit::FuncArgsAssignment] -// ============================================================================ - -//! A helper class that can be used to assign a physical register for each -//! function argument. Use with `BaseEmitter::emitArgsAssignment()`. +//! A helper class that can be used to assign a physical register for each function argument. Use with +//! `BaseEmitter::emitArgsAssignment()`. class FuncArgsAssignment { public: + //! \name Members + //! \{ + //! Function detail. - const FuncDetail* _funcDetail; + const FuncDetail* _funcDetail {}; //! Register that can be used to access arguments passed by stack. - uint8_t _saRegId; + uint8_t _saRegId = uint8_t(BaseReg::kIdBad); //! Reserved for future use. - uint8_t _reserved[3]; + uint8_t _reserved[3] {}; //! Mapping of each function argument. - FuncValue _args[kFuncArgCountLoHi]; + FuncValuePack _argPacks[Globals::kMaxFuncArgs] {}; + + //! \} //! \name Construction & Destruction //! \{ - inline explicit FuncArgsAssignment(const FuncDetail* fd = nullptr) noexcept { reset(fd); } + //! Creates either a default initialized `FuncArgsAssignment` or to assignment that links to `fd`, if non-null. + ASMJIT_INLINE_NODEBUG explicit FuncArgsAssignment(const FuncDetail* fd = nullptr) noexcept { reset(fd); } - inline FuncArgsAssignment(const FuncArgsAssignment& other) noexcept { - memcpy(this, &other, sizeof(*this)); - } + //! Copy constructor. + ASMJIT_INLINE_NODEBUG FuncArgsAssignment(const FuncArgsAssignment& other) noexcept = default; + //! Resets this `FuncArgsAssignment` to either default constructed state or to assignment that links to `fd`, + //! if non-null. inline void reset(const FuncDetail* fd = nullptr) noexcept { _funcDetail = fd; _saRegId = uint8_t(BaseReg::kIdBad); memset(_reserved, 0, sizeof(_reserved)); - memset(_args, 0, sizeof(_args)); + memset(_argPacks, 0, sizeof(_argPacks)); } //! \} + //! \name Overloaded Operators + //! \{ + + //! Copy assignment. + ASMJIT_INLINE_NODEBUG FuncArgsAssignment& operator=(const FuncArgsAssignment& other) noexcept = default; + + //! \} + //! \name Accessors //! \{ - inline const FuncDetail* funcDetail() const noexcept { return _funcDetail; } - inline void setFuncDetail(const FuncDetail* fd) noexcept { _funcDetail = fd; } + //! Returns the associated \ref FuncDetail of this `FuncArgsAssignment`. + ASMJIT_INLINE_NODEBUG const FuncDetail* funcDetail() const noexcept { return _funcDetail; } + //! Associates \ref FuncDetails with this `FuncArgsAssignment`. + ASMJIT_INLINE_NODEBUG void setFuncDetail(const FuncDetail* fd) noexcept { _funcDetail = fd; } + + ASMJIT_INLINE_NODEBUG bool hasSARegId() const noexcept { return _saRegId != BaseReg::kIdBad; } + ASMJIT_INLINE_NODEBUG uint32_t saRegId() const noexcept { return _saRegId; } + ASMJIT_INLINE_NODEBUG void setSARegId(uint32_t regId) { _saRegId = uint8_t(regId); } + ASMJIT_INLINE_NODEBUG void resetSARegId() { _saRegId = uint8_t(BaseReg::kIdBad); } + + //! Returns assigned argument at `argIndex` and `valueIndex`. + //! + //! \note `argIndex` refers to he function argument and `valueIndex` refers to a value pack (in case multiple + //! values are passed as a single argument). + inline FuncValue& arg(size_t argIndex, size_t valueIndex) noexcept { + ASMJIT_ASSERT(argIndex < ASMJIT_ARRAY_SIZE(_argPacks)); + return _argPacks[argIndex][valueIndex]; + } + //! \overload + inline const FuncValue& arg(size_t argIndex, size_t valueIndex) const noexcept { + ASMJIT_ASSERT(argIndex < ASMJIT_ARRAY_SIZE(_argPacks)); + return _argPacks[argIndex][valueIndex]; + } - inline bool hasSARegId() const noexcept { return _saRegId != BaseReg::kIdBad; } - inline uint32_t saRegId() const noexcept { return _saRegId; } - inline void setSARegId(uint32_t regId) { _saRegId = uint8_t(regId); } - inline void resetSARegId() { _saRegId = uint8_t(BaseReg::kIdBad); } + //! Tests whether argument at `argIndex` and `valueIndex` has been assigned. + inline bool isAssigned(size_t argIndex, size_t valueIndex) const noexcept { + ASMJIT_ASSERT(argIndex < ASMJIT_ARRAY_SIZE(_argPacks)); + return _argPacks[argIndex][valueIndex].isAssigned(); + } - inline FuncValue& arg(uint32_t index) noexcept { - ASMJIT_ASSERT(index < ASMJIT_ARRAY_SIZE(_args)); - return _args[index]; + //! Assigns register at `argIndex` and value index of 0 to `reg` and an optional `typeId`. + inline void assignReg(size_t argIndex, const BaseReg& reg, TypeId typeId = TypeId::kVoid) noexcept { + ASMJIT_ASSERT(argIndex < ASMJIT_ARRAY_SIZE(_argPacks)); + ASMJIT_ASSERT(reg.isPhysReg()); + _argPacks[argIndex][0].initReg(reg.type(), reg.id(), typeId); } - inline const FuncValue& arg(uint32_t index) const noexcept { - ASMJIT_ASSERT(index < ASMJIT_ARRAY_SIZE(_args)); - return _args[index]; + + //! Assigns register at `argIndex` and value index of 0 to `regType`, `regId`, and an optional `typeId`. + inline void assignReg(size_t argIndex, RegType regType, uint32_t regId, TypeId typeId = TypeId::kVoid) noexcept { + ASMJIT_ASSERT(argIndex < ASMJIT_ARRAY_SIZE(_argPacks)); + _argPacks[argIndex][0].initReg(regType, regId, typeId); } - inline bool isAssigned(uint32_t argIndex) const noexcept { - ASMJIT_ASSERT(argIndex < ASMJIT_ARRAY_SIZE(_args)); - return _args[argIndex].isAssigned(); + //! Assigns stack at `argIndex` and value index of 0 to `offset` and an optional `typeId`. + inline void assignStack(size_t argIndex, int32_t offset, TypeId typeId = TypeId::kVoid) noexcept { + ASMJIT_ASSERT(argIndex < ASMJIT_ARRAY_SIZE(_argPacks)); + _argPacks[argIndex][0].initStack(offset, typeId); } - inline void assignReg(uint32_t argIndex, const BaseReg& reg, uint32_t typeId = Type::kIdVoid) noexcept { - ASMJIT_ASSERT(argIndex < ASMJIT_ARRAY_SIZE(_args)); + //! Assigns register at `argIndex` and `valueIndex` to `reg` and an optional `typeId`. + inline void assignRegInPack(size_t argIndex, size_t valueIndex, const BaseReg& reg, TypeId typeId = TypeId::kVoid) noexcept { + ASMJIT_ASSERT(argIndex < ASMJIT_ARRAY_SIZE(_argPacks)); ASMJIT_ASSERT(reg.isPhysReg()); - _args[argIndex].initReg(reg.type(), reg.id(), typeId); + _argPacks[argIndex][valueIndex].initReg(reg.type(), reg.id(), typeId); } - inline void assignReg(uint32_t argIndex, uint32_t regType, uint32_t regId, uint32_t typeId = Type::kIdVoid) noexcept { - ASMJIT_ASSERT(argIndex < ASMJIT_ARRAY_SIZE(_args)); - _args[argIndex].initReg(regType, regId, typeId); + //! Assigns register at `argIndex` and `valueIndex` to `regType`, `regId`, and an optional `typeId`. + inline void assignRegInPack(size_t argIndex, size_t valueIndex, RegType regType, uint32_t regId, TypeId typeId = TypeId::kVoid) noexcept { + ASMJIT_ASSERT(argIndex < ASMJIT_ARRAY_SIZE(_argPacks)); + _argPacks[argIndex][valueIndex].initReg(regType, regId, typeId); } - inline void assignStack(uint32_t argIndex, int32_t offset, uint32_t typeId = Type::kIdVoid) { - ASMJIT_ASSERT(argIndex < ASMJIT_ARRAY_SIZE(_args)); - _args[argIndex].initStack(offset, typeId); + //! Assigns stack at `argIndex` and `valueIndex` to `offset` and an optional `typeId`. + inline void assignStackInPack(size_t argIndex, size_t valueIndex, int32_t offset, TypeId typeId = TypeId::kVoid) noexcept { + ASMJIT_ASSERT(argIndex < ASMJIT_ARRAY_SIZE(_argPacks)); + _argPacks[argIndex][valueIndex].initStack(offset, typeId); } - // NOTE: All `assignAll()` methods are shortcuts to assign all arguments at - // once, however, since registers are passed all at once these initializers - // don't provide any way to pass TypeId and/or to keep any argument between + // NOTE: All `assignAll()` methods are shortcuts to assign all arguments at once, however, since registers are + // passed all at once these initializers don't provide any way to pass TypeId and/or to keep any argument between // the arguments passed unassigned. - inline void _assignAllInternal(uint32_t argIndex, const BaseReg& reg) noexcept { + inline void _assignAllInternal(size_t argIndex, const BaseReg& reg) noexcept { assignReg(argIndex, reg); } template<typename... Args> - inline void _assignAllInternal(uint32_t argIndex, const BaseReg& reg, Args&&... args) noexcept { + inline void _assignAllInternal(size_t argIndex, const BaseReg& reg, Args&&... args) noexcept { assignReg(argIndex, reg); _assignAllInternal(argIndex + 1, std::forward<Args>(args)...); } + //! Assigns all argument at once. + //! + //! \note This function can be only used if the arguments don't contain value packs (multiple values per argument). template<typename... Args> inline void assignAll(Args&&... args) noexcept { _assignAllInternal(0, std::forward<Args>(args)...); @@ -960,9 +1581,8 @@ public: //! Update `FuncFrame` based on function's arguments assignment. //! - //! \note You MUST call this in orher to use `BaseEmitter::emitArgsAssignment()`, - //! otherwise the FuncFrame would not contain the information necessary to - //! assign all arguments into the registers and/or stack specified. + //! \note This function must be called in order to use `BaseEmitter::emitArgsAssignment()`, otherwise the \ref FuncFrame + //! would not contain the information necessary to assign all arguments into the registers and/or stack specified. ASMJIT_API Error updateFuncFrame(FuncFrame& frame) const noexcept; //! \} @@ -973,4 +1593,3 @@ public: ASMJIT_END_NAMESPACE #endif // ASMJIT_CORE_FUNC_H_INCLUDED - |