Skip to content

Commit

Permalink
[WIP] Support for Arm64 Vector ABI
Browse files Browse the repository at this point in the history
Extend HFA support to support vectors as well as floating point types.
Also, fix coreclr to preserve 128-bit argument registers.

Fix #16022
  • Loading branch information
CarolEidt committed Mar 27, 2019
1 parent 98b0e9a commit 28a5208
Show file tree
Hide file tree
Showing 31 changed files with 706 additions and 369 deletions.
4 changes: 2 additions & 2 deletions src/jit/codegenarm64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1836,10 +1836,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 = (varTypeIsFloating(treeNode) || varTypeIsSIMD(treeNode)) ? REG_FLOATRET : REG_INTRET;

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

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

if (call->gtType == TYP_REF)
{
Expand Down Expand Up @@ -2499,9 +2499,13 @@ void CodeGen::genCallInstruction(GenTreeCall* call)
// TCB in REG_PINVOKE_TCB. fgMorphCall() sets the correct argument registers.
returnReg = REG_PINVOKE_TCB;
}
else
#endif // _TARGET_ARM_
if (varTypeIsFloating(returnType) && !compiler->opts.compUseSoftFP)
#elif defined(_TARGET_ARM64_)
if (varTypeIsSIMD(returnType))
{
returnReg = REG_FLOATRET;
}
#endif // _TARGET_ARM64_
else if (varTypeIsFloating(returnType) && !compiler->opts.compUseSoftFP)
{
returnReg = REG_FLOATRET;
}
Expand Down
32 changes: 27 additions & 5 deletions src/jit/codegencommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9892,7 +9892,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
return (varTypeIsStruct(returnType));
#endif
}

//----------------------------------------------
Expand All @@ -9902,10 +9906,16 @@ bool Compiler::IsMultiRegReturnedType(CORINFO_CLASS_HANDLE hClass)
bool Compiler::IsHfa(CORINFO_CLASS_HANDLE hClass)
{
#ifdef FEATURE_HFA
return varTypeIsFloating(GetHfaType(hClass));
#else
var_types hfaType = GetHfaType(hClass);
assert((hfaType == TYP_UNDEF) || varTypeIsSIMD(hfaType) || varTypeIsFloating(hfaType));
#ifdef _TARGET_ARM64_
return (hfaType != TYP_UNDEF);
#else // !_TARGET_ARM64_
return varTypeIsFloating(hfaType);
#endif // !_TARGET_ARM64_
#else // !FEATURE_HFA
return false;
#endif
#endif // FEATURE_HFA
}

bool Compiler::IsHfa(GenTree* tree)
Expand Down Expand Up @@ -9938,7 +9948,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 sizes of 8 or 16 bytes.
// For 8-byte vectors corType will be returned as CORINFO_TYPE_DOUBLE.
result = TYP_SIMD16;
JITDUMP("Found an HVA of SIMD16\n");
// This type may not appear elsewhere, but it will occupy a floating point register.
compFloatingPointUsed = true;
}
else if (corType != CORINFO_TYPE_UNDEF)
#endif // _TARGET_ARM64_
{
result = JITtype2varType(corType);
}
Expand Down Expand Up @@ -11345,7 +11367,7 @@ void CodeGen::genReturn(GenTree* treeNode)
else
#endif // _TARGET_X86_ || _TARGET_ARM_
{
if (isStructReturn(treeNode))
if (targetType == TYP_STRUCT)
{
genStructReturn(treeNode);
}
Expand Down
190 changes: 84 additions & 106 deletions src/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -569,8 +569,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 @@ -588,13 +588,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 @@ -614,15 +665,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 @@ -636,86 +680,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 @@ -798,11 +769,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 @@ -825,14 +796,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's are passed by value in multiple registers.
// Arm64 Windows VarArg methods arguments will not classify HFA types, they will need to be treated
// as if they are not HFA 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 @@ -845,7 +823,6 @@ var_types Compiler::getArgTypeForStruct(CORINFO_CLASS_HANDLE clsHnd,
}
else // Not an HFA struct type
{

#ifdef UNIX_AMD64_ABI

// The case of (structDesc.eightByteCount == 1) should have already been handled
Expand Down Expand Up @@ -1031,10 +1008,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 @@ -1066,7 +1043,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 All @@ -1090,7 +1067,8 @@ var_types Compiler::getReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd,
if ((FEATURE_MULTIREG_RET == 1) && (structSize <= MAX_RET_MULTIREG_BYTES))
{
// Structs that are HFA's are returned in multiple registers
if (IsHfa(clsHnd))
var_types hfaType = GetHfaType(clsHnd);
if (varTypeIsValidHfaType(hfaType))
{
// HFA's of count one should have been handled by getPrimitiveTypeForStruct
assert(GetHfaCount(clsHnd) >= 2);
Expand Down
Loading

0 comments on commit 28a5208

Please sign in to comment.