Skip to content
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

JIT: Fold string fields in structs stored in static readonly fields #80431

Merged
merged 23 commits into from Mar 11, 2023

Conversation

EgorBo
Copy link
Member

@EgorBo EgorBo commented Jan 10, 2023

struct MyStruct
{
    public string Name;
}

// static readonly field holding a struct
static readonly MyStruct MyStr = new() { Name = "Hey!" };

static string GetName() => MyStr.Name; // optimized to just "Hey!"

Codegen for GetName() before:

       48B8C01E8057D9020000 mov      rax, 0x2D957801EC0
       488B00               mov      rax, gword ptr [rax]
       488B4008             mov      rax, gword ptr [rax+08H]
       C3                   ret      
; Total bytes of code 18

after:

       48B8B884DAABFD7F0000 mov      rax, 0x7FFDABDA84B8      ; 'Hey!'
       C3                   ret      
; Total bytes of code 11,

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jan 10, 2023
@ghost ghost assigned EgorBo Jan 10, 2023
@ghost
Copy link

ghost commented Jan 10, 2023

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

Issue Details
struct MyStruct
{
    public string Name;
}

// static readonly field holding a struct
static readonly MyStruct MyStr = new() { Name = "Hey!" };

static string GetName() => MyStr.Name; // optimized to just "Hey!"

Codegen for GetName() before:

       48B8C01E8057D9020000 mov      rax, 0x2D957801EC0
       488B00               mov      rax, gword ptr [rax]
       488B4008             mov      rax, gword ptr [rax+08H]
       C3                   ret      
; Total bytes of code 18

after:

       48B8B884DAABFD7F0000 mov      rax, 0x7FFDABDA84B8      ; 'Hey!'
       C3                   ret      
; Total bytes of code 11,
Author: EgorBo
Assignees: -
Labels:

area-CodeGen-coreclr

Milestone: -

@gfoidl
Copy link
Member

gfoidl commented Jan 11, 2023

Aside from the actual PR and work:

Big thanks @EgorBo for the way you write the PR descriptions that show an example, before and after codegen.
So it's really tangible what optimizations we can expect to happen.

@EgorBo EgorBo marked this pull request as ready for review January 11, 2023 17:00
@EgorBo
Copy link
Member Author

EgorBo commented Jan 11, 2023

@jkotas could you please take a look at the VM side? I had to modify the API to scan structs for GC fields

@EgorBo
Copy link
Member Author

EgorBo commented Jan 11, 2023

The PR doesn't produce lots of diffs but it's currently blocked on #78736

e.g.

static OSPlatform OS { get; } = new OSPlatform("Windows");

static string Test() => OS.Name;

get_OS() getter won't be inlined because of that.

if ((baseAddr == nullptr) && loadTree->TypeIs(TYP_REF) &&
// it should be a static field
(fieldSeq->GetKind() == FieldSeq::FieldKind::SimpleStatic) &&
// boxed static to be precise
Copy link
Member

Choose a reason for hiding this comment

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

This is fragile condition. We are assuming that the runtime uses boxed statics for this pattern. Is there a way to write this in a more general way?

Copy link
Member Author

Choose a reason for hiding this comment

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

void Compiler::fgValueNumberFieldLoad(GenTree* loadTree, GenTree* baseAddr, FieldSeq* fieldSeq, ssize_t offset)
{
noway_assert(fieldSeq != nullptr);
// Check if the load represents a frozen gc object located in a struct which is stored in a static readonly field,
// e.g.:
//
// struct MyStruct { string Name; }
// static readonly MyStruct MyStr = new() { Name = "Hey!" };
//
// string GetName() => MyStr.Name; // <- loadTree
//
if ((baseAddr == nullptr) && loadTree->TypeIs(TYP_REF) &&
// it should be a static field
(fieldSeq->GetKind() == FieldSeq::FieldKind::SimpleStatic) &&
// boxed static to be precise
(fieldSeq->GetOffset() == TARGET_POINTER_SIZE))
{
uint8_t buffer[TARGET_POINTER_SIZE] = {0};
if (((UINT)offset < INT_MAX) &&
info.compCompHnd->getReadonlyStaticFieldValue(fieldSeq->GetFieldHandle(), buffer, TARGET_POINTER_SIZE,

we accept loadTree that looks like this:
image

and the fieldseq that represents that load. So technically I don't need to check that tree at all. @SingleAccretion @jakobbotsch could you please validate JIT part of this PR?

@ghost ghost closed this Mar 1, 2023
@ghost
Copy link

ghost commented Mar 1, 2023

Draft Pull Request was automatically closed for 30 days of inactivity. Please let us know if you'd like to reopen it.

@EgorBo EgorBo reopened this Mar 6, 2023
EgorBo and others added 3 commits March 6, 2023 19:44
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
…runtime-1 into vn-static-readonly-struct-string
@EgorBo EgorBo marked this pull request as ready for review March 6, 2023 23:04
Copy link
Contributor

@SingleAccretion SingleAccretion left a comment

Choose a reason for hiding this comment

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

I am curious why is this not done in fgValueNumberConstLoad like for other types.

@@ -9965,6 +9965,19 @@ void Compiler::fgValueNumberSsaVarDef(GenTreeLclVarCommon* lcl)
//
static bool GetStaticFieldSeqAndAddress(ValueNumStore* vnStore, GenTree* tree, ssize_t* byteOffset, FieldSeq** pFseq)
{
VNFuncApp funcApp;
if (tree->TypeIs(TYP_REF) && vnStore->GetVNFunc(tree->gtVNPair.GetLiberal(), &funcApp) &&
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be TYP_BYREF?

Copy link
Member

Choose a reason for hiding this comment

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

Is there any point in checking the type of the address?

Copy link
Member Author

Choose a reason for hiding this comment

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

Is there any point in checking the type of the address?

no, removed 🙂

@EgorBo
Copy link
Member Author

EgorBo commented Mar 8, 2023

I am curious why is this not done in fgValueNumberConstLoad like for other types.

good point, addressed.

jit-diff is around -5kb. Overall, it was an exercise to fold RuntimeInformation.IsOSPlatform(...) but can be useful for other cases as well in real world.

@EgorBo
Copy link
Member Author

EgorBo commented Mar 8, 2023

@jkotas could you please review the VM side?

NativeAOT side is not implemented because of #83043 (comment)

Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

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

LGTM

EgorBo and others added 2 commits March 9, 2023 19:06
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
@EgorBo EgorBo merged commit 3816ed1 into dotnet:main Mar 11, 2023
134 checks passed
@EgorBo EgorBo deleted the vn-static-readonly-struct-string branch March 11, 2023 01:35
@dotnet dotnet locked as resolved and limited conversation to collaborators Apr 10, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants