diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index 9b7e4989514..87fcd750640 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -1,5 +1,6 @@ ### Fixed +* Fix incorrect `StructLayout(Size = 1)` emission for data-less struct unions where the compiler-generated tag field makes the actual runtime size larger. ([PR #19759](https://github.com/dotnet/fsharp/pull/19759)) * Fix FS0750 "This construct may only be used within computation expressions" incorrectly raised for `let!`/`use!`/`do!` appearing in the right-hand side of a plain `let` binding inside a computation expression. The right-hand side is now desugared as a nested computation of the same builder whose result is bound with `let!`, keeping its bindings correctly scoped. ([Issue #19457](https://github.com/dotnet/fsharp/issues/19457), [PR #19868](https://github.com/dotnet/fsharp/pull/19868)) * Stop leaking a `System.Diagnostics.Metrics.MeterListener` per `Cache` in DEBUG builds. Each cache created a `CacheMetrics.CacheMetricsListener` (which starts a `MeterListener` registered in the process-global metrics registry) and never disposed it, so listeners accumulated for the lifetime of the process. Because every cache hit/miss/add published to all registered listeners, the per-operation cost grew linearly with the number of leaked listeners, so repeated checks (and Debug FCS test runs) slowed down over time. The per-cache `CacheMetricsListener` and the per-instance `cacheId` tag are removed; `DebugDisplay` and tests now read the existing name-aggregated stats populated by the single `ListenToAll` listener, so no per-cache listener is created and no per-operation cost is added. ([PR #19995](https://github.com/dotnet/fsharp/pull/19995)) * Fix state machine lowering dropping the side-effectful receiver of an unused unit-typed member access (e.g. inside `task { (effectful()).UnitProp }`). ([Issue #13099](https://github.com/dotnet/fsharp/issues/13099), [PR #19885](https://github.com/dotnet/fsharp/pull/19885)) diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index 6e6f252606c..c170a757715 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -12228,18 +12228,10 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option } let layout = - // Structs with no instance fields get size 1, pack 0 + // Multi-case struct unions carry a hidden tag field; single-case struct unions + // are handled by the CLR's minimum-1-byte guarantee. No explicit size needed. if isStructTy g thisTy then - if - (tycon.AllFieldsArray.Length = 0 - || tycon.AllFieldsArray |> Array.exists (fun f -> not f.IsStatic)) - && (alternatives - |> Array.collect (fun a -> a.FieldDefs) - |> Array.exists (fun fd -> not fd.ILField.IsStatic)) - then - ILTypeDefLayout.Sequential { Size = None; Pack = None } - else - ILTypeDefLayout.Sequential { Size = Some 1; Pack = Some 0us } + ILTypeDefLayout.Sequential { Size = None; Pack = None } else ILTypeDefLayout.Auto diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/Basic.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/Basic.fs index dde97126f13..d47e20daefc 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/Basic.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/Basic.fs @@ -439,7 +439,7 @@ if Convert.ToString(prop, Globalization.CultureInfo.InvariantCulture) <> "B" the ] [] - let ``StructLayoutAttribute has size=1 for struct DUs with no instance fields`` () = + let ``StructLayoutAttribute doesn't have size=1 for multi-case struct DUs with no instance fields`` () = Fsx """ [] type Option<'T> = None | Some """ @@ -455,8 +455,6 @@ if Convert.ToString(prop, Globalization.CultureInfo.InvariantCulture) <> "B" the [runtime]System.IComparable, [runtime]System.Collections.IStructuralComparable { - .pack 0 - .size 1 .custom instance void [FSharp.Core]Microsoft.FSharp.Core.StructAttribute::.ctor() = ( 01 00 00 00 ) .custom instance void [runtime]System.Diagnostics.DebuggerDisplayAttribute::.ctor(string) = ( 01 00 15 7B 5F 5F 44 65 62 75 67 44 69 73 70 6C 61 79 28 29 2C 6E 71 7D 00 00 ) @@ -468,4 +466,28 @@ if Convert.ToString(prop, Globalization.CultureInfo.InvariantCulture) <> "B" the .field public static literal int32 Some = int32(0x00000001) } """ - ] \ No newline at end of file + ] + + [] + let ``StructLayoutAttribute doesn't have size=1 for single-case struct DU`` () = + Fsx """ + [] type X = | Y + """ + |> compile + |> shouldSucceed + |> verifyIL [ + """ + .class sequential autochar serializable sealed nested public beforefieldinit X + extends [runtime]System.ValueType + implements class [runtime]System.IEquatable`1, + [runtime]System.Collections.IStructuralEquatable, + class [runtime]System.IComparable`1, + [runtime]System.IComparable, + [runtime]System.Collections.IStructuralComparable + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.StructAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [runtime]System.Diagnostics.DebuggerDisplayAttribute::.ctor(string) = ( 01 00 15 7B 5F 5F 44 65 62 75 67 44 69 73 70 6C + 61 79 28 29 2C 6E 71 7D 00 00 ) + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 01 00 00 00 00 00 ) + """ + ] diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Structure/Structure.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Structure/Structure.fs index cef637ee350..e0179800b93 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Structure/Structure.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Structure/Structure.fs @@ -235,3 +235,21 @@ module Structure = compilation |> getCompilation |> verifyExecution + + [] + let ``sizeof reports correct sizes for various struct DU forms`` () = + Fsx """ +[] type SingleCase = | Only +[] type MultiNoData = A | B | C +[] type OneIntField = N | S of int +[] type TwoIntFields = T0 | T1 of x: int * y: int + +[] +let main _ = + printf "SingleCase=%i;MultiNoData=%i;OneIntField=%i;TwoIntFields=%i" sizeof sizeof sizeof sizeof + 0 + """ + |> asExe + |> compileAndRun + |> shouldSucceed + |> verifyOutput "SingleCase=1;MultiNoData=4;OneIntField=8;TwoIntFields=12"