Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Arm64 vector ABI #23675

Merged
merged 7 commits into from
Apr 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/jit/assertionprop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ void Compiler::optAddCopies()
// We only add copies for non temp local variables
// that have a single def and that can possibly be enregistered

if (varDsc->lvIsTemp || !varDsc->lvSingleDef || !varTypeCanReg(typ))
if (varDsc->lvIsTemp || !varDsc->lvSingleDef || !varTypeIsEnregisterable(typ))
{
continue;
}
Expand Down
4 changes: 2 additions & 2 deletions src/jit/codegenarm64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2023,10 +2023,10 @@ void CodeGen::genSimpleReturn(GenTree* treeNode)
GenTree* op1 = treeNode->gtGetOp1();
var_types targetType = treeNode->TypeGet();

assert(!isStructReturn(treeNode));
assert(targetType != TYP_STRUCT);
assert(targetType != TYP_VOID);

regNumber retReg = varTypeIsFloating(treeNode) ? REG_FLOATRET : REG_INTRET;
regNumber retReg = varTypeUsesFloatArgReg(treeNode) ? REG_FLOATRET : REG_INTRET;

bool movRequired = (op1->gtRegNum != retReg);

Expand Down
15 changes: 12 additions & 3 deletions src/jit/codegenarmarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2355,7 +2355,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call)
}
else
{
assert(!varTypeIsStruct(call));
assert(call->gtType != TYP_STRUCT);

if (call->gtType == TYP_REF)
{
Expand Down Expand Up @@ -2509,9 +2509,13 @@ void CodeGen::genCallInstruction(GenTreeCall* call)
// TCB in REG_PINVOKE_TCB. fgMorphCall() sets the correct argument registers.
returnReg = REG_PINVOKE_TCB;
}
else if (compiler->opts.compUseSoftFP)
{
returnReg = REG_INTRET;
}
else
#endif // _TARGET_ARM_
if (varTypeIsFloating(returnType) && !compiler->opts.compUseSoftFP)
if (varTypeUsesFloatArgReg(returnType))
{
returnReg = REG_FLOATRET;
}
Expand Down Expand Up @@ -3501,8 +3505,13 @@ bool CodeGen::isStructReturn(GenTree* treeNode)
// For the GT_RET_FILT, the return is always
// a bool or a void, for the end of a finally block.
noway_assert(treeNode->OperGet() == GT_RETURN || treeNode->OperGet() == GT_RETFILT);
var_types returnType = treeNode->TypeGet();

return varTypeIsStruct(treeNode);
#ifdef _TARGET_ARM64_
return varTypeIsStruct(returnType) && (compiler->info.compRetNativeType == TYP_STRUCT);
#else
return varTypeIsStruct(returnType);
#endif
}

//------------------------------------------------------------------------
Expand Down
26 changes: 19 additions & 7 deletions src/jit/codegencommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3305,7 +3305,7 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
{
// A struct might be passed partially in XMM register for System V calls.
// So a single arg might use both register files.
if (isFloatRegType(regType) != doingFloat)
if (emitter::isFloatReg(varDsc->lvArgReg) != doingFloat)
{
continue;
}
Expand Down Expand Up @@ -10158,7 +10158,11 @@ bool Compiler::IsMultiRegReturnedType(CORINFO_CLASS_HANDLE hClass)
structPassingKind howToReturnStruct;
var_types returnType = getReturnTypeForStruct(hClass, &howToReturnStruct);

#ifdef _TARGET_ARM64_
return (varTypeIsStruct(returnType) && (howToReturnStruct != SPK_PrimitiveType));
#else
AndyAyersMS marked this conversation as resolved.
Show resolved Hide resolved
return (varTypeIsStruct(returnType));
#endif
}

//----------------------------------------------
Expand All @@ -10167,11 +10171,7 @@ bool Compiler::IsMultiRegReturnedType(CORINFO_CLASS_HANDLE hClass)

bool Compiler::IsHfa(CORINFO_CLASS_HANDLE hClass)
{
#ifdef FEATURE_HFA
return varTypeIsFloating(GetHfaType(hClass));
#else
return false;
#endif
return varTypeIsValidHfaType(GetHfaType(hClass));
}

bool Compiler::IsHfa(GenTree* tree)
Expand Down Expand Up @@ -10204,7 +10204,19 @@ var_types Compiler::GetHfaType(CORINFO_CLASS_HANDLE hClass)
{
#ifdef FEATURE_HFA
CorInfoType corType = info.compCompHnd->getHFAType(hClass);
if (corType != CORINFO_TYPE_UNDEF)
#ifdef _TARGET_ARM64_
if (corType == CORINFO_TYPE_VALUECLASS)
{
// This is a vector type.
// HVAs are only supported on ARM64, and only for homogeneous aggregates of 8 or 16 byte vectors.
// For 8-byte vectors corType will be returned as CORINFO_TYPE_DOUBLE.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't classified independently as a short vector?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No; for ABI purposes it is the same (an 8-byte vector is passed in the lower 8-bytes of the register, just as for double)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this something that might be useful to differentiate at some point in the future?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be, but it will come at a (probably not large) price in complexity and change to the JIT/EE interface (unless the need for it is internal to the JIT), so I'd propose we wait until such time as we need it.

result = TYP_SIMD16;
// This type may not appear elsewhere, but it will occupy a floating point register.
compFloatingPointUsed = true;
}
else
#endif // _TARGET_ARM64_
if (corType != CORINFO_TYPE_UNDEF)
{
result = JITtype2varType(corType);
}
Expand Down
4 changes: 2 additions & 2 deletions src/jit/codegenxarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1133,9 +1133,9 @@ void CodeGen::genStructReturn(GenTree* treeNode)
unsigned regCount = retTypeDesc.GetReturnRegCount();
assert(regCount == MAX_RET_REG_COUNT);

if (varTypeIsEnregisterableStruct(op1))
if (varTypeIsEnregisterable(op1))
{
// Right now the only enregistrable structs supported are SIMD vector types.
// Right now the only enregisterable structs supported are SIMD vector types.
assert(varTypeIsSIMD(op1));
assert(op1->isUsedFromReg());

Expand Down
187 changes: 82 additions & 105 deletions src/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -573,8 +573,8 @@ bool Compiler::isSingleFloat32Struct(CORINFO_CLASS_HANDLE clsHnd)
// of size 'structSize'.
// We examine 'clsHnd' to check the GC layout of the struct and
// return TYP_REF for structs that simply wrap an object.
// If the struct is a one element HFA, we will return the
// proper floating point type.
// If the struct is a one element HFA/HVA, we will return the
// proper floating point or vector type.
//
// Arguments:
// structSize - the size of the struct type, cannot be zero
Expand All @@ -592,13 +592,64 @@ bool Compiler::isSingleFloat32Struct(CORINFO_CLASS_HANDLE clsHnd)
// same way as any other 8-byte struct
// For ARM32 if we have an HFA struct that wraps a 64-bit double
// we will return TYP_DOUBLE.
// For vector calling conventions, a vector is considered a "primitive"
// type, as it is passed in a single register.
//
var_types Compiler::getPrimitiveTypeForStruct(unsigned structSize, CORINFO_CLASS_HANDLE clsHnd, bool isVarArg)
{
assert(structSize != 0);

var_types useType;
var_types useType = TYP_UNKNOWN;

// Start by determining if we have an HFA/HVA with a single element.
#ifdef FEATURE_HFA
#if defined(_TARGET_WINDOWS_) && defined(_TARGET_ARM64_)
// Arm64 Windows VarArg methods arguments will not classify HFA types, they will need to be treated
// as if they are not HFA types.
if (!isVarArg)
#endif // defined(_TARGET_WINDOWS_) && defined(_TARGET_ARM64_)
{
switch (structSize)
{
case 4:
case 8:
#ifdef _TARGET_ARM64_
case 16:
#endif // _TARGET_ARM64_
{
var_types hfaType;
#ifdef ARM_SOFTFP
// For ARM_SOFTFP, HFA is unsupported so we need to check in another way.
// This matters only for size-4 struct because bigger structs would be processed with RetBuf.
if (isSingleFloat32Struct(clsHnd))
{
hfaType = TYP_FLOAT;
}
#else // !ARM_SOFTFP
hfaType = GetHfaType(clsHnd);
#endif // ARM_SOFTFP
// We're only interested in the case where the struct size is equal to the size of the hfaType.
if (varTypeIsValidHfaType(hfaType))
{
if (genTypeSize(hfaType) == structSize)
{
useType = hfaType;
}
else
{
return TYP_UNKNOWN;
}
}
}
}
if (useType != TYP_UNKNOWN)
{
return useType;
}
}
#endif // FEATURE_HFA

// Now deal with non-HFA/HVA structs.
switch (structSize)
{
case 1:
Expand All @@ -618,15 +669,8 @@ var_types Compiler::getPrimitiveTypeForStruct(unsigned structSize, CORINFO_CLASS

#ifdef _TARGET_64BIT_
case 4:
if (IsHfa(clsHnd))
{
// A structSize of 4 with IsHfa, it must be an HFA of one float
useType = TYP_FLOAT;
}
else
{
useType = TYP_INT;
}
// We dealt with the one-float HFA above. All other 4-byte structs are handled as INT.
useType = TYP_INT;
break;

#if !defined(_TARGET_XARCH_) || defined(UNIX_AMD64_ABI)
Expand All @@ -640,86 +684,13 @@ var_types Compiler::getPrimitiveTypeForStruct(unsigned structSize, CORINFO_CLASS
#endif // _TARGET_64BIT_

case TARGET_POINTER_SIZE:
#ifdef ARM_SOFTFP
// For ARM_SOFTFP, HFA is unsupported so we need to check in another way
// This matters only for size-4 struct cause bigger structs would be processed with RetBuf
if (isSingleFloat32Struct(clsHnd))
#else // !ARM_SOFTFP
if (IsHfa(clsHnd)
#if defined(_TARGET_WINDOWS_) && defined(_TARGET_ARM64_)
// Arm64 Windows VarArg methods arguments will not
// classify HFA types, they will need to be treated
// as if they are not HFA types.
&& !isVarArg
#endif // defined(_TARGET_WINDOWS_) && defined(_TARGET_ARM64_)
)
#endif // ARM_SOFTFP
{
#ifdef _TARGET_64BIT_
var_types hfaType = GetHfaType(clsHnd);

// A structSize of 8 with IsHfa, we have two possiblities:
// An HFA of one double or an HFA of two floats
//
// Check and exclude the case of an HFA of two floats
if (hfaType == TYP_DOUBLE)
{
// We have an HFA of one double
useType = TYP_DOUBLE;
}
else
{
assert(hfaType == TYP_FLOAT);

// We have an HFA of two floats
// This should be passed or returned in two FP registers
useType = TYP_UNKNOWN;
}
#else // a 32BIT target
// A structSize of 4 with IsHfa, it must be an HFA of one float
useType = TYP_FLOAT;
#endif // _TARGET_64BIT_
}
else
{
BYTE gcPtr = 0;
// Check if this pointer-sized struct is wrapping a GC object
info.compCompHnd->getClassGClayout(clsHnd, &gcPtr);
useType = getJitGCType(gcPtr);
}
break;

#ifdef _TARGET_ARM_
case 8:
if (IsHfa(clsHnd))
{
var_types hfaType = GetHfaType(clsHnd);

// A structSize of 8 with IsHfa, we have two possiblities:
// An HFA of one double or an HFA of two floats
//
// Check and exclude the case of an HFA of two floats
if (hfaType == TYP_DOUBLE)
{
// We have an HFA of one double
useType = TYP_DOUBLE;
}
else
{
assert(hfaType == TYP_FLOAT);

// We have an HFA of two floats
// This should be passed or returned in two FP registers
useType = TYP_UNKNOWN;
}
}
else
{
// We don't have an HFA
useType = TYP_UNKNOWN;
}
break;
#endif // _TARGET_ARM_
{
BYTE gcPtr = 0;
// Check if this pointer-sized struct is wrapping a GC object
info.compCompHnd->getClassGClayout(clsHnd, &gcPtr);
useType = getJitGCType(gcPtr);
}
break;

default:
useType = TYP_UNKNOWN;
Expand Down Expand Up @@ -802,11 +773,11 @@ var_types Compiler::getArgTypeForStruct(CORINFO_CLASS_HANDLE clsHnd,
else
#endif // UNIX_AMD64_ABI

// The largest primitive type is 8 bytes (TYP_DOUBLE)
// The largest arg passed in a single register is MAX_PASS_SINGLEREG_BYTES,
// so we can skip calling getPrimitiveTypeForStruct when we
// have a struct that is larger than that.
//
if (structSize <= sizeof(double))
if (structSize <= MAX_PASS_SINGLEREG_BYTES)
{
// We set the "primitive" useType based upon the structSize
// and also examine the clsHnd to see if it is an HFA of count one
Expand All @@ -829,14 +800,21 @@ var_types Compiler::getArgTypeForStruct(CORINFO_CLASS_HANDLE clsHnd,
//
if (structSize <= MAX_PASS_MULTIREG_BYTES)
{
// Structs that are HFA's are passed by value in multiple registers
if (IsHfa(clsHnd)
// Structs that are HFA/HVA's are passed by value in multiple registers.
// Arm64 Windows VarArg methods arguments will not classify HFA/HVA types, they will need to be treated
// as if they are not HFA/HVA types.
var_types hfaType;
#if defined(_TARGET_WINDOWS_) && defined(_TARGET_ARM64_)
&& !isVarArg // Arm64 Windows VarArg methods arguments will not
// classify HFA types, they will need to be treated
// as if they are not HFA types.
#endif // defined(_TARGET_WINDOWS_) && defined(_TARGET_ARM64_)
)
if (isVarArg)
{
hfaType = TYP_UNDEF;
}
else
#endif // defined(_TARGET_WINDOWS_) && defined(_TARGET_ARM64_)
{
hfaType = GetHfaType(clsHnd);
}
if (varTypeIsValidHfaType(hfaType))
{
// HFA's of count one should have been handled by getPrimitiveTypeForStruct
assert(GetHfaCount(clsHnd) >= 2);
Expand All @@ -851,7 +829,6 @@ var_types Compiler::getArgTypeForStruct(CORINFO_CLASS_HANDLE clsHnd,
{

#ifdef UNIX_AMD64_ABI

// The case of (structDesc.eightByteCount == 1) should have already been handled
if ((structDesc.eightByteCount > 1) || !structDesc.passedInRegisters)
{
Expand Down Expand Up @@ -1035,10 +1012,10 @@ var_types Compiler::getReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd,
// Check for cases where a small struct is returned in a register
// via a primitive type.
//
// The largest primitive type is 8 bytes (TYP_DOUBLE)
// The largest "primitive type" is MAX_PASS_SINGLEREG_BYTES
// so we can skip calling getPrimitiveTypeForStruct when we
// have a struct that is larger than that.
if (canReturnInRegister && (useType == TYP_UNKNOWN) && (structSize <= sizeof(double)))
if (canReturnInRegister && (useType == TYP_UNKNOWN) && (structSize <= MAX_PASS_SINGLEREG_BYTES))
{
// We set the "primitive" useType based upon the structSize
// and also examine the clsHnd to see if it is an HFA of count one
Expand Down Expand Up @@ -1070,7 +1047,7 @@ var_types Compiler::getReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd,
// because when HFA are enabled, normally we would use two FP registers to pass or return it
//
// But if we don't have support for multiple register return types, we have to change this.
// Since we what we have an 8-byte struct (float + float) we change useType to TYP_I_IMPL
// Since what we have is an 8-byte struct (float + float) we change useType to TYP_I_IMPL
// so that the struct is returned instead using an 8-byte integer register.
//
if ((FEATURE_MULTIREG_RET == 0) && (useType == TYP_UNKNOWN) && (structSize == (2 * sizeof(float))) && IsHfa(clsHnd))
Expand Down
Loading