`Tracking` F# AOT, Trimmability, ILLink
Note This is a "meta" issue for tracking purposes, since GitHub does not support special types of issues. @kant2002 I've converted it to the tracking issue, if you don't mind.
Original description
AOT and trimmability is around the corner, and FSharp has issues with some of these technologies. There visible issues with NativeAOT, but I suspect R2R may AOT less code due to these issues or maybe trimmer keep more code.
Describe the solution you'd like
I would like to create action plan of known issues for NativeAOT and trimmability of generated FSharp code to have smaller steps toward F# ❤️ AOT goal. I would like that reflection-free mode of NativeAOT, as opposed to normal NativeAOT mode where reflection working would be also considered.
Describe alternatives you've considered
Seems to be https://github.com/fsharp/fslang-suggestions/issues/919 reach some conclusion, so I think we should go to action. Also I consider target just NativeAOT with reflection (default), but seems to be having no-reflection is easy way to uncover issues with trimmability and unnecessary reflection usage in app. I consider that approach more of technique to find issues faster, plus I may have smaller app.
Open issues
- [ ] https://github.com/dotnet/fsharp/issues/14355
- [x] https://github.com/dotnet/fsharp/issues/14873
- [ ]
printfusage requires a lot of rd.xml boilerplate, like it shows here. - [ ]
FSharp.Core.OptimizedClosures.FSharpFunchas recusive generics (?) - [ ]
printf "%А"does not work in reflection-free mode. There preliminary work here https://github.com/dotnet/fsharp/pull/12960 - [ ] Interpolated strings like
$"{x}"breaks in reflection-free mode. - [x] https://github.com/dotnet/fsharp/issues/12819
- [ ] https://github.com/dotnet/fsharp/issues/6329
- [x] https://github.com/dotnet/fsharp/issues/9283
I'll make a start on the RFC this weekend (reflection free codegen).
What is the "recursive generics" issue? Does it manifest in non-string-related code? If not, then this issue is a subset of the other one on string functions.
@charlesroddie the recursive generic issue can be triggered by printf I suppose, but I can reproduce with Array2D.init 2 3 (fun i j -> i + j) and ILC would print scary warning. Sample which I give, does not crash app, but I maybe I did not put enough efforts into trying. Maybe @MichalStrehovsky can tell us - does issue with recursive generics crash some sample app of him, or not?
Maybe @MichalStrehovsky can tell us - does issue with recursive generics crash some sample app of him, or not?
You can make generic recursion crash something at runtime. I'm not too keen on fiddling with F# to find it, but here's some C#:
System.Console.WriteLine(Count<int>(3));
System.Console.WriteLine(Count<int>(100));
static int Count<T>(int i) =>
i switch
{
0 => 0,
_ => 1 + Count<Foo<T>>(i - 1)
};
struct Foo<T> { }
This will print following warning at compile time:
ILC: AOT analysis warning IL3054: Program.<<Main>$>g__Count|0_0<Foo`1<Foo`1<Foo`1<Foo`1<Int32>>>>>(Int32): Generic expansion to 'Program.<<Main>$>g__Count|0_0<Foo`1<Foo`1<Foo`1<Foo`1<Foo`1<Int32>>>>>>(Int32)' 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 of generic recursion might include: 'Program.<<Main>$>g__Count|0_0<T>(Int32)'
The at runtime, the first call will succeed and print the number (because the depth was sufficiently low), but throws for the second call:
3
Unhandled Exception: System.TypeLoadException: Could not load type 'Program' from assembly 'repro, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null'.
at Internal.Runtime.CompilerHelpers.ThrowHelpers.ThrowTypeLoadExceptionWithArgument(ExceptionStringID, String, String, String) + 0x42
at Program.<<Main>$>g__Count|0_0[T](Int32) + 0x29
at Program.<<Main>$>g__Count|0_0[T](Int32) + 0x3c
at Program.<<Main>$>g__Count|0_0[T](Int32) + 0x3c
at Program.<<Main>$>g__Count|0_0[T](Int32) + 0x3c
at Program.<<Main>$>g__Count|0_0[T](Int32) + 0x3c
at Program.<Main>$(String[]) + 0x2f
Interpolated strings like $"{x}" breaks in reflection-free mode.
We don't make any effort in the dotnet/runtime repo to make libraries work with reflection-free mode, besides no-brainer easy fixes that would be accepted even without reflection-free mode. Reflection-free mode things should ideally be separate issues that either have a no-brainer easy fix that would be acceptable even without the NativeAOT reflection-free mode as a motivating scenario, or can be easily Won't fixed as undesirable.
Trimming annotations tracked here: https://github.com/dotnet/fsharp/issues/15980
Resolved issues
- System.ArgumentException when publishing trimmed, self-contained app: resolved with a trimming annotation
-
printfusage requires a lot of rd.xml boilerplate: This is resolved by--reflectionfree(which disallowsprintf). - printf "%А" does not work in reflection-free mode. Same as above
Probably not issues/need more info
- FSharp.Core.OptimizedClosures.FSharpFunc has recusive generics (?): aside from string functions I don't see an example of this and I don't for example see an ILC warning on Array2D.init.
- A function calling Assembly.GetExecutingAssembly() gets inlined across assembly boundaries: not AOT-specific
- Tail opcode being emitted for normal methods, destroys JIT optimizations: not AOT-specific
Remaining issues
- Hello world uses
printf: S to fix - Ensure that FSharp.Core is trimmable. Trimming annotations issue here: https://github.com/dotnet/fsharp/issues/15980
- Interpolated strings like
$"{x}"breaks in reflection-free mode. This is a big one and needs tracking. I can write this up. Currently$"Hello world"also has the problems that it bloats the size of an assembly by several megabytes, and generates ludicrous code with extremely complex generics. I think it's easy to conceive what the solution should be as it's just a matter of properly compiling the code.
Wanted to add here that PrintfFormat is used not only for string printing, so it shouldn't be regarded as deprecated version of interpolation. For example Giraffe (and Oxpecker) use it for type inference from route to handler where part of route should match parameter handler. So I expect printf to work with non-reflection-free AOT mode as well.