You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 fsharp/fslang-suggestions#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.
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?
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.
System.ArgumentException when publishing trimmed, self-contained app: resolved with a trimming annotation
printf usage requires a lot of rd.xml boilerplate: This is resolved by --reflectionfree (which disallows printf).
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
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.
Reacted by vmenge, Adelar da Silva Queiróz, Kevin Barabash, Suraj and John Wostenberg
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.
This discussion in #17323 brought up two more problems from @abklearnhere : #17356 (try .. with inside seq) #17355 (lazy)
The lazy problem also applies to lazy constructs sometimes, e.g. Seq.init.
Another round of trim annotations will be needed to catch this stuff.
I will apply for an Ampifying F# session to make progress on trimming and either do the first round of interpolated strings (which will also improve their performance) or emitting better ToString() methdos from DUs.
Just let you know that the c style fmt print is not available for AOT or Reflectionfree:
Details
Code
[<EntryPoint>]namespaceMyFSharpAppopenSystemmoduleMain =[<EntryPoint>]letmain args =
printfn $"Arguments passed to function : {args}"// good
printfn "Arguments passed to function : %A" args // not working0
LSP Feedback:
Unable to parse format string 'The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'.
This construct implicitly uses reflection.'
Just let you know that the c style fmt print is not available for AOT or Reflectionfree:
I think the restriction is only for the "%A" specifier.
I wonder if it's possible to make a reflection free path in "%A" implementation, and only error if there really is no way to resolve the types at compile time.
I wonder if it's possible to make a reflection free path in "%A" implementation, and only error if there really is no way to resolve the types at compile time.
Thinking out loud about the "best attempt" approach for compiler to take here:
A. Fully inlined at %A usage - will fail if not possible, compiler can try. Will also work with libs built using older compiler, but code size grows
B. Change codegen for .ToString() (or introduce another sibling to it). Smaller code size, but then you have to wait for all types to be recompiled. I guess also harder for F# compiler to detect impossibility.
Activity
charlesroddie commentedon Jul 2, 2022
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.
kant2002 commentedon Jul 3, 2022
@charlesroddie the
recursive genericissue can be triggered byprintfI suppose, but I can reproduce withArray2D.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?MichalStrehovsky commentedon Jul 5, 2022
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#:
This will print following warning at compile time:
The at runtime, the first call will succeed and print the number (because the depth was sufficiently low), but throws for the second call:
MichalStrehovsky commentedon Jul 5, 2022
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.
[-]F# AOT story[/-][+]`Tracking` F# AOT story[/+][-]`Tracking` F# AOT story[/-][+]`Tracking` F# AOT, Trimmability, ILLink[/+]charlesroddie commentedon Sep 14, 2023
Trimming annotations tracked here: #15980
Resolved issues
printfusage requires a lot of rd.xml boilerplate: This is resolved by--reflectionfree(which disallowsprintf).Probably not issues/need more info
Remaining issues
printf: S to fix$"{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.Lanayx commentedon Jan 25, 2024
Wanted to add here that
PrintfFormatis 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.charlesroddie commentedon Nov 23, 2025
This discussion in #17323 brought up two more problems from @abklearnhere :
#17356 (try .. with inside seq)
#17355 (lazy)
The lazy problem also applies to lazy constructs sometimes, e.g.
Seq.init.Another round of trim annotations will be needed to catch this stuff.
I will apply for an Ampifying F# session to make progress on trimming and either do the first round of interpolated strings (which will also improve their performance) or emitting better ToString() methdos from DUs.
I also found issues around Array2D.init, documented here: fsharp/fslang-suggestions#1454
T-Gro commentedon Nov 24, 2025
What do you have in mind for interpolated strings?
Do you plan to shift the work from Printf module at runtime to compile-time work?
charlesroddie commentedon Nov 25, 2025
My plan for interpolated strings would be a stringbuilder version of Support
DefaultInterpolatedStringHandler.Sorrow-Scarlet commentedon Apr 17, 2026
Just let you know that the c style fmt print is not available for AOT or Reflectionfree:
Details
Code
LSP Feedback:
Compiler Feedback
error FS0741
Additions
Code
Debug Build
SUCCESS
AOT Build
AOT WARNING!
Fsproj
Environment
majocha commentedon Apr 17, 2026
I think the restriction is only for the
"%A"specifier.I wonder if it's possible to make a reflection free path in
"%A"implementation, and only error if there really is no way to resolve the types at compile time.T-Gro commentedon Apr 17, 2026
Thinking out loud about the "best attempt" approach for compiler to take here:
A. Fully inlined at
%Ausage - will fail if not possible, compiler can try. Will also work with libs built using older compiler, but code size growsB. Change codegen for
.ToString()(or introduce another sibling to it). Smaller code size, but then you have to wait for all types to be recompiled. I guess also harder for F# compiler to detect impossibility.Is there a C. ?
Add NativeAOT test for interpolated strings