Skip to content

Optimisation failure with structs in referenced projects #3923

@saul

Description

@saul

Structs referenced from other projects have a significant optimisation failure whereby an instance of the struct cannot be passed to functions - it is always re-instantiated to empty just before the function call.

Repro steps

Imagine you have the following C# project with a single file:

namespace ClassLibrary1
{
    public struct SomeStruct
    {
        private string _value;
        public string Value => _value;

        public void Set(string value)
        {
            _value = value;
        }
    }
}

The above project is referenced from F#:

open ClassLibrary1

let someFunc (s : SomeStruct) =
    printfn "From inner: %A" s.Value

[<EntryPoint>]
let main argv = 
    
    let s = SomeStruct()
    s.Set("there")
    printfn "Pre call:   %A" s.Value
    someFunc s
    printfn "Post call:  %A" s.Value

    0

Expected behavior

Output of the program is:

Pre call:   "there"
From inner: "there"
Post call:  "there"

Actual behavior

With optimisations enabled (e.g. Release mode), the output is:

Pre call:   "there"
From inner: <null>
Post call:  "there"

Known workarounds

No known workaround.

Related information

Severe bug, stopping me from using a C# library that uses structs.

The IL for the main function looks like:

.method public static int32  main(string[] argv) cil managed
{
  .entrypoint
  .custom instance void [FSharp.Core]Microsoft.FSharp.Core.EntryPointAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       40 (0x28)
  .maxstack  4
  .locals init ([0] valuetype [ClassLibrary1]ClassLibrary1.SomeStruct s,
           [1] valuetype [ClassLibrary1]ClassLibrary1.SomeStruct V_1)
  IL_0000:  nop
  IL_0001:  ldloca.s   V_1
  IL_0003:  initobj    [ClassLibrary1]ClassLibrary1.SomeStruct
  IL_0009:  ldloc.1
  IL_000a:  stloc.0
  IL_000b:  ldloca.s   s
  IL_000d:  ldstr      "there"
  IL_0012:  call       instance void [ClassLibrary1]ClassLibrary1.SomeStruct::Set(string)
  IL_0017:  ldloca.s   V_1 // WHY IS THIS HERE?
  IL_0019:  initobj    [ClassLibrary1]ClassLibrary1.SomeStruct // WHY IS THIS HERE?
  IL_001f:  ldloc.1
  IL_0020:  call       void Program::someFunc(valuetype [ClassLibrary1]ClassLibrary1.SomeStruct)
  IL_0025:  nop
  IL_0026:  ldc.i4.0
  IL_0027:  ret
} // end of method Program::main

IL_0017 and IL_0019 have been added by the optimiser - in Debug mode these instructions are not here, and the bad behaviour is not present.

Thanks

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area-Compiler-OptimizationThe F# optimizer, release code gen etc.BugImpact-Medium(Internal MS Team use only) Describes an issue with moderate impact on existing code.

    Type

    No fields configured for Bug.

    Projects

    Status

    In Progress

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions