Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
Original file line number Diff line number Diff line change
@@ -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))
Expand Down
14 changes: 3 additions & 11 deletions src/Compiler/CodeGen/IlxGen.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ if Convert.ToString(prop, Globalization.CultureInfo.InvariantCulture) <> "B" the
]

[<Fact>]
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 """
[<Struct>] type Option<'T> = None | Some
"""
Expand All @@ -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 )
Expand All @@ -468,4 +466,28 @@ if Convert.ToString(prop, Globalization.CultureInfo.InvariantCulture) <> "B" the
.field public static literal int32 Some = int32(0x00000001)
}
"""
]
]

[<Fact>]
let ``StructLayoutAttribute doesn't have size=1 for single-case struct DU`` () =
Fsx """
[<Struct>] 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<valuetype Test/X>,
[runtime]System.Collections.IStructuralEquatable,
class [runtime]System.IComparable`1<valuetype Test/X>,
[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 )
"""
]
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,21 @@ module Structure =
compilation
|> getCompilation
|> verifyExecution

[<Fact>]
let ``sizeof reports correct sizes for various struct DU forms`` () =
Fsx """
[<Struct>] type SingleCase = | Only
[<Struct>] type MultiNoData = A | B | C
[<Struct>] type OneIntField = N | S of int
[<Struct>] type TwoIntFields = T0 | T1 of x: int * y: int

[<EntryPoint>]
let main _ =
printf "SingleCase=%i;MultiNoData=%i;OneIntField=%i;TwoIntFields=%i" sizeof<SingleCase> sizeof<MultiNoData> sizeof<OneIntField> sizeof<TwoIntFields>
0
"""
|> asExe
|> compileAndRun
|> shouldSucceed
|> verifyOutput "SingleCase=1;MultiNoData=4;OneIntField=8;TwoIntFields=12"
Loading