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

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.

Fix #16022
  • Loading branch information
CarolEidt committed Apr 4, 2019
1 parent ea83460 commit 22909c0
Show file tree
Hide file tree
Showing 31 changed files with 722 additions and 360 deletions.
4 changes: 2 additions & 2 deletions src/jit/codegenarm64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2021,10 +2021,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
8 changes: 6 additions & 2 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
27 changes: 20 additions & 7 deletions src/jit/codegencommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10160,7 +10160,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 @@ -10169,11 +10173,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 @@ -10206,7 +10206,20 @@ 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
#endif // _TARGET_ARM64_
if (corType != CORINFO_TYPE_UNDEF)
{
result = JITtype2varType(corType);
}
Expand Down Expand Up @@ -11515,7 +11528,7 @@ void CodeGen::genReturn(GenTree* treeNode)
else
#endif // _TARGET_X86_ || _TARGET_ARM_
{
if (isStructReturn(treeNode))
if (targetType == TYP_STRUCT)
{
genStructReturn(treeNode);
}
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'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 @@ -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

0 comments on commit 22909c0

Please sign in to comment.