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

JIT: fix bug returning small structs on linux x64 #18563

Merged
merged 7 commits into from
Jun 28, 2018
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
50 changes: 35 additions & 15 deletions src/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1004,37 +1004,57 @@ var_types Compiler::getReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd,
assert(structSize > 0);

#ifdef UNIX_AMD64_ABI

// An 8-byte struct may need to be returned in a floating point register
// So we always consult the struct "Classifier" routine
//
SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR structDesc;
eeGetSystemVAmd64PassStructInRegisterDescriptor(clsHnd, &structDesc);

// If we have one eightByteCount then we can set 'useType' based on that
if (structDesc.eightByteCount == 1)
{
// Set 'useType' to the type of the first eightbyte item
useType = GetEightByteType(structDesc, 0);
assert(structDesc.passedInRegisters == true);
assert(structSize <= sizeof(double));

if (structDesc.eightByteClassifications[0] == SystemVClassificationTypeSSE)
{
// If this is returned as a floating type, use that.
// Otherwise, leave as TYP_UNKONWN and we'll sort things out below.
useType = GetEightByteType(structDesc, 0);
howToReturnStruct = SPK_PrimitiveType;
}
}

#else // not UNIX_AMD64
#endif // UNIX_AMD64_ABI

// 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)
// so we can skip calling getPrimitiveTypeForStruct when we
// have a struct that is larger than that.
//
if (structSize <= sizeof(double))
if ((useType == TYP_UNKNOWN) && (structSize <= sizeof(double)))
{
// We set the "primitive" useType based upon the structSize
// and also examine the clsHnd to see if it is an HFA of count one
//
// The ABI for struct returns in varArg methods, is same as the normal case, so pass false for isVararg
// The ABI for struct returns in varArg methods, is same as the normal case,
// so pass false for isVararg
useType = getPrimitiveTypeForStruct(structSize, clsHnd, /*isVararg=*/false);
}

#endif // UNIX_AMD64_ABI
if (useType != TYP_UNKNOWN)
{
if (structSize == genTypeSize(useType))
{
// Currently: 1, 2, 4, or 8 byte structs
howToReturnStruct = SPK_PrimitiveType;
}
else
{
// Currently: 3, 5, 6, or 7 byte structs
assert(structSize < genTypeSize(useType));
howToReturnStruct = SPK_EnclosingType;
}
}
}

#ifdef _TARGET_64BIT_
// Note this handles an odd case when FEATURE_MULTIREG_RET is disabled and HFAs are enabled
Expand All @@ -1048,16 +1068,16 @@ var_types Compiler::getReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd,
//
if ((FEATURE_MULTIREG_RET == 0) && (useType == TYP_UNKNOWN) && (structSize == (2 * sizeof(float))) && IsHfa(clsHnd))
{
useType = TYP_I_IMPL;
useType = TYP_I_IMPL;
howToReturnStruct = SPK_PrimitiveType;
}
#endif

// Did we change this struct type into a simple "primitive" type?
//
if (useType != TYP_UNKNOWN)
{
// Yes, we should use the "primitive" type in 'useType'
howToReturnStruct = SPK_PrimitiveType;
// If so, we should have already set howToReturnStruct, too.
assert(howToReturnStruct != SPK_Unknown);
}
else // We can't replace the struct with a "primitive" type
{
Expand Down
5 changes: 5 additions & 0 deletions src/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -1691,6 +1691,8 @@ class Compiler
GenTree* impAssignMultiRegTypeToVar(GenTree* op, CORINFO_CLASS_HANDLE hClass);
#endif // FEATURE_MULTIREG_RET

GenTree* impAssignSmallStructTypeToVar(GenTree* op, CORINFO_CLASS_HANDLE hClass);

#ifdef ARM_SOFTFP
bool isSingleFloat32Struct(CORINFO_CLASS_HANDLE hClass);
#endif // ARM_SOFTFP
Expand Down Expand Up @@ -4223,6 +4225,9 @@ class Compiler
{
SPK_Unknown, // Invalid value, never returned
SPK_PrimitiveType, // The struct is passed/returned using a primitive type.
SPK_EnclosingType, // Like SPK_Primitive type, but used for return types that
// require a primitive type temp that is larger than the struct size.
// Currently used for structs of size 3, 5, 6, or 7 bytes.
SPK_ByValue, // The struct is passed/returned by value (using the ABI rules)
// for ARM64 and UNIX_X64 in multiple registers. (when all of the
// parameters registers are used, then the stack will be used)
Expand Down
112 changes: 96 additions & 16 deletions src/jit/flowgraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22324,33 +22324,113 @@ Compiler::fgWalkResult Compiler::fgUpdateInlineReturnExpressionPlaceHolder(GenTr
}
}

#if FEATURE_MULTIREG_RET

// Did we record a struct return class handle above?
// If an inline was rejected and the call returns a struct, we may
// have deferred some work when importing call for cases where the
// struct is returned in register(s).
//
// See the bail-out clauses in impFixupCallStructReturn for inline
// candidates.
//
// Do the deferred work now.
if (retClsHnd != NO_CLASS_HANDLE)
{
// Is this a type that is returned in multiple registers?
// if so we need to force into into a form we accept.
// i.e. LclVar = call()
//
if (comp->IsMultiRegReturnedType(retClsHnd))
structPassingKind howToReturnStruct;
var_types returnType = comp->getReturnTypeForStruct(retClsHnd, &howToReturnStruct);
GenTree* parent = data->parent;

switch (howToReturnStruct)
{
GenTree* parent = data->parent;
// See assert below, we only look one level above for an asg parent.
if (parent->gtOper == GT_ASG)

#if FEATURE_MULTIREG_RET

// Is this a type that is returned in multiple registers
// or a via a primitve type that is larger than the struct type?
// if so we need to force into into a form we accept.
// i.e. LclVar = call()
case SPK_ByValue:
case SPK_ByValueAsHfa:
{
// Either lhs is a call V05 = call(); or lhs is addr, and asg becomes a copyBlk.
comp->fgAttachStructInlineeToAsg(parent, tree, retClsHnd);
// See assert below, we only look one level above for an asg parent.
if (parent->gtOper == GT_ASG)
{
// Either lhs is a call V05 = call(); or lhs is addr, and asg becomes a copyBlk.
comp->fgAttachStructInlineeToAsg(parent, tree, retClsHnd);
}
else
{
// Just assign the inlinee to a variable to keep it simple.
tree->ReplaceWith(comp->fgAssignStructInlineeToVar(tree, retClsHnd), comp);
}
}
else
break;

#endif // FEATURE_MULTIREG_RET

case SPK_EnclosingType:
{
// Just assign the inlinee to a variable to keep it simple.
tree->ReplaceWith(comp->fgAssignStructInlineeToVar(tree, retClsHnd), comp);
// For enclosing type returns, we must return the call value to a temp since
// the return type is larger than the struct type.
if (!tree->IsCall())
{
break;
}

GenTreeCall* call = tree->AsCall();

assert(call->gtReturnType == TYP_STRUCT);

if (call->gtReturnType != TYP_STRUCT)
{
break;
}

JITDUMP("\nCall returns small struct via enclosing type, retyping. Before:\n");
DISPTREE(call);

// Create new struct typed temp for return value
const unsigned tmpNum =
comp->lvaGrabTemp(true DEBUGARG("small struct return temp for rejected inline"));
comp->lvaSetStruct(tmpNum, retClsHnd, false);
GenTree* assign = comp->gtNewTempAssign(tmpNum, call);

// Modify assign tree and call return types to the primitive return type
call->gtReturnType = returnType;
call->gtType = returnType;
assign->gtType = returnType;

// Modify the temp reference in the assign as a primitive reference via GT_LCL_FLD
GenTree* tempAsPrimitive = assign->gtOp.gtOp1;
assert(tempAsPrimitive->gtOper == GT_LCL_VAR);
tempAsPrimitive->gtType = returnType;
tempAsPrimitive->ChangeOper(GT_LCL_FLD);

// Return temp as value of call tree via comma
GenTree* tempAsStruct = comp->gtNewLclvNode(tmpNum, TYP_STRUCT);
GenTree* comma = comp->gtNewOperNode(GT_COMMA, TYP_STRUCT, assign, tempAsStruct);
parent->ReplaceOperand(pTree, comma);

JITDUMP("\nAfter:\n");
DISPTREE(comma);
}
break;

case SPK_PrimitiveType:
// We should have already retyped the call as a primitive type
// when we first imported the call
break;

case SPK_ByReference:
// We should have already added the return buffer
// when we first imported the call
break;

default:
noway_assert(!"Unexpected struct passing kind");
break;
}
}

#if FEATURE_MULTIREG_RET
#if defined(DEBUG)

// Make sure we don't have a tree like so: V05 = (, , , retExpr);
Expand Down
4 changes: 4 additions & 0 deletions src/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17679,6 +17679,10 @@ void ReturnTypeDesc::InitializeStructReturnType(Compiler* comp, CORINFO_CLASS_HA

switch (howToReturnStruct)
{
case Compiler::SPK_EnclosingType:
m_isEnclosingType = true;
__fallthrough;

case Compiler::SPK_PrimitiveType:
{
assert(returnType != TYP_UNKNOWN);
Expand Down
9 changes: 9 additions & 0 deletions src/jit/gentree.h
Original file line number Diff line number Diff line change
Expand Up @@ -3089,6 +3089,7 @@ struct ReturnTypeDesc
{
private:
var_types m_regType[MAX_RET_REG_COUNT];
bool m_isEnclosingType;

#ifdef DEBUG
bool m_inited;
Expand All @@ -3114,6 +3115,7 @@ struct ReturnTypeDesc
{
m_regType[i] = TYP_UNKNOWN;
}
m_isEnclosingType = false;
#ifdef DEBUG
m_inited = false;
#endif
Expand Down Expand Up @@ -3206,6 +3208,13 @@ struct ReturnTypeDesc
return result;
}

// True if this value is returned in integer register
// that is larger than the type itself.
bool IsEnclosingType() const
{
return m_isEnclosingType;
}

// Get ith ABI return register
regNumber GetABIReturnReg(unsigned idx);

Expand Down
Loading