-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Fix explicit offset of ByRefLike fields. #111584
Conversation
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.
src/coreclr/tools/Common/TypeSystem/Common/ExplicitLayoutValidator.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thank you!
There was a problem hiding this 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.)
src/coreclr/tools/Common/TypeSystem/Common/ExplicitLayoutValidator.cs
Outdated
Show resolved
Hide resolved
src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs
Outdated
Show resolved
Hide resolved
+1. I wasn't aware of this document, but I've updated it accordingly. |
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
orobject
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we mention it in https://learn.microsoft.com/en-us/dotnet/standard/native-interop/best-practices
Opened dotnet/docs#44672
native AOT legs failing due to dotnet/dnceng#4892 |
/ba-g Unrelated failures to PR. All failures are in Mono or related to script failures captured in dotnet/dnceng#4892. |
* 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)
New rule based on #111584 --------- Co-authored-by: Stephen Toub <stoub@microsoft.com>
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