Skip to content

Optimize out metadata for typeof(X).IsValueType and .IsEnum #118528

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,24 @@ private void ImportLdToken(int token)
}
}

// We might also be able to optimize this if this is something like typeof(Foo).IsValueType
// that doesn't need reflection metadata for the answer.
if (helperId != ReadyToRunHelperId.NecessaryTypeHandle)
{
reader = new ILReader(_ilBytes, _currentOffset);
if (reader.HasNext
&& reader.ReadILOpcode() == ILOpcode.call
&& IsTypeGetTypeFromHandle((MethodDesc)_methodIL.GetObject(reader.ReadILToken())))
{
if (reader.HasNext
&& reader.ReadILOpcode() is ILOpcode.callvirt or ILOpcode.call
&& _methodIL.GetObject(reader.ReadILToken()) is MethodDesc { Name: "get_IsValueType" or "get_IsEnum"})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add IsPrimitive too? It is used in some places in BCL.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's see what rt-sz says. We can do this for several other properties but I wasn't sure how much of a coupling between the compiler and the BCL we are okay with in this area.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we wanted to avoid coupling between the compiler and the BCL, we would make this pattern must-expand in the JIT. It would reduce the coupling to JIT/scanner that exists in many places.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a test coverage for the (rare) situation when the JIT does not expand this pattern as an intrinsic?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we wanted to avoid coupling between the compiler and the BCL, we would make this pattern must-expand in the JIT. It would reduce the coupling to JIT/scanner that exists in many places.

Hmm, I haven't even though about it in the context of the JIT optimization.

My original reason for why we don't need the metadata is because the implementation in RuntimeType will just go straight to the MethodTable if there is one and ask there. But yeah, RyuJIT will expand it before that, so we won't even get there.

I guess the only way to ensure this codepath really works would be to run unoptimized mode with the scanner enabled. I don't really know if I want to add a test pass with that. Or make it MustExpand in RyuJIT but not sure if that would be okay (cc @EgorBo).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I understand how e.g. IsPrimitive can be a must-expand intrinsic -- ho do we expand it for bool Foo(Type t) => t.IsPrimitive; ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would have to be a special "must expand if this comes from a typeof" rule (this is the logic that scanner is doing.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that will work, but it kind of relies that JIT doesn't spill typeof to a locally anyhow (e.g. under some stress)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is the patch to do that:

From b9ee418054231419e5cbd0e7a8f8da1a329758d5 Mon Sep 17 00:00:00 2001
From: EgorBo <[email protected]>
Date: Tue, 12 Aug 2025 16:55:12 +0200
Subject: [PATCH] make typeof().IsX must expand

---
 src/coreclr/jit/importercalls.cpp | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp
index a4e2e454b96..431668fc403 100644
--- a/src/coreclr/jit/importercalls.cpp
+++ b/src/coreclr/jit/importercalls.cpp
@@ -3500,6 +3500,19 @@ GenTree* Compiler::impIntrinsic(CORINFO_CLASS_HANDLE    clsHnd,
                 mustExpand = true;
                 break;
 
+            case NI_System_Type_get_IsEnum:
+            case NI_System_Type_get_IsValueType:
+            case NI_System_Type_get_IsByRefLike:
+            case NI_System_Type_get_IsPrimitive:
+                {
+                    CORINFO_CLASS_HANDLE hClass = NO_CLASS_HANDLE;
+                    if (gtIsTypeof(impStackTop().val, &hClass))
+                    {
+                        mustExpand = true;
+                    }
+                }
+                break;
+
             case NI_System_Runtime_InteropService_MemoryMarshal_GetArrayDataReference:
                 mustExpand |= sig->sigInst.methInstCount == 1;
                 break;
-- 
2.45.2.windows.1

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, if this gets sketchy, we can just come back to #118528 (comment): "but maybe worth it given the low complexity" - if the complexity is in ensuring test coverage in corner cases, this makes it no longer "low complexity" and maybe not worth it.

{
helperId = ReadyToRunHelperId.NecessaryTypeHandle;
}
}
}

_factory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _factory, _methodIL, (TypeDesc)_canonMethodIL.GetObject(token));

_dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GetRuntimeTypeHandle), "ldtoken");
Expand Down
Loading