| | | 1 | | namespace FSharp.Interop.Dlr |
| | | 2 | | |
| | | 3 | | open System |
| | | 4 | | open System.Linq.Expressions |
| | | 5 | | open FSharp.Quotations |
| | | 6 | | open FSharp.Quotations.Patterns |
| | | 7 | | open FSharp.Quotations.DerivedPatterns |
| | | 8 | | open FSharp.Quotations.ExprShape |
| | | 9 | | open FSharp.Reflection |
| | | 10 | | open Microsoft.FSharp.Linq.RuntimeHelpers |
| | | 11 | | open System.Runtime.CompilerServices |
| | | 12 | | |
| | | 13 | | module 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. |
| | 18680 | 21 | | s.Split([| '_' |], StringSplitOptions.RemoveEmptyEntries) |
| | 93789 | 22 | | |> Array.map (fun part -> let p = String(part |> Seq.filter Char.IsLetterOrDigit |> Array.ofSeq) in if p = " |
| | 18680 | 23 | | |> String.concat "" |
| | 1068 | 24 | | let lowerFirst (s: string) = if s = "" then s else string (Char.ToLowerInvariant s.[0]) + s.Substring 1 |
| | 22863 | 25 | | match binder with |
| | 3419 | 26 | | | :? MetaObjectAwareBinder as m -> operationName m.Inner |
| | 9139 | 27 | | | :? System.Dynamic.ConvertBinder as c -> "convert" + clean c.Type.Name |
| | 4696 | 28 | | | :? System.Dynamic.GetMemberBinder as g -> "get" + clean g.Name |
| | 240 | 29 | | | :? System.Dynamic.SetMemberBinder as s -> "set" + clean s.Name |
| | 4415 | 30 | | | :? System.Dynamic.InvokeMemberBinder as i -> "invoke" + clean i.Name |
| | 274 | 31 | | | :? System.Dynamic.InvokeBinder -> "invoke" |
| | 82 | 32 | | | :? System.Dynamic.GetIndexBinder -> "getIndex" |
| | 64 | 33 | | | :? System.Dynamic.SetIndexBinder -> "setIndex" |
| | 324 | 34 | | | :? System.Dynamic.BinaryOperationBinder as b -> lowerFirst (string b.Operation) |
| | 20 | 35 | | | :? System.Dynamic.UnaryOperationBinder as u -> lowerFirst (string u.Operation) |
| | 190 | 36 | | | 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 = |
| | 8348 | 42 | | let kinds = |
| | 8348 | 43 | | dict [ "forEach", [ "for" ]; "forRange", [ "for" ]; "whileLoop", [ "whileGuard"; "while" ] |
| | 8348 | 44 | | "tryWith", [ "try"; "with" ]; "tryFinally", [ "try"; "finally" ]; "using", [ "use" ] ] |
| | 8348 | 45 | | let visitor = |
| | 8348 | 46 | | { new ExpressionVisitor() with |
| | 8348 | 47 | | // Each lambda is named before its body is visited, so numbers follow source order: |
| | 8348 | 48 | | // an outer loop is `-for`, the loop inside it `-for-2`. |
| | 8348 | 49 | | override this.VisitLambda<'T>(node: Expression<'T>) : Expression = |
| | 4202 | 50 | | let name = nameFor "fun" |
| | 4202 | 51 | | Expression.Lambda(node.Type, this.Visit node.Body, name, node.Parameters) :> Expression |
| | 8348 | 52 | | override this.VisitMethodCall(node) = |
| | 41455 | 53 | | match node.Method.DeclaringType, kinds.TryGetValue node.Method.Name with |
| | 3714 | 54 | | | t, (true, names) when not (isNull t) && t.FullName = "FSharp.Interop.Dlr.DlrRuntime" -> |
| | 2476 | 55 | | let mutable next = names |
| | 2476 | 56 | | let args = |
| | 10004 | 57 | | [ for a in node.Arguments -> |
| | 5052 | 58 | | match a with |
| | 4052 | 59 | | | :? LambdaExpression as l when not next.IsEmpty -> |
| | 4052 | 60 | | let name = nameFor (List.head next) |
| | 4052 | 61 | | next <- List.tail next |
| | 4052 | 62 | | Expression.Lambda(l.Type, this.Visit l.Body, name, l.Parameters) :> Expression |
| | 3476 | 63 | | | a -> this.Visit a ] |
| | 2476 | 64 | | node.Update(this.Visit node.Object, args) :> Expression |
| | 47327 | 65 | | | _ -> base.VisitMethodCall node } |
| | 8348 | 66 | | 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 = |
| | 19444 | 71 | | match operationName binder with |
| | 0 | 72 | | | "" -> "site" |
| | 19444 | 73 | | | n when Char.IsDigit n.[0] -> "site" + n |
| | 19444 | 74 | | | n -> n |
| | 19444 | 75 | | let taken = Set.ofSeq taken |
| | 68122 | 76 | | 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() = |
| | 10875 | 88 | | inherit ExpressionVisitor() |
| | 10875 | 89 | | 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 = |
| | 19129 | 104 | | let saved = current |
| | 19129 | 105 | | current <- System.Collections.Generic.Dictionary(HashIdentity.Reference) |
| | 19129 | 106 | | let body = this.Visit node.Body |
| | 19129 | 107 | | let mine = current |
| | 19129 | 108 | | current <- saved |
| | 27245 | 109 | | if mine.Count = 0 then node.Update(body, node.Parameters) :> Expression |
| | | 110 | | else |
| | 71927 | 111 | | let assigns = [ for KeyValue(site, var) in mine -> Expression.Assign(var, Expression.Constant(site, var. |
| | 11013 | 112 | | let block = Expression.Block(body.Type, mine.Values, assigns @ [ body ]) |
| | 11013 | 113 | | node.Update(block, node.Parameters) :> Expression |
| | | 114 | | |
| | | 115 | | override _.VisitConstant(node: ConstantExpression) : Expression = |
| | 62660 | 116 | | match node.Value with |
| | 36770 | 117 | | | :? CallSite as site when not (isNull current) -> |
| | 36770 | 118 | | match current.TryGetValue site with |
| | 17326 | 119 | | | true, var -> var :> Expression |
| | | 120 | | | _ -> |
| | 77898 | 121 | | let var = Expression.Variable(node.Type, siteName site.Binder [ for v in current.Values -> v.Name ]) |
| | 19444 | 122 | | current.[site] <- var |
| | 19444 | 123 | | var :> Expression |
| | 25890 | 124 | | | _ -> 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 = |
| | 47502 | 130 | | if node.Method.DeclaringType = typeof<Binders.WideSite> then |
| | 60 | 131 | | let site = this.Visit node.Arguments.[0] |
| | 60 | 132 | | let siteDelegate = (node.Arguments.[1] :?> ConstantExpression).Value :?> Type |
| | 60 | 133 | | let parameters = siteDelegate.GetMethod("Invoke").GetParameters() |
| | 60 | 134 | | let elements = (node.Arguments.[2] :?> NewArrayExpression).Expressions |
| | 3624 | 135 | | let args = [ for i in 0 .. elements.Count - 1 -> unboxed (this.Visit elements.[i]) parameters.[i + 1].Pa |
| | 60 | 136 | | let invoke = Expression.Invoke(siteTarget site siteDelegate, (Expression.Convert(site, typeof<CallSite>) |
| | 64 | 137 | | if node.Method.Name = "InvokeVoid" then invoke :> Expression |
| | 112 | 138 | | elif invoke.Type = typeof<obj> then invoke :> Expression |
| | 0 | 139 | | 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. |
| | 47442 | 144 | | elif node.Method.DeclaringType = typeof<Binders.ByRefSite> then |
| | 1270 | 145 | | let site = this.Visit node.Arguments.[0] |
| | 1270 | 146 | | let siteDelegate = (node.Arguments.[1] :?> ConstantExpression).Value :?> Type |
| | 1270 | 147 | | let invokeMethod = siteDelegate.GetMethod("Invoke") |
| | 1270 | 148 | | let parameters = invokeMethod.GetParameters() |
| | 1270 | 149 | | 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]. |
| | 1270 | 152 | | let outs = (node.Arguments.[4] :?> ConstantExpression).Value :?> int[] |
| | 1270 | 153 | | let sameAs = (node.Arguments.[5] :?> ConstantExpression).Value :?> int[] |
| | 1270 | 154 | | let tempAt = System.Collections.Generic.Dictionary<int, ParameterExpression>() |
| | 1270 | 155 | | let temps = ResizeArray<ParameterExpression>() |
| | 1270 | 156 | | let inits = ResizeArray<Expression>() |
| | 1270 | 157 | | let args = |
| | 9206 | 158 | | [ for i in 0 .. elements.Count - 1 -> |
| | 3968 | 159 | | let p = parameters.[i + 1] |
| | 3968 | 160 | | let e = this.Visit elements.[i] |
| | 9394 | 161 | | if p.ParameterType.IsByRef && sameAs.[i] >= 0 then |
| | 1270 | 162 | | // The same variable as an earlier ref: the same storage, as C# passes it. |
| | 4 | 163 | | tempAt.[i] <- tempAt.[sameAs.[i]] |
| | 4 | 164 | | tempAt.[i] :> Expression |
| | 3964 | 165 | | elif p.ParameterType.IsByRef then |
| | 1454 | 166 | | let t = p.ParameterType.GetElementType() |
| | 1454 | 167 | | let temp = Expression.Variable(t, "byRef") |
| | 1454 | 168 | | temps.Add temp |
| | 1454 | 169 | | tempAt.[i] <- temp |
| | 2908 | 170 | | let initial = if Array.contains i outs then Expression.Default(t) :> Expression else unboxed |
| | 1454 | 171 | | inits.Add(Expression.Assign(temp, initial)) |
| | 1454 | 172 | | temp :> Expression |
| | 3780 | 173 | | else unboxed e p.ParameterType ] |
| | 1270 | 174 | | let call = Expression.Call(siteTarget site siteDelegate, invokeMethod, (Expression.Convert(site, typeof< |
| | | 175 | | let result, callStep = |
| | 1346 | 176 | | if invokeMethod.ReturnType = typeof<Void> then (Expression.Constant(null, typeof<obj>) :> Expression |
| | | 177 | | else |
| | 1194 | 178 | | let r = Expression.Variable(invokeMethod.ReturnType, "result") |
| | 1194 | 179 | | temps.Add r |
| | 1194 | 180 | | (Expression.Convert(r, typeof<obj>) :> Expression), (Expression.Assign(r, call) :> Expression) |
| | | 181 | | // The holder `node.Type` (Binders.byRefHolderType). |
| | 17172 | 182 | | let values = Tuples.newExpression node.Type (result :: [ for i in 0 .. elements.Count - 1 do if paramete |
| | 1270 | 183 | | 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. |
| | 46172 | 185 | | elif node.Method.DeclaringType = typeof<Binders.InPlace> then |
| | 988 | 186 | | Expression.TryFinally(this.Visit node.Arguments.[0], this.Visit node.Arguments.[1]) :> Expression |
| | 45184 | 187 | | else base.VisitMethodCall node |
| | | 188 | | |
| | | 189 | | let onWasm = |
| | | 190 | | string System.Runtime.InteropServices.RuntimeInformation.OSArchitecture = "Wasm" // no Architecture.Wasm on ne |