summaryrefslogtreecommitdiffstatshomepage
path: root/3rdparty/asmjit/src/asmjit/x86/x86func.cpp
diff options
context:
space:
mode:
Diffstat (limited to '3rdparty/asmjit/src/asmjit/x86/x86func.cpp')
-rw-r--r--3rdparty/asmjit/src/asmjit/x86/x86func.cpp503
1 files changed, 503 insertions, 0 deletions
diff --git a/3rdparty/asmjit/src/asmjit/x86/x86func.cpp b/3rdparty/asmjit/src/asmjit/x86/x86func.cpp
new file mode 100644
index 00000000000..bba9eef148c
--- /dev/null
+++ b/3rdparty/asmjit/src/asmjit/x86/x86func.cpp
@@ -0,0 +1,503 @@
+// This file is part of AsmJit project <https://asmjit.com>
+//
+// See asmjit.h or LICENSE.md for license and copyright information
+// SPDX-License-Identifier: Zlib
+
+#include "../core/api-build_p.h"
+#if !defined(ASMJIT_NO_X86)
+
+#include "../x86/x86func_p.h"
+#include "../x86/x86emithelper_p.h"
+#include "../x86/x86operand.h"
+
+ASMJIT_BEGIN_SUB_NAMESPACE(x86)
+
+namespace FuncInternal {
+
+static inline bool shouldThreatAsCDeclIn64BitMode(CallConvId ccId) noexcept {
+ return ccId == CallConvId::kCDecl ||
+ ccId == CallConvId::kStdCall ||
+ ccId == CallConvId::kThisCall ||
+ ccId == CallConvId::kFastCall ||
+ ccId == CallConvId::kRegParm1 ||
+ ccId == CallConvId::kRegParm2 ||
+ ccId == CallConvId::kRegParm3;
+}
+
+ASMJIT_FAVOR_SIZE Error initCallConv(CallConv& cc, CallConvId ccId, const Environment& environment) noexcept {
+ constexpr uint32_t kZax = Gp::kIdAx;
+ constexpr uint32_t kZbx = Gp::kIdBx;
+ constexpr uint32_t kZcx = Gp::kIdCx;
+ constexpr uint32_t kZdx = Gp::kIdDx;
+ constexpr uint32_t kZsp = Gp::kIdSp;
+ constexpr uint32_t kZbp = Gp::kIdBp;
+ constexpr uint32_t kZsi = Gp::kIdSi;
+ constexpr uint32_t kZdi = Gp::kIdDi;
+
+ bool winABI = environment.isPlatformWindows() || environment.isMSVC();
+
+ cc.setArch(environment.arch());
+ cc.setSaveRestoreRegSize(RegGroup::kVec, 16);
+ cc.setSaveRestoreRegSize(RegGroup::kX86_MM, 8);
+ cc.setSaveRestoreRegSize(RegGroup::kX86_K, 8);
+ cc.setSaveRestoreAlignment(RegGroup::kVec, 16);
+ cc.setSaveRestoreAlignment(RegGroup::kX86_MM, 8);
+ cc.setSaveRestoreAlignment(RegGroup::kX86_K, 8);
+
+ if (environment.is32Bit()) {
+ bool isStandardCallConv = true;
+
+ cc.setSaveRestoreRegSize(RegGroup::kGp, 4);
+ cc.setSaveRestoreAlignment(RegGroup::kGp, 4);
+
+ cc.setPreservedRegs(RegGroup::kGp, Support::bitMask(Gp::kIdBx, Gp::kIdSp, Gp::kIdBp, Gp::kIdSi, Gp::kIdDi));
+ cc.setNaturalStackAlignment(4);
+
+ switch (ccId) {
+ case CallConvId::kCDecl:
+ break;
+
+ case CallConvId::kStdCall:
+ cc.setFlags(CallConvFlags::kCalleePopsStack);
+ break;
+
+ case CallConvId::kFastCall:
+ cc.setFlags(CallConvFlags::kCalleePopsStack);
+ cc.setPassedOrder(RegGroup::kGp, kZcx, kZdx);
+ break;
+
+ case CallConvId::kVectorCall:
+ cc.setFlags(CallConvFlags::kCalleePopsStack);
+ cc.setPassedOrder(RegGroup::kGp, kZcx, kZdx);
+ cc.setPassedOrder(RegGroup::kVec, 0, 1, 2, 3, 4, 5);
+ break;
+
+ case CallConvId::kThisCall:
+ // NOTE: Even MINGW (starting with GCC 4.7.0) now uses __thiscall on MS Windows, so we won't bail to any
+ // other calling convention if __thiscall was specified.
+ if (winABI) {
+ cc.setFlags(CallConvFlags::kCalleePopsStack);
+ cc.setPassedOrder(RegGroup::kGp, kZcx);
+ }
+ else {
+ ccId = CallConvId::kCDecl;
+ }
+ break;
+
+ case CallConvId::kRegParm1:
+ cc.setPassedOrder(RegGroup::kGp, kZax);
+ break;
+
+ case CallConvId::kRegParm2:
+ cc.setPassedOrder(RegGroup::kGp, kZax, kZdx);
+ break;
+
+ case CallConvId::kRegParm3:
+ cc.setPassedOrder(RegGroup::kGp, kZax, kZdx, kZcx);
+ break;
+
+ case CallConvId::kLightCall2:
+ case CallConvId::kLightCall3:
+ case CallConvId::kLightCall4: {
+ uint32_t n = uint32_t(ccId) - uint32_t(CallConvId::kLightCall2) + 2;
+
+ cc.setFlags(CallConvFlags::kPassFloatsByVec);
+ cc.setPassedOrder(RegGroup::kGp, kZax, kZdx, kZcx, kZsi, kZdi);
+ cc.setPassedOrder(RegGroup::kVec, 0, 1, 2, 3, 4, 5, 6, 7);
+ cc.setPassedOrder(RegGroup::kX86_K, 0, 1, 2, 3, 4, 5, 6, 7);
+ cc.setPassedOrder(RegGroup::kX86_MM, 0, 1, 2, 3, 4, 5, 6, 7);
+ cc.setPreservedRegs(RegGroup::kGp, Support::lsbMask<uint32_t>(8));
+ cc.setPreservedRegs(RegGroup::kVec, Support::lsbMask<uint32_t>(8) & ~Support::lsbMask<uint32_t>(n));
+
+ cc.setNaturalStackAlignment(16);
+ isStandardCallConv = false;
+ break;
+ }
+
+ default:
+ return DebugUtils::errored(kErrorInvalidArgument);
+ }
+
+ if (isStandardCallConv) {
+ // MMX arguments is something where compiler vendors disagree. For example GCC and MSVC would pass first three
+ // via registers and the rest via stack, however Clang passes all via stack. Returning MMX registers is even
+ // more fun, where GCC uses MM0, but Clang uses EAX:EDX pair. I'm not sure it's something we should be worried
+ // about as MMX is deprecated anyway.
+ cc.setPassedOrder(RegGroup::kX86_MM, 0, 1, 2);
+
+ // Vector arguments (XMM|YMM|ZMM) are passed via registers. However, if the function is variadic then they have
+ // to be passed via stack.
+ cc.setPassedOrder(RegGroup::kVec, 0, 1, 2);
+
+ // Functions with variable arguments always use stack for MM and vector arguments.
+ cc.addFlags(CallConvFlags::kPassVecByStackIfVA);
+ }
+
+ if (ccId == CallConvId::kCDecl) {
+ cc.addFlags(CallConvFlags::kVarArgCompatible);
+ }
+ }
+ else {
+ cc.setSaveRestoreRegSize(RegGroup::kGp, 8);
+ cc.setSaveRestoreAlignment(RegGroup::kGp, 8);
+
+ // Preprocess the calling convention into a common id as many conventions are normally ignored even by C/C++
+ // compilers and treated as `__cdecl`.
+ if (shouldThreatAsCDeclIn64BitMode(ccId))
+ ccId = winABI ? CallConvId::kX64Windows : CallConvId::kX64SystemV;
+
+ switch (ccId) {
+ case CallConvId::kX64SystemV: {
+ cc.setFlags(CallConvFlags::kPassFloatsByVec |
+ CallConvFlags::kPassMmxByXmm |
+ CallConvFlags::kVarArgCompatible);
+ cc.setNaturalStackAlignment(16);
+ cc.setRedZoneSize(128);
+ cc.setPassedOrder(RegGroup::kGp, kZdi, kZsi, kZdx, kZcx, 8, 9);
+ cc.setPassedOrder(RegGroup::kVec, 0, 1, 2, 3, 4, 5, 6, 7);
+ cc.setPreservedRegs(RegGroup::kGp, Support::bitMask(kZbx, kZsp, kZbp, 12, 13, 14, 15));
+ break;
+ }
+
+ case CallConvId::kX64Windows: {
+ cc.setStrategy(CallConvStrategy::kX64Windows);
+ cc.setFlags(CallConvFlags::kPassFloatsByVec |
+ CallConvFlags::kIndirectVecArgs |
+ CallConvFlags::kPassMmxByGp |
+ CallConvFlags::kVarArgCompatible);
+ cc.setNaturalStackAlignment(16);
+ // Maximum 4 arguments in registers, each adds 8 bytes to the spill zone.
+ cc.setSpillZoneSize(4 * 8);
+ cc.setPassedOrder(RegGroup::kGp, kZcx, kZdx, 8, 9);
+ cc.setPassedOrder(RegGroup::kVec, 0, 1, 2, 3);
+ cc.setPreservedRegs(RegGroup::kGp, Support::bitMask(kZbx, kZsp, kZbp, kZsi, kZdi, 12, 13, 14, 15));
+ cc.setPreservedRegs(RegGroup::kVec, Support::bitMask(6, 7, 8, 9, 10, 11, 12, 13, 14, 15));
+ break;
+ }
+
+ case CallConvId::kVectorCall: {
+ cc.setStrategy(CallConvStrategy::kX64VectorCall);
+ cc.setFlags(CallConvFlags::kPassFloatsByVec |
+ CallConvFlags::kPassMmxByGp );
+ cc.setNaturalStackAlignment(16);
+ // Maximum 6 arguments in registers, each adds 8 bytes to the spill zone.
+ cc.setSpillZoneSize(6 * 8);
+ cc.setPassedOrder(RegGroup::kGp, kZcx, kZdx, 8, 9);
+ cc.setPassedOrder(RegGroup::kVec, 0, 1, 2, 3, 4, 5);
+ cc.setPreservedRegs(RegGroup::kGp, Support::bitMask(kZbx, kZsp, kZbp, kZsi, kZdi, 12, 13, 14, 15));
+ cc.setPreservedRegs(RegGroup::kVec, Support::bitMask(6, 7, 8, 9, 10, 11, 12, 13, 14, 15));
+ break;
+ }
+
+ case CallConvId::kLightCall2:
+ case CallConvId::kLightCall3:
+ case CallConvId::kLightCall4: {
+ uint32_t n = uint32_t(ccId) - uint32_t(CallConvId::kLightCall2) + 2;
+
+ cc.setFlags(CallConvFlags::kPassFloatsByVec);
+ cc.setNaturalStackAlignment(16);
+ cc.setPassedOrder(RegGroup::kGp, kZax, kZdx, kZcx, kZsi, kZdi);
+ cc.setPassedOrder(RegGroup::kVec, 0, 1, 2, 3, 4, 5, 6, 7);
+ cc.setPassedOrder(RegGroup::kX86_K, 0, 1, 2, 3, 4, 5, 6, 7);
+ cc.setPassedOrder(RegGroup::kX86_MM, 0, 1, 2, 3, 4, 5, 6, 7);
+
+ cc.setPreservedRegs(RegGroup::kGp, Support::lsbMask<uint32_t>(16));
+ cc.setPreservedRegs(RegGroup::kVec, ~Support::lsbMask<uint32_t>(n));
+ break;
+ }
+
+ default:
+ return DebugUtils::errored(kErrorInvalidArgument);
+ }
+ }
+
+ cc.setId(ccId);
+ return kErrorOk;
+}
+
+ASMJIT_FAVOR_SIZE void unpackValues(FuncDetail& func, FuncValuePack& pack) noexcept {
+ TypeId typeId = pack[0].typeId();
+ switch (typeId) {
+ case TypeId::kInt64:
+ case TypeId::kUInt64: {
+ if (Environment::is32Bit(func.callConv().arch())) {
+ // Convert a 64-bit return value to two 32-bit return values.
+ pack[0].initTypeId(TypeId::kUInt32);
+ pack[1].initTypeId(TypeId(uint32_t(typeId) - 2));
+ break;
+ }
+ break;
+ }
+
+ default: {
+ break;
+ }
+ }
+}
+
+ASMJIT_FAVOR_SIZE Error initFuncDetail(FuncDetail& func, const FuncSignature& signature, uint32_t registerSize) noexcept {
+ const CallConv& cc = func.callConv();
+ Arch arch = cc.arch();
+ uint32_t stackOffset = cc._spillZoneSize;
+ uint32_t argCount = func.argCount();
+
+ // Up to two return values can be returned in GP registers.
+ static const uint8_t gpReturnIndexes[4] = {
+ uint8_t(Gp::kIdAx),
+ uint8_t(Gp::kIdDx),
+ uint8_t(BaseReg::kIdBad),
+ uint8_t(BaseReg::kIdBad)
+ };
+
+ if (func.hasRet()) {
+ unpackValues(func, func._rets);
+ for (uint32_t valueIndex = 0; valueIndex < Globals::kMaxValuePack; valueIndex++) {
+ TypeId typeId = func._rets[valueIndex].typeId();
+
+ // Terminate at the first void type (end of the pack).
+ if (typeId == TypeId::kVoid)
+ break;
+
+ switch (typeId) {
+ case TypeId::kInt64:
+ case TypeId::kUInt64: {
+ if (gpReturnIndexes[valueIndex] != BaseReg::kIdBad)
+ func._rets[valueIndex].initReg(RegType::kX86_Gpq, gpReturnIndexes[valueIndex], typeId);
+ else
+ return DebugUtils::errored(kErrorInvalidState);
+ break;
+ }
+
+ case TypeId::kInt8:
+ case TypeId::kInt16:
+ case TypeId::kInt32: {
+ if (gpReturnIndexes[valueIndex] != BaseReg::kIdBad)
+ func._rets[valueIndex].initReg(RegType::kX86_Gpd, gpReturnIndexes[valueIndex], TypeId::kInt32);
+ else
+ return DebugUtils::errored(kErrorInvalidState);
+ break;
+ }
+
+ case TypeId::kUInt8:
+ case TypeId::kUInt16:
+ case TypeId::kUInt32: {
+ if (gpReturnIndexes[valueIndex] != BaseReg::kIdBad)
+ func._rets[valueIndex].initReg(RegType::kX86_Gpd, gpReturnIndexes[valueIndex], TypeId::kUInt32);
+ else
+ return DebugUtils::errored(kErrorInvalidState);
+ break;
+ }
+
+ case TypeId::kFloat32:
+ case TypeId::kFloat64: {
+ RegType regType = Environment::is32Bit(arch) ? RegType::kX86_St : RegType::kX86_Xmm;
+ func._rets[valueIndex].initReg(regType, valueIndex, typeId);
+ break;
+ }
+
+ case TypeId::kFloat80: {
+ // 80-bit floats are always returned by FP0.
+ func._rets[valueIndex].initReg(RegType::kX86_St, valueIndex, typeId);
+ break;
+ }
+
+ case TypeId::kMmx32:
+ case TypeId::kMmx64: {
+ // MM registers are returned through XMM (SystemV) or GPQ (Win64).
+ RegType regType = RegType::kX86_Mm;
+ uint32_t regIndex = valueIndex;
+ if (Environment::is64Bit(arch)) {
+ regType = cc.strategy() == CallConvStrategy::kDefault ? RegType::kX86_Xmm : RegType::kX86_Gpq;
+ regIndex = cc.strategy() == CallConvStrategy::kDefault ? valueIndex : gpReturnIndexes[valueIndex];
+
+ if (regIndex == BaseReg::kIdBad)
+ return DebugUtils::errored(kErrorInvalidState);
+ }
+
+ func._rets[valueIndex].initReg(regType, regIndex, typeId);
+ break;
+ }
+
+ default: {
+ func._rets[valueIndex].initReg(vecTypeIdToRegType(typeId), valueIndex, typeId);
+ break;
+ }
+ }
+ }
+ }
+
+ switch (cc.strategy()) {
+ case CallConvStrategy::kDefault: {
+ uint32_t gpzPos = 0;
+ uint32_t vecPos = 0;
+
+ for (uint32_t argIndex = 0; argIndex < argCount; argIndex++) {
+ unpackValues(func, func._args[argIndex]);
+
+ for (uint32_t valueIndex = 0; valueIndex < Globals::kMaxValuePack; valueIndex++) {
+ FuncValue& arg = func._args[argIndex][valueIndex];
+
+ // Terminate if there are no more arguments in the pack.
+ if (!arg)
+ break;
+
+ TypeId typeId = arg.typeId();
+
+ if (TypeUtils::isInt(typeId)) {
+ uint32_t regId = BaseReg::kIdBad;
+
+ if (gpzPos < CallConv::kMaxRegArgsPerGroup)
+ regId = cc._passedOrder[RegGroup::kGp].id[gpzPos];
+
+ if (regId != BaseReg::kIdBad) {
+ RegType regType = typeId <= TypeId::kUInt32 ? RegType::kX86_Gpd : RegType::kX86_Gpq;
+ arg.assignRegData(regType, regId);
+ func.addUsedRegs(RegGroup::kGp, Support::bitMask(regId));
+ gpzPos++;
+ }
+ else {
+ uint32_t size = Support::max<uint32_t>(TypeUtils::sizeOf(typeId), registerSize);
+ arg.assignStackOffset(int32_t(stackOffset));
+ stackOffset += size;
+ }
+ continue;
+ }
+
+ if (TypeUtils::isFloat(typeId) || TypeUtils::isVec(typeId)) {
+ uint32_t regId = BaseReg::kIdBad;
+
+ if (vecPos < CallConv::kMaxRegArgsPerGroup)
+ regId = cc._passedOrder[RegGroup::kVec].id[vecPos];
+
+ if (TypeUtils::isFloat(typeId)) {
+ // If this is a float, but `kFlagPassFloatsByVec` is false, we have to use stack instead. This should
+ // be only used by 32-bit calling conventions.
+ if (!cc.hasFlag(CallConvFlags::kPassFloatsByVec))
+ regId = BaseReg::kIdBad;
+ }
+ else {
+ // Pass vector registers via stack if this is a variable arguments function. This should be only used
+ // by 32-bit calling conventions.
+ if (signature.hasVarArgs() && cc.hasFlag(CallConvFlags::kPassVecByStackIfVA))
+ regId = BaseReg::kIdBad;
+ }
+
+ if (regId != BaseReg::kIdBad) {
+ arg.initTypeId(typeId);
+ arg.assignRegData(vecTypeIdToRegType(typeId), regId);
+ func.addUsedRegs(RegGroup::kVec, Support::bitMask(regId));
+ vecPos++;
+ }
+ else {
+ uint32_t size = TypeUtils::sizeOf(typeId);
+ arg.assignStackOffset(int32_t(stackOffset));
+ stackOffset += size;
+ }
+ continue;
+ }
+ }
+ }
+ break;
+ }
+
+ case CallConvStrategy::kX64Windows:
+ case CallConvStrategy::kX64VectorCall: {
+ // Both X64 and VectorCall behave similarly - arguments are indexed from left to right. The position of the
+ // argument determines in which register the argument is allocated, so it's either GP or one of XMM/YMM/ZMM
+ // registers.
+ //
+ // [ X64 ] [VecCall]
+ // Index: #0 #1 #2 #3 #4 #5
+ //
+ // GP : RCX RDX R8 R9
+ // VEC : XMM0 XMM1 XMM2 XMM3 XMM4 XMM5
+ //
+ // For example function `f(int a, double b, int c, double d)` will be:
+ //
+ // (a) (b) (c) (d)
+ // RCX XMM1 R8 XMM3
+ //
+ // Unused vector registers are used by HVA.
+ bool isVectorCall = (cc.strategy() == CallConvStrategy::kX64VectorCall);
+
+ for (uint32_t argIndex = 0; argIndex < argCount; argIndex++) {
+ unpackValues(func, func._args[argIndex]);
+
+ for (uint32_t valueIndex = 0; valueIndex < Globals::kMaxValuePack; valueIndex++) {
+ FuncValue& arg = func._args[argIndex][valueIndex];
+
+ // Terminate if there are no more arguments in the pack.
+ if (!arg)
+ break;
+
+ TypeId typeId = arg.typeId();
+ uint32_t size = TypeUtils::sizeOf(typeId);
+
+ if (TypeUtils::isInt(typeId) || TypeUtils::isMmx(typeId)) {
+ uint32_t regId = BaseReg::kIdBad;
+
+ if (argIndex < CallConv::kMaxRegArgsPerGroup)
+ regId = cc._passedOrder[RegGroup::kGp].id[argIndex];
+
+ if (regId != BaseReg::kIdBad) {
+ RegType regType = size <= 4 && !TypeUtils::isMmx(typeId) ? RegType::kX86_Gpd : RegType::kX86_Gpq;
+ arg.assignRegData(regType, regId);
+ func.addUsedRegs(RegGroup::kGp, Support::bitMask(regId));
+ }
+ else {
+ arg.assignStackOffset(int32_t(stackOffset));
+ stackOffset += 8;
+ }
+ continue;
+ }
+
+ if (TypeUtils::isFloat(typeId) || TypeUtils::isVec(typeId)) {
+ uint32_t regId = BaseReg::kIdBad;
+
+ if (argIndex < CallConv::kMaxRegArgsPerGroup)
+ regId = cc._passedOrder[RegGroup::kVec].id[argIndex];
+
+ if (regId != BaseReg::kIdBad) {
+ // X64-ABI doesn't allow vector types (XMM|YMM|ZMM) to be passed via registers, however, VectorCall
+ // was designed for that purpose.
+ if (TypeUtils::isFloat(typeId) || isVectorCall) {
+ RegType regType = vecTypeIdToRegType(typeId);
+ arg.assignRegData(regType, regId);
+ func.addUsedRegs(RegGroup::kVec, Support::bitMask(regId));
+ continue;
+ }
+ }
+
+ // Passed via stack if the argument is float/double or indirectly. The trap is - if the argument is
+ // passed indirectly, the address can be passed via register, if the argument's index has GP one.
+ if (TypeUtils::isFloat(typeId)) {
+ arg.assignStackOffset(int32_t(stackOffset));
+ }
+ else {
+ uint32_t gpRegId = cc._passedOrder[RegGroup::kGp].id[argIndex];
+ if (gpRegId != BaseReg::kIdBad)
+ arg.assignRegData(RegType::kX86_Gpq, gpRegId);
+ else
+ arg.assignStackOffset(int32_t(stackOffset));
+ arg.addFlags(FuncValue::kFlagIsIndirect);
+ }
+
+ // Always 8 bytes (float/double/pointer).
+ stackOffset += 8;
+ continue;
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ func._argStackSize = stackOffset;
+ return kErrorOk;
+}
+
+} // {FuncInternal}
+
+ASMJIT_END_SUB_NAMESPACE
+
+#endif // !ASMJIT_NO_X86