-
Notifications
You must be signed in to change notification settings - Fork 786
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Some FSharp.Core constructs don't run on CoreRT #4954
Comments
Well, CoreRT is not a complete implementation of .NET. And TBH is very difficult to design, implement and test .NET libraries that run on incomplete implementations of .NET. Also, historically, incomplete implementations of .NET have often eventually been forced to "complete" the implementation w.r.t. the ECMA standards, e.g. by providing backup implementations of generic code that doesn't require further code generation at runtime. For example Mono on Android and iOS. At least to the extent that MakeGenericType and MakeGenericMethod are supported (which is the limit of the code generation that we do). If .NET provides official ways to conditionally dynamically probe for an official, documented, well-defined set of implementation capabilities then we could use those. At the moment I don't think there is a mechanism to do that - is there any official guidance on this? |
I think we should narrow down this issue. It is about the usage of From browsing the F# test suite, the implementations of To get CoreRT testing going using the F# test suite, we could replace Does this make sense? |
Printf is tested in
Yes, for the rest of the test suite perhaps a naive implementation of printf restricted to 10-20 basic format strings would do to get started. You'll need an implementation of It would, however, be even better if CoreRT just implemented .NET properly. Can CoreRT cope with |
My understanding is that the Mono AOT compiler doesn't suffer this problem (depending on configuration), because a "last restort" representation of generic code is produced that uses some kind of wide/tagged-representation for naked That's how Mono ends up running on so many devices and it's fundamental to Xamarin. I don't know the details however. |
It looks like there is relevant active work in CoreRT: dotnet/corert#5011 However we shouldn't get sidetracked with getting printf to work. We should just find a way to work around it. @zpodlovics Can you just copy |
Just to give some details about CoreRT's restriction on
|
@MichalStrehovsky Thanks for the ideas! Actually, I did try to do pregenerated code with rd.xml for printf module but without success (I did try to use earlier too, when I did the F# helloworld project). Unfortunately it's not yet clear to me to what assembly / types should I specify (and why!) for method generic calls as MethodInstantiation arguments in rd.xml Eg.: The following functions / methods use MakeGenericMethod: module internal PrintfImpl =
let private getValueConverter (ty : Type) (spec : FormatSpecifier) : obj = Generated IL for method signature:
type private PrintfBuilder<'S, 'Re, 'Res>() =
let buildSpecialChained(spec : FormatSpecifier, argTys : Type[], prefix : string, tail : obj, retTy) = Generated IL for method singature:
type private PrintfBuilder<'S, 'Re, 'Res>() =
let buildPlainFinal(args : obj[], argTypes : Type[]) = Generated IL for method signature:
type private PrintfBuilder<'S, 'Re, 'Res>() =
let buildPlainChained(args : obj[], argTypes : Type[]) = Generated IL for method signature:
Here is my rd.xml <Directives>
<Application>
<Type Name="Microsoft.FSharp.Core.PrintfImpl">
<MethodInstantiation Name="getValueConverter" Arguments="System.Type,Microsoft.FSharp.Core.PrintfImpl/FormatSpecifier" Dynamic="Required All">
</Type>
<Type Name="Microsoft.FSharp.Core.PrintfImpl/PrintfBuilder`3">
<MethodInstantiation Name="buildSpecialChained" Arguments="Microsoft.FSharp.Core.PrintfImpl/FormatSpecifier,System.Type[],System.String,System.Object,System.Type,Microsoft.FSharp.Core.Unit" Dynamic="Required All">
<MethodInstantiation Name="buildPlainFinal" Arguments="System.Object[],System.Type[]" Dynamic="Required All">
<MethodInstantiation Name="buildPlainChained" Arguments="System.Object[],System.Type[]" Dynamic="Required All">
</Type>
</Application>
</Directives> What should I exactly (and why?) specify in rd.xml ? Example attached: It still fails with the following message when I ran:
Printf module source code: Printf module also use the following library: |
I see you're using the actual documented RD.XML syntax here that the .NET Native for Universal Windows Apps uses - that's not quite the syntax the temporary RD.XML parser in CoreRT uses. The fact that you're not getting a compiler failure also indicates the compiler is not actually looking at this file. We track full reflection story in dotnet/corert#5001 including figuring out the right way to express compilation roots.
|
@MichalStrehovsky Thank you for the ideas! I did some experiment with the aspnet sample earlier this year, but I did not checked the different rd.xml before. The updated rd.xml and checked with this: <Directives>
<Application>
<Assembly Name="FSharp.Core" Dynamic="Required All" />
</Application>
</Directives> And later with this: <Directives>
<Application>
<Assembly Name="FSharp.Core" Dynamic="Required All">
<Type Name="Microsoft.FSharp.Core.PrintfImpl" Dynamic="Required All" />
<Type Name="Microsoft.FSharp.Core.PrintfImpl/PrintfBuilder`3[[TYPE1,TYPE2,TYPE3]]" Dynamic="Required All" />
</Assembly>
</Application>
</Directives> Until I have a working trivial example compling in the whole FSharp.Core is perfectly fine (the compiled binary will be big). This could be trim down later. However figuring out the generic type type arguments looks harder than it seems - because according to the comments in the code this may depends on the program input format string and the inferred type by the compiler. Please correct me if I am wrong, but based on the comment and the generated IL actual type argument here could be a deeply nested generic tuple / tree-like structure (from code comment :" First it recursively consumes format string up to the end, then during unwinding builds printer using PrintfBuilderStack as storage for arguments.") . I guess unless some tool will be developed to analyze the compiled IL code and extract the actual type instantiation information and generate the rd.xml automatically, because doing it manually seems unfeasible ...
Printf example snippet: printfn "argv: %d" argv.Length /// Parses format string and creates result printer function.
/// First it recursively consumes format string up to the end, then during unwinding builds printer using PrintfBuilderStack as storage for arguments.
/// idea of implementation is very simple: every step can either push argument to the stack (if current block of 5 format specifiers is not yet filled)
// or grab the content of stack, build intermediate printer and push it back to stack (so it can later be consumed by as argument)
type private PrintfBuilder<'S, 'Re, 'Res>() =
PrintfBuilder instance creation: type Cache<'T, 'State, 'Residue, 'Result>() =
static let generate(fmt) = PrintfBuilder<'State, 'Residue, 'Result>().Build<'T>(fmt) Type instatiation I found for Microsoft.FSharp.Core.CoreRT.PrintfImpl/PrintfBuilder`3 in generated IL for the trivial printf example: type Cache<'T, 'State, 'Residue, 'Result>() =
static let generate(fmt) = PrintfBuilder<'State, 'Residue, 'Result>().Build<'T>(fmt) Generated IL code:
|
This kind of patterns is the reason why F# is problematic to support in AoT compilers in a performant way. In an environment that has a JIT, In an AoT compiler, the code needs to be pregenerated. We either pregenerate code that is efficient (specialized for the given T), but there will be a size on disk penalty from doing that (plus the hassle of having to specify all the possibilities in advance), or we generate code that is "universal", but pretty inefficient at runtime (.NET Native compiler for Windows UWP apps can already do that, and I assume so does Mono; the CoreRT compiler can't do that for now, so it's not an option there). Such universal code is slower and allocates more things on the GC heap than what one would expect. |
A bit old now, but still related publication from @dsyme: Pre-compilation for .NET Generics http://citeseerx.ist.psu.edu/viewdoc/download?rep=rep1&type=pdf&doi=10.1.1.124.3911 @MichalStrehovsky If I remember correctly CoreRT try to solve these pre-generation problems by resurrecting the IL interpreter. Is there any other way exists to solve this more efficiently? Is it event possible to solve more efficiently? However it seems OCaml was able to solve the typed printing/formatting problem efficiently with their implementation of printf and runtime both in bytecode format and both in native format. |
It seems that F# OCaml compatibility pack printf will not solve the printf AOT problem due the missing format string implementation: // NOTE : The types and functions in this section
// are already available in the F# Core Library (FSharp.Core). |
The code patterns are not so different from many C# ones. Honestly all .NET implementations that use the name ".NET" should implement these correctly before going to V1, otherwise they are not a correct implementation of .NET Yes, Mono's compilation to iOS appears to handle these cases reasonably performantly through the generation of wide code. I don't know the full details, but it is at least a correct and complete story. |
@zpodlovics for simple printfn like let sayHello name = printfn "Hello, %s!" name the next rd works fine to me: <Assembly Name="FSharp.Core" Dynamic="Required All">
<Type Name="Microsoft.FSharp.Core.PrintfImpl+Specializations`3[[System.Object,System.Private.CoreLib],[System.Object,System.Private.CoreLib],[System.Object,System.Private.CoreLib]]" Dynamic="Required All">
<Method Name="Final1" Dynamic="Required">
<GenericArgument Name="System.Object, System.Private.CoreLib" />
</Method>
<Method Name="FinalFastEnd1" Dynamic="Required">
<GenericArgument Name="System.Object, System.Private.CoreLib" />
</Method>
<Method Name="FinalFastStart1" Dynamic="Required">
<GenericArgument Name="System.Object, System.Private.CoreLib" />
</Method>
<Method Name="FinalFast1" Dynamic="Required">
<GenericArgument Name="System.Object, System.Private.CoreLib" />
</Method>
<Method Name="Final2" Dynamic="Required">
<GenericArgument Name="System.Object, System.Private.CoreLib" />
<GenericArgument Name="System.Object, System.Private.CoreLib" />
</Method>
<Method Name="FinalFastEnd2" Dynamic="Required">
<GenericArgument Name="System.Object, System.Private.CoreLib" />
<GenericArgument Name="System.Object, System.Private.CoreLib" />
</Method>
</Type>
</Assembly> for full use |
@FoggyFinder Thanks for the idea, but did not work on linux with 1.0.0-alpha-26614-02.
Unfortunately except the highly specialized few cases - you'll probably not be able to specify the (deeply nested) instantiation types especially not for %A, take a look at the generated IL signature at the end of this comment: #4954 (comment) |
I got the same exception for another parameter of printfn, but I was able to avoid this with using another methods of
I think it requires including only two types - |
@zpodlovics can you check this sample? Does it get the exception? |
@FoggyFinder Nice trick, your example project compiles and runs (using linux-x64). However how this supposed to be type (and crash) safe when the generic argument instantiation types forced to be objects? Reference types have shared code for all generic instantiation, so it supposed to works for reference types. But how about the cases for value type generic instantiations? These type instantiations will be interesting: I have an out of tree printf module for experiments, I may try add some reference type constraints to these generic methods later. It may help to reveal the paths for the not yet handled value type generic instantiations. |
Not sure yet how it will handle custom types, I thought
Sadly, probably we have to specify all basic types separately. Well, if someone will write script for generating rd it will be awsome. (I not so good for code analysis). I can only create some helper functions to avoid boring manuall work. |
#@FoggyFinder Yes, but I am afraid solving the generic instantiation types to generate rd.xml means reimplementing printf module again. In theory printf could generate some metadata about the static instantiation types created during the specialization, but will still not work with %A (the actual argument types may depend on the program execution eg: open System
type [<Class>] O = class end
type [<Struct>] V1 = struct end
type [<Struct>] V2 = struct end
[<EntryPoint>]
let main argv =
let p v = printfn "string: %A" v
let o = Unchecked.defaultof<O>
let v1 = Unchecked.defaultof<V1>
let v2 = Unchecked.defaultof<V2>
p o
p v1
p v2
0 // return an integer exit code
.NET Core Run:
.NET CoreRT Run:
|
rd <Type Name="Microsoft.FSharp.Core.PrintfImpl+ObjectPrinter" Dynamic="Required All">
<Method Name="GenericToString" Dynamic="Required">
<GenericArgument Name="System.Int32, System.Private.CoreLib" />
</Method>
<Method Name="GenericToString" Dynamic="Required">
<GenericArgument Name="System.Double, System.Private.CoreLib" />
</Method>
<Method Name="GenericToString" Dynamic="Required">
<GenericArgument Name="System.Object, System.Private.CoreLib" />
</Method>
</Type> .Core RT: printfn "%A" [| 10..20 |] // [|10; 11; 12; 13; 14; 15; 16; 17; 18; 19; 20|]
printfn "%A" 42 //42
printfn "%A" 25.0 //25.0
printfn "%A" "string" //"string" |
@FoggyFinder It's not accidental that I created my own value types in the example. Yes, you can list the base value types there, and you can also list all value types from F# and from the .NET class libraries, and all the NuGet packages, and so on. However due the closed world assumption the CoreRT will know all the value types. @MichalStrehovsky Would it be possible to have something like this to express all the known value types in the rd.xml (at least as a workaround because it will create some code bloat)? <Type Name="Microsoft.FSharp.Core.PrintfImpl+ObjectPrinter" Dynamic="Required All">
<Method Name="GenericToString" Dynamic="Required">
<ValueTypes Filter="None" />
</Method>
</Type> |
This will still not help if the value type itself is generic, e.g. Still, you might be surprised how much code that will generate. If you create a pull request that adds the syntax to RdXmlRootProvider, I wouldn't push back too hard against it. But do note that all of the RD.XML processing code is only temporary, like the warning says. At some point in the future, we'll want to replace it with something sustainable. |
Message from @dsyme : you can submit a PR to remove the use of sprintf throughout FSharp.Core.UnitTests except where that's the thing explicitly being tested. It's useful anyway to reduce the complexity of what's being executed for different tests. |
It seems that %A, %O is usually used to do string based structural comparison in the tests. Can we assume in the testsuite that the F# structural comparison is correct (except tests that target the structural comparison itself)? So instead of using printf generated strings to do structural comparison we can use the F# compiler to do structural comparison eg.: by defining a few specific F# record type. However these f# structural comparisons works without dynamically generated code. I also have a few examples to remove sprintf and add CoreRT build support: CoreRT is still work in progress (~alpha) not-yet released product, the F# development / testing should not held back by CoreRT issues. It's not yet clear how the CoreRT support should be added to the automatical builds and to the test runner. However right now it should work independently and disabled by default from the main testsuite as the CoreRT native builds will require platform specific tools installed and the test runner should be able to execute standalone native executable. |
That makes sense to me. Can you put a sample change here that could be a template? The function definitions don't seem to provide much benefit to me. The ones that get used many times yes, but the ones that only get used once no. It should be OK to use them, also OK not to. Can we divide and conquer on this? Alphabetical and reverse alphabetical? Please add to the PR if you can. We haven't yet decided how to test CoreRT and .Net Native once this is done. But it could just be done manually. sprintf removal (via string.format and structural comparison) will clearly make things easier however it ends up being done. For us the important thing is to test .Net Native thoroughly right now and automated testing is not an immediate goal, although it would be nice to have if it's doable. |
@charlesroddie It's still benefically, because it will help other F# implementations too (eg.: Fable, WebSharper, Fez, etc) - it's far easier to replace a few function at the beginng of each test than 1) provide a System.String.Format reimplementation or replace it everywhere (the same problem with printf). Unfortunately my time is farily limited and mostly focused on paid work or my FsHlvm OSS project. |
Building off of @FoggyFinder's comment, here is an example using Argu. This is the rd.xml: <Directives>
<Application>
<Assembly Name="FSharp.Core" Dynamic="Required All">
<Type Name="Microsoft.FSharp.Core.PrintfImpl+Specializations`3[[System.Object,System.Private.CoreLib],[System.Object,System.Private.CoreLib],[System.Object,System.Private.CoreLib]]" Dynamic="Required All">
<Method Name="Final1" Dynamic="Required">
<GenericArgument Name="System.Object, System.Private.CoreLib" />
</Method>
<Method Name="FinalFastEnd1" Dynamic="Required">
<GenericArgument Name="System.Object, System.Private.CoreLib" />
</Method>
<Method Name="FinalFastStart1" Dynamic="Required">
<GenericArgument Name="System.Object, System.Private.CoreLib" />
</Method>
<Method Name="FinalFastStart2" Dynamic="Required">
<GenericArgument Name="System.Object, System.Private.CoreLib" />
<GenericArgument Name="System.Object, System.Private.CoreLib" />
</Method>
<Method Name="FinalFast1" Dynamic="Required">
<GenericArgument Name="System.Object, System.Private.CoreLib" />
</Method>
<Method Name="Final2" Dynamic="Required">
<GenericArgument Name="System.Object, System.Private.CoreLib" />
<GenericArgument Name="System.Object, System.Private.CoreLib" />
</Method>
<Method Name="FinalFastEnd2" Dynamic="Required">
<GenericArgument Name="System.Object, System.Private.CoreLib" />
<GenericArgument Name="System.Object, System.Private.CoreLib" />
</Method>
</Type>
</Assembly>
<Assembly Name="littleClient" Dynamic="Required All">
<Type Name="Argu.Samples.LS.Size[]" Dynamic="Required All" />
<Type Name="Argu.Samples.LS.QuotingStyle[]" Dynamic="Required All" />
</Assembly>
<Assembly Name="Argu" Dynamic="Required All">
<Type Name="Argu.Utils+Existential`1[[Argu.Samples.LS.Size, littleClient]]" Dynamic="Required All" />
<Type Name="Argu.Utils+Existential`1[[Argu.Samples.LS.ColorWhen, littleClient]]" Dynamic="Required All" />
<Type Name="Argu.Utils+Existential`1[[System.Char, System.Private.CoreLib]]" Dynamic="Required All" />
<Type Name="Argu.Utils+Existential`1[[Argu.Samples.LS.QuotingStyle, littleClient]]>" Dynamic="Required All" />
<Type Name="Argu.Utils+Existential`1[[System.Int32, System.Private.CoreLib]]" Dynamic="Required All" />
</Assembly>
<Assembly Name="System.Private.CoreLib" Dynamic="Required All">
<Type Name="System.Tuple`5[[System.String,System.Private.CoreLib],[System.Int32,System.Private.CoreLib],[System.Int32,System.Private.CoreLib],[System.Int32,System.Private.CoreLib],[System.Int32,System.Private.CoreLib]]" Dynamic="Required All" />
</Assembly>
</Application>
</Directives> As you can see it isn't the easiest. Each type is added one runtime error at a time. Plans for replacing rd.xml with something newer are being tracked in dotnet/corert#5001. Lots of stuff in .NET works off of reflection and reflection is problematic for AOT. |
Three possible solutions:
|
Was this above ^ ever done?
I think this is the best approach ^ Any chance you can sneak it in the ".NET5 roadmap"? |
That PR didn't get merged, and then became lower priority after F# on UWP was unblocked.
We have to wait and see. There are many strands of AOT development (Mono AOT, CoreRT, Crossgen2) so it's not clear which will emerge with .Net 5 and in what state, and what the UWP AOT technology will be is unknown.
|
Closing out this old issue |
I am experimenting with porting some of the F# testsuite cases to CoreRT (dotnet/corert#2057 (comment), dotnet/corert#5827) However right now some of the Core F# modules does not support code paths without dynamic code generation. For example the F# test suite written in a style that contains lot's of printf and sprintf code (
egrep -r "printf" tests/|wc -l
will found more than 10k cases). Earlier I intentionally avoided printf in the code bases that may also used in fully static compilation (Mono Full AOT). Due the large number of usage everywhere (eg.: testsuite) rewrite the existing printf/sprintf usage to System.String.Format and System.Console.WriteLine would be a huge waste of time.Please note, some earlier test cases was carefully designed and intentionally avoided this issue by factoring out the printing functionality to the beginning of the test code:
Is there any earlier Printf module version that could be used for this purpose - but without dynamic code generation (performance does not matter in this case)?
By dynamic code generation I mean code snippets like this:
https://github.com/Microsoft/visualfsharp/blob/master/src/fsharp/FSharp.Core/printf.fs#L1170
Repro steps
printf.zip
Expected behavior
Core modules should also work without dynamic code generation path (as a fallback option).
Actual behavior
Due the dynamic code generation the Printf module does not work with CoreRT.
Known workarounds
Rewrite everything to System.Console.WriteLine and System.String.Format or write Printf module without dynamic codegen.
Related information
The text was updated successfully, but these errors were encountered: