@@ -185,12 +185,6 @@ var_types Compiler::impImportCall(OPCODE opcode,
185
185
compInlineResult->NoteFatal(InlineObservation::CALLEE_HAS_MANAGED_VARARGS);
186
186
return TYP_UNDEF;
187
187
}
188
-
189
- if ((mflags & CORINFO_FLG_VIRTUAL) && (sig->sigInst.methInstCount != 0) && (opcode == CEE_CALLVIRT))
190
- {
191
- compInlineResult->NoteFatal(InlineObservation::CALLEE_IS_GENERIC_VIRTUAL);
192
- return TYP_UNDEF;
193
- }
194
188
}
195
189
196
190
clsHnd = pResolvedToken->hClass;
@@ -384,12 +378,6 @@ var_types Compiler::impImportCall(OPCODE opcode,
384
378
385
379
case CORINFO_VIRTUALCALL_LDVIRTFTN:
386
380
{
387
- if (compIsForInlining())
388
- {
389
- compInlineResult->NoteFatal(InlineObservation::CALLSITE_HAS_CALL_VIA_LDVIRTFTN);
390
- return TYP_UNDEF;
391
- }
392
-
393
381
assert(!(mflags & CORINFO_FLG_STATIC)); // can't call a static method
394
382
assert(!(clsFlags & CORINFO_FLG_VALUECLASS));
395
383
// OK, We've been told to call via LDVIRTFTN, so just
@@ -402,11 +390,20 @@ var_types Compiler::impImportCall(OPCODE opcode,
402
390
thisPtr = impTransformThis(thisPtr, pConstrainedResolvedToken, callInfo->thisTransform);
403
391
assert(thisPtr != nullptr);
404
392
393
+ GenTree* origThisPtr = thisPtr;
394
+
405
395
// Clone the (possibly transformed) "this" pointer
406
396
GenTree* thisPtrCopy;
407
397
thisPtr =
408
398
impCloneExpr(thisPtr, &thisPtrCopy, CHECK_SPILL_ALL, nullptr DEBUGARG("LDVIRTFTN this pointer"));
409
399
400
+ // We cloned the "this" pointer, mark it as a single def and set the class for it
401
+ if (thisPtr->TypeIs(TYP_REF) && (origThisPtr != thisPtr))
402
+ {
403
+ lvaGetDesc(thisPtr->AsLclVar())->lvSingleDef = 1;
404
+ lvaSetClass(thisPtr->AsLclVar()->GetLclNum(), origThisPtr);
405
+ }
406
+
410
407
GenTree* fptr = impImportLdvirtftn(thisPtr, pResolvedToken, callInfo);
411
408
assert(fptr != nullptr);
412
409
@@ -415,10 +412,6 @@ var_types Compiler::impImportCall(OPCODE opcode,
415
412
416
413
// Now make an indirect call through the function pointer
417
414
418
- unsigned lclNum = lvaGrabTemp(true DEBUGARG("VirtualCall through function pointer"));
419
- impStoreToTemp(lclNum, fptr, CHECK_SPILL_ALL);
420
- fptr = gtNewLclvNode(lclNum, TYP_I_IMPL);
421
-
422
415
call->AsCall()->gtCallAddr = fptr;
423
416
call->gtFlags |= GTF_EXCEPT | (fptr->gtFlags & GTF_GLOB_EFFECT);
424
417
@@ -439,7 +432,7 @@ var_types Compiler::impImportCall(OPCODE opcode,
439
432
// Sine we are jumping over some code, check that its OK to skip that code
440
433
assert((sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_VARARG &&
441
434
(sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_NATIVEVARARG);
442
- goto DONE ;
435
+ goto DEVIRT ;
443
436
}
444
437
445
438
case CORINFO_CALL:
@@ -933,6 +926,8 @@ var_types Compiler::impImportCall(OPCODE opcode,
933
926
}
934
927
}
935
928
929
+ DEVIRT:
930
+
936
931
bool probing;
937
932
probing = impConsiderCallProbe(call->AsCall(), rawILOffset);
938
933
@@ -1232,7 +1227,7 @@ var_types Compiler::impImportCall(OPCODE opcode,
1232
1227
// If the call is virtual, record the inliner's context for possible use during late devirt inlining.
1233
1228
// Also record the generics context if there is any.
1234
1229
//
1235
- if (call->AsCall()->IsVirtual() && (call->AsCall()->gtCallType != CT_INDIRECT ))
1230
+ if (call->AsCall()->IsDevirtualizationCandidate(this ))
1236
1231
{
1237
1232
JITDUMP("\nSaving generic context %p and inline context %p for call [%06u]\n", dspPtr(exactContextHnd),
1238
1233
dspPtr(compInlineContext), dspTreeID(call->AsCall()));
@@ -1449,8 +1444,7 @@ var_types Compiler::impImportCall(OPCODE opcode,
1449
1444
varDsc->lvType = call->AsCall()->gtReturnType;
1450
1445
}
1451
1446
1452
- // TODO-Bug: CHECK_SPILL_NONE here looks wrong.
1453
- impStoreToTemp(calliSlot, call, CHECK_SPILL_NONE);
1447
+ impStoreToTemp(calliSlot, call, CHECK_SPILL_ALL);
1454
1448
// impStoreToTemp can change src arg list and return type for call that returns struct.
1455
1449
var_types type = genActualType(lvaTable[calliSlot].TypeGet());
1456
1450
call = gtNewLclvNode(calliSlot, type);
@@ -7136,7 +7130,7 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call,
7136
7130
}
7137
7131
7138
7132
addGuardedDevirtualizationCandidate(call, exactMethod, exactCls, exactContext, exactMethodAttrs,
7139
- clsAttrs, likelyHood, dvInfo.wasArrayInterfaceDevirt ,
7133
+ clsAttrs, likelyHood, dvInfo.hasGenericMethodHandleContext ,
7140
7134
dvInfo.isInstantiatingStub, originalContext);
7141
7135
}
7142
7136
@@ -7208,7 +7202,7 @@ void Compiler::considerGuardedDevirtualization(GenTreeCall* call,
7208
7202
7209
7203
likelyContext = dvInfo.exactContext;
7210
7204
likelyMethod = dvInfo.devirtualizedMethod;
7211
- arrayInterface = dvInfo.wasArrayInterfaceDevirt ;
7205
+ arrayInterface = dvInfo.hasGenericMethodHandleContext ;
7212
7206
instantiatingStub = dvInfo.isInstantiatingStub;
7213
7207
}
7214
7208
else
@@ -8210,14 +8204,14 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call,
8210
8204
8211
8205
if (((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_CLASS)
8212
8206
{
8213
- assert(!dvInfo.wasArrayInterfaceDevirt );
8207
+ assert(!dvInfo.hasGenericMethodHandleContext );
8214
8208
derivedClass = (CORINFO_CLASS_HANDLE)((size_t)exactContext & ~CORINFO_CONTEXTFLAGS_MASK);
8215
8209
}
8216
8210
else
8217
8211
{
8218
8212
// Array interface devirt can return a nonvirtual generic method of the non-generic SZArrayHelper class.
8219
8213
//
8220
- assert(dvInfo.wasArrayInterfaceDevirt );
8214
+ assert(dvInfo.hasGenericMethodHandleContext );
8221
8215
assert(((size_t)exactContext & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD);
8222
8216
derivedClass = info.compCompHnd->getMethodClass(derivedMethod);
8223
8217
}
@@ -8238,17 +8232,17 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call,
8238
8232
8239
8233
if (dvInfo.isInstantiatingStub)
8240
8234
{
8241
- // We should only end up with generic methods for array interface devirt.
8235
+ // We should only end up with generic methods for array interface or GVM devirt.
8242
8236
//
8243
- assert(dvInfo.wasArrayInterfaceDevirt );
8237
+ assert(dvInfo.hasGenericMethodHandleContext );
8244
8238
8245
8239
// We don't expect NAOT to end up here, since it has Array<T>
8246
- // and normal devirtualization.
8240
+ // and normal devirtualization. For GVM devirt we don't support NAOT yet.
8247
8241
//
8248
8242
assert(!IsTargetAbi(CORINFO_NATIVEAOT_ABI));
8249
8243
8250
8244
// We don't expect R2R to end up here, since it does not (yet) support
8251
- // array interface devirtualization.
8245
+ // array interface or GVM devirtualization.
8252
8246
//
8253
8247
assert(!opts.IsReadyToRun());
8254
8248
@@ -8354,26 +8348,43 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call,
8354
8348
8355
8349
JITDUMP(" %s; can devirtualize\n", note);
8356
8350
8357
- // Make the updates.
8358
- call->gtFlags &= ~GTF_CALL_VIRT_VTABLE;
8359
- call->gtFlags &= ~GTF_CALL_VIRT_STUB;
8360
- call->gtCallMethHnd = derivedMethod;
8361
- call->gtCallType = CT_USER_FUNC;
8362
- call->gtControlExpr = nullptr;
8363
- INDEBUG(call->gtCallDebugFlags |= GTF_CALL_MD_DEVIRTUALIZED);
8364
-
8365
8351
if (dvInfo.isInstantiatingStub)
8366
8352
{
8367
8353
// Pass the instantiating stub method desc as the inst param arg.
8368
8354
//
8369
8355
// Note different embedding would be needed for NAOT/R2R,
8370
8356
// but we have ruled those out above.
8371
8357
//
8372
- GenTree* const instParam =
8373
- gtNewIconEmbHndNode(instantiatingStub, nullptr, GTF_ICON_METHOD_HDL, instantiatingStub);
8358
+ GenTree* instParam = nullptr;
8359
+ // See if this is a generic virtual method call.
8360
+ if (call->gtCallType == CT_INDIRECT && call->gtCallAddr->IsHelperCall(this, CORINFO_HELP_VIRTUAL_FUNC_PTR))
8361
+ {
8362
+ // We need to pass the RUNTIMELOOKUP helper call as the inst param arg
8363
+ // instead of the instantiating stub if there is one.
8364
+ //
8365
+ GenTree* const methHndNode =
8366
+ call->gtCallAddr->AsCall()->gtArgs.FindWellKnownArg(WellKnownArg::RuntimeMethodHandle)->GetEarlyNode();
8367
+ if (methHndNode->OperIs(GT_RUNTIMELOOKUP))
8368
+ {
8369
+ instParam = methHndNode;
8370
+ }
8371
+ }
8372
+ // For cases where we don't have a RUNTIMELOOKUP helper call, we pass the instantiating stub.
8373
+ if (instParam == nullptr)
8374
+ {
8375
+ instParam = gtNewIconEmbHndNode(instantiatingStub, nullptr, GTF_ICON_METHOD_HDL, instantiatingStub);
8376
+ }
8374
8377
call->gtArgs.InsertInstParam(this, instParam);
8375
8378
}
8376
8379
8380
+ // Make the updates.
8381
+ call->gtFlags &= ~GTF_CALL_VIRT_VTABLE;
8382
+ call->gtFlags &= ~GTF_CALL_VIRT_STUB;
8383
+ call->gtCallMethHnd = derivedMethod;
8384
+ call->gtCallType = CT_USER_FUNC;
8385
+ call->gtControlExpr = nullptr;
8386
+ INDEBUG(call->gtCallDebugFlags |= GTF_CALL_MD_DEVIRTUALIZED);
8387
+
8377
8388
// Virtual calls include an implicit null check, which we may
8378
8389
// now need to make explicit.
8379
8390
if (!objIsNonNull)
0 commit comments