Closed zpodlovics closed 4 years ago
IIRC this is because of a .NET limitation that all structs must not be zero-sized.
It seems that it is not required. Createing an empty struct in C# works as expected:
C# code:
public struct Empty
{
// nothing
}
Result IL:
class public sealed sequential ansi beforefieldinit
Empty
extends [mscorlib]System.ValueType
{
} // end of class Empty
It also seems to work already with F# compiler:
module Test
[<Struct>]
type Empty =
struct
end
Result IL:
// Type: Test
// Assembly: empty, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
// MVID: 59C4E333-C00F-397C-A745-038333E3C459
// Location: C:\development\Tests\emptystruct\empty.dll
// Sequence point data from decompiler
.class public abstract sealed auto ansi
Test
extends [mscorlib]System.Object
{
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags)
= (01 00 07 00 00 00 00 00 ) // ........
// int32(7) // 0x00000007
.class nested public sealed sequential ansi serializable
Empty
extends [mscorlib]System.ValueType
implements class [mscorlib]System.IEquatable`1<valuetype Test/Empty>, [mscorlib]System.Collections.IStructuralEquatable, class [mscorlib]System.IComparable`1<valuetype Test/Empty>, [mscorlib]System.IComparable, [mscorlib]System.Collections.IStructuralComparable
{
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.StructAttribute::.ctor()
= (01 00 00 00 )
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags)
= (01 00 03 00 00 00 00 00 ) // ........
// int32(3) // 0x00000003
.method public final hidebysig virtual instance int32
CompareTo(
valuetype Test/Empty obj
) cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
= (01 00 00 00 )
.maxstack 3
.locals init (
[0] valuetype Test/Empty& V_0
)
IL_0000: ldarga.s obj
IL_0002: stloc.0 // V_0
IL_0003: ldc.i4.0
IL_0004: ret
} // end of method Empty::CompareTo
.method public final hidebysig virtual instance int32
CompareTo(
object obj
) cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
= (01 00 00 00 )
.maxstack 3
.locals init (
[0] valuetype Test/Empty V_0,
[1] valuetype Test/Empty& V_1
)
IL_0000: ldarg.1 // obj
IL_0001: unbox.any Test/Empty
IL_0006: stloc.0 // V_0
IL_0007: ldloca.s V_0
IL_0009: stloc.1 // V_1
IL_000a: ldc.i4.0
IL_000b: ret
} // end of method Empty::CompareTo
.method public final hidebysig virtual instance int32
CompareTo(
object obj,
class [mscorlib]System.Collections.IComparer comp
) cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
= (01 00 00 00 )
.maxstack 3
.locals init (
[0] valuetype Test/Empty V_0
)
IL_0000: ldarg.1 // obj
IL_0001: unbox.any Test/Empty
IL_0006: stloc.0 // V_0
IL_0007: ldc.i4.0
IL_0008: ret
} // end of method Empty::CompareTo
.method public final hidebysig virtual instance int32
GetHashCode(
class [mscorlib]System.Collections.IEqualityComparer comp
) cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
= (01 00 00 00 )
.maxstack 8
IL_0000: ldc.i4.0
IL_0001: ret
} // end of method Empty::GetHashCode
.method public final hidebysig virtual instance int32
GetHashCode() cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
= (01 00 00 00 )
.maxstack 8
IL_0000: ldarg.0 // this
IL_0001: call class [mscorlib]System.Collections.IEqualityComparer [FSharp.Core]Microsoft.FSharp.Core.LanguagePrimitives::get_GenericEqualityComparer()
IL_0006: call instance int32 Test/Empty::GetHashCode(class [mscorlib]System.Collections.IEqualityComparer)
IL_000b: ret
} // end of method Empty::GetHashCode
.method public final hidebysig virtual instance bool
Equals(
object obj,
class [mscorlib]System.Collections.IEqualityComparer comp
) cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
= (01 00 00 00 )
.maxstack 3
.locals init (
[0] valuetype Test/Empty V_0
)
IL_0000: ldarg.1 // obj
IL_0001: call bool [FSharp.Core]Microsoft.FSharp.Core.LanguagePrimitives/IntrinsicFunctions::TypeTestGeneric<valuetype Test/Empty>(object)
IL_0006: brtrue.s IL_000a
IL_0008: ldc.i4.0
IL_0009: ret
IL_000a: ldarg.1 // obj
IL_000b: unbox.any Test/Empty
IL_0010: stloc.0 // V_0
IL_0011: ldc.i4.1
IL_0012: ret
} // end of method Empty::Equals
.method public final hidebysig virtual instance bool
Equals(
valuetype Test/Empty obj
) cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
= (01 00 00 00 )
.maxstack 3
.locals init (
[0] valuetype Test/Empty& V_0
)
IL_0000: ldarga.s obj
IL_0002: stloc.0 // V_0
IL_0003: ldc.i4.1
IL_0004: ret
} // end of method Empty::Equals
.method public final hidebysig virtual instance bool
Equals(
object obj
) cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
= (01 00 00 00 )
.maxstack 3
.locals init (
[0] valuetype Test/Empty V_0,
[1] valuetype Test/Empty V_1,
[2] valuetype Test/Empty& V_2
)
IL_0000: ldarg.1 // obj
IL_0001: call bool [FSharp.Core]Microsoft.FSharp.Core.LanguagePrimitives/IntrinsicFunctions::TypeTestGeneric<valuetype Test/Empty>(object)
IL_0006: brtrue.s IL_000a
IL_0008: ldc.i4.0
IL_0009: ret
IL_000a: ldarg.1 // obj
IL_000b: unbox.any Test/Empty
IL_0010: stloc.0 // V_0
IL_0011: ldloc.0 // V_0
IL_0012: stloc.1 // V_1
IL_0013: ldloca.s V_1
IL_0015: stloc.2 // V_2
IL_0016: ldc.i4.1
IL_0017: ret
} // end of method Empty::Equals
} // end of class Empty
} // end of class Test
Which is only longer because of equality impl
The struct without member require non-zero sized runtime representation, however it does not mean that it needs dummy members to have non-zero runtime runtime representation. For an empty struct the CoreCLR will generate a one byte runtime representation (this is what the sizeof
CoreCLR EmptyStructs examples: https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/tests/src/JIT/jit64/valuetypes/nullable/box-unbox/structdef.cs https://github.com/dotnet/coreclr/blob/e65bb61cdd856af63e4c096632dca33ec3984ef4/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EmptyStruct.cs https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/tests/src/JIT/Directed/nullabletypes/Desktop/StructDefinitions.cs https://github.com/dotnet/coreclr/search?q=EmptyStruct&type=Code&utf8=%E2%9C%93
Mono EmptyStruct examples: https://github.com/mono/mono/search?utf8=%E2%9C%93&q=emptystruct&type=
true emitted struct seems to have a [<StructLayout(Size=1)>] attribute.
I'm not inclined to fix this in the name of stability w.r.t. various runtimes and reflection emit. It was needed once and causes no real harm.
In F# Interactive single case struct discriminated union wrongly have a __dummy field. I can reproduce on Ubuntu 16.04 + Mono 5.2.x + F# 4.1 and Windows7 + F# 4.1 (standalone) Interactive.
Repro steps
Provide the steps required to reproduce the problem
Start an F# Interactive console
Copy the folowing content to the console
Please note, the following expression will crash Mono 5.2.x on Linux in F# interactive but it works on .NET.
Expected behavior
Measure generics erased on compilation.
Actual behavior
__dummy fields generated when used in F# Interactive
Known workarounds
None.
Related information
Related issues:
654
655
668
Related pull requests:
656
Provide any related information
.NET Runtime (Windows7) + F# 4.1 (standalone install)