extend Expr with inline call information #12908
Replies: 15 comments
-
@krauthaufen I agree this would increase the utility of quotations substantially. It is implementable but not that easy - I think it could also be made a non-breaking change. by having a special quotation active pattern to look into the inlined/resolved code. I suppose this could also be made to work for op_Addition if we added a quotation node that represents a sequence of simple IL instructions |
Beta Was this translation helpful? Give feedback.
-
@dsyme I agree that having IL instructions would make the thing even more powerful. I'm currently trying to understand the translation scheme in QuotationTranslator.fs and how to get the definition-code for those mustinline ValRefs but it seems to be quite complicated ;-) I will continue to look into that and maybe I can get something running without needing your help here... |
Beta Was this translation helpful? Give feedback.
-
@krauthaufen Yes, it's not at all simple to implement this. The inlined expressions are determined by the optimizer, but quotation translation is done earlier. |
Beta Was this translation helpful? Give feedback.
-
After looking at it for a few hours I finally figured out how these things work together. All quote expressions get their ExprData in PostInferenceChecks which mutates the ref-cell in Expr.Quote. Wouldn't it be valid to recreate the ExprData whenever optimizations are possible directly in the Optimizer code? |
Beta Was this translation helpful? Give feedback.
-
I managed to get a very simple test working that way: type Vec(x : float, y : float) =
member x.X = x
member x.Y = y
let inline x< ^a, ^b when ^a : (member X : ^b)> (v : ^a) =
(^a : (member X : ^b) (v))
let q = <@ fun (v : Vec) -> x v @>
printfn "%s" (string q) which prints:
|
Beta Was this translation helpful? Give feedback.
-
@GeorgeHahn When you're ready please add a link to the branch, I'm interested |
Beta Was this translation helpful? Give feedback.
-
@dsyme I'm not sure I'm the person you're looking for. Love your work though, cheers! |
Beta Was this translation helpful? Give feedback.
-
@dsyme This sketch isn't quite what we wanted since it does not include the original call anymore.
since we don't want the quotations to be "really" optimized. |
Beta Was this translation helpful? Give feedback.
-
@GeorgeHahn :) Thanks for stopping by :) @krauthaufen Thanks for the info, that's what I figured. I'm not opposed to dividing the optimizer - phases don't cost that much in themselves, it's the processing you do in them that matters. We could perhaps alternatively do a separate phase to collect and prepare quotation data - the uses of the reference cell hack has been problematic (it caused come very obscure bugs) and we should maybe just do quotation data preparation as a distinct rewriting phase. That phase could collect up inline information (and reuse the inline information provided cross-assembly) It's important that we present the original body (and not the optimized one). One issue might be that the inline information saved cross-assembly has already been subject to optimization, I'm not sure. I suppose we could save a separate, unoptimized version of that information in a new pickled resource. I think we would window-dress this in a similar we did |
Beta Was this translation helpful? Give feedback.
-
@dsyme Doing this in a separate phase would be nice but when doing so, the optimizer would need to optimize quote/reflecteddef things while keeping the original expressions somehow. (since they are needed later on) Wouldn't it be easier to translate expressions inside the optimizer-phase where all the needed information exists? The cross-assembly thing is a little complicated. As far as I understand it you mean something like: a.dll:let inline f a b = a + b
<@ f 1 2 @> = InlineCall(None, f, [Value 1; Value 2], IL [Add]) b.dll:let inline g a b = f a b * 2
<@ g 1 2 @> = InlineCall(None, g, [Value 1; Value 2], IL [Add; LdConst 2; Mul]) Here the information that g calls f would be lost, but (at least for my purposes) this would be fine. |
Beta Was this translation helpful? Give feedback.
-
@dsyme i finally got something working. With the changes from https://github.com/krauthaufen/visualfsharp/commit/ea0bc38244be19d56aeb6122fac25fd15632d8e2 calls are properly inlined (using the extended Expr ctors) So far i handled single-argument function-calls in direct quotes and ReflectedDefinitions.
The calls to the QuotationTranslator are now in
Since the optimizer returns a optimize-function However the following snippet: open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns
type Vec(x : float, y : float) =
member __.X = x
member __.Y = y
let inline x< ^a, ^b when ^a : (member X : ^b)> (v : ^a) =
(^a : (member X : ^b) (v))
type Foo() =
static member inline x< ^a, ^b when ^a : (member X : ^b)> (v : ^a) =
(^a : (member X : ^b) (v))
[<ReflectedDefinition>]
static member Bar(v : Vec) = x v
[<ReflectedDefinition>]
member __.Baz(v : Vec) = x v
[<ReflectedDefinition>]
let test (a : Vec) = x a
let run = <@ fun (v : Vec) -> Foo.x v @>
printfn "run: %s" (string run)
match <@ test (Vec(1.0, 2.0)) @> with
| Call(None, mi, _) ->
match Expr.TryGetReflectedDefinition mi with
| Some v -> printfn "test: %s" (string v)
| None -> printfn "test: NO DEF"
| _ -> printfn "test: invalid"
let bar = typeof<Foo>.GetMethod("Bar")
match Expr.TryGetReflectedDefinition bar with
| Some bar -> printfn "bar: %s" (string bar)
| None -> printfn "bar: invalid"
let baz = typeof<Foo>.GetMethod("Baz")
match Expr.TryGetReflectedDefinition baz with
| Some baz -> printfn "baz: %s" (string baz)
| None -> printfn "baz: invalid" prints the following:
As you see the last expr in InlineCall is the optimized variant. |
Beta Was this translation helpful? Give feedback.
-
@dsyme I managed to simplify everything a little by moving all translations to the IlxGen (which seems like a good place for that) I also removed the ref-cell from Expr.Quote and adjusted the affected code. I'm not sure whether things actually needed the cache-value and therefore marked all of those uses with The changes are in https://github.com/krauthaufen/visualfsharp/commit/d056a5fefe4323a0b7960c405e722cccaf89a8e5 I think I'll leave this for today ;-) cheers |
Beta Was this translation helpful? Give feedback.
-
@dsyme I was thinking the whole thing over again and came up with some questions:
When adding a new IL representation to FSharp.Core we'd have all the things necessary for compiling quotations to DynamicMethods. Wouldn't it be nice to have a utility cheers |
Beta Was this translation helpful? Give feedback.
-
No breaking changes to the format. Extensions are "OK" but need careful thought about whether old compilers will still read the format successfully. If there's no good story, we have either "plan ahead" by making the compiler future-robust ASAP, or make the feature conditional on FSharp.Core latest being referenced, or something like that.
Perhaps just the ASCII for the instructions if
I assume it should be the latter, and then you beta-reduce against the arguments explicitly
I think that just adds work - it's impossible to check without effectively doing the translation (the F# type checker itself works by the same principle, though we only run it once)
Skipped. It's optional information logically associated with a MethodInfo somewhat like the ReflectedDefinition info
It shouldn't be in FSharp.Core. Perhaps in FSharp.Compiler.Service. But I'm ok with it being in QuotationCompiler etc. |
Beta Was this translation helpful? Give feedback.
-
@krauthaufen Great work by the way, send an udpated link (or branch comparison link) when you get the chance |
Beta Was this translation helpful? Give feedback.
-
Hi everyone,
In our project FShade we're making heavy use of quotations and we're totally happy with most of the features available for inspecting and changing them.
However there is one big issue I'd like to share since I could not quite get my head around the compiler-code responsible for that.
Statically resolved parameters
When using functions like
inside quotations, the resulting Expr will look like
Call(runMeth, [a])
and there is (to my knowledge) no way of getting its definition for the concrete instantiation in question.When adding the
[<ReflectedDefinition>]
attribute torun
the compiler complains withQuotations cannot contain expressions that make member constraint calls, or uses of operators that implicitly resolve to a member constraint call
Motivation
Currently our C99/GLSL compiler maintains a mapping from all known inline functions to their respective backend counterpart which makes it hard to add utility-functions.
Consider the following example:
Since
x
is treated as a call our compiler needs to know about its definition. However if the code was inlined in the Expr it would only need to know about the propertyX
(which it does anyways).Suggestion
Would it be possible to resolve all inline functions at compile-time and simply inline their (resolved) definition in quotations like that?
I'm aware that this scheme will not work for built-in functions (e.g. op_Addition) since they use inline-IL which cannot be represented in Expr.
Since this change would break the current behaviour of quotations one could (for example) add the inlined Expr to the
CustomAttributes
list for the expression.any thoughts on that?
cheers
Beta Was this translation helpful? Give feedback.
All reactions