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.93k stars 787 forks source link

TypeLoadException using CE applicatives and AOT #15488

Open thinkbeforecoding opened 1 year ago

thinkbeforecoding commented 1 year ago

Please provide a succinct description of the issue.

When using applicatives CE (let! and!) with 6 or more bindings, the code generated fails one compiled with AOT. Everything is fine with 5 bindings or less.

Provide the steps required to reproduce the problem:

Implement a Computation Expression with applicative and use it with 6 or more bindings:

type Identity<'t> = Identity of 't

let ret x = Identity x
let map f (Identity x) = Identity (f x)
let map2 f (Identity x) (Identity y) = Identity(f x y)

type IdentityBuilder() =
    member _.Return(x) = ret x
    member _.BindReturn(x, f) = map f x
    member _.MergeSources(x,y) = map2 (fun a b -> a,b) x y

let ident = IdentityBuilder()

[<EntryPoint>]
let main (args: string[]) =
    let (Identity(a,b,c,d,e,f)) =
        ident {
            let! a = ret "a"
            and! b = ret "b"
            and! c = ret "c"
            and! d = ret "d"
            and! e = ret "e"
            and! f = ret "f"

            return a,b,c,d,e,f

        }

    printfn $"{a}{b}{c}{d}{e}{f}"
    0

Here this is a simple Identity functor implementing map2.

Compile it with AOT:

dotnet publish -c Release -p:SelfContained=true -p:PublishAot=true -o .\dist\ 

And run it. It fails:

Unhandled Exception: System.TypeLoadException: Attempted to load a type that was not created during ahead of time compilation.
   at Internal.Runtime.CompilerHelpers.ThrowHelpers.ThrowUnavailableType() + 0x33
   at Microsoft.FSharp.Core.FSharpFunc`2.InvokeFast[V](FSharpFunc`2, T, TResult) + 0x1a
   at Program.main(String[]) + 0x227
   at tstconsole!<BaseAddress>+0x1c779b

Expected behavior

The application should print "abcdef".

Actual behavior

The application fails at runtime with a TypeLoadException.

Known workarounds

It is possible to workaround by splitting the bindings in smaller chunks:

    let (Identity(a,b,c,d,e,f)) =
        ident {
            let! a = ret "a"
            and! b = ret "b"
            and! c = ret "c"
            and! (d,e,f) =
                ident {
                    let! d = ret "d"
                    and! e = ret "e"
                    and! f = ret "f"
                    return d,e,f
                }     
            return a,b,c,d,e,f
        }

Related information

I discovered this while working on an applicative command line argument parser that is not using reflection to make it work nicely with AOT. However, this bug - while having a workaround - makes it likely to break weirdly when the code is modified.

The way CE works should not produce dynamically loaded types that are not compatible with AOT.

Numpsy commented 4 months ago

I tried building this with all the trim analysis warnings enabled, and got some warnings like this:

ILC : AOT analysis warning IL3054: Microsoft.FSharp.Core.FSharpFunc`2<String,String>.InvokeFast<Tuple`3<String,String,Tuple`3<String,String,Tuple`3<String,String,Tuple`3<S
tring,String,String>>>>,Tuple`3<String,String,Tuple`3<String,String,Tuple`3<String,String,Tuple`3<String,String,Tuple`3<String,String,String>>>>>>(FSharpFunc`2<String,FSha
rpFunc`2<String,FSharpFunc`2<Tuple`3<String,String,Tuple`3<String,String,Tuple`3<String,String,Tuple`3<String,String,String>>>>,Tuple`3<String,String,Tuple`3<String,String
,Tuple`3<String,String,Tuple`3<String,String,Tuple`3<String,String,String>>>>>>>>,String,String,Tuple`3<String,String,Tuple`3<String,String,Tuple`3<String,String,Tuple`3<S
tring,String,String>>>>): Generic expansion to 'Microsoft.FSharp.Core.OptimizedClosures.FSharpFunc`4<String,String,Tuple`3<String,String,Tuple`3<String,String,Tuple`3<Stri
ng,String,Tuple`3<String,String,String>>>>,Tuple`3<String,String,Tuple`3<String,String,Tuple`3<String,String,Tuple`3<String,String,Tuple`3<String,String,String>>>>>>' was
aborted due to generic recursion. An exception will be thrown at runtime if this codepath is ever reached. Generic recursion also negatively affects compilation speed and
the size of the compilation output. It is advisable to remove the source of the generic recursion by restructuring the program around the source of recursion. The source o
f generic recursion might include: 'Microsoft.FSharp.Core.FSharpFunc`2', '<StartupCode$FSharp-Core>.$Prim-types.op_Implicit@3875', '<StartupCode$FSharp-Core>.$Prim-types.o
p_Implicit@3880-1', '<StartupCode$FSharp-Core>.$Prim-types.op_Implicit@3883-2', '<StartupCode$FSharp-Core>.$Prim-types.op_Implicit@3886-3', '<StartupCode$FSharp-Core>.$Pri
m-types.FromConverter@3889', '<StartupCode$FSharp-Core>.$Prim-types.ToConverter@3892', 'Microsoft.FSharp.Core.OptimizedClosures.FSharpFunc`3', 'Microsoft.FSharp.Core.Optim
izedClosures.FSharpFunc`4', 'Microsoft.FSharp.Core.OptimizedClosures.FSharpFunc`5', 'Microsoft.FSharp.Core.OptimizedClosures.FSharpFunc`6', 'Microsoft.FSharp.Core.Optimize
dClosures.Invoke@3756', 'Microsoft.FSharp.Core.OptimizedClosures.Adapt@3763', 'Microsoft.FSharp.Core.OptimizedClosures.Invoke@3770-1', 'Microsoft.FSharp.Core.OptimizedClos
ures.Adapt@3779-1', 'Microsoft.FSharp.Core.OptimizedClosures.Adapt@3783-2', 'Microsoft.FSharp.Core.OptimizedClosures.Adapt@3797-3', 'Microsoft.FSharp.Core.OptimizedClosure
s.Adapt@3802-4', 'Microsoft.FSharp.Core.OptimizedClosures.Adapt@3806-5', 'Microsoft.FSharp.Core.OptimizedClosures.Invoke@3809-2', 'Microsoft.FSharp.Core.OptimizedClosures.
Invoke@3817-3', 'Microsoft.FSharp.Core.OptimizedClosures.Adapt@3827-6', 'Microsoft.FSharp.Core.OptimizedClosures.Adapt@3832-7', 'Microsoft.FSharp.Core.OptimizedClosures.Ad
apt@3837-8', 'Microsoft.FSharp.Core.OptimizedClosures.Adapt@3841-9' [S:\DevTest\ce_test\ce_test.fsproj]

Which makes it sound like the issue is that there are too many layers of nested tuples and the AOT compiler is giving up?

A quick test makes it seem like the issue can be alleviated to a degree by implementing MergeSourcesN - e.g. if I add an implementation of MergeSources3 to IdentityBuilder the it works with 10 bindings and fails with 11.