-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Import isinst/castclass Nullable as underlying type #87241
Conversation
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch Issue DetailsAccording to ECMA-335 III.4.6:
In my understanding, it means that Code gen: bool TestNullable(object o) => o is int?;
int TestCoalesce(object o) => (o as int?) ?? 1234; Before: ; Method CSPlayground.Program:TestNullable(System.Object):bool:this
G_M50267_IG01: ;; offset=0000H
sub rsp, 40
;; size=4 bbWeight=1 PerfScore 0.25
G_M50267_IG02: ;; offset=0004H
mov rcx, 0x7FF8A0D4A9B8 ; System.Nullable`1[int]
call [CORINFO_HELP_ISINSTANCEOFANY]
test rax, rax
setne al
movzx rax, al
;; size=25 bbWeight=1 PerfScore 4.75
G_M50267_IG03: ;; offset=001DH
add rsp, 40
ret
;; size=5 bbWeight=1 PerfScore 1.25
; Total bytes of code: 34
; Method CSPlayground.Program:TestCoalesce(System.Object):int:this
G_M24774_IG01: ;; offset=0000H
sub rsp, 40
xor eax, eax
mov qword ptr [rsp+20H], rax
;; size=11 bbWeight=1 PerfScore 1.50
G_M24774_IG02: ;; offset=000BH
mov rcx, 0x7FF8A0D1A9B8 ; System.Nullable`1[int]
call [CORINFO_HELP_ISINSTANCEOFANY]
mov r8, rax
lea rcx, [rsp+20H]
mov rdx, 0x7FF8A0D1A9B8 ; System.Nullable`1[int]
call CORINFO_HELP_UNBOX_NULLABLE
mov eax, 0x4D2
cmp byte ptr [rsp+20H], 0
cmovne eax, dword ptr [rsp+24H]
;; size=54 bbWeight=1 PerfScore 9.50
G_M24774_IG03: ;; offset=0041H
add rsp, 40
ret
;; size=5 bbWeight=1 PerfScore 1.25
; Total bytes of code: 70 After: ; Method CSPlayground.Program:TestNullable(System.Object):bool:this
G_M50267_IG01: ;; offset=0000H
;; size=0 bbWeight=1 PerfScore 0.00
G_M50267_IG02: ;; offset=0000H
test rdx, rdx
je SHORT G_M50267_IG04
;; size=5 bbWeight=1 PerfScore 1.25
G_M50267_IG03: ;; offset=0005H
mov rax, 0x7FF89421E7E8 ; System.Int32
xor rcx, rcx
cmp qword ptr [rdx], rax
cmovne rdx, rcx
;; size=19 bbWeight=0.25 PerfScore 0.94
G_M50267_IG04: ;; offset=0018H
xor eax, eax
test rdx, rdx
setne al
;; size=8 bbWeight=1 PerfScore 1.50
G_M50267_IG05: ;; offset=0020H
ret
;; size=1 bbWeight=1 PerfScore 1.00
; Total bytes of code: 33
; Method CSPlayground.Program:TestCoalesce(System.Object):int:this
G_M24774_IG01: ;; offset=0000H
sub rsp, 40
xor eax, eax
mov qword ptr [rsp+20H], rax
;; size=11 bbWeight=1 PerfScore 1.50
G_M24774_IG02: ;; offset=000BH
mov r8, rdx
test r8, r8
je SHORT G_M24774_IG04
;; size=8 bbWeight=1 PerfScore 1.50
G_M24774_IG03: ;; offset=0013H
mov rcx, 0x7FF89420E7E8 ; System.Int32
xor rdx, rdx
cmp qword ptr [r8], rcx
cmovne r8, rdx
;; size=19 bbWeight=0.25 PerfScore 0.94
G_M24774_IG04: ;; offset=0026H
lea rcx, [rsp+20H]
mov rdx, 0x7FF89451A9B8 ; System.Nullable`1[int]
call CORINFO_HELP_UNBOX_NULLABLE
mov eax, 0x4D2
cmp byte ptr [rsp+20H], 0
cmovne eax, dword ptr [rsp+24H]
;; size=35 bbWeight=1 PerfScore 6.00
G_M24774_IG05: ;; offset=0049H
add rsp, 40
ret
;; size=5 bbWeight=1 PerfScore 1.25
; Total bytes of code: 78 Still not ideal (#47920 (comment)), but reduces a helper call.
|
src/coreclr/vm/jitinterface.cpp
Outdated
if (!clsHnd.IsTypeDesc() && !(Nullable::IsNullableType(clsHnd) && fThrowing)) | ||
{ | ||
// If it is a non-variant class, use the fast class helper | ||
// Also use fast helper for isinst Nullable, which is equal to isinst of underlying type |
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 not sure where to handle this. JIT is passing the token of original nullable type. Is there any way to get the token of underlying type?
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.
Is there any way to get the token of underlying type?
Do you mean to get int
from Nullable<int>
? in that case it should be clsHnd->AsMethodTable()->GetInstantiation()[0]
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 mean to get it in JIT instead of VM.
I didn't update jitinterface for native aot (if exists). If done in JIT, there's no need to update in two places.
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.
um.. getTypeForBox
that you already using?
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 returns a VM handle, but the JIT interface requires a metadata handle, if I'm correct.
Or we can skip the call to JIT interface and hard code to exact type helper when nullable case detected. Is there any risk?
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.
Presumably, you need to check that you don't deal with Nullable<MyValueType<_Canon>>
(or emit a runtime type lookup)
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.
public class Program
{
public static void Main()
{
Test<string>(new MyStruct<string>());
}
[MethodImpl(MethodImplOptions.NoInlining)]
static bool Test<T>(object o) => o is MyStruct<T>?;
}
public struct MyStruct<T>
{
public T val;
}
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.
Solutions I can see:
- Call
getCastingHelperStatic
again aftergetCastingHelper
. Is the call toclassMustBeLoadedBeforeCodeIsRun
reliable to mark for underlying type too? - Find a way to convert
CORINFO_RESOLVED_TOKEN
to underlying type - Update JIT interface and tell
getCastingHelper
to use underlying type.
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.
Since the implementation of getCastingHelper
are all converting it to vm handle immediately, I think it's better to update jit interface to accept CORINFO_CLASS_HANDLE
.
Null check difference is for |
CI failures look related? |
The codegen for if (obj != null && obj->pMT == int32)
return Nullable<int> { hasValue = true, Value = unboxedValu }
else
fallback |
src/coreclr/jit/importer.cpp
Outdated
@@ -5710,7 +5720,7 @@ GenTree* Compiler::impCastClassOrIsInstToTree( | |||
assert(lclDsc->lvSingleDef == 0); | |||
lclDsc->lvSingleDef = 1; | |||
JITDUMP("Marked V%02u as a single def temp\n", tmp); | |||
lvaSetClass(tmp, pResolvedToken->hClass); |
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.
Is there any observable effect for changing the return type of the expression?
src/coreclr/jit/importer.cpp
Outdated
@@ -5422,9 +5422,19 @@ GenTree* Compiler::impCastClassOrIsInstToTree( | |||
{ | |||
assert(op1->TypeGet() == TYP_REF); | |||
|
|||
// Import isinst Nullable<V> as isinst V |
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.
If all you want to do is to convert Nullable<T>
into T
for casing helpers, the best place to do it is resolveToken
on the VM side. resolveToken
on VM side does similar conversion for NewArr
here: https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/jitinterface.cpp#L1170. You can do similar conversion for tokens that come from isinst/castclass.
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.
The conversion has to be also taken into account when embedding the handle (look for other places that check for CORINFO_TOKENKIND_Newarr
in the VM). I think your current change is missing this part.
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.
There's null check difference for castclass, so the nullable type should be visible to JIT if we want to do optimization for castclass.
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.
There's null check difference for castclass
Could you please elaborate? Both isinst
on null and castclass
on null return null.
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 misunderstood. NRE for casting to value type is thrown by unbox.
src/coreclr/vm/jitinterface.cpp
Outdated
@@ -1170,6 +1170,16 @@ void CEEInfo::resolveToken(/* IN, OUT */ CORINFO_RESOLVED_TOKEN * pResolvedToken | |||
th = ClassLoader::LoadArrayTypeThrowing(th); | |||
break; | |||
|
|||
case CORINFO_TOKENKIND_Casting: | |||
// Disallow ELEMENT_TYPE_BYREF and ELEMENT_TYPE_VOID |
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.
This is duplicated for 3rd time here. We should move it in front of the switch under if (tokenType != CORINFO_TOKENKIND_Ldtoken)
It seems that roslyn doesn't emit int^ Test(Object^ o)
{
return safe_cast<int^>(o);
}
It's quite complicated to verify it since |
yes, look for *.ilproj in the tests folder |
Yes I know that folder. It seems there's currently no cases covering |
src/coreclr/vm/jitinterface.cpp
Outdated
COMPlusThrow(kInvalidProgramException); | ||
break; | ||
default: | ||
// No additional checks. Satisfy switch exhaustiveness check. |
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.
// No additional checks. Satisfy switch exhaustiveness check. |
Nit. I do not think this comment is useful.
JIT\opt\Casts may be a good place. |
I think you would want to check |
I do not see problems with this, as long as you avoid using the original |
a15e668
to
2b10425
Compare
/azp run runtime-coreclr outerloop |
Azure Pipelines successfully started running 1 pipeline(s). |
/azp run runtime-extra-platforms |
Azure Pipelines successfully started running 1 pipeline(s). |
/azp run runtime-coreclr outerloop |
Azure Pipelines successfully started running 1 pipeline(s). |
Test failure should be the known one. |
src/coreclr/jit/importer.cpp
Outdated
@@ -5425,6 +5425,24 @@ GenTree* Compiler::impCastClassOrIsInstToTree( | |||
// Optimistically assume the jit should expand this as an inline test | |||
bool shouldExpandInline = true; | |||
bool isClassExact = impIsClassExact(pResolvedToken->hClass); | |||
bool isSharedInst = info.compCompHnd->getClassAttribs(pResolvedToken->hClass) & CORINFO_FLG_SHAREDINST; |
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 can avoid the JIT/EE interface call for !isClassExact
case by inlining the JIT/EE interface into the condition below.
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.
LGTM otherwise. @dotnet/jit-contrib Any addition feedback?
{ | ||
.method public hidebysig static int32 Main() cil managed | ||
{ | ||
.custom instance void [xunit.core]Xunit.FactAttribute::.ctor() = ( |
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 assume the [Fact] attribute is not needed here (or then .entrypoint is not needed if it's part of the merged runtime tests)?
I'm not asking to remove it, just curious
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.
[Fact] is needed as JIT/opt is a merged test group. The .entrypoint will have no impact on merged test group execution (this test is a library that is referenced by the merged test group, so this .entrypoint isn't the overall entry).
We've been keeping the .entrypoint annotations in ilproj tests. I'm not entirely sure of the details, but my guess is that the basic wrappers (for BuildAsStandalone or RequiresProcessIsolation) have C#-specific aspects to them.
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.
@markples I see, thanks, I thought that .entrypoint will confuse a merged test group
LGTM assuming the newly added outerloop test passed during manual I've restarted the failing CI job |
@jkotas anything remaining? |
Thank you! |
According to ECMA-335 III.4.6:
In my understanding, it means that
isinst V?
is the same asisinst V
.Code gen:
Before:
After:
Still not ideal (#47920 (comment)), but reduces a helper call.