< Summary - FSharp.Interop.Dlr

Information
Class: FSharp.Interop.Dlr.SiteHoisting
Assembly: FSharp.Interop.Dlr
File(s): /home/runner/work/FSharp.Interop.Dlr/FSharp.Interop.Dlr/FSharp.Interop.Dlr/SiteHoister.fs
Line coverage
98%
Covered lines: 112
Uncovered lines: 2
Coverable lines: 114
Total lines: 190
Line coverage: 98.2%
Branch coverage
87%
Covered branches: 79
Total branches: 90
Branch coverage: 87.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
clean@19(...)75%88100%
Invoke(...)100%11100%
lowerFirst@24(...)75%44100%
Invoke(...)50%22100%
operationName(...)100%2020100%
nameNested(...)100%11100%
siteName(...)50%4483.33%
.ctor()100%11100%
VisitLambda(...)100%44100%
VisitConstant(...)90%1010100%
VisitMethodCall(...)90.62%323297.91%

File(s)

/home/runner/work/FSharp.Interop.Dlr/FSharp.Interop.Dlr/FSharp.Interop.Dlr/SiteHoister.fs

#LineLine coverage
 1namespace FSharp.Interop.Dlr
 2
 3open System
 4open System.Linq.Expressions
 5open FSharp.Quotations
 6open FSharp.Quotations.Patterns
 7open FSharp.Quotations.DerivedPatterns
 8open FSharp.Quotations.ExprShape
 9open FSharp.Reflection
 10open Microsoft.FSharp.Linq.RuntimeHelpers
 11open System.Runtime.CompilerServices
 12
 13module internal SiteHoisting =
 14    open TranslatePatterns
 15
 16    /// A readable name for a hoisted site's local, after its operation (`getCount`, `invokeAdd`,
 17    /// `convertInt32`, `add`): only a debugger or a rendering of the tree ever sees it.
 18    let rec private operationName (binder: CallSiteBinder) : string =
 19        let clean (s: string) =
 20            // `add_Clicked` → `AddClicked`: each part capitalised, so `invoke` + it reads as one name.
 1868021            s.Split([| '_' |], StringSplitOptions.RemoveEmptyEntries)
 9378922            |> Array.map (fun part -> let p = String(part |> Seq.filter Char.IsLetterOrDigit |> Array.ofSeq) in if p = "
 1868023            |> String.concat ""
 106824        let lowerFirst (s: string) = if s = "" then s else string (Char.ToLowerInvariant s.[0]) + s.Substring 1
 2286325        match binder with
 341926        | :? MetaObjectAwareBinder as m -> operationName m.Inner
 913927        | :? System.Dynamic.ConvertBinder as c -> "convert" + clean c.Type.Name
 469628        | :? System.Dynamic.GetMemberBinder as g -> "get" + clean g.Name
 24029        | :? System.Dynamic.SetMemberBinder as s -> "set" + clean s.Name
 441530        | :? System.Dynamic.InvokeMemberBinder as i -> "invoke" + clean i.Name
 27431        | :? System.Dynamic.InvokeBinder -> "invoke"
 8232        | :? System.Dynamic.GetIndexBinder -> "getIndex"
 6433        | :? System.Dynamic.SetIndexBinder -> "setIndex"
 32434        | :? System.Dynamic.BinaryOperationBinder as b -> lowerFirst (string b.Operation)
 2035        | :? System.Dynamic.UnaryOperationBinder as u -> lowerFirst (string u.Operation)
 19036        | b -> lowerFirst (clean (b.GetType().Name.Replace("CSharp", "").Replace("FSharp", "").Replace("Binder", "")))
 37
 38    /// Each lambda nested in `tree` named for its stack frame by `nameFor kind`: the kind is the
 39    /// `DlrRuntime` helper the lambda is handed to (`for`, `while` and its `whileGuard`, `try`,
 40    /// `with`, `finally`, `use`), else `fun`. The tree's own name is kept.
 41    let nameNested (nameFor: string -> string) (tree: LambdaExpression) : LambdaExpression =
 834842        let kinds =
 834843            dict [ "forEach", [ "for" ]; "forRange", [ "for" ]; "whileLoop", [ "whileGuard"; "while" ]
 834844                   "tryWith", [ "try"; "with" ]; "tryFinally", [ "try"; "finally" ]; "using", [ "use" ] ]
 834845        let visitor =
 834846            { new ExpressionVisitor() with
 834847                // Each lambda is named before its body is visited, so numbers follow source order:
 834848                // an outer loop is `-for`, the loop inside it `-for-2`.
 834849                override this.VisitLambda<'T>(node: Expression<'T>) : Expression =
 420250                    let name = nameFor "fun"
 420251                    Expression.Lambda(node.Type, this.Visit node.Body, name, node.Parameters) :> Expression
 834852                override this.VisitMethodCall(node) =
 4145553                    match node.Method.DeclaringType, kinds.TryGetValue node.Method.Name with
 371454                    | t, (true, names) when not (isNull t) && t.FullName = "FSharp.Interop.Dlr.DlrRuntime" ->
 247655                        let mutable next = names
 247656                        let args =
 1000457                            [ for a in node.Arguments ->
 505258                                match a with
 405259                                | :? LambdaExpression as l when not next.IsEmpty ->
 405260                                    let name = nameFor (List.head next)
 405261                                    next <- List.tail next
 405262                                    Expression.Lambda(l.Type, this.Visit l.Body, name, l.Parameters) :> Expression
 347663                                | a -> this.Visit a ]
 247664                        node.Update(this.Visit node.Object, args) :> Expression
 4732765                    | _ -> base.VisitMethodCall node }
 834866        Expression.Lambda(tree.Type, visitor.Visit tree.Body, tree.Name, tree.Parameters)
 67
 68    /// `operationName`, made a valid identifier, and unique among the names already in `taken`.
 69    let private siteName (binder: CallSiteBinder) (taken: string seq) =
 70        let name =
 1944471            match operationName binder with
 072            | "" -> "site"
 1944473            | n when Char.IsDigit n.[0] -> "site" + n
 1944474            | n -> n
 1944475        let taken = Set.ofSeq taken
 6812276        Seq.initInfinite (fun i -> if i = 0 then name else name + string (i + 1)) |> Seq.find (taken.Contains >> not)
 77
 78    /// The call sites of a compiled block hoisted into locals of the lambda that uses them.
 79    /// `LambdaExpression.Compile` keeps a reference-type constant in its closure's `Constants`
 80    /// array and re-reads and casts it at every use — two per site call (`site.Target` and the
 81    /// `site` argument). This visitor gives each lambda — the block's own and every nested one
 82    /// (loop and try bodies, user lambdas) — a `Block` binding the sites its body uses directly to
 83    /// variables assigned once at entry, so a use is a local read. Per lambda, not at the block's
 84    /// entry: a variable captured by a nested lambda would be a `StrongBox` read, no better than
 85    /// the constant. Done on the LINQ tree rather than as quotation `Let`s, which FSharp.Core
 86    /// before 10.1 converts to nested lambda invocations (50× slower, measured).
 87    type SiteHoister() =
 1087588        inherit ExpressionVisitor()
 1087589        let mutable current : System.Collections.Generic.Dictionary<CallSite, ParameterExpression> = null
 90
 91        /// A placeholder's argument, boxed to `obj` in its array, back at the parameter type it was
 92        /// boxed from (the box dropped when it is that type already).
 93        static let unboxed (e: Expression) (wanted: Type) =
 94            match e with
 95            | :? UnaryExpression as u when u.NodeType = ExpressionType.Convert && u.Type = typeof<obj> && u.Operand.Type
 96            | e when e.Type = wanted -> e
 97            | e -> Expression.Convert(e, wanted) :> Expression
 98
 99        /// The site's `Target` delegate, the site cast from the placeholder's `CallSite` to its type.
 100        static let siteTarget (site: Expression) (siteDelegate: Type) =
 101            Expression.Field(Expression.Convert(site, typedefof<CallSite<_>>.MakeGenericType siteDelegate), "Target")
 102
 103        override this.VisitLambda<'T>(node: Expression<'T>) : Expression =
 19129104            let saved = current
 19129105            current <- System.Collections.Generic.Dictionary(HashIdentity.Reference)
 19129106            let body = this.Visit node.Body
 19129107            let mine = current
 19129108            current <- saved
 27245109            if mine.Count = 0 then node.Update(body, node.Parameters) :> Expression
 110            else
 71927111                let assigns = [ for KeyValue(site, var) in mine -> Expression.Assign(var, Expression.Constant(site, var.
 11013112                let block = Expression.Block(body.Type, mine.Values, assigns @ [ body ])
 11013113                node.Update(block, node.Parameters) :> Expression
 114
 115        override _.VisitConstant(node: ConstantExpression) : Expression =
 62660116            match node.Value with
 36770117            | :? CallSite as site when not (isNull current) ->
 36770118                match current.TryGetValue site with
 17326119                | true, var -> var :> Expression
 120                | _ ->
 77898121                    let var = Expression.Variable(node.Type, siteName site.Binder [ for v in current.Values -> v.Name ])
 19444122                    current.[site] <- var
 19444123                    var :> Expression
 25890124            | _ -> node :> Expression
 125
 126        /// A wide site's placeholder (`Binders.WideSite`) becomes the typed `Invoke` on the site's
 127        /// `Target`: the delegate type from the placeholder's constant, each argument unboxed
 128        /// back to the parameter type it was boxed from.
 129        override this.VisitMethodCall(node: MethodCallExpression) : Expression =
 47502130            if node.Method.DeclaringType = typeof<Binders.WideSite> then
 60131                let site = this.Visit node.Arguments.[0]
 60132                let siteDelegate = (node.Arguments.[1] :?> ConstantExpression).Value :?> Type
 60133                let parameters = siteDelegate.GetMethod("Invoke").GetParameters()
 60134                let elements = (node.Arguments.[2] :?> NewArrayExpression).Expressions
 3624135                let args = [ for i in 0 .. elements.Count - 1 -> unboxed (this.Visit elements.[i]) parameters.[i + 1].Pa
 60136                let invoke = Expression.Invoke(siteTarget site siteDelegate, (Expression.Convert(site, typeof<CallSite>)
 64137                if node.Method.Name = "InvokeVoid" then invoke :> Expression
 112138                elif invoke.Type = typeof<obj> then invoke :> Expression
 0139                else Expression.Convert(invoke, typeof<obj>) :> Expression
 140            // A byref site's placeholder (`Binders.ByRefSite`) becomes the typed `Invoke` over a
 141            // variable per byref parameter (a ref's value in, the default for an out), which LINQ
 142            // writes back; then the holder `'H` — result, then each byref's value, typed — the
 143            // translator unpacks. Required on wasm, where DynamicInvoke does not write byrefs back.
 47442144            elif node.Method.DeclaringType = typeof<Binders.ByRefSite> then
 1270145                let site = this.Visit node.Arguments.[0]
 1270146                let siteDelegate = (node.Arguments.[1] :?> ConstantExpression).Value :?> Type
 1270147                let invokeMethod = siteDelegate.GetMethod("Invoke")
 1270148                let parameters = invokeMethod.GetParameters()
 1270149                let elements = (node.Arguments.[2] :?> NewArrayExpression).Expressions
 150                // Out positions are explicit: in a per-key template an out's value in is a parameter,
 151                // not the null constant, and an emitted delegate's parameter carries no [Out].
 1270152                let outs = (node.Arguments.[4] :?> ConstantExpression).Value :?> int[]
 1270153                let sameAs = (node.Arguments.[5] :?> ConstantExpression).Value :?> int[]
 1270154                let tempAt = System.Collections.Generic.Dictionary<int, ParameterExpression>()
 1270155                let temps = ResizeArray<ParameterExpression>()
 1270156                let inits = ResizeArray<Expression>()
 1270157                let args =
 9206158                    [ for i in 0 .. elements.Count - 1 ->
 3968159                        let p = parameters.[i + 1]
 3968160                        let e = this.Visit elements.[i]
 9394161                        if p.ParameterType.IsByRef && sameAs.[i] >= 0 then
 1270162                            // The same variable as an earlier ref: the same storage, as C# passes it.
 4163                            tempAt.[i] <- tempAt.[sameAs.[i]]
 4164                            tempAt.[i] :> Expression
 3964165                        elif p.ParameterType.IsByRef then
 1454166                            let t = p.ParameterType.GetElementType()
 1454167                            let temp = Expression.Variable(t, "byRef")
 1454168                            temps.Add temp
 1454169                            tempAt.[i] <- temp
 2908170                            let initial = if Array.contains i outs then Expression.Default(t) :> Expression else unboxed
 1454171                            inits.Add(Expression.Assign(temp, initial))
 1454172                            temp :> Expression
 3780173                        else unboxed e p.ParameterType ]
 1270174                let call = Expression.Call(siteTarget site siteDelegate, invokeMethod, (Expression.Convert(site, typeof<
 175                let result, callStep =
 1346176                    if invokeMethod.ReturnType = typeof<Void> then (Expression.Constant(null, typeof<obj>) :> Expression
 177                    else
 1194178                        let r = Expression.Variable(invokeMethod.ReturnType, "result")
 1194179                        temps.Add r
 1194180                        (Expression.Convert(r, typeof<obj>) :> Expression), (Expression.Assign(r, call) :> Expression)
 181                // The holder `node.Type` (Binders.byRefHolderType).
 17172182                let values = Tuples.newExpression node.Type (result :: [ for i in 0 .. elements.Count - 1 do if paramete
 1270183                Expression.Block(node.Type, temps, List.ofSeq inits @ [ callStep; values ]) :> Expression
 184            // An in-place struct operation's write-back (`Binders.InPlace`): in a finally.
 46172185            elif node.Method.DeclaringType = typeof<Binders.InPlace> then
 988186                Expression.TryFinally(this.Visit node.Arguments.[0], this.Visit node.Arguments.[1]) :> Expression
 45184187            else base.VisitMethodCall node
 188
 189    let onWasm =
 190        string System.Runtime.InteropServices.RuntimeInformation.OSArchitecture = "Wasm"   // no Architecture.Wasm on ne