Skip to content

Commit

Permalink
JIT: Add a uniform representation for parameter ABI information (#100138
Browse files Browse the repository at this point in the history
)

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 authored Mar 25, 2024
1 parent 1fecfbc commit 13a9036
Show file tree
Hide file tree
Showing 8 changed files with 385 additions and 76 deletions.
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;

// 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
// - 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;
};
11 changes: 4 additions & 7 deletions src/coreclr/jit/codegencommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6796,27 +6796,24 @@ unsigned Compiler::GetHfaCount(CORINFO_CLASS_HANDLE hClass)
unsigned CodeGen::getFirstArgWithStackSlot()
{
#if defined(UNIX_AMD64_ABI) || defined(TARGET_ARMARCH) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
unsigned baseVarNum = 0;
// Iterate over all the lvParam variables in the Lcl var table until we find the first one
// that's passed on the stack.
LclVarDsc* varDsc = nullptr;
for (unsigned i = 0; i < compiler->info.compArgsCount; i++)
{
varDsc = compiler->lvaGetDesc(i);
LclVarDsc* varDsc = compiler->lvaGetDesc(i);

// We should have found a stack parameter (and broken out of this loop) before
// we find any non-parameters.
assert(varDsc->lvIsParam);

if (varDsc->GetArgReg() == REG_STK)
{
baseVarNum = i;
break;
return i;
}
}
assert(varDsc != nullptr);

return baseVarNum;
assert(!"Expected to find a parameter passed on the stack");
return BAD_VAR_NUM;
#elif defined(TARGET_AMD64)
return 0;
#else // TARGET_X86
Expand Down
5 changes: 4 additions & 1 deletion 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 @@ -3783,6 +3784,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 @@ -8136,7 +8139,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

0 comments on commit 13a9036

Please sign in to comment.