Skip to content

JIT: use type argument as the exact type for Activator.CreateInstance<T>#130074

Open
hez2010 wants to merge 10 commits into
dotnet:mainfrom
hez2010:activator-exact-type
Open

JIT: use type argument as the exact type for Activator.CreateInstance<T>#130074
hez2010 wants to merge 10 commits into
dotnet:mainfrom
hez2010:activator-exact-type

Conversation

@hez2010

@hez2010 hez2010 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Activator.CreateInstance<T> can only result in an instance of exact T. Teach the JIT so that when the JIT sees a call to Activator.CreateInstance<T>, it will know the result must be the exact T. Then any subsequent calls against the allocated object instance can be devirtualized.

This also applies to any where T : new() patterns.

Example:

class Program
{
    static T Get<T>() where T : new()
    {
        return new T();
    }

    class A
    {
        public virtual int Value => 1;
    }

    class B : A
    {
        public override int Value => 2;
    }

    static void Main()
    {
        Console.WriteLine(Get<A>().Value);
        Console.WriteLine(Get<B>().Value);
    }
}

Before:

G_M27646_IG01:  ;; offset=0x0000
       sub      rsp, 40
G_M27646_IG02:  ;; offset=0x0004
       mov      rcx, 0x7FF92B6AB428      ; System.Activator:CreateInstance[Program+A]():Program+A
       call     [System.Activator:CreateInstance[System.__Canon]():System.__Canon]
       mov      rcx, rax
       mov      rax, qword ptr [rax]
       mov      rax, qword ptr [rax+0x48]
       call     [rax+0x20]Program+A:get_Value():int:this
       mov      ecx, eax
       call     [System.Console:WriteLine(int)]
       mov      rcx, 0x7FF92B6AB890      ; System.Activator:CreateInstance[Program+B]():Program+B
       call     [System.Activator:CreateInstance[System.__Canon]():System.__Canon]
       mov      rcx, rax
       mov      rax, qword ptr [rax]
       mov      rax, qword ptr [rax+0x48]
       call     [rax+0x20]Program+A:get_Value():int:this
       mov      ecx, eax
       call     [System.Console:WriteLine(int)]
       nop
G_M27646_IG03:  ;; offset=0x004F
       add      rsp, 40
       ret

After:

G_M27646_IG01:  ;; offset=0x0000
       sub      rsp, 40
G_M27646_IG02:  ;; offset=0x0004
       mov      rcx, 0x7FF92C79B428      ; System.Activator:CreateInstance[Program+A]():Program+A
       call     [System.Activator:CreateInstance[System.__Canon]():System.__Canon]
       mov      ecx, 1
       call     [System.Console:WriteLine(int)]
       mov      rcx, 0x7FF92C79B890      ; System.Activator:CreateInstance[Program+B]():Program+B
       call     [System.Activator:CreateInstance[System.__Canon]():System.__Canon]
       mov      ecx, 2
       call     [System.Console:WriteLine(int)]
       nop
G_M27646_IG03:  ;; offset=0x003B
       add      rsp, 40
       ret

In the future we can apply escape analysis against CreateInstance<T> as well to omit unescaped object allocations.

Copilot AI review requested due to automatic review settings July 1, 2026 10:02
@github-actions github-actions Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jul 1, 2026
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Jul 1, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

@hez2010

hez2010 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

@MihuBot -nuget

Comment thread src/coreclr/jit/gentree.cpp Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR extends CoreCLR JIT intrinsic recognition so calls to System.Activator.CreateInstance<T>() can be treated as producing an instance of the exact type argument T, enabling downstream optimizations (e.g., devirtualization) when the type argument is known.

Changes:

  • Adds a new named intrinsic NI_System_Activator_CreateInstance_T.
  • Recognizes Activator.CreateInstance with a type argument in lookupNamedIntrinsic and marks it as a “special intrinsic” call site.
  • Teaches gtGetClassHandle to recover the exact T for CreateInstance<T> calls to sharpen return type information.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/coreclr/jit/namedintrinsiclist.h Adds a new NamedIntrinsic enum value for Activator.CreateInstance<T>.
src/coreclr/jit/importercalls.cpp Recognizes Activator.CreateInstance<T> as a named intrinsic and flags it as “special” for later optimizations.
src/coreclr/jit/gentree.cpp Attempts to infer the exact return type of Activator.CreateInstance<T> in gtGetClassHandle.

Comment thread src/coreclr/jit/gentree.cpp Outdated
Comment thread src/coreclr/jit/importercalls.cpp
Copilot AI review requested due to automatic review settings July 1, 2026 10:21
Comment thread src/coreclr/jit/importercalls.cpp Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/coreclr/jit/importercalls.cpp:10706

  • lookupNamedIntrinsic now unconditionally calls eeGetMethodSig(method, &sig) even though the signature is only used for the Activator.CreateInstance check. Since lookupNamedIntrinsic runs for every intrinsic call import, this adds extra EE work on a hot import path. Consider fetching the signature only inside the Activator/CreateInstance branch (or using a cheaper generic-method check) so most intrinsics avoid the extra eeGetMethodSig call.
        info.compCompHnd->getMethodNameFromMetadata(method, &className, &namespaceName, enclosingClassNames,
                                                    ArrLen(enclosingClassNames));

    JITDUMP("Named Intrinsic ");

Comment thread src/coreclr/jit/importercalls.cpp Outdated
Copilot AI review requested due to automatic review settings July 1, 2026 10:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread src/coreclr/jit/importercalls.cpp
Copilot AI review requested due to automatic review settings July 1, 2026 10:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment thread src/coreclr/jit/importercalls.cpp Outdated
Comment thread src/coreclr/jit/importercalls.cpp
Copilot AI review requested due to automatic review settings July 1, 2026 10:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread src/coreclr/jit/importercalls.cpp Outdated
Copilot AI review requested due to automatic review settings July 1, 2026 10:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread src/coreclr/jit/importercalls.cpp
@huoyaoyuan

Copy link
Copy Markdown
Member

Activator.CreateInstance<T> can only result in an instance of exact T.

Is this also applicable to COM imported types?

@hez2010

hez2010 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Is this also applicable to COM imported types?

Unlike new T(), Activator.CreateInstance<T> doesn't allow instantiating an interface type even if the interface type has a CoClass attribute.

For example:

using System.Runtime.InteropServices;

internal static class Program
{
    [STAThread]
    static void Main()
    {
        ShellWindows windows = new ShellWindows(); // changing to `Activator.CreateInstance<ShellWindows>()` will result in a runtime exception
        Console.WriteLine(windows.Count);
    }
}

[ComImport]
[Guid("9BA05972-F6A8-11CF-A442-00A0C90A8F39")]
internal class ShellWindowsClass
{
}

[ComImport]
[Guid("85CB6900-4D95-11CF-960C-0080C7F4EE85")]
[CoClass(typeof(ShellWindowsClass))]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
internal interface ShellWindows
{
    [DispId(0x60020000)]
    int Count { get; }
}

@jakobbotsch jakobbotsch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This LGTM. @EgorBo what is your opinion?

@EgorBo

EgorBo commented Jul 1, 2026

Copy link
Copy Markdown
Member

LGTM, but to be honest, 29 total methods with Code Size differences accross a huge -nuget collection sounds very small to me

@EgorBo

EgorBo commented Jul 1, 2026

Copy link
Copy Markdown
Member

BTW, it looks like there are no hits in BCL so it's basically an untested bit of code?

@hez2010

hez2010 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

We do have many coverages: https://github.com/search?q=repo%3Adotnet%2Fruntime+%22Activator.CreateInstance%3C%22+OR+repo%3Adotnet%2Fruntime+%2Fwhere.*%3F%3A.*%3Fnew%5C%28%5C%29%2F&type=code

It's not showing up in the spmi because it enables more devirtualization so we end up with context mismatches.

assert(sig.sigInst.classInstCount == 0);

CORINFO_CLASS_HANDLE typeHnd = sig.sigInst.methInst[0];
assert(typeHnd != nullptr);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 AI review

typeHnd is a dead assignment here. It's computed and asserted, but result is only written inside the instParam != nullptr branch, so typeHnd is never read.

Impact: for non-shared/exact instantiations there's no InstParam, so we fall through to "undetermined" and drop the opt — even though methInst[0] already is the exact T (the value-type / R2R-AOT case where it matters most).

Fix (mirrors the SZArrayHelper_GetEnumerator case above — refine typeHnd, assign once, no else):

CORINFO_CLASS_HANDLE typeHnd = sig.sigInst.methInst[0];
assert(typeHnd != nullptr);

CallArg* instParam = call->gtArgs.FindWellKnownArg(WellKnownArg::InstParam);
if (instParam != nullptr)
{
    assert(instParam->GetNext() == nullptr);
    CORINFO_METHOD_HANDLE hMethod = gtGetHelperArgMethodHandle(instParam->GetNode());
    if (hMethod != NO_METHOD_HANDLE)
    {
        typeHnd = getMethodInstantiationArgument(hMethod, 0);
    }
}

result = typeHnd;

One thing to confirm: can methInst[0] be __Canon when InstParam is absent for CreateInstance<T>? If so, result = typeHnd; would report __Canon as exact, and the fallback should be guarded to the instParam == nullptr path instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants