Open charlesroddie opened 4 years ago
I was testing .NET 7 preview 5 SDK (daily build), which has added out of the box support for NativeAot. It seems to handle %A
with a simple program (despite some errors and warnings in ILC step).
Despite the error:
Failed to load type 'Microsoft.FSharp.Core.FSharpFunc
2<T1_System.__Canon, Microsoft.FSharp.Core.FSharpFunc
2<T2_System.Canon, Microsoft.FSharp.Core.FSharpFunc2<T3_System.__Canon, Microsoft.FSharp.Core.FSharpFunc
2<T4_System.Canon, Microsoft.FSharp.Core.FSharpFunc`2<T5_System.__Canon, TResult_System.__Canon>>>>>' from assembly 'FSharp.Core, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
ILC succeeds. If we can work this error out first in .NET 7 timeframe, that would be cool (since it seems benign, and showing up for a simple "Hello from F#" app as well).
cc @MichalStrehovsky
Regular NativeAOT works just fine with %A but there extra goal to make it work in reflection-free mode which is not supported (but working).
Having reflection obviously fine, but being not rely on it is also goal which a lot of people want. This issue is to improve reflectionfree mode too.
https://github.com/kant2002/RdXmlLibrary/blob/main/FSharp.Core.xml
You can plug this file with
<RdXmlFile Include="FSharp.Core.rd.xml" />
Failed to load type 'Microsoft.FSharp.Core.FSharpFunc2<T1_System.Canon, Microsoft.FSharp.Core.FSharpFunc2<T2_System.__Canon, Microsoft.FSharp.Core.FSharpFunc2<T3_System.Canon, Microsoft.FSharp.Core.FSharpFunc2<T4_System.Canon, Microsoft.FSharp.Core.FSharpFunc`2<T5_System.__Canon, TResult_System.Canon>>>>>' from assembly 'FSharp.Core, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
The compiler detected there's a generic cycle in the assembly and instead of compiling until it runs out of memory (or the heath death of the universe, whichever comes first) cut off the generic expansion at the point when it ran over the cutoff. If the reported method is reached at runtime, it will throw.
FWIW, the cycle(s) involve following entities. The compiler should print them, not sure why it's not kicking in here:
[0]: {[FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2}
[1]: {[FSharp.Core]<StartupCode$FSharp-Core>.$Prim-types+op_Implicit@3421}
[2]: {[FSharp.Core]<StartupCode$FSharp-Core>.$Prim-types+op_Implicit@3426-1}
[3]: {[FSharp.Core]<StartupCode$FSharp-Core>.$Prim-types+op_Implicit@3429-2}
[4]: {[FSharp.Core]<StartupCode$FSharp-Core>.$Prim-types+op_Implicit@3432-3}
[5]: {[FSharp.Core]<StartupCode$FSharp-Core>.$Prim-types+FromConverter@3434}
[6]: {[FSharp.Core]<StartupCode$FSharp-Core>.$Prim-types+ToConverter@3436}
[7]: {[FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures+FSharpFunc`3}
[8]: {[FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures+FSharpFunc`4}
[9]: {[FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures+FSharpFunc`5}
[10]: {[FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures+FSharpFunc`6}
[11]: {[FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures+Invoke@3302}
[12]: {[FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures+Adapt@3309}
[13]: {[FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures+Invoke@3316-1}
[14]: {[FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures+Adapt@3325-1}
[15]: {[FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures+Adapt@3329-2}
[16]: {[FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures+Adapt@3343-3}
[17]: {[FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures+Adapt@3348-4}
[18]: {[FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures+Adapt@3352-5}
[19]: {[FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures+Invoke@3355-2}
[20]: {[FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures+Invoke@3363-3}
[21]: {[FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures+Adapt@3373-6}
[22]: {[FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures+Adapt@3378-7}
[23]: {[FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures+Adapt@3383-8}
[24]: {[FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures+Adapt@3387-9}
Just for information about other reflection-free mode limitations.
Construct $"test {name}"
produce exceptions like this.
Unhandled Exception: EETypeRva:0x004CDE08: TypeInitialization_Type_NoTypeAvailable
---> EETypeRva:0x004CDE08: TypeInitialization_Type_NoTypeAvailable
---> EETypeRva:0x004CD0E8: Reflection_Disabled
at Internal.Reflection.RuntimeTypeInfo.GetMethodImpl(String, BindingFlags, Binder, CallingConventions, Type[], ParameterModifier[]) + 0x33
at System.Type.GetMethod(String, BindingFlags) + 0x27
at <StartupCode$FSharp-Core>.$Printf..cctor() + 0x3c
at System.Runtime.CompilerServices.ClassConstructorRunner.EnsureClassConstructorRun(StaticClassConstructionContext*) + 0xc6
Exception_EndOfInnerExceptionStack
at System.Runtime.CompilerServices.ClassConstructorRunner.EnsureClassConstructorRun(StaticClassConstructionContext*) + 0x167
at System.Runtime.CompilerServices.ClassConstructorRunner.CheckStaticClassConstructionReturnNonGCStaticBase(StaticClassConstructionContext*, IntPtr) + 0xd
at Microsoft.FSharp.Core.PrintfImpl..cctor() + 0x9
at System.Runtime.CompilerServices.ClassConstructorRunner.EnsureClassConstructorRun(StaticClassConstructionContext*) + 0xc6
Exception_EndOfInnerExceptionStack
at System.Runtime.CompilerServices.ClassConstructorRunner.EnsureClassConstructorRun(StaticClassConstructionContext*) + 0x167
at System.Runtime.CompilerServices.ClassConstructorRunner.CheckStaticClassConstructionReturnNonGCStaticBase(StaticClassConstructionContext*, IntPtr) + 0xd
at Microsoft.FSharp.Core.PrintfImpl.buildStep$cont@1141(PrintfImpl.FormatSpecifier, Type[], String, Unit) + 0x16
at Microsoft.FSharp.Core.PrintfImpl.FormatParser`4.parseAndCreateStepsForCapturedFormatAux(FSharpList`1, String, Int32&) + 0xb3
at Microsoft.FSharp.Core.PrintfImpl.FormatParser`4.parseAndCreateStepsForCapturedFormat() + 0x3a
at Microsoft.FSharp.Core.PrintfImpl.FormatParser`4.GetStepsForCapturedFormat() + 0x17
at Microsoft.FSharp.Core.PrintfModule.PrintFormatToStringThen[T](PrintfFormat`4) + 0x4b
at Program.drawScene@108-1.Invoke(IImageProcessingContext) + 0xcf
at Program.drawScene(Image`1, Font, JS.DataView, Int32, PlatformModel.Texture[], Model.Game) + 0x26b
at Program.initScene@117-1.Invoke(PlatformModel.Texture[], Model.Game) + 0x2d
at App.Game.gameLoop@38.Invoke(Model.Game, Double) + 0x2b
at Program.render(Double) + 0x4a
at Program.main@211-1.Invoke(Double) + 0x9
at Silk.NET.Windowing.Internals.ViewImplementationBase.DoRender() + 0x16e
at Silk.NET.Windowing.Internals.ViewImplementationBase.Run(Action) + 0x15
at Silk.NET.Windowing.WindowExtensions.Run(IView) + 0x61
at Program.main(String[]) + 0x227
at FSharpWolfenstein.Desktop!<BaseAddress>+0x381723
I also receive this error with string interpolation in regular NativeAOT
Unhandled Exception: System.Collections.Generic.KeyNotFoundException: An index satisfying the predicate was not found in the collection.
at Microsoft.FSharp.Collections.ArrayModule.loop@596-36[T, TResult](FSharpFunc`2, T[], Int32) + 0x9e
at Microsoft.FSharp.Reflection.Impl.getUnionCaseTyp(Type, Int32, BindingFlags) + 0x3e
at Microsoft.FSharp.Reflection.Impl.fieldsPropsOfUnionCase(Type, Int32, BindingFlags) + 0x143
at Microsoft.FSharp.Reflection.FSharpValue.GetUnionFields(Object, Type, FSharpOption`1) + 0x6f
at Microsoft.FSharp.Text.StructuredPrintfImpl.ReflectUtils.Value.GetValueInfoOfObject$cont@525(BindingFlags, Object, Type, Unit) + 0x57
at Microsoft.FSharp.Text.StructuredPrintfImpl.Display.ObjectGraphFormatter.objL(Display.ShowMode, Int32, Display.Precedence, Object, Type) + 0x3f
at Microsoft.FSharp.Text.StructuredPrintfImpl.Display.Format@1515.Invoke(Int32, Display.Precedence, Tuple`2) + 0x3a
at Microsoft.FSharp.Text.StructuredPrintfImpl.Display.ObjectGraphFormatter.Format[a](Display.ShowMode, a, Type) + 0x83
at Microsoft.FSharp.Text.StructuredPrintfImpl.Display.anyToStringForPrintf[T](FormatOptions, BindingFlags, T, Type) + 0x5c
at Microsoft.FSharp.Core.PrintfImpl.ObjectPrinter.GenericToStringCore[T](T, FormatOptions, BindingFlags) + 0x47
at Microsoft.FSharp.Core.PrintfImpl.OneStepWithArg@508-1.Invoke(A) + 0x37
at System.Text.ValueStringBuilder.AppendFormatHelper(IFormatProvider, String, ParamsArray) + 0x644
at System.String.FormatHelper(IFormatProvider, String, ParamsArray) + 0xae
at Microsoft.FSharp.Core.PrintfImpl.InterpolandToString@924.Invoke(Object) + 0x75
at Microsoft.FSharp.Core.PrintfImpl.PrintfEnv`3.RunSteps(Object[], Type[], PrintfImpl.Step[]) + 0xb4
at Microsoft.FSharp.Core.PrintfModule.PrintFormatToStringThen[T](PrintfFormat`4) + 0x6a
at App.AI.preProcess(Model.Game, Model.Enemy) + 0x110
at App.AI.applyAi(Double, Model.Game, Model.GameObject) + 0x5b
at App.Update.updatedGameObjects@254.Invoke(Int32, Model.GameObject) + 0x95
at Microsoft.FSharp.Primitives.Basics.List.mapiToFreshConsTail[a, b](FSharpList`1, OptimizedClosures.FSharpFunc`3, FSharpList`1, Int32) + 0x5c
at Microsoft.FSharp.Primitives.Basics.List.mapi[T, TResult](FSharpFunc`2, FSharpList`1) + 0xb1
at App.Update.updateEnemies@251(Double, Model.WallRenderingResult, Model.Game, Boolean) + 0x67
at App.Update.updateFrame(Model.Game, Double, Model.WallRenderingResult) + 0x295
at Program.render(Double) + 0x4a
at Program.main@198-1.Invoke(Double) + 0x9
at Silk.NET.Windowing.Internals.ViewImplementationBase.DoRender() + 0x16e
at Silk.NET.Windowing.Internals.ViewImplementationBase.Run(Action) + 0x15
at Silk.NET.Windowing.WindowExtensions.Run(IView) + 0x61
at Program.main(String[]) + 0x149
at FSharpWolfenstein.Desktop!<BaseAddress>+0x462583
Related, adding for prosperity: https://github.com/fsharp/fslang-suggestions/issues/429
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 %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 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,
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>
:sprintf
that it is likely to replacesprintf
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
: https://github.com/dotnet/fsharp/issues/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" t
can'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