Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JIT: Add a uniform representation for parameter ABI information #100138

Merged
merged 9 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
JIT: Add a uniform representation for parameter ABI information
This adds a uniform representation that can represent the ABI
information for all of our targets without needing to fall back to
handling ABI specific details in all places that need to handle calling
conventions.

Currently nothing is using this information. I want to incrementally
migrate our ABI handling to use this representation. Also, there are
several potential future improvements:

- Split out ABI classification per ABI instead of keeping them all
  within the same function
- Unify `InitVarDscInfo::stackArgSize` and `InitVarDscInfo::argSize`. I
  am unsure why the latter is needed
- Remove `LclVarDsc::GetArgReg()`, `LclVarDscInfo::GetOtherArgReg()`,
  HFA related members
- Reuse the representation in `CallArgABIInformation` and unify the
  classifiers

The end goal here is rewriting `genFnPrologCalleeRegArgs` to handle
float and integer registers simultaneously, and to support some of the
registers that the Swift calling convention is using.
  • Loading branch information
jakobbotsch committed Mar 22, 2024
commit 1a5d1f77eb16c7813c50254d133a9fe43f36ca94
2 changes: 2 additions & 0 deletions src/coreclr/jit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ if(CLR_CMAKE_TARGET_WIN32)
endif(CLR_CMAKE_TARGET_WIN32)

set( JIT_SOURCES
abi.cpp
alloc.cpp
assertionprop.cpp
bitset.cpp
Expand Down Expand Up @@ -289,6 +290,7 @@ set( JIT_HEADERS
../inc/corjitflags.h
../inc/corjithost.h
_typeinfo.h
abi.h
alloc.h
arraystack.h
bitset.h
Expand Down
122 changes: 122 additions & 0 deletions src/coreclr/jit/abi.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#include "jitpch.h"
#include "abi.h"

//-----------------------------------------------------------------------------
// IsPassedInRegister:
// Check if this segment is passed in a register.
//
// Return Value:
// True if this is passed in a register.
//
bool AbiPassingSegment::IsPassedInRegister() const
{
return m_register != REG_NA;
}

//-----------------------------------------------------------------------------
// IsPassedOnStack:
// Check if this segment is passed on the stack.
//
// Return Value:
// True if this is passed on the stack.
//
bool AbiPassingSegment::IsPassedOnStack() const
{
return m_register == REG_NA;
}

//-----------------------------------------------------------------------------
// GetRegister:
// Get the register that this segment is passed in.
//
// Return Value:
// The register.
//
regNumber AbiPassingSegment::GetRegister() const
{
assert(IsPassedInRegister());
return m_register;
}

//-----------------------------------------------------------------------------
// GetStackOffset:
// Get the stack offset where this segment is passed.
//
// Return Value:
// Offset relative to the first stack argument.
//
unsigned AbiPassingSegment::GetStackOffset() const
{
assert(IsPassedOnStack());
return m_stackOffset;
}

//-----------------------------------------------------------------------------
// InRegister:
// Create an AbiPassingSegment representing that a segment is passed in a
// register.
//
// Parameters:
// reg - The register the segment is passed in
// offset - The offset of the segment that is passed in the register
// size - The size of the segment passed in the register
//
// Return Value:
// New instance of AbiPassingSegment.
//
AbiPassingSegment AbiPassingSegment::InRegister(regNumber reg, unsigned offset, unsigned size)
{
assert(reg != REG_NA);
AbiPassingSegment segment;
segment.m_register = reg;
segment.m_stackOffset = 0;
segment.Offset = offset;
segment.Size = size;
return segment;
}

//-----------------------------------------------------------------------------
// OnStack:
// Create an AbiPassingSegment representing that a segment is passed on the
// stack.
//
// Parameters:
// stackOffset - Offset relative to the first stack parameter/argument
// offset - The offset of the segment that is passed in the register
// size - The size of the segment passed in the register
//
// Return Value:
// New instance of AbiPassingSegment.
//
AbiPassingSegment AbiPassingSegment::OnStack(unsigned stackOffset, unsigned offset, unsigned size)
{
AbiPassingSegment segment;
segment.m_register = REG_NA;
segment.m_stackOffset = stackOffset;
segment.Offset = offset;
segment.Size = size;
return segment;
}

//-----------------------------------------------------------------------------
// IsSplitAcrossRegistersAndStack:
// Check if this AbiPassingInformation represents passing a value in both
// registers and on stack.
//
// Return Value:
// True if the value is passed in both registers and on stack.
//
bool AbiPassingInformation::IsSplitAcrossRegistersAndStack() const
{
bool anyReg = false;
bool anyStack = false;
for (unsigned i = 0; i < NumSegments; i++)
{
anyReg |= Segments[i].IsPassedInRegister();
anyStack |= Segments[i].IsPassedOnStack();
}
return anyReg && anyStack;
}
53 changes: 53 additions & 0 deletions src/coreclr/jit/abi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#pragma once

class AbiPassingSegment
{
regNumber m_register = REG_NA;
unsigned m_stackOffset = 0;

public:
bool IsPassedInRegister() const;
bool IsPassedOnStack() const;
Comment on lines +12 to +13
Copy link
Member

Choose a reason for hiding this comment

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

Is it relevant to track the other common qualifications like HFA and HVA?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think so. HFA's/HVA's are just passed in more registers than other struct arguments, so hopefully by the end of this clean up that's a detail that's constrained fully to be within the ABI classification and not leaked out anywhere to the rest of the JIT.


// Start offset of the segment within the parameter/argument. For example, a struct like { int32_t x; uint64_t y }
// may have two segments
// 1. Register(Offset=0, Type=TYP_INT, Size=4, Register=REG_ESI)
// 2. Register(Offset=8, Type=TYP_LONG, Size=8, Register=REG_EDI)
// on some ABIs, where the size of the first segment is not sufficient to
// compute the offset of the second.
unsigned Offset = 0;
// Size of the segment being passed.
unsigned Size = 0;

// If this segment is passed in a register, return the particular register.
regNumber GetRegister() const;

// If this segment is passed on the stack then return the particular stack
// offset, relative to the first stack argument's offset.
unsigned GetStackOffset() const;

static AbiPassingSegment InRegister(regNumber reg, unsigned offset, unsigned size);
static AbiPassingSegment OnStack(unsigned stackOffset, unsigned offset, unsigned size);
};

struct AbiPassingInformation
{
// The number of segments used to pass the value. Examples:
// - On x86, TYP_LONG can be passed in two registers, resulting in two
// register segments
// - On SysV x64, structs can be passed in two registers, resulting in two
// register segments
// - On arm64/arm32, HFAs can be passed in up to four registers, giving
// four register segments
Comment on lines +43 to +44
Copy link
Member

Choose a reason for hiding this comment

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

This should also include HVAs, which looks like we might be missing handling around:

godbolt HFA/HVA for Arm64

godbolt HVA/HVA for x64

Copy link
Member Author

Choose a reason for hiding this comment

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

These are just examples to indicate examples of the representation. The intention of this PR is not to make any functional changes.

// - On arm32, structs can be split out over register and stack, giving
// multiple register segments and a struct segment.
// - On Windows x64, all parameters always belong into one stack slot or register,
// and thus always have NumSegments == 1
unsigned NumSegments = 0;
AbiPassingSegment* Segments = nullptr;

bool IsSplitAcrossRegistersAndStack() const;
};
22 changes: 7 additions & 15 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

#include "codegeninterface.h"
#include "regset.h"
#include "abi.h"
#include "jitgcinfo.h"

#if DUMP_GC_TABLES && defined(JIT32_GCENCODER)
Expand Down Expand Up @@ -487,19 +488,6 @@ enum class AddressExposedReason
class LclVarDsc
{
public:
// The constructor. Most things can just be zero'ed.
//
// Initialize the ArgRegs to REG_STK.
LclVarDsc()
: _lvArgReg(REG_STK)
#if FEATURE_MULTIREG_ARGS
, _lvOtherArgReg(REG_STK)
#endif // FEATURE_MULTIREG_ARGS
, lvClassHnd(NO_CLASS_HANDLE)
, lvPerSsaData()
{
}

// note this only packs because var_types is a typedef of unsigned char
var_types lvType : 5; // TYP_INT/LONG/FLOAT/DOUBLE/REF

Expand Down Expand Up @@ -1105,7 +1093,7 @@ class LclVarDsc
unsigned lvSlotNum; // original slot # (if remapped)

// class handle for the local or null if not known or not a class
CORINFO_CLASS_HANDLE lvClassHnd;
CORINFO_CLASS_HANDLE lvClassHnd = NO_CLASS_HANDLE;

private:
ClassLayout* m_layout; // layout info for structs
Expand Down Expand Up @@ -3783,6 +3771,8 @@ class Compiler
LclVarDsc* lvaTable; // variable descriptor table
unsigned lvaTableCnt; // lvaTable size (>= lvaCount)

AbiPassingInformation* lvaParameterPassingInfo;

unsigned lvaTrackedCount; // actual # of locals being tracked
unsigned lvaTrackedCountInSizeTUnits; // min # of size_t's sufficient to hold a bit for all the locals being tracked

Expand Down Expand Up @@ -3993,6 +3983,8 @@ class Compiler

bool lvaInitSpecialSwiftParam(InitVarDscInfo* varDscInfo, CorInfoType type, CORINFO_CLASS_HANDLE typeHnd);

void lvaDeterminePassingInformation(InitVarDscInfo* varDscInfo, CorInfoType corInfoType, CORINFO_CLASS_HANDLE typeHnd);

var_types lvaGetActualType(unsigned lclNum);
var_types lvaGetRealType(unsigned lclNum);

Expand Down Expand Up @@ -8134,7 +8126,7 @@ class Compiler
var_types eeGetArgType(CORINFO_ARG_LIST_HANDLE list, CORINFO_SIG_INFO* sig, bool* isPinned);
CORINFO_CLASS_HANDLE eeGetArgClass(CORINFO_SIG_INFO* sig, CORINFO_ARG_LIST_HANDLE list);
CORINFO_CLASS_HANDLE eeGetClassFromContext(CORINFO_CONTEXT_HANDLE context);
unsigned eeGetArgSize(CORINFO_ARG_LIST_HANDLE list, CORINFO_SIG_INFO* sig);
unsigned eeGetArgSize(CorInfoType corInfoType, CORINFO_CLASS_HANDLE typeHnd);
static unsigned eeGetArgSizeAlignment(var_types type, bool isFloatHfa);

// VOM info, method sigs
Expand Down
26 changes: 11 additions & 15 deletions src/coreclr/jit/ee_il_dll.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -345,56 +345,52 @@ void CILJit::setTargetOS(CORINFO_OS os)
// including padding after the actual value.
//
// Arguments:
// list - the arg list handle pointing to the argument
// sig - the signature for the arg's method
// corInfoType - EE type of the argument
// typeHnd - if the type is a value class, its class handle
//
// Return value:
// the number of stack slots in stack arguments for the call.
// the size in bytes when the type is passed on the stack for the call.
//
// Notes:
// - On most platforms arguments are passed with TARGET_POINTER_SIZE alignment,
// so all types take an integer number of TARGET_POINTER_SIZE slots.
// It is different for arm64 apple that packs some types without alignment and padding.
// If the argument is passed by reference then the method returns REF size.
//
unsigned Compiler::eeGetArgSize(CORINFO_ARG_LIST_HANDLE list, CORINFO_SIG_INFO* sig)
unsigned Compiler::eeGetArgSize(CorInfoType corInfoType, CORINFO_CLASS_HANDLE typeHnd)
{
var_types argType = JITtype2varType(corInfoType);

#if defined(TARGET_AMD64)

// Everything fits into a single 'slot' size
// to accommodate irregular sized structs, they are passed byref
CLANG_FORMAT_COMMENT_ANCHOR;

#ifdef UNIX_AMD64_ABI
CORINFO_CLASS_HANDLE argClass;
CorInfoType argTypeJit = strip(info.compCompHnd->getArgType(sig, list, &argClass));
var_types argType = JITtype2varType(argTypeJit);
if (varTypeIsStruct(argType))
{
unsigned structSize = info.compCompHnd->getClassSize(argClass);
unsigned structSize = info.compCompHnd->getClassSize(typeHnd);
return roundUp(structSize, TARGET_POINTER_SIZE);
}
#endif // UNIX_AMD64_ABI
return TARGET_POINTER_SIZE;

#else // !TARGET_AMD64

CORINFO_CLASS_HANDLE argClass;
CorInfoType argTypeJit = strip(info.compCompHnd->getArgType(sig, list, &argClass));
var_types argType = JITtype2varType(argTypeJit);
unsigned argSize;
unsigned argSize;

var_types hfaType = TYP_UNDEF;
bool isHfa = false;

if (varTypeIsStruct(argType))
{
hfaType = GetHfaType(argClass);
hfaType = GetHfaType(typeHnd);
isHfa = (hfaType != TYP_UNDEF);
unsigned structSize = info.compCompHnd->getClassSize(argClass);
unsigned structSize = info.compCompHnd->getClassSize(typeHnd);

// make certain the EE passes us back the right thing for refanys
assert(argTypeJit != CORINFO_TYPE_REFANY || structSize == 2 * TARGET_POINTER_SIZE);
assert(corInfoType != CORINFO_TYPE_REFANY || structSize == 2 * TARGET_POINTER_SIZE);

// For each target that supports passing struct args in multiple registers
// apply the target specific rules for them here:
Expand Down
Loading