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
.Net code is deployed to devices where performance - including startup time - and download size are important.
JIT is ruled out by this and may be explicitly prohibited (UWP and iOS) or result in unacceptably slow application startup (Xamarin.Android).
Current .Net plans (.Net Form Factors) involve a supported AOT option across .Net, with greater usage of linkers:
We will introduce analyzers that report errors or warnings for code patterns that are not compatible with the specific form factors.
We will introduce a linker compatibility analyzer to detect code patterns that cannot be reliably analyzed by the linker.
Source generators are going to be the preferred [reflection/runtime code generation] mitigation for AOT compatibility, same as for linker compatibility above.
F# string problems affecting AOT/linker support
F# Support lists incompatibilities with CoreRT and .Net Native. These pick out the aspects of F# that are likely to be problematic for any performant AOT and linkers. (Note: F# is generally compatible with current mono AOT but this is not performant, and compatibility may not include linking.)
The largest problem here is F# string methods:
F# string methods use reflection and codegen, so are slow and AOT and linker unfriendly (i.e. are liable to crash on runtime).
The variety of string methods is difficult to understand: it is hard to impossible for F# users to find out what string, ToString(), and sprintf %A do.
The offending part of FSharp is lacking in type safety, with everything done via casting, uses reflection without restraint, and encapsulates poorly (e.g. .ToString() methods in FSharp record and DU types just calling sprintf "%A" on the entire record).
Solution part 1: localize by generating ToString() overrides on F# types
We have effectively, on F# record and DU types (just check SharpLab to confirm this):
overridet.ToString()= sprintf "%A" t
The sprintf "function" doesn't have legitimate access to the data needed to generate the string, so has to use reflection to get the structure.
Instead this method should be compiled:
typeDU=| Case0 ofint| Case1 ofRecord| Case2 ofobj// A compiled version of the following should be generated:overridet.ToString()=match t with| Case0 i ->// I.e. "Case0" + i.ToString() + ")"// The inner string results from CompiledToString<int>(i)"Case0(%s{i.ToString()})"// i.e. "Case0" + i.ToString() + ")"| Case1 r ->"Case1(%s{r.ToSting()})"| Case2 o ->// if we absolutely need to preserve backwards compat"Case1(%A{o})"// i.e. "Case1(" + FSharp.FormatObj o + ")"// otherwise"Case2(%s{o.ToString()})"// i.e. "Case1(" + o.ToString() + ")"
Note that once this is done, CompiledToString does not need to know how to print records and DUs.
Solution part 2: compile
A method (represented above as pseudocode CompiledToString<'t>) should be created to generate compiled code for any concrete type 't, to be used in place of the current dynamic sprintf code where possible.
Where the method sees only obj it could preferably use .ToString(), or else use a very light form of reflection, making sure that codegen is not used.
Solution part 3: integrate
We need to decide where to use the method CompiledToString<'t>:
String interpolation.
This has so much nicer ergonomics than sprintf that it is likely to replace sprintf in most F# code. If string interpolation is always compiled and ToString() methods work as above, then it will be easy for F# users to avoid AOT/linker incompatibilities.
sprintf: Compile sprintf where possible dotnet/fsharp#8621 . Note that we'd need to preserve some functionality for displaying records to deal with F# libraries compiled with earlier versions of F#: if they have override t.ToString() = sprintf "%A" t, then sprintf "%A" t can't use ToString().
It may be simplest to start by doing this for string interpolation as the first step, adding extra methods and preserving existing sprintf code, and afterwards migrate this work to sprintf.
The need for AOT and linker support
.Net code is deployed to devices where performance - including startup time - and download size are important.
JIT is ruled out by this and may be explicitly prohibited (UWP and iOS) or result in unacceptably slow application startup (Xamarin.Android).
Current .Net plans (.Net Form Factors) involve a supported AOT option across .Net, with greater usage of linkers:
F# string problems affecting AOT/linker support
F# Support lists incompatibilities with CoreRT and .Net Native. These pick out the aspects of F# that are likely to be problematic for any performant AOT and linkers. (Note: F# is generally compatible with current mono AOT but this is not performant, and compatibility may not include linking.)
The largest problem here is F# string methods:
string,ToString(), andsprintf %Ado.The offending part of FSharp is lacking in type safety, with everything done via casting, uses reflection without restraint, and encapsulates poorly (e.g.
.ToString()methods in FSharp record and DU types just callingsprintf "%A"on the entire record).Solution part 1: localize by generating ToString() overrides on F# types
We have effectively, on F# record and DU types (just check SharpLab to confirm this):
The sprintf "function" doesn't have legitimate access to the data needed to generate the string, so has to use reflection to get the structure.
Instead this method should be compiled:
Note that once this is done,
CompiledToStringdoes not need to know how to print records and DUs.Solution part 2: compile
A method (represented above as pseudocode
CompiledToString<'t>) should be created to generate compiled code for any concrete type't, to be used in place of the current dynamic sprintf code where possible.Where the method sees only
objit could preferably use .ToString(), or else use a very light form of reflection, making sure that codegen is not used.Solution part 3: integrate
We need to decide where to use the method
CompiledToString<'t>:This has so much nicer ergonomics than
sprintfthat it is likely to replacesprintfin most F# code. If string interpolation is always compiled and ToString() methods work as above, then it will be easy for F# users to avoid AOT/linker incompatibilities.sprintf: Compilesprintfwhere possible dotnet/fsharp#8621 . Note that we'd need to preserve some functionality for displaying records to deal with F# libraries compiled with earlier versions of F#: if they haveoverride t.ToString() = sprintf "%A" t, thensprintf "%A" tcan't useToString().It may be simplest to start by doing this for string interpolation as the first step, adding extra methods and preserving existing sprintf code, and afterwards migrate this work to sprintf.
TBD
Generics/inlines