in erts/emulator/asmjit/x86/x86internal.cpp [86:348]
ASMJIT_FAVOR_SIZE Error X86Internal::initFuncDetail(FuncDetail& func, const FuncSignature& signature, uint32_t registerSize) noexcept {
const CallConv& cc = func.callConv();
uint32_t 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++) {
uint32_t typeId = func._rets[valueIndex].typeId();
// Terminate at the first void type (end of the pack).
if (!typeId)
break;
switch (typeId) {
case Type::kIdI64:
case Type::kIdU64: {
if (gpReturnIndexes[valueIndex] != BaseReg::kIdBad)
func._rets[valueIndex].initReg(Reg::kTypeGpq, gpReturnIndexes[valueIndex], typeId);
else
return DebugUtils::errored(kErrorInvalidState);
break;
}
case Type::kIdI8:
case Type::kIdI16:
case Type::kIdI32: {
if (gpReturnIndexes[valueIndex] != BaseReg::kIdBad)
func._rets[valueIndex].initReg(Reg::kTypeGpd, gpReturnIndexes[valueIndex], Type::kIdI32);
else
return DebugUtils::errored(kErrorInvalidState);
break;
}
case Type::kIdU8:
case Type::kIdU16:
case Type::kIdU32: {
if (gpReturnIndexes[valueIndex] != BaseReg::kIdBad)
func._rets[valueIndex].initReg(Reg::kTypeGpd, gpReturnIndexes[valueIndex], Type::kIdU32);
else
return DebugUtils::errored(kErrorInvalidState);
break;
}
case Type::kIdF32:
case Type::kIdF64: {
uint32_t regType = Environment::is32Bit(arch) ? Reg::kTypeSt : Reg::kTypeXmm;
func._rets[valueIndex].initReg(regType, valueIndex, typeId);
break;
}
case Type::kIdF80: {
// 80-bit floats are always returned by FP0.
func._rets[valueIndex].initReg(Reg::kTypeSt, valueIndex, typeId);
break;
}
case Type::kIdMmx32:
case Type::kIdMmx64: {
// MM registers are returned through XMM (SystemV) or GPQ (Win64).
uint32_t regType = Reg::kTypeMm;
uint32_t regIndex = valueIndex;
if (Environment::is64Bit(arch)) {
regType = cc.strategy() == CallConv::kStrategyDefault ? Reg::kTypeXmm : Reg::kTypeGpq;
regIndex = cc.strategy() == CallConv::kStrategyDefault ? valueIndex : gpReturnIndexes[valueIndex];
if (regIndex == BaseReg::kIdBad)
return DebugUtils::errored(kErrorInvalidState);
}
func._rets[valueIndex].initReg(regType, regIndex, typeId);
break;
}
default: {
func._rets[valueIndex].initReg(x86VecTypeIdToRegType(typeId), valueIndex, typeId);
break;
}
}
}
}
switch (cc.strategy()) {
case CallConv::kStrategyDefault: {
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;
uint32_t typeId = arg.typeId();
if (Type::isInt(typeId)) {
uint32_t regId = BaseReg::kIdBad;
if (gpzPos < CallConv::kMaxRegArgsPerGroup)
regId = cc._passedOrder[Reg::kGroupGp].id[gpzPos];
if (regId != BaseReg::kIdBad) {
uint32_t regType = (typeId <= Type::kIdU32) ? Reg::kTypeGpd : Reg::kTypeGpq;
arg.assignRegData(regType, regId);
func.addUsedRegs(Reg::kGroupGp, Support::bitMask(regId));
gpzPos++;
}
else {
uint32_t size = Support::max<uint32_t>(Type::sizeOf(typeId), registerSize);
arg.assignStackOffset(int32_t(stackOffset));
stackOffset += size;
}
continue;
}
if (Type::isFloat(typeId) || Type::isVec(typeId)) {
uint32_t regId = BaseReg::kIdBad;
if (vecPos < CallConv::kMaxRegArgsPerGroup)
regId = cc._passedOrder[Reg::kGroupVec].id[vecPos];
if (Type::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(CallConv::kFlagPassFloatsByVec))
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(CallConv::kFlagPassVecByStackIfVA))
regId = BaseReg::kIdBad;
}
if (regId != BaseReg::kIdBad) {
arg.initTypeId(typeId);
arg.assignRegData(x86VecTypeIdToRegType(typeId), regId);
func.addUsedRegs(Reg::kGroupVec, Support::bitMask(regId));
vecPos++;
}
else {
uint32_t size = Type::sizeOf(typeId);
arg.assignStackOffset(int32_t(stackOffset));
stackOffset += size;
}
continue;
}
}
}
break;
}
case CallConv::kStrategyX64Windows:
case CallConv::kStrategyX64VectorCall: {
// 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() == CallConv::kStrategyX64VectorCall);
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;
uint32_t typeId = arg.typeId();
uint32_t size = Type::sizeOf(typeId);
if (Type::isInt(typeId) || Type::isMmx(typeId)) {
uint32_t regId = BaseReg::kIdBad;
if (argIndex < CallConv::kMaxRegArgsPerGroup)
regId = cc._passedOrder[Reg::kGroupGp].id[argIndex];
if (regId != BaseReg::kIdBad) {
uint32_t regType = (size <= 4 && !Type::isMmx(typeId)) ? Reg::kTypeGpd : Reg::kTypeGpq;
arg.assignRegData(regType, regId);
func.addUsedRegs(Reg::kGroupGp, Support::bitMask(regId));
}
else {
arg.assignStackOffset(int32_t(stackOffset));
stackOffset += 8;
}
continue;
}
if (Type::isFloat(typeId) || Type::isVec(typeId)) {
uint32_t regId = BaseReg::kIdBad;
if (argIndex < CallConv::kMaxRegArgsPerGroup)
regId = cc._passedOrder[Reg::kGroupVec].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 (Type::isFloat(typeId) || isVectorCall) {
uint32_t regType = x86VecTypeIdToRegType(typeId);
arg.assignRegData(regType, regId);
func.addUsedRegs(Reg::kGroupVec, 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 (Type::isFloat(typeId)) {
arg.assignStackOffset(int32_t(stackOffset));
}
else {
uint32_t gpRegId = cc._passedOrder[Reg::kGroupGp].id[argIndex];
if (gpRegId != BaseReg::kIdBad)
arg.assignRegData(Reg::kTypeGpq, 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;
}