The RFC FS-1097 and FS-1087 highlighted the need for some additional optimizations.
The F# optimizer is considered part of tooling and, while its optimizations are not all documented via RFCs it is worth writing out the spec of the new optimizations here and their rationale as a tooling RFC. THis is partly to ensure we have test coverage for these.
-
__stack_prefixed vars are not eliminated during optimization. The rationale is that these are only used when describing resumable code, primarily for tasks. -
InlineIfLambdaattribute is recognized and applied. The rationale is covered in RFC FS-1098. -
If a computed function is immediately executed in a sequential block the code is reduced, e.g.
let part1 = fexpr in part1 arg; rest ---> fexpr arg; restif
fexpris not used inrest. Importantly, this applies no matter what expressionfexpris.Likewise if a computed delegate is immediately executed in a sequential block the code is reduced, e.g.
let part1 = delexpr in part1(); rest ---> delexpr.Invoke(arg); restIn both cases the application of the function/delegate is a beta-reduction application with subsequent optimization.
-
Delegate construction of F#-defined delegate types now gives rise to a "known lambda value" in the internal operation of the optimizer and cross-module optimization information. This is a non-breaking conservative addition to the cross-module optimization information.
This ony applies to F#-defined delegate types. This could be extended to .NET-defined delegate types but there was no need for this for the above RFCs.
When F#-defined delegate expressions with known value are invoked, the corresponding reduction occurs. So
type D = delegate of int -> int let v = D(fun x -> x + 1) v.Invoke(3) // reduces to '4'This also applies in cases such as where the delegate type takes byref arguments.
-
Optimization of
match __resumableEntry() withconstructs is completely skipped. This is because they have special semantics for resumable code, see the RFC. -
We optimize certain common patterns related to computed functions that are rare in normal F# code, but very common in F# lambda code representing synchronous machines.
Specifically we "lift" pre-computations off computed functions, allowing the resulting function lambda to be "exposed" so reduction can occur.
For this case, we always lift 'let', 'letrec', sequentials and 'match' off computed functions so:
(let x = 1 in fexpr) arg ---> let x = 1 in fexpr arg (let rec binds in fexpr) arg ---> let rec binds in fexpr arg (e; fexpr) arg ---> e; fexpr arg (match e with pat1 -> func1 | pat2 -> func2) args --> (match e with pat1 -> func1 args | pat2 -> func2 args)
These can be nested. The lifting of
matchinvolves duplicating 'args' and is limited to matches with two cases.This applies to both function and F#-defined delegate invocations.