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

Fix explicit offset of ByRefLike fields. #111584

Merged
merged 16 commits into from
Jan 31, 2025

Conversation

AaronRobinsonMSFT
Copy link
Member

A ByRefLike type was previously forced to be pointer aligned so ref fields were implicitly aligned. Using Explicit layout one can create valid aligned ByRefLike types with no object pointers. This change propagates the base offset of the ByRefLike type and includes that for determining offsets of fields in the ByRefLike type.

Fixes #111260

A ByRefLike type was previously forced to be
pointer aligned so ref fields were implicitly aligned.
Using Explicit layout one can create valid aligned
ByRefLike types with no object pointers. This change
propagates the base offset of the ByRefLike type and
includes that for determining offsets of fields in the
ByRefLike type.
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. Thank you!

Karinza38

This comment was marked as spam.

Copy link
Member

@MichalStrehovsky MichalStrehovsky left a comment

Choose a reason for hiding this comment

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

Do we need to add this to breaking-change-rules.md? Would we consider it a breaking change to add a ref field to a ref struct that didn't previously have a ref field?

(We do consider switching between struct and ref struct a breaking change in that doc.)

@AaronRobinsonMSFT
Copy link
Member Author

Do we need to add this to breaking-change-rules.md?

+1. I wasn't aware of this document, but I've updated it accordingly.

AaronRobinsonMSFT and others added 3 commits January 30, 2025 04:54
Co-authored-by: Michal Strehovský <MichalStrehovsky@users.noreply.github.com>
@@ -169,6 +169,8 @@ Breaking Change Rules

* Changing a `struct` type to a `ref struct` type and vice versa

* Adding a new `ref` field to a type that didn't have any `ref` field before (recursively)
Copy link
Member

Choose a reason for hiding this comment

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

I am not following what's the rationale for this. How is this different from adding/removing any fields? We have no guarantees for internal type layout compatibility, unless the guarantee is explicitly documented.

"Changing a struct type to a ref struct type and vice versa" is very different - it is very obvious compile time breaking change.

Copy link
Member Author

@AaronRobinsonMSFT AaronRobinsonMSFT Jan 30, 2025

Choose a reason for hiding this comment

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

I think the point of it is that now adding a ref field could break someone if they were using the type in an Explicit offset scenario. For example, the BCL has some ref struct that just has an int field. A user consumes that type in a user defined type as an Explicit field and the containing library is published to NuGet. In the next major version a ref int field is added. When that NuGet package is consumed on a new BCL, the library fails to load because the ref int field forces a new alignment restriction.

By definition this would now break. We can argue if it matters, but it would cause a failure at type load time.

Copy link
Member

Choose a reason for hiding this comment

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

I think the point of it is that now adding a ref field could break someone if they were using the type in an Explicit offset scenario.

It is the case for any field changes in public BCL value types. You can get the same break by adding/removing int or object fields too. There is nothing special about ref fields here.

Field layout of BCL value types is not a public contract that people can depend on, unless it is explicitly documented for specific types.

Copy link
Member Author

Choose a reason for hiding this comment

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

"Changing a struct type to a ref struct type and vice versa" is very different - it is very obvious compile time breaking change.

It is the case for any field changes in public BCL value types. You can get the same break by adding/removing int or object fields too. There is nothing special about ref fields here.

Between the two statements above, it looks like the distinction in this case is compile time vs run time failure. Is that the conversation we are having? In the sense that compile time failure is not acceptable, but run time failure is?

Copy link
Member

Choose a reason for hiding this comment

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

There happens to be a compile time vs. runtime time difference between these two case, but that's just accidental. Runtime breaking changes are not acceptable either in general.

I think that the discussion that we are having here is about distinction between what we define as a breaking change vs. change that can break apps that depend on undocumented behaviors.

Copy link
Member

@MichalStrehovsky MichalStrehovsky Jan 30, 2025

Choose a reason for hiding this comment

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

It is the case for any field changes in public BCL value types. You can get the same break by adding/removing int or object fields too. There is nothing special about ref fields here.

I thought we already consider such things breaking as well - for example adding an object typed field to a struct makes the struct no longer meet unmanaged constraint. I recall Roslyn team even did work to put a dummy object field into such struct for reference assemblies because these non-contractual private implementation details actually matter.

Copy link
Member

Choose a reason for hiding this comment

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

If the libraries folks agree, I think it would be fine to explicitly match the current Roslyn behavior and say that adding first field of a new kind to an existing value type is disallowed change. Kind can be any of objref, byref or generic type argument, and the rule applies recursively.

Introducing new significant state in existing value types is close to impossible to do in compatible way for other reasons (e.g. it is why we have both DateTime and DateTimeOffset), so these situations do not come up in practice. I guess it is the reason why nobody bothered to mention it in the breaking changes doc up until now.

Copy link
Member Author

Choose a reason for hiding this comment

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

I will create a discussion issue for this topic and loop in libraries folks. Updating the markdown is a cheaper PR than continually running the build/test legs.

Copy link
Member

Choose a reason for hiding this comment

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

This falls into the same category as dangers of private reflection that are not particularly well documented either.

I have created #112114

Copy link
Member

Choose a reason for hiding this comment

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

@AaronRobinsonMSFT
Copy link
Member Author

native AOT legs failing due to dotnet/dnceng#4892

@AaronRobinsonMSFT
Copy link
Member Author

/ba-g Unrelated failures to PR. All failures are in Mono or related to script failures captured in dotnet/dnceng#4892.

@AaronRobinsonMSFT AaronRobinsonMSFT merged commit 847a8c2 into dotnet:main Jan 31, 2025
142 of 147 checks passed
@AaronRobinsonMSFT AaronRobinsonMSFT deleted the runtime_111260 branch January 31, 2025 12:03
grendello added a commit to grendello/runtime that referenced this pull request Feb 3, 2025
* main:
  System.Net.Http.WinHttpHandler.StartRequestAsync assertion failed (dotnet#109799)
  Keep test PDB in helix payload for native AOT (dotnet#111949)
  Build the RID-specific System.IO.Ports packages in the VMR (dotnet#112054)
  Always inline number conversions (dotnet#112061)
  Use Contains{Any} in Regex source generator (dotnet#112065)
  Update dependencies from https://github.com/dotnet/arcade build 20250130.5 (dotnet#112013)
  JIT: Transform single-reg args to FIELD_LIST in physical promotion (dotnet#111590)
  Ensure that math calls into the CRT are tracked as needing vzeroupper (dotnet#112011)
  Use double.ConvertToIntegerNative where safe to do in System.Random (dotnet#112046)
  JIT: Compute `fgCalledCount` after synthesis (dotnet#112041)
  Simplify boolean logic in `TimeZoneInfo` (dotnet#112062)
  JIT: Update type when return temp is freshly created (dotnet#111948)
  Remove unused build controls and simplify DotNetBuild.props (dotnet#111986)
  Fix case-insensitive JSON deserialization of enum member names (dotnet#112028)
  WasmAppBuilder: Remove double computation of a value (dotnet#112047)
  Disable LTCG for brotli and zlibng. (dotnet#111805)
  JIT: Improve x86 unsigned to floating cast codegen (dotnet#111595)
  simplify x86 special intrinsic imports (dotnet#111836)
  JIT: Try to retain entry weight during profile synthesis (dotnet#111971)
  Fix explicit offset of ByRefLike fields. (dotnet#111584)
AaronRobinsonMSFT added a commit that referenced this pull request Feb 3, 2025
New rule based on #111584

---------

Co-authored-by: Stephen Toub <stoub@microsoft.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

TypeLoadException when a ref struct using explicit layout has fields of type ref struct
4 participants