Skip to content

Commit

Permalink
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.
This requires that the JIT recognize vector types even during crossgen,
so that the ABI is supported consistently.

Fix #16022
  • Loading branch information
CarolEidt committed Apr 13, 2019
1 parent 0fed62e commit bc14374
Show file tree
Hide file tree
Showing 34 changed files with 773 additions and 420 deletions.
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
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 sizes of 8 or 16 bytes.
// For 8-byte vectors corType will be returned as CORINFO_TYPE_DOUBLE.
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'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 bc14374

Please sign in to comment.