dotnet / fsharp

The F# compiler, F# core library, F# language service, and F# tooling integration for Visual Studio
https://dotnet.microsoft.com/languages/fsharp
MIT License
3.87k stars 783 forks source link

In F# Interactive single case struct discriminated union wrongly have a __dummy field #3491

Closed zpodlovics closed 4 years ago

zpodlovics commented 7 years ago

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

  1. Start an F# Interactive console

  2. Copy the folowing content to the console


open System

[<Measure>] type m1

[<Struct>]
type StructDU =
    | Case1

[<Struct>]
type StructDUM<[<Measure>]'M> =
    | Case1

printfn "sizeof<StructDU>: %d" sizeof<StructDU>
printfn "sizeof<StructDUM<_>: %d" sizeof<StructDUM<_>>

Please note, the following expression will crash Mono 5.2.x on Linux in F# interactive but it works on .NET.

typeof<StructDUM<_>>.GetField("__dummy", enum 0xffffffff) 

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)

Mono JIT compiler version 5.2.0.215 (tarball Mon Aug 14 15:46:23 UTC 2017)
Copyright (C) 2002-2014 Novell, Inc, Xamarin Inc and Contributors. www.mono-project.com
    TLS:           __thread
    SIGSEGV:       altstack
    Notifications: epoll
    Architecture:  amd64
    Disabled:      none
    Misc:          softdebug 
    LLVM:          supported, not enabled.
    GC:            sgen (concurrent by default)
dsyme commented 7 years ago

IIRC this is because of a .NET limitation that all structs must not be zero-sized.

thinkbeforecoding commented 7 years ago

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
thinkbeforecoding commented 7 years ago

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

zpodlovics commented 7 years ago

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 will return). It fact both Mono both CoreCLR codebase have several tests for empty struct cases, because it important to test every corner cases for JIT / GC and the other parts.

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=

thinkbeforecoding commented 7 years ago

true emitted struct seems to have a [<StructLayout(Size=1)>] attribute.

dsyme commented 4 years ago

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.