Skip to content

Conversation

@jakobbotsch
Copy link
Member

The JIT has an optimization to recognize recomputations of field offsets, but this optimization failed to trigger in certain cases when the struct was promoted with old promotion.

public unsafe nint Test()
{
    MyStruct dummy;
    return (nint)((byte*)&dummy.Field - (byte*)&dummy);
}

private struct MyStruct
{
    public int A;
    public int Field;
}

Before:

G_M27992_IG01:  ;; offset=0x0000
       push     rax
       xor      eax, eax
       mov      qword ptr [rsp], rax
						;; size=7 bbWeight=1 PerfScore 2.25

G_M27992_IG02:  ;; offset=0x0007
       lea      rax, [rsp+0x04]
       lea      rcx, [rsp]
       sub      rax, rcx
						;; size=12 bbWeight=1 PerfScore 1.25

G_M27992_IG03:  ;; offset=0x0013
       add      rsp, 8
       ret
						;; size=5 bbWeight=1 PerfScore 1.25

After:

G_M27992_IG02:  ;; offset=0x0000
       mov      eax, 4
						;; size=5 bbWeight=1 PerfScore 0.25

G_M27992_IG03:  ;; offset=0x0005
       ret
						;; size=1 bbWeight=1 PerfScore 1.00

The JIT has an optimization to recognize recomputations of field
offsets, but this optimization failed to trigger in certain cases when
the struct was promoted with old promotion.

```csharp
public unsafe nint Test()
{
    MyStruct dummy;
    return (nint)((byte*)&dummy.Field - (byte*)&dummy);
}
```
Before:
```asm
G_M27992_IG01:  ;; offset=0x0000
       push     rax
       xor      eax, eax
       mov      qword ptr [rsp], rax
						;; size=7 bbWeight=1 PerfScore 2.25

G_M27992_IG02:  ;; offset=0x0007
       lea      rax, [rsp+0x04]
       lea      rcx, [rsp]
       sub      rax, rcx
						;; size=12 bbWeight=1 PerfScore 1.25

G_M27992_IG03:  ;; offset=0x0013
       add      rsp, 8
       ret
						;; size=5 bbWeight=1 PerfScore 1.25
```
After:
```asm
G_M27992_IG02:  ;; offset=0x0000
       mov      eax, 4
						;; size=5 bbWeight=1 PerfScore 0.25

G_M27992_IG03:  ;; offset=0x0005
       ret
						;; size=1 bbWeight=1 PerfScore 1.00
```
@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 Dec 8, 2025
@dotnet-policy-service
Copy link
Contributor

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

@jakobbotsch jakobbotsch marked this pull request as ready for review December 9, 2025 10:52
Copilot AI review requested due to automatic review settings December 9, 2025 10:52
@jakobbotsch
Copy link
Member Author

PTAL @dotnet/jit-contrib

Minor improvements

Copy link
Contributor

Copilot AI left a comment

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 enhances the JIT compiler's optimization to recognize and fold struct field offset computations when structs are promoted using old promotion. Previously, the optimization only worked when computing offsets between the same local variable, but failed when promoted struct fields were involved.

  • Extends the SUB node optimization to resolve parent local variables and aggregate field offsets for promoted struct fields
  • Enables compile-time constant folding of field offset patterns that previously required runtime computation
  • Significantly improves code generation by eliminating unnecessary address arithmetic

Copy link
Contributor

@adamperlin adamperlin left a comment

Choose a reason for hiding this comment

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

Just so that I understand here: this opt wasn't getting applied to some struct fields which were promoted to locals because they would each have their own local numbers. However, in these cases, the locals should share a parent local, so we can change the condition to be whether the parent local matches to ensure that the two locals are part of the same struct?

@jakobbotsch
Copy link
Member Author

Just so that I understand here: this opt wasn't getting applied to some struct fields which were promoted to locals because they would each have their own local numbers. However, in these cases, the locals should share a parent local, so we can change the condition to be whether the parent local matches to ensure that the two locals are part of the same struct?

Yes, that's pretty much it. When a promoted struct has its address taken (either the struct local itself or any of its fields) then the storage of the struct local and all its fields will be shared. This is called dependent promotion. It is a bit unusual -- in those cases we cannot enregister the struct fields, and we would usually have preferred not to promote at all. But we don't know whether the address is taken until after promotion today.
In more common cases the address is not taken and the struct local disappears completely. In those cases the fields are truly independent locals with their own storage (which can be registers instead of stack). That's called independent promotion.

In this particular case since we know the address is taken we know that the field would end up sharing its storage with the parent. And hence we can still compute the address statically.

Note that we are working towards removing this promotion mechanism (which we usually call "old" promotion) in favor of a new promotion mechanism called "physical promotion". Physical promotion does not have a concept of dependent promotion or shared storage, fields always get their own completely independent locals.

@jakobbotsch
Copy link
Member Author

Ping @dotnet/jit-contrib, need an approval here.

Copy link
Contributor

@adamperlin adamperlin left a comment

Choose a reason for hiding this comment

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

This looks good to me from what I understand!

@adamperlin
Copy link
Contributor

Thank you for the detailed explanation here. I was wondering what was meant by "old" vs. "new" promotion!

@jakobbotsch jakobbotsch merged commit 87b69c5 into dotnet:main Jan 7, 2026
128 of 130 checks passed
@jakobbotsch jakobbotsch deleted the struct-field-offset-pattern branch January 7, 2026 09:54
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants