Skip to content

Commit e17cc67

Browse files
committed
Transform indirect calls to direct ones in the importer
Imports indirect calls as direct ones when the target method is known. Only handles addresses from ldftn as the VM has no way to verify pointers from static readonly fields and crashes on invalid ones. The helpers currently contain a small dead path that I'll soon use when extending the code to also handle delegates. Opening as a draft so that the code can be reviewed while I finish the tests. Fixes dotnet#44610
1 parent a193cb5 commit e17cc67

File tree

5 files changed

+442
-10
lines changed

5 files changed

+442
-10
lines changed

src/coreclr/jit/compiler.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3830,6 +3830,15 @@ class Compiler
38303830
bool impCanPInvokeInlineCallSite(BasicBlock* block);
38313831
void impCheckForPInvokeCall(
38323832
GenTreeCall* call, CORINFO_METHOD_HANDLE methHnd, CORINFO_SIG_INFO* sig, unsigned mflags, BasicBlock* block);
3833+
3834+
enum SigTransform
3835+
{
3836+
LeaveIntact = 0,
3837+
DeleteThis = 1 << 0,
3838+
ReplaceRefThis = 1 << 1,
3839+
};
3840+
3841+
bool impCanSubstituteSig(CORINFO_SIG_INFO* sourceSig, CORINFO_SIG_INFO* targetSig, SigTransform transformation);
38333842
GenTreeCall* impImportIndirectCall(CORINFO_SIG_INFO* sig, const DebugInfo& di = DebugInfo());
38343843
void impPopArgsForUnmanagedCall(GenTreeCall* call, CORINFO_SIG_INFO* sig);
38353844

@@ -3858,6 +3867,7 @@ class Compiler
38583867

38593868
GenTree* impInitClass(CORINFO_RESOLVED_TOKEN* pResolvedToken);
38603869

3870+
GenTree* impGetNodeFromLocal(GenTree* node);
38613871
GenTree* impImportStaticReadOnlyField(CORINFO_FIELD_HANDLE field, CORINFO_CLASS_HANDLE ownerCls);
38623872
GenTree* impImportCnsTreeFromBuffer(uint8_t* buffer, var_types valueType);
38633873

src/coreclr/jit/importer.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3764,6 +3764,65 @@ GenTree* Compiler::impInitClass(CORINFO_RESOLVED_TOKEN* pResolvedToken)
37643764
return node;
37653765
}
37663766

3767+
//------------------------------------------------------------------------
3768+
// impGetNodeFromLocal: Tries to return the node that's assigned
3769+
// to the provided local.
3770+
//
3771+
// Arguments:
3772+
// node - GT_LCL_VAR whose value is searched for
3773+
//
3774+
// Return Value:
3775+
// The tree representing the node assigned to the variable when possible,
3776+
// nullptr otherwise.
3777+
//
3778+
GenTree* Compiler::impGetNodeFromLocal(GenTree* node)
3779+
{
3780+
assert(node != nullptr);
3781+
assert(node->OperIs(GT_LCL_VAR));
3782+
3783+
unsigned lclNum = node->AsLclVarCommon()->GetLclNum();
3784+
3785+
if (lvaTable[lclNum].lvSingleDef == 0)
3786+
{
3787+
return nullptr;
3788+
}
3789+
3790+
auto findValue = [&](Statement* stmtList) -> GenTree* {
3791+
for (Statement* stmt : StatementList(stmtList))
3792+
{
3793+
GenTree* root = stmt->GetRootNode();
3794+
if (root->OperIs(GT_ASG) && root->AsOp()->gtOp1->OperIs(GT_LCL_VAR) &&
3795+
root->AsOp()->gtOp1->AsLclVarCommon()->GetLclNum() == lclNum)
3796+
{
3797+
return root->AsOp()->gtOp2;
3798+
}
3799+
}
3800+
return nullptr;
3801+
};
3802+
3803+
GenTree* valueNode = findValue(impStmtList);
3804+
BasicBlock* bb = fgFirstBB;
3805+
while (valueNode == nullptr && bb != nullptr)
3806+
{
3807+
valueNode = findValue(bb->bbStmtList);
3808+
if (valueNode == nullptr && bb->NumSucc(this) == 1)
3809+
{
3810+
bb = bb->GetSucc(0, this);
3811+
}
3812+
else
3813+
{
3814+
bb = nullptr;
3815+
}
3816+
}
3817+
3818+
if (valueNode != nullptr && valueNode->OperIs(GT_LCL_VAR))
3819+
{
3820+
return impGetNodeFromLocal(valueNode);
3821+
}
3822+
3823+
return valueNode;
3824+
}
3825+
37673826
//------------------------------------------------------------------------
37683827
// impImportStaticReadOnlyField: Tries to import 'static readonly' field
37693828
// as a constant if the host type is statically initialized.

src/coreclr/jit/importercalls.cpp

Lines changed: 198 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,16 @@ var_types Compiler::impImportCall(OPCODE opcode,
104104
bool checkForSmallType = false;
105105
bool bIntrinsicImported = false;
106106

107-
CORINFO_SIG_INFO calliSig;
107+
CORINFO_SIG_INFO originalSig;
108108
NewCallArg extraArg;
109109

110-
/*-------------------------------------------------------------------------
111-
* First create the call node
112-
*/
110+
// run transformations when instrumenting to not pollute PGO data
111+
bool optimizedOrInstrumented = opts.OptimizationEnabled() || opts.IsInstrumented();
112+
CORINFO_METHOD_HANDLE replacementMethod = nullptr;
113+
GenTree* newThis = nullptr;
114+
SigTransform sigTransformation = SigTransform::LeaveIntact;
113115

116+
// handle special import cases
114117
if (opcode == CEE_CALLI)
115118
{
116119
if (IsTargetAbi(CORINFO_NATIVEAOT_ABI))
@@ -125,25 +128,102 @@ var_types Compiler::impImportCall(OPCODE opcode,
125128
}
126129

127130
/* Get the call site sig */
128-
eeGetSig(pResolvedToken->token, pResolvedToken->tokenScope, pResolvedToken->tokenContext, &calliSig);
131+
eeGetSig(pResolvedToken->token, pResolvedToken->tokenScope, pResolvedToken->tokenContext, &originalSig);
129132

130-
callRetTyp = JITtype2varType(calliSig.retType);
133+
if (!optimizedOrInstrumented)
134+
{
135+
// ignore
136+
}
137+
else if (originalSig.getCallConv() == CORINFO_CALLCONV_DEFAULT)
138+
{
139+
JITDUMP("\n\nimpImportCall trying to import calli as call\n");
140+
GenTree* fptr = impStackTop().val;
141+
if (fptr->OperIs(GT_LCL_VAR))
142+
{
143+
JITDUMP("impImportCall trying to import calli as call - trying to substitute LCL_VAR\n");
144+
GenTree* lclValue = impGetNodeFromLocal(fptr);
145+
if (lclValue != nullptr)
146+
{
147+
fptr = lclValue;
148+
}
149+
}
150+
if (fptr->OperIs(GT_FTN_ADDR))
151+
{
152+
replacementMethod = fptr->AsFptrVal()->gtFptrMethod;
153+
}
154+
else
155+
{
156+
JITDUMP("impImportCall failed to import calli as call - address node not found\n");
157+
}
158+
}
159+
else
160+
{
161+
JITDUMP("impImportCall failed to import calli as call - call conv %u is not managed\n",
162+
originalSig.getCallConv());
163+
}
164+
}
165+
166+
if (replacementMethod != nullptr)
167+
{
168+
JITDUMP("impImportCall trying to transform call - found target method %s\n",
169+
eeGetMethodName(replacementMethod));
170+
CORINFO_SIG_INFO methodSig;
171+
CORINFO_CLASS_HANDLE targetClass = info.compCompHnd->getMethodClass(replacementMethod);
172+
info.compCompHnd->getMethodSig(replacementMethod, &methodSig, targetClass);
131173

132-
call = impImportIndirectCall(&calliSig, di);
174+
unsigned replacementFlags = info.compCompHnd->getMethodAttribs(replacementMethod);
175+
176+
if ((replacementFlags & CORINFO_FLG_PINVOKE) != 0)
177+
{
178+
JITDUMP("impImportCall aborting transformation - found PInvoke\n");
179+
}
180+
else if (impCanSubstituteSig(&originalSig, &methodSig, sigTransformation))
181+
{
182+
impPopStack();
183+
if (newThis != nullptr)
184+
{
185+
assert(sigTransformation == SigTransform::ReplaceRefThis);
186+
CORINFO_CLASS_HANDLE thisCls = NO_CLASS_HANDLE;
187+
info.compCompHnd->getArgType(&methodSig, methodSig.args, &thisCls);
188+
impPushOnStack(newThis, typeInfo(TI_REF, thisCls));
189+
}
190+
JITDUMP("impImportCall transforming call\n");
191+
pResolvedToken->hMethod = replacementMethod;
192+
pResolvedToken->hClass = targetClass;
193+
194+
callInfo->sig = methodSig;
195+
callInfo->hMethod = replacementMethod;
196+
callInfo->methodFlags = replacementFlags;
197+
callInfo->classFlags = info.compCompHnd->getClassAttribs(targetClass);
198+
199+
return impImportCall(CEE_CALL, pResolvedToken, nullptr, nullptr,
200+
prefixFlags, callInfo, rawILOffset);
201+
}
202+
}
203+
204+
/*-------------------------------------------------------------------------
205+
* First create the call node
206+
*/
207+
208+
if (opcode == CEE_CALLI)
209+
{
210+
callRetTyp = JITtype2varType(originalSig.retType);
211+
212+
call = impImportIndirectCall(&originalSig, di);
133213

134214
// We don't know the target method, so we have to infer the flags, or
135215
// assume the worst-case.
136-
mflags = (calliSig.callConv & CORINFO_CALLCONV_HASTHIS) ? 0 : CORINFO_FLG_STATIC;
216+
mflags = (originalSig.callConv & CORINFO_CALLCONV_HASTHIS) ? 0 : CORINFO_FLG_STATIC;
137217

138218
#ifdef DEBUG
139219
if (verbose)
140220
{
141-
unsigned structSize = (callRetTyp == TYP_STRUCT) ? eeTryGetClassSize(calliSig.retTypeSigClass) : 0;
221+
unsigned structSize = (callRetTyp == TYP_STRUCT) ? eeTryGetClassSize(originalSig.retTypeSigClass) : 0;
142222
printf("\nIn Compiler::impImportCall: opcode is %s, kind=%d, callRetType is %s, structSize is %u\n",
143223
opcodeNames[opcode], callInfo->kind, varTypeName(callRetTyp), structSize);
144224
}
145225
#endif
146-
sig = &calliSig;
226+
sig = &originalSig;
147227
}
148228
else // (opcode != CEE_CALLI)
149229
{
@@ -1622,6 +1702,114 @@ GenTree* Compiler::impFixupCallStructReturn(GenTreeCall* call, CORINFO_CLASS_HAN
16221702
#endif // FEATURE_MULTIREG_RET
16231703
}
16241704

1705+
//-----------------------------------------------------------------------------------
1706+
// impCanSubstituteSig: Checks whether it's safe to replace a call with another one.
1707+
// This DOES NOT check if the calls are actually compatible, it only checks if their trees are.
1708+
// Use ONLY when code will call the method with target sig anyway.
1709+
//
1710+
// Arguments:
1711+
// sourceSig - original call signature
1712+
// targetSig - new call signature
1713+
// transformation - transformations performed on the original signature
1714+
//
1715+
// Return Value:
1716+
// Whether it's safe to change the IR to call the target method
1717+
//
1718+
bool Compiler::impCanSubstituteSig(CORINFO_SIG_INFO* sourceSig, CORINFO_SIG_INFO* targetSig, SigTransform transformation)
1719+
{
1720+
const SigTransform thisChangeMask = (SigTransform)(SigTransform::DeleteThis | SigTransform::ReplaceRefThis);
1721+
assert((transformation & thisChangeMask) != thisChangeMask);
1722+
1723+
if (sourceSig->getCallConv() != targetSig->getCallConv())
1724+
{
1725+
JITDUMP("impCanSubstituteSig returning false - call conv %u != %u\n", sourceSig->callConv, targetSig->callConv);
1726+
return false;
1727+
}
1728+
1729+
unsigned sourceArgCount = sourceSig->numArgs;
1730+
if ((transformation & SigTransform::DeleteThis) != 0)
1731+
{
1732+
sourceArgCount--;
1733+
}
1734+
1735+
if (sourceArgCount != targetSig->numArgs)
1736+
{
1737+
JITDUMP("impCanSubstituteSig returning false - args count %u != %u\n", sourceArgCount, targetSig->numArgs);
1738+
return false;
1739+
}
1740+
1741+
if (sourceSig->retType != targetSig->retType)
1742+
{
1743+
JITDUMP("impCanSubstituteSig returning false - return type %u != %u\n",
1744+
(unsigned)sourceSig->retType, (unsigned)targetSig->retType);
1745+
return false;
1746+
}
1747+
1748+
if (sourceSig->retType == CORINFO_TYPE_VALUECLASS || sourceSig->retType == CORINFO_TYPE_REFANY)
1749+
{
1750+
ClassLayout* layoutRetA = typGetObjLayout(sourceSig->retTypeClass);
1751+
ClassLayout* layoutRetB = typGetObjLayout(targetSig->retTypeClass);
1752+
1753+
if (!ClassLayout::AreCompatible(layoutRetA, layoutRetB))
1754+
{
1755+
JITDUMP("impCanSubstituteSig returning false - return type %u is incompatible with %u\n",
1756+
(unsigned)sourceSig->retType, (unsigned)targetSig->retType);
1757+
return false;
1758+
}
1759+
}
1760+
1761+
CORINFO_ARG_LIST_HANDLE sourceArg = sourceSig->args;
1762+
CORINFO_ARG_LIST_HANDLE targetArg = targetSig->args;
1763+
1764+
assert((transformation & (SigTransform::DeleteThis | SigTransform::ReplaceRefThis)) == 0 ||
1765+
eeGetArgType(sourceArg, sourceSig) == TYP_REF);
1766+
1767+
if ((transformation & SigTransform::DeleteThis) != 0)
1768+
{
1769+
sourceArg = info.compCompHnd->getArgNext(sourceArg);
1770+
}
1771+
1772+
if ((transformation & SigTransform::ReplaceRefThis) != 0 && eeGetArgType(targetArg, targetSig) != TYP_REF)
1773+
{
1774+
JITDUMP("impCanSubstituteSig returning false - this is not TYP_REF\n");
1775+
return false;
1776+
}
1777+
1778+
for (unsigned i = 0; i < targetSig->numArgs; i++,
1779+
sourceArg = info.compCompHnd->getArgNext(sourceArg),
1780+
targetArg = info.compCompHnd->getArgNext(targetArg))
1781+
{
1782+
var_types sourceType = eeGetArgType(sourceArg, sourceSig);
1783+
var_types targetType = eeGetArgType(targetArg, targetSig);
1784+
if (sourceType != targetType)
1785+
{
1786+
JITDUMP("impCanSubstituteSig returning false - parameter %u type %s != %s\n",
1787+
i, varTypeName(sourceType), varTypeName(targetType));
1788+
return false;
1789+
}
1790+
1791+
if (varTypeIsStruct(sourceType) && varTypeIsStruct(targetType))
1792+
{
1793+
CORINFO_CLASS_HANDLE sourceClassHnd = NO_CLASS_HANDLE;
1794+
CORINFO_CLASS_HANDLE targetClassHnd = NO_CLASS_HANDLE;
1795+
info.compCompHnd->getArgType(sourceSig, sourceArg, &sourceClassHnd);
1796+
info.compCompHnd->getArgType(targetSig, targetArg, &targetClassHnd);
1797+
1798+
ClassLayout* sourceLayout = typGetObjLayout(sourceClassHnd);
1799+
ClassLayout* targetLayout = typGetObjLayout(targetClassHnd);
1800+
1801+
if (!ClassLayout::AreCompatible(sourceLayout, targetLayout))
1802+
{
1803+
JITDUMP("impCanSubstituteSig returning false - parameter %u type %s is inconmpatible with %s\n",
1804+
i, varTypeName(sourceType), varTypeName(targetType));
1805+
return false;
1806+
}
1807+
}
1808+
}
1809+
1810+
return true;
1811+
}
1812+
16251813
GenTreeCall* Compiler::impImportIndirectCall(CORINFO_SIG_INFO* sig, const DebugInfo& di)
16261814
{
16271815
var_types callRetTyp = JITtype2varType(sig->retType);

0 commit comments

Comments
 (0)