Open manofstick opened 5 years ago
I think this is a duplicate of https://github.com/dotnet/fsharp/issues/7110
@dsyme This talks about a runtime error, #7110 about a compile time error. #7110 is arguably by design but this looks like a faulty optimization by the compiler (=bug). @manofstick A sample would obviously help :)
Ah yes.
The FSharp compiler shouldn't inline methods that have calls to methods that have been exposed via InternalsVisibleTo.
As mentioned in #7110, the intention is that the compiler doesn't inline in this case. So there may be a bug here
Sample of this bug...
Clone the repository https://github.com/manofstick/Cistern.Linq
Go to branch fsharp_7422
Load solution from src
directory. Set Cistern.Linq.Playground.FSharp
as the startup project.
Execute and get the following error at runtime:
Unhandled Exception: System.MethodAccessException: Attempt by method 'Program.main(System.String[])' to access method 'Cistern.Linq.ChainLinq.Links.Identity`1<System.Int32>.get_Instance()' failed.
at Program.main(String[] _arg1) in C:\src\Cistern.Linq\src\Cistern.Linq.Playground.FSharp\Program.fs:line 17
C:\Program Files\dotnet\dotnet.exe (process 7060) exited with code -1073741510.
Press any key to close this window . . .
To workaround, uncomment the [<MethodImpl(MethodImplOptions.NoInlining)>]
in on unfold Linq.fs
As promised in https://github.com/fscheck/FsCheck/issues/549 I made a simple example repository to reproduce this issue. It is constructed the same way as the scenario which caused this issue in FsCheck.
When looking at the generated IL, it indeed emits the newobj
calls instead of call
statements as we saw in https://github.com/fscheck/FsCheck/issues/549:
open assembly_A_optimized.referenceModulePublicA
open assembly_B_not_optimized.referenceModulePublicB
[<EntryPoint>]
let main _ =
makeExceptionA ()
makeExceptionAggressiveInliningA ()
makeExceptionNoInliningA ()
makeExceptionB ()
makeExceptionAggressiveInliningB ()
makeExceptionNoInliningB ()
0
compiles into (optimization on for using program):
// Location: C:\git\inlined_internal\usage_optimized\bin\Debug\netcoreapp3.1\usage_optimized.dll
// Sequence point data from C:\git\inlined_internal\usage_optimized\bin\Debug\netcoreapp3.1\usage_optimized.pdb
.class public abstract sealed auto ansi
ProgramOptimized
extends [System.Runtime]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
.method public static int32
main(
string[] _arg1
) cil managed
{
.entrypoint
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.EntryPointAttribute::.ctor()
= (01 00 00 00 )
.maxstack 8
// [6 5 - 6 22]
IL_0000: ldc.i4.0
IL_0001: brfalse.s IL_000b
IL_0003: ldnull
IL_0004: unbox.any [FSharp.Core]Microsoft.FSharp.Core.Unit
IL_0009: br.s IL_0011
IL_000b: newobj instance void [assembly_A_optimized]assembly_A_optimized.referenceModuleInternalA/TestExceptionA::.ctor()
IL_0010: throw
IL_0011: pop
// [7 5 - 7 40]
IL_0012: ldc.i4.0
IL_0013: brfalse.s IL_001d
IL_0015: ldnull
IL_0016: unbox.any [FSharp.Core]Microsoft.FSharp.Core.Unit
IL_001b: br.s IL_0023
IL_001d: newobj instance void [assembly_A_optimized]assembly_A_optimized.referenceModuleInternalA/TestExceptionA::.ctor()
IL_0022: throw
IL_0023: pop
// [8 5 - 8 29]
IL_0024: call !!0/*class [FSharp.Core]Microsoft.FSharp.Core.Unit*/ [assembly_A_optimized]assembly_A_optimized.referenceModulePublicA::makeExceptionNoInliningA<class [FSharp.Core]Microsoft.FSharp.Core.Unit>()
IL_0029: pop
// [10 5 - 10 19]
IL_002a: call !!0/*class [FSharp.Core]Microsoft.FSharp.Core.Unit*/ [assembly_B_not_optimized]assembly_B_not_optimized.referenceModulePublicB::makeExceptionB<class [FSharp.Core]Microsoft.FSharp.Core.Unit>()
IL_002f: pop
// [11 5 - 11 37]
IL_0030: call !!0/*class [FSharp.Core]Microsoft.FSharp.Core.Unit*/ [assembly_B_not_optimized]assembly_B_not_optimized.referenceModulePublicB::makeExceptionAggressiveInliningB<class [FSharp.Core]Microsoft.FSharp.Core.Unit>()
IL_0035: pop
// [12 5 - 12 29]
IL_0036: call !!0/*class [FSharp.Core]Microsoft.FSharp.Core.Unit*/ [assembly_B_not_optimized]assembly_B_not_optimized.referenceModulePublicB::makeExceptionNoInliningB<class [FSharp.Core]Microsoft.FSharp.Core.Unit>()
IL_003b: pop
// [14 5 - 14 6]
IL_003c: ldc.i4.0
IL_003d: ret
} // end of method ProgramOptimized::main
} // end of class ProgramOptimized
and with optimization off for the using program, this compiles into:
// Location: C:\git\inlined_internal\usage_not_optimized\bin\Debug\netcoreapp3.1\usage_not_optimized.dll
// Sequence point data from C:\git\inlined_internal\usage_not_optimized\bin\Debug\netcoreapp3.1\usage_not_optimized.pdb
.class public abstract sealed auto ansi
ProgramNotOptimized
extends [System.Runtime]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
.method public static int32
main(
string[] _arg1
) cil managed
{
.entrypoint
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.EntryPointAttribute::.ctor()
= (01 00 00 00 )
.maxstack 3
.locals init (
[0] string[] V_0
)
IL_0000: ldarg.0 // _arg1
IL_0001: stloc.0 // V_0
// [7 5 - 7 22]
IL_0002: call !!0/*class [FSharp.Core]Microsoft.FSharp.Core.Unit*/ [assembly_A_optimized]assembly_A_optimized.referenceModulePublicA::makeExceptionA<class [FSharp.Core]Microsoft.FSharp.Core.Unit>()
IL_0007: pop
// [8 5 - 8 40]
IL_0008: call !!0/*class [FSharp.Core]Microsoft.FSharp.Core.Unit*/ [assembly_A_optimized]assembly_A_optimized.referenceModulePublicA::makeExceptionAggressiveInliningA<class [FSharp.Core]Microsoft.FSharp.Core.Unit>()
IL_000d: pop
// [9 5 - 9 32]
IL_000e: call !!0/*class [FSharp.Core]Microsoft.FSharp.Core.Unit*/ [assembly_A_optimized]assembly_A_optimized.referenceModulePublicA::makeExceptionNoInliningA<class [FSharp.Core]Microsoft.FSharp.Core.Unit>()
IL_0013: pop
// [11 5 - 11 22]
IL_0014: call !!0/*class [FSharp.Core]Microsoft.FSharp.Core.Unit*/ [assembly_B_not_optimized]assembly_B_not_optimized.referenceModulePublicB::makeExceptionB<class [FSharp.Core]Microsoft.FSharp.Core.Unit>()
IL_0019: pop
// [12 5 - 12 40]
IL_001a: call !!0/*class [FSharp.Core]Microsoft.FSharp.Core.Unit*/ [assembly_B_not_optimized]assembly_B_not_optimized.referenceModulePublicB::makeExceptionAggressiveInliningB<class [FSharp.Core]Microsoft.FSharp.Core.Unit>()
IL_001f: pop
// [13 5 - 13 32]
IL_0020: call !!0/*class [FSharp.Core]Microsoft.FSharp.Core.Unit*/ [assembly_B_not_optimized]assembly_B_not_optimized.referenceModulePublicB::makeExceptionNoInliningB<class [FSharp.Core]Microsoft.FSharp.Core.Unit>()
IL_0025: pop
// [15 5 - 15 6]
IL_0026: ldc.i4.0
IL_0027: ret
} // end of method ProgramNotOptimized::main
} // end of class ProgramNotOptimized
Thing to notice is that both the reference assembly and the 'using' program must be compiled with optimization on for this to occur. When the using program does not enable optimization all calls emit correct IL code. When the using program has optimization enabled only the function calls to the optimized reference assembly have inlined internal calls.
AlsoInternalsVisibleTo
is not used in this example.
I hope this helps.
Assembly A exposed it's internal to Assembly B via the
InternalsVisibleTo
attribute. Assembly B provides a simple method to Assembly C, but because it's a simple method, it is inlined by the f# compiler into Assembly C. This results in Runtime error.Expected behavior
The FSharp compiler shouldn't inline methods that have calls to methods that have been exposed via InternalsVisibleTo.
Actual behavior
Runtime error.
Known workarounds
As per #5178, the use of
[<MethodImpl(MethodImplOptions.NoInlining)>]
stops the f# compiler from inling the method, but has the side effect that the JIT also stops inlining the call.Related information