< 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
Invoke(...)100%11100%
clean@19(...)75%88100%
Invoke(...)50%22100%
lowerFirst@24(...)75%44100%
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.
 2907821            s.Split([| '_' |], StringSplitOptions.RemoveEmptyEntries)
 14646122            |> Array.map (fun part -> let p = String(part |> Seq.filter Char.IsLetterOrDigit |> Array.ofSeq) in if p = "
 2907823            |> String.concat ""
 159624        let lowerFirst (s: string) = if s = "" then s else string (Char.ToLowerInvariant s.[0]) + s.Substring 1
 3568325        match binder with
 543326        | :? MetaObjectAwareBinder as m -> operationName m.Inner
 1424227        | :? System.Dynamic.ConvertBinder as c -> "convert" + clean c.Type.Name
 729128        | :? System.Dynamic.GetMemberBinder as g -> "get" + clean g.Name
 36229        | :? System.Dynamic.SetMemberBinder as s -> "set" + clean s.Name
 690130        | :? System.Dynamic.InvokeMemberBinder as i -> "invoke" + clean i.Name
 42031        | :? System.Dynamic.InvokeBinder -> "invoke"
 13632        | :? System.Dynamic.GetIndexBinder -> "getIndex"
 10033        | :? System.Dynamic.SetIndexBinder -> "setIndex"
 48634        | :? System.Dynamic.BinaryOperationBinder as b -> lowerFirst (string b.Operation)
 3035        | :? System.Dynamic.UnaryOperationBinder as u -> lowerFirst (string u.Operation)
 28236        | 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 =
 1310942        let kinds =
 1310943            dict [ "forEach", [ "for" ]; "forRange", [ "for" ]; "whileLoop", [ "whileGuard"; "while" ]
 1310944                   "tryWith", [ "try"; "with" ]; "tryFinally", [ "try"; "finally" ]; "using", [ "use" ] ]
 1310945        let visitor =
 1310946            { new ExpressionVisitor() with
 1310947                // Each lambda is named before its body is visited, so numbers follow source order:
 1310948                // an outer loop is `-for`, the loop inside it `-for-2`.
 1310949                override this.VisitLambda<'T>(node: Expression<'T>) : Expression =
 583950                    let name = nameFor "fun"
 583951                    Expression.Lambda(node.Type, this.Visit node.Body, name, node.Parameters) :> Expression
 1310952                override this.VisitMethodCall(node) =
 6313853                    match node.Method.DeclaringType, kinds.TryGetValue node.Method.Name with
 565454                    | t, (true, names) when not (isNull t) && t.FullName = "FSharp.Interop.Dlr.DlrRuntime" ->
 377055                        let mutable next = names
 377056                        let args =
 1529057                            [ for a in node.Arguments ->
 775058                                match a with
 613459                                | :? LambdaExpression as l when not next.IsEmpty ->
 613460                                    let name = nameFor (List.head next)
 613461                                    next <- List.tail next
 613462                                    Expression.Lambda(l.Type, this.Visit l.Body, name, l.Parameters) :> Expression
 538663                                | a -> this.Visit a ]
 377064                        node.Update(this.Visit node.Object, args) :> Expression
 7247765                    | _ -> base.VisitMethodCall node }
 1310966        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 =
 3025071            match operationName binder with
 072            | "" -> "site"
 3025073            | n when Char.IsDigit n.[0] -> "site" + n
 3025074            | n -> n
 3025075        let taken = Set.ofSeq taken
 10610476        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() =
 1691688        inherit ExpressionVisitor()
 1691689        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 =
 28889104            let saved = current
 28889105            current <- System.Collections.Generic.Dictionary(HashIdentity.Reference)
 28889106            let body = this.Visit node.Body
 28889107            let mine = current
 28889108            current <- saved
 40708109            if mine.Count = 0 then node.Update(body, node.Parameters) :> Expression
 110            else
 111710111                let assigns = [ for KeyValue(site, var) in mine -> Expression.Assign(var, Expression.Constant(site, var.
 17070112                let block = Expression.Block(body.Type, mine.Values, assigns @ [ body ])
 17070113                node.Update(block, node.Parameters) :> Expression
 114
 115        override _.VisitConstant(node: ConstantExpression) : Expression =
 97072116            match node.Value with
 57340117            | :? CallSite as site when not (isNull current) ->
 57340118                match current.TryGetValue site with
 27090119                | true, var -> var :> Expression
 120                | _ ->
 121154121                    let var = Expression.Variable(node.Type, siteName site.Binder [ for v in current.Values -> v.Name ])
 30250122                    current.[site] <- var
 30250123                    var :> Expression
 39732124            | _ -> 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 =
 72241130            if node.Method.DeclaringType = typeof<Binders.WideSite> then
 90131                let site = this.Visit node.Arguments.[0]
 90132                let siteDelegate = (node.Arguments.[1] :?> ConstantExpression).Value :?> Type
 90133                let parameters = siteDelegate.GetMethod("Invoke").GetParameters()
 90134                let elements = (node.Arguments.[2] :?> NewArrayExpression).Expressions
 5436135                let args = [ for i in 0 .. elements.Count - 1 -> unboxed (this.Visit elements.[i]) parameters.[i + 1].Pa
 90136                let invoke = Expression.Invoke(siteTarget site siteDelegate, (Expression.Convert(site, typeof<CallSite>)
 96137                if node.Method.Name = "InvokeVoid" then invoke :> Expression
 168138                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.
 72151144            elif node.Method.DeclaringType = typeof<Binders.ByRefSite> then
 1898145                let site = this.Visit node.Arguments.[0]
 1898146                let siteDelegate = (node.Arguments.[1] :?> ConstantExpression).Value :?> Type
 1898147                let invokeMethod = siteDelegate.GetMethod("Invoke")
 1898148                let parameters = invokeMethod.GetParameters()
 1898149                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].
 1898152                let outs = (node.Arguments.[4] :?> ConstantExpression).Value :?> int[]
 1898153                let sameAs = (node.Arguments.[5] :?> ConstantExpression).Value :?> int[]
 1898154                let tempAt = System.Collections.Generic.Dictionary<int, ParameterExpression>()
 1898155                let temps = ResizeArray<ParameterExpression>()
 1898156                let inits = ResizeArray<Expression>()
 1898157                let args =
 13766158                    [ for i in 0 .. elements.Count - 1 ->
 5934159                        let p = parameters.[i + 1]
 5934160                        let e = this.Visit elements.[i]
 14048161                        if p.ParameterType.IsByRef && sameAs.[i] >= 0 then
 1898162                            // The same variable as an earlier ref: the same storage, as C# passes it.
 6163                            tempAt.[i] <- tempAt.[sameAs.[i]]
 6164                            tempAt.[i] :> Expression
 5928165                        elif p.ParameterType.IsByRef then
 2174166                            let t = p.ParameterType.GetElementType()
 2174167                            let temp = Expression.Variable(t, "byRef")
 2174168                            temps.Add temp
 2174169                            tempAt.[i] <- temp
 4348170                            let initial = if Array.contains i outs then Expression.Default(t) :> Expression else unboxed
 2174171                            inits.Add(Expression.Assign(temp, initial))
 2174172                            temp :> Expression
 5652173                        else unboxed e p.ParameterType ]
 1898174                let call = Expression.Call(siteTarget site siteDelegate, invokeMethod, (Expression.Convert(site, typeof<
 175                let result, callStep =
 2012176                    if invokeMethod.ReturnType = typeof<Void> then (Expression.Constant(null, typeof<obj>) :> Expression
 177                    else
 1784178                        let r = Expression.Variable(invokeMethod.ReturnType, "result")
 1784179                        temps.Add r
 1784180                        (Expression.Convert(r, typeof<obj>) :> Expression), (Expression.Assign(r, call) :> Expression)
 181                // The holder `node.Type` (Binders.byRefHolderType).
 25676182                let values = Tuples.newExpression node.Type (result :: [ for i in 0 .. elements.Count - 1 do if paramete
 1898183                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.
 70253185            elif node.Method.DeclaringType = typeof<Binders.InPlace> then
 1482186                Expression.TryFinally(this.Visit node.Arguments.[0], this.Visit node.Arguments.[1]) :> Expression
 68771187            else base.VisitMethodCall node
 188
 189    let onWasm =
 190        string System.Runtime.InteropServices.RuntimeInformation.OSArchitecture = "Wasm"   // no Architecture.Wasm on ne