19

Using F# Interactive, you can verify the following sizes:

// sizeof<A> = 4 bytes
type A (i: int) = struct end

// sizeof<B<int>> = 8 bytes (use any type parameter)
type B<'T> (i: int) = struct end

The reason for the extra size seems to be the presence of an integer __dummy field in the generic case. Using F# Interactive again, you can see this using typeof:

  • typeof<A> shows DeclaredFields = [|Int32 i|]
  • typeof<B<int>> shows DeclaredFields = [|Int32 i; Int32 __dummy|]

I don't understand why this __dummy field has been added.

I think the code responsible for adding it is here:

https://github.com/fsharp/FSharp.Compiler.Service/blob/master/src/fsharp/ilxgen.fs

Line 6377 shows this:

if requiresExtraField then 
    yield mkILInstanceField("__dummy",cenv.g.ilg.typ_int32,None,ILMemberAccess.Assembly) ]

Line 6290 is where requiresExtraField is defined:

let requiresExtraField = 
    let isEmptyStruct = 
        (match ilTypeDefKind with ILTypeDefKind.ValueType -> true | _ -> false) &&
        // All structs are sequential by default 
        // Structs with no instance fields get size 1, pack 0
        tycon.AllFieldsAsList |> List.exists (fun f -> not f.IsStatic)

    isEmptyStruct && cenv.opts.workAroundReflectionEmitBugs && not tycon.TyparsNoRange.IsEmpty

I assume that isEmptyStruct is supposed to mean that the struct does not have any instance fields. But the code as written is testing whether the struct does have any instance fields, which for most structs, including mine, is going to be true. I think the last part of the final test is whether there are any generic type parameters. So requiresExtraField is false for type A (not generic) and true for type B (generic type).

Is this a compiler bug, or is the code correct? If it is correct, then what's the purpose of this __dummy field? Is there some way I can avoid having it?

As another test, I removed my one and only instance field, and not surprisingly, I got the following sizes, showing that the __dummy field was no longer added:

// sizeof<AA> = 1
type AA = struct end

// sizeof<BB<int>> = 1
type BB<'T> = struct end

The reason I want to have a value type, rather than a reference type, is that I will be storing lots of these objects in my data structures, not just passing them around.

bananasareyellow
  • 341
  • 1
  • 12
  • "cenv.opts.workAroundReflectionEmitBugs" suggests that this is a workaround to some reflection emit bug. Have you checked if C# does the same thing? - A quick look at the code history shows that this dates back to at least November 2010, F# 2.0 - the earliest version of the code available online AFAIK. – Asik Sep 15 '14 at 20:35
  • 1
    Since sizeof> = 4 in a program, is its size in the interactive really an issue for you? – jyoung Sep 15 '14 at 22:21
  • @jyoung: Oh, I never realized that this was only the case with F# Interactive. I just tried sizeof> in a program, like you said, and it gives 4, whereas F# Interactive gives 8. Then my issue is solved, thank you! I found that source file using Google, so I didn't know the bigger picture of what it is for. In the corresponding fsi file, the public `IlxAssemblyGenerator` type at the end says that it is _An incremental ILX code generator for a single assembly_. Just for my edification, what is that all about? – bananasareyellow Sep 16 '14 at 08:39
  • 1
    Type the lines let f()=1;; let g()=f();; let f()=2;; g();; You'll see that g() is still 1. So a 'new' f() was (incrementally) added to the assembly, but the 'old' f() is still there being referenced by the previous increments of the code. – jyoung Sep 16 '14 at 13:11
  • @jyoung: Thanks. So the "ILX code generator" is only used in situations like F# Interactive, and is not part of the regular F# compiler, I assume. – bananasareyellow Sep 16 '14 at 13:23
  • I think it is the same generator. Your problem arises because of the fscopts.fs's line: 'workAroundReflectionEmitBugs=tcConfig.isInteractive; // REVIEW: is this still required?' – jyoung Sep 16 '14 at 14:38
  • @jyoung: Ah ha, the mystery is now solved. Thanks once again. By the way, how do I mark my own posting as 'answered'? Do I use the _Answer Your Question_ button? – bananasareyellow Sep 16 '14 at 15:45
  • 1
    bananasareyellow, I would invite @JYoung to post the answer as an answer, so credit can be given where credit is due. If JYoung is unconcerned about reputation and declines the invitation, you could post the answer yourself and accept that. – phoog Sep 16 '14 at 19:12
  • 1
    Go ahead bananasareyellow, post the answer. You understand it better than me now. – jyoung Sep 16 '14 at 21:22

1 Answers1

1

The explanation is given by @jyoung in the comments below my original posting.

The last line of requiresExtraField also tests cenv.opts.workAroundReflectionEmitBugs. This flag appears to be set in fscopts.fs. The line of code is:

workAroundReflectionEmitBugs=tcConfig.isInteractive; // REVIEW: is this still required?

So the problem of the extra __dummy field only occurs in F# Interactive.

bananasareyellow
  • 341
  • 1
  • 12
  • This doesn't answer the question of whether the logic for `isEmptyStruct` is backwards, as it appears to be. – ildjarn Sep 18 '14 at 06:16
  • @ildjarn: You are right, it doesn't. I marked it as answered because my principle issue was the size of the struct due to the presence of the dummy field. That test is still questionable. – bananasareyellow Sep 19 '14 at 07:58