-
Notifications
You must be signed in to change notification settings - Fork 200
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
[NativeAOT-LLVM]: Initial support for IsVirtualVtable calls #1768
Conversation
add flag to call to indicate if there is a hidden parameter include delegates test fix for store in between phi nodes
@@ -491,7 +494,14 @@ class IndirectCallTransformer | |||
} | |||
|
|||
private: | |||
// Wasm stores function pointers as indicies in a function table |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Wasm stores function pointers as indicies in a function table | |
// Wasm stores function pointers as indices in a function table. |
src/coreclr/jit/llvm.cpp
Outdated
// assume ExternalLinkage, if the function is defined in the clrjit module, then it is replaced and an | ||
// extern added to the Ilc module | ||
llvmFunc = | ||
Function::Create(getFunctionTypeForCall(call), Function::ExternalLinkage, 0U, symbolName, _module); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to rename getFunctionTypeForCall
to createFunctionTypeForCall
, for symmetry.
src/coreclr/jit/llvm.cpp
Outdated
// For CT_INDIRECT calls, the callNode->callSig is present, | ||
// other times it's not, so get the siginfo from the GenTree call node if possible, else from eeGetMethodSig | ||
CORINFO_SIG_INFO* calleeSigInfo = callNode->callSig; | ||
if (calleeSigInfo == nullptr) | ||
{ | ||
CORINFO_SIG_INFO eeCalleeSigInfo; | ||
_compiler->eeGetMethodSig(callNode->gtCallMethHnd, &eeCalleeSigInfo); | ||
calleeSigInfo = &eeCalleeSigInfo; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is the case that for indirect calls the method handle is in a union with gtCallMethHnd
:
union {
CORINFO_METHOD_HANDLE gtCallMethHnd; // CT_USER_FUNC or CT_HELPER
GenTree* gtCallAddr; // CT_INDIRECT
};
So it is not clear how does this work to me.
In what cases is a nullptr
signature seen? We should probably fix those. I note that the signature field is under #ifdef DEBUG
, so it is not terribly surprising we can see it not preserved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
now we are not doing CT_INDIRECT
but CALLV
I'm just reverting this.
src/coreclr/jit/llvm.cpp
Outdated
// calculate hiddenArg number if present | ||
unsigned hiddenArgNum = 0; | ||
if (callNode->gtHasHiddenArgument) | ||
{ | ||
if (callThisArg != nullptr) | ||
{ | ||
hiddenArgNum++; | ||
} | ||
if (callNode->HasRetBufArg()) | ||
{ | ||
hiddenArgNum++; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually wonder if we need gtHasHiddenArgument
and this logic. It seems we will need to handle every arg-not-in-signature here, which seems unfortunate (we will essentially have to duplicate part of logic from fgMorphArgs
).
We can exploit the fact that in the sorted args order, all non-user (and thus non-signature) args always come first. Subtracting the number of args in the signature from the number of args in the arg info should give us the threshold after which we can start using the signature for argument types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, I've removed it.
src/coreclr/jit/llvm.cpp
Outdated
Function* llvmFunc = getOrCreateLlvmFunction(symbolName, call); | ||
llvmFuncCallee = llvmFunc; | ||
} | ||
else if (call->gtCallType == CT_INDIRECT) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
else if (call->gtCallType == CT_INDIRECT) | |
else if (call->gtCallType == CT_INDIRECT) |
I would drop the else if
, leaving only the else
, and assert under it that the call is indirect.
This captures the idea that this method only builds 2 call types, and will only ever do that (well, it could eventually morph into just buildCall
, but that'll be a different function).
src/coreclr/jit/llvm.cpp
Outdated
if (call->gtEntryPoint.accessType == IAT_VALUE) | ||
{ | ||
FunctionType* functionType = getFunctionTypeForCall(call); | ||
|
||
for (GenTreeCall::Use& use : call->Args()) | ||
//TODO-LLVM: how best to detect there is no runtime lookup required when the target is a constant and not abstract or virtual | ||
if (llvm::ConstantInt* llvmConstantInt = llvm::dyn_cast<llvm::ConstantInt>(funcValue)) { | ||
void* methodHandle = (void*)(llvmConstantInt->getZExtValue()); | ||
|
||
const char* methodName = _getMangledSymbolNameFromHelperTarget(_thisPtr, methodHandle); | ||
if (methodName == nullptr) // TODO-LLVM: abstract or virtual call | ||
{ | ||
failFunctionCompilation(); | ||
} | ||
|
||
(*_addCodeReloc)(_thisPtr, methodHandle); | ||
|
||
llvmFuncCallee = { functionType, getOrCreateLlvmFunction(methodName, call) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are effectively doing "devirtualization for indirect calls" here, manually.
Why?
Is the issue that we need to call (*_addCodeReloc)(_thisPtr, methodHandle);
? It does not seem right to have it be "pulled" from under the call, it should instead be registered at the logical ldftn
point, when building the the icon node.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, it's good that you've questioned this, as it gives something concrete to talk about. I didn't understand how to do the virtual calls so restricted myself to those calls that could be determined at runtime. The addCodeReloc
is there to make sure we get the target method compiled. Happy to expand the PR to cover virtual calls but I didn't see how to make the slot lookup work, and I suspected I was missing something. How do the other Ilc backends do this I wonder?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jkotas @MichalStrehovsky Maybe I can pull one of you in here please ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yowl let me test my understanding here. Is the following true?
addCodeReloc
is only required for helpers. User methods are fine as they have been added as dependencies by the ILC's front-end.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ummm.... If I delete the call to _addCodeReloc
, then it works. Oh dear, sorry about that. I still could use some help on the slot lookup side though. Let me get some IR for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that looks right to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When we lower the call args, we remove the gtCallThisArg
as its pushed to the shadowstack. I'm going to need that aren't I, to get to the method table?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to need that aren't I, to get to the method table?
Why? The idea here is that we don't need to do that, the control expression (== function address) has already been computed for us by morph.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, just me being slow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MichalStrehovsky Thanks, looking at runtimelab/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs Lines 120 to 138 in dd8c43a
runtimelab/src/coreclr/jit/importer.cpp Line 2230 in dd8c43a
ComputeGenericLookup are:
Does that sound right? I'm going to |
Not sure that one is needed. It should match the indirections that are done for the ReadyToRunGenericLookupHelper (the LLVMSharp-based WASM backend already does all of this because it needs to generate the corresponding helper; it's just in a different location). |
…ble slots (#1834) This PR enables the IL scanner for LLVM. The scanner is always on for LLVM to enable precomputed vtables slots. This will unblock the `CT_INDIRECT` work #1768 . At ComputeGenericLookup in `Compilation.cs` you can see that it never takes the fixed lookup path for generic calls, so not sure that if that is going to be a problem. There are some changes to the scanner to add dependencies required by the IL->LLVM compilation which should be removed when all code is generated by clrjit. Including - The scanner has the same stubbing out of methods not implemented for LLVM, `GetRandomBytes` and `EnumCalendarInfo` - For expanding intrinsics, the scanner has to add dependencies for both expanding and not expanding as it doesn't know which module will compile the method. - Adding dependencies for throwing exceptions and exception handling - Various other places where the IL->LLVM compilation differs from the scanner
…table Remove gtHasHiddenArgument Addresses feedback
Latest commit addresses remaining feedback. Thanks for reviewing. |
Disabling the Delegates test which I had enabled here. It fails in Release config. |
@SingleAccretion Could you please do final review of this change? |
Sorry for the delay, will do so tomorrow. |
src/coreclr/jit/llvm.cpp
Outdated
if (use.GetNode() == call->gtControlExpr) | ||
continue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When is this case hit?
The control expression should never be in the args (in fact, it would violate the "single use" invariant of an SDSU).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Never apparently. Have deleted it.
src/coreclr/jit/llvm.cpp
Outdated
@@ -1026,7 +1058,7 @@ void Llvm::buildCall(GenTree* node) | |||
{ | |||
buildHelperFuncCall(call); | |||
} | |||
else if (call->gtCallType == CT_USER_FUNC && !call->IsVirtualStub() /* TODO: Virtual stub not implemented */) | |||
else if ((call->gtCallType == CT_USER_FUNC || call->gtCallType == CT_INDIRECT) && !call->IsVirtualStub() /* TODO: Virtual stub not implemented */) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
call->gtCallType == CT_INDIRECT
no longer needed (as it is no longer handled)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, deleted.
src/coreclr/jit/llvm.cpp
Outdated
@@ -1988,7 +2017,7 @@ void Llvm::lowerCallToShadowStack(GenTreeCall* callNode, CORINFO_SIG_INFO& calle | |||
GenTreeCall::Use* callThisArg = callNode->gtCallThisArg; | |||
|
|||
callNode->ResetArgInfo(); | |||
callNode->gtCallThisArg = nullptr; | |||
//callNode->gtCallThisArg = nullptr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why comment it out?
The way I read the code, it will cause a call node to reference the this
SDSU twice (e. g. for VisitOperands
).
(If some dumping code relies on it - we should adjust it)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mm, Have reinstated it, thanks
src/coreclr/jit/llvm.cpp
Outdated
@@ -2014,26 +2043,39 @@ void Llvm::lowerCallToShadowStack(GenTreeCall* callNode, CORINFO_SIG_INFO& calle | |||
for (unsigned i = 0; i < argCount; i++) | |||
{ | |||
fgArgTabEntry* curArgTabEntry = argTable[i]; | |||
if (curArgTabEntry->nonStandardArgKind == NonStandardArgKind::VirtualStubCell) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cleaner to catch it in failUnsupportedCalls
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, that's better
|
||
for (OperandArgNum opAndArg : sortedArgs) | ||
{ | ||
CORINFO_CLASS_HANDLE clsHnd = NO_CLASS_HANDLE; | ||
CorInfoType corInfoType = CORINFO_TYPE_UNDEF; | ||
|
||
// "this" not in sigInfo arg list | ||
bool isThis = callThisArg != nullptr && opAndArg.argNum == 0 && calleeSigInfo.hasThis(); | ||
if (!isThis) | ||
bool isThis = callThisArg != nullptr && opAndArg.argNum == 0 && calleeSigInfo->hasThis(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be a little more robust to check against callThisArg
:
isThis = callThisArg != nullptr && callThisArg->GetNode() == opAndArg.operand
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is not quite the same. E.g. for the IR
------------ BB01 [???..???), preds={} succs={BB02}
N001 ( 0, 0) [000000] ------------ NOP void
------------ BB02 [000..00B) -> BB04 (cond), preds={BB01} succs={BB03,BB04}
[000021] ------------ IL_OFFSET void IL offset: 0x0
N001 ( 1, 1) [000001] ------------ NO_OP void
[000022] ------------ IL_OFFSET void IL offset: 0x1
N002 ( 3, 2) [000002] ------------ t2 = LCL_VAR byref V00 this
/--* t2 byref this in rcx
N003 ( 17, 6) [000003] --CXG------- t3 = * CALL int System.Threading.CancellationToken.get_IsCancellationRequested
/--* t3 int
N004 ( 18, 8) [000004] ---XG------- t4 = * CAST int <- bool <- int
/--* t4 int
N006 ( 22, 11) [000006] DA-XG------- * STORE_LCL_VAR int V03 tmp1
[000023] ------------ IL_OFFSET void IL offset: 0x7
N001 ( 3, 2) [000007] ------------ t7 = LCL_VAR int V03 tmp1
/--* t7 int
N002 ( 4, 4) [000019] ------------ t19 = * CAST int <- bool <- int
/--* t19 int
N004 ( 8, 7) [000009] DA---------- * STORE_LCL_VAR int V01 loc0
[000024] ------------ IL_OFFSET void IL offset: 0x8
N001 ( 3, 2) [000010] ------------ t10 = LCL_VAR int V01 loc0
N002 ( 1, 1) [000011] ------------ t11 = CNS_INT int 0
/--* t10 int
+--* t11 int
N003 ( 5, 4) [000012] J------N---- t12 = * EQ int
/--* t12 int
N004 ( 7, 6) [000013] ------------ * JTRUE void
------------ BB03 [00B..012), preds={BB02} succs={BB04}
[000025] ------------ IL_OFFSET void IL offset: 0xb
N002 ( 3, 2) [000015] ------------ t15 = LCL_VAR byref V00 this
/--* t15 byref this in rcx
N003 ( 17, 6) [000016] --CXG------- * CALL void System.Threading.CancellationToken.ThrowOperationCanceledException
[000026] ------------ IL_OFFSET void IL offset: 0x11
N001 ( 1, 1) [000017] ------------ NO_OP void
------------ BB04 [012..013) (return), preds={BB02,BB03} succs={}
[000027] ------------ IL_OFFSET void IL offset: 0x12
N001 ( 0, 0) [000014] ------------ RETURN void
For the call at tree id 16, callThisArg->GetNode()
is a GT_ARGPLACE
whereas opAndArg.operand
has become GT_IND
at treed id 15.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, the ever-present late-arg-not-late arg distinction. You could obtain the real node from the arg info but that point I agree it is not worth the trouble.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok,thanks
Co-authored-by: SingleAccretion <62474226+SingleAccretion@users.noreply.github.com>
The OSX build failed
And
Doesn't seem relevant to this PR. See what else fails... |
Will retry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
@yowl @SingleAccretion Thank you! |
This PR adds the start of support for
CT_INDIRECT
by supporting the most simple cases, when the target is known at compile time, and when the target is a simple indirect call from a variable.To support this I've added a flag to the call node to indicate if there is a hidden parameter so that the signature can be generated correctly (similar to how
this
is handled)Added a fix for a store op in between phi nodes which is illegal in LLVM. I thought I'd already done that but apparently not.
Included the delegates test as it passes