Skip to content

Tracking F# AOT, Trimmability, ILLink #13398

Open

Description

@kant2002

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.

Open issues

Activity

  1. charlesroddie commented on Jul 2, 2022

    @charlesroddie
    Contributor

    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.

  2. kant2002 commented on Jul 3, 2022

    @kant2002
    ContributorAuthor

    @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?

  3. MichalStrehovsky commented on Jul 5, 2022

    @MichalStrehovsky
    Member

    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
    
  4. MichalStrehovsky commented on Jul 5, 2022

    @MichalStrehovsky
    Member

    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.

  5. added this to the Backlog milestone on Jul 7, 2022
  6. changed the title [-]F# AOT story[/-] [+]`Tracking` F# AOT story[/+] on Oct 3, 2022
  7. moved this from Not Planned to Planned in F# Compiler and Toolingon Oct 3, 2022
  8. changed the title [-]`Tracking` F# AOT story[/-] [+]`Tracking` F# AOT, Trimmability, ILLink[/+] on Oct 6, 2022
  9. charlesroddie commented on Sep 14, 2023

    @charlesroddie
    Contributor

    Trimming annotations tracked here: #15980

    Resolved issues

    • 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

    Remaining issues

    • Hello world uses printf: S to fix
    • Ensure that FSharp.Core is trimmable. Trimming annotations issue here: Annotate FSharp.Core for trimmability #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.
  10. Lanayx commented on Jan 25, 2024

    @Lanayx
    Contributor

    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.

  11. removed this from the Backlog milestone on Feb 7, 2024
  12. charlesroddie commented on Nov 23, 2025

    @charlesroddie
    Contributor

    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

  13. T-Gro commented on Nov 24, 2025

    @T-Gro
    Member

    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?

  14. charlesroddie commented on Nov 25, 2025

    @charlesroddie
    Contributor

    My plan for interpolated strings would be a stringbuilder version of Support DefaultInterpolatedStringHandler .

  15. Sorrow-Scarlet commented on Apr 17, 2026

    @Sorrow-Scarlet

    Just let you know that the c style fmt print is not available for AOT or Reflectionfree:

    Details

    Code

    [<EntryPoint>]
    namespace MyFSharpApp
    
    open System
    
    module Main =
    
        [<EntryPoint>]
        let main args =
            printfn $"Arguments passed to function : {args}" // good
            printfn "Arguments passed to function : %A" args // not working
            0

    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.'
    

    Compiler Feedback

    error FS0741

    还原完成(0.3)
      MyFSharpApp net10.0 失败,出现 1 错误 (1.1 秒)
        D:\Github\MyFSharpApp\Program.fs(10,17): error FS0741: 无法分析格式字符串“"%A" 格式说明符不能在用选项 "--reflectionfree" 进行编译的程序集中使用。此构造隐式使用反射。”
    
    在 1.8 秒内生成 失败,出现 1 错误
    

    Additions

    Code

    namespace MyFSharpApp
    
    open System
    
    module Main =
    
        [<EntryPoint>]
        let main args =
            printfn $"Arguments passed to function : {args}"
            0

    Debug Build

    SUCCESS

    还原完成(0.3)
      MyFSharpApp net10.0 已成功 (1.5 秒) → bin\Debug\net10.0\MyFSharpApp.dll
    
    在 2.2 秒内生成 已成功
    

    AOT Build

    AOT WARNING!

    还原完成(0.3)
      MyFSharpApp net10.0 win-x64 成功,出现 2 警告 (7.3 秒) → bin\Release\net10.0\win-x64\publish\
        C:\Users\jackz\.nuget\packages\fsharp.core\10.1.201\lib\netstandard2.1\FSharp.Core.dll : warning IL3053: Assembly 'FSharp.Core' produced AOT analysis warnings.
        C:\Users\jackz\.nuget\packages\fsharp.core\10.1.201\lib\netstandard2.1\FSharp.Core.dll : warning IL2104: Assembly 'FSharp.Core' produced trim warnings. For more information see https://aka.ms/il2104
    
    在 7.9 秒内生成 成功,出现 2 警告
    

    Fsproj

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net10.0</TargetFramework>
        <Nullable>enable</Nullable>
        <PublishAot>true</PublishAot>
        <EnableAotAnalyzer>true</EnableAotAnalyzer>
        <OtherFlags>--reflectionfree</OtherFlags>
      </PropertyGroup>
    
      <ItemGroup>
        <Compile Include="Program.fs" />
      </ItemGroup>
    
    </Project>

    Environment

    • Win 11 Home CN
    • VS Code with Ionide
    • VS & C++ Workload installed
  16. majocha commented on Apr 17, 2026

    @majocha
    Contributor

    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.

  17. T-Gro commented on Apr 17, 2026

    @T-Gro
    Member

    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.

    Is there a C. ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions