performance/deep_call_in_fork.bend
bend-lint-rules@0.2.0.0 · 257 lines · 19 definitions
1
2
3
4
5
6
7
8
9
10
11
12
13import Base
14import bend-lint-api@0.2.0.0/linter/lint.bend as Lint
15import bend-lint-api@0.2.0.0/shared/lists.bend as Lists
16
17
18
19
20
21
22type Owner is Data:
23 Owner{deeps: List<&2, String>, target: String, name: String}
24
25
26
27
28
29def is_last(nodes: List<&2, Lint.Node>, at: U32) -> Bool:
30 U32.is_eq((at + 1: U32), U32.from_nat(Lists.length(&2, Lint.Node, nodes)))
31
32
33
34def is_tail(path: List<&2, Lint.Above>, ends: Bool) -> Bool:
35 match path ends:
36 case _ False{}:
37 False{}
38 case Con{Lint.Above{Lint.Node{Lint.Body{}, n, s, k, f}, at}, rest} True{}:
39 True{}
40 case Con{Lint.Above{Lint.Node{Lint.Case{}, n, s, k, f}, at}, rest} True{}:
41 is_tail(rest, True{})
42 case Con{Lint.Above{Lint.Node{Lint.Match{}, n, s, k, f}, at}, rest} True{}:
43 is_tail(rest, True{})
44 case Con{Lint.Above{Lint.Node{Lint.Annotation{}, n, s, k, f}, at}, rest} True{}:
45 is_tail(rest, True{})
46 case Con{Lint.Above{Lint.Node{Lint.Destructure{q}, n, s, kids, f}, at}, rest} True{}:
47 is_tail(rest, is_last(kids, at))
48 case Con{Lint.Above{Lint.Node{Lint.Let{}, n, s, kids, f}, at}, rest} True{}:
49 is_tail(rest, is_last(kids, at))
50 case _ _:
51 False{}
52
53
54def deep.of(recursive: Bool, path: List<&2, Lint.Above>) -> List<&2, Bool>:
55 match recursive:
56 case True{}:
57 [Bool.not(is_tail(path, True{}))]
58 case False{}:
59 []
60
61def deep.visit(+u: Unit, +path: List<&2, Lint.Above>, +node: Lint.Node) -> List<&2, Bool>:
62 deep.of(Lint.is_recursive(path, node), path)
63
64
65
66def deep.if(found: Bool, forking: Bool, k: List<&2, Lint.Node>) -> Bool:
67 match found forking:
68 case False{} _:
69 False{}
70 case True{} True{}:
71 True{}
72 case True{} False{}:
73 Bool.not(Lint.forks(k, False{}))
74
75
76
77def deep(nodes: List<&2, Lint.Node>, +forking: Bool, out: List<&2, String>) -> List<&2, String>:
78 match nodes:
79 case Nil{}:
80 out
81 case Con{Lint.Node{Lint.Definition{+t, u}, n, s, +k, f}, rest}:
82 deep(
83 rest,
84 forking,
85 Lists.push(
86 String,
87 Lists.when(
88 String,
89 deep.if(
90 List.contains(
91 ~Bool,
92 ~Bool.and,
93 Lint.visit(
94 ~Unit,
95 ~Bool,
96 ~deep.visit,
97 Unit{},
98 [Lint.Node{Lint.Definition{t, u}, n, s, k, f}]
99 ),
100 True{}
101 ),
102 forking,
103 k
104 ),
105 t
106 ),
107 out
108 )
109 )
110 case Con{node, rest}:
111 deep(rest, forking, out)
112
113def is_pattern(syntax: Lint.Syntax) -> Bool:
114 match syntax:
115 case Lint.PatternVariable{b, q}:
116 True{}
117 case Lint.PatternConstructor{t}:
118 True{}
119 case _:
120 False{}
121
122def binders(nodes: List<&2, Lint.Node>, n: Nat) -> Nat:
123 match nodes:
124 case Nil{}:
125 n
126 case Con{Lint.Node{Lint.PatternVariable{b, q}, nm, s, k, f}, rest}:
127 binders(rest, 1n+n)
128 case Con{node, rest}:
129 binders(rest, n)
130
131
132
133
134def leads(nodes: List<&2, Lint.Node>, many: Bool, one: Bool) -> Bool:
135 match nodes many one:
136 case _ True{} _:
137 True{}
138 case _ False{} False{}:
139 False{}
140 case Con{Lint.Node{Lint.Destructure{q}, n, s, +kids, f}, Nil{}} False{} True{}:
141 +count = binders(kids, 0n)
142 leads(kids, Nat.is_gt(count, 1n), Nat.is_eq(count, 1n))
143 case Con{x, Nil{}} False{} True{}:
144 False{}
145 case Con{x, rest} False{} True{}:
146 leads(rest, False{}, True{})
147 case Nil{} False{} True{}:
148 False{}
149
150
151
152def is_value(x: Lint.Node, +parent: Lint.Node, at: U32) -> Bool:
153 match x parent:
154 case Lint.Node{s, n, sp, k, f} Lint.Node{ps, pn, psp, +kids, pf}:
155 leads([parent], False{}, Bool.not(is_pattern(s)) && Bool.not(is_last(kids, at)))
156
157
158def is_inside(path: List<&2, Lint.Above>, x: Lint.Node, found: Bool) -> Bool:
159 match path found:
160 case _ True{}:
161 True{}
162 case Nil{} False{}:
163 False{}
164 case Con{Lint.Above{+p, at}, rest} False{}:
165 is_inside(rest, p, is_value(x, p, at))
166
167def message(+callee: String, loop: String) -> String:
168 callee
169 ++ " calls itself outside a tail position, so Bend gives each of its calls a frame, and "
170 ++ loop
171 ++ " calls it in a fork (a value of its parallel let, or of a let just before it). CPU: measured 4% to 8% faster for a raytracer after the fix; in a benchmark, such a call at each fork cut a 10x speedup to 1.9x. GPU: such a recursion in a `!` made a draw 2.3x slower (\"bend guide shaders\"). JS: not measured. Confidence: medium, as the cost grows with the depth of the recursion. Make "
172 ++ callee
173 ++ " tail-recursive with an accumulator, compute its value with native operations (such as U32.shln), or compute it once and pass it down as a parameter. If its input is short, expect this finding."
174
175
176def judge.deep(
177 deep: Bool,
178 path: List<&2, Lint.Above>,
179 +node: Lint.Node,
180 callee: String,
181 name: String
182) -> List<&2, Lint.Finding>:
183 match deep:
184 case True{}:
185 Lint.finding(
186 Lint.Information{},
187 is_inside(path, node, False{}),
188 Lint.Node.span(Some{node}),
189 message(callee, name)
190 )
191 case False{}:
192 []
193
194def judge.call(
195 +owner: Owner,
196 path: List<&2, Lint.Above>,
197 node: Lint.Node,
198 callee: String,
199 +called: String
200) -> List<&2, Lint.Finding>:
201 match owner:
202 case Owner{deeps, target, name}:
203 judge.deep(
204 List.contains(~String, ~String.eq, deeps, called) && Bool.not(String.eq(called, target)),
205 path,
206 node,
207 callee,
208 name
209 )
210
211def visit(+owner: Owner, +path: List<&2, Lint.Above>, +node: Lint.Node) -> List<&2, Lint.Finding>:
212 match node:
213 case Lint.Node{
214 Lint.Application{},
215 n,
216 s,
217 Con{Lint.Node{Lint.Reference{called}, callee, rs, rk, rf}, args},
218 f
219 }:
220 judge.call(owner, path, node, callee, called)
221 case _:
222 []
223
224def defs(
225 nodes: List<&2, Lint.Node>,
226 +deeps: List<&2, String>,
227 out: List<&2, Lint.Finding>
228) -> List<&2, Lint.Finding>:
229 match nodes:
230 case Nil{}:
231 List.reverse(&2, Lint.Finding, out)
232 case Con{Lint.Node{Lint.Definition{+t, u}, +n, s, k, f}, rest}:
233 defs(
234 rest,
235 deeps,
236 Lists.onto(
237 &2,
238 Lint.Finding,
239 Lint.visit(
240 ~Owner,
241 ~Lint.Finding,
242 ~visit,
243 Owner{deeps, t, n},
244 [Lint.Node{Lint.Definition{t, u}, n, s, k, f}]
245 ),
246 out
247 )
248 )
249 case Con{node, rest}:
250 defs(rest, deeps, out)
251
252def run(+here: Lint.Here) -> Lint.Ask(List<&2, Lint.Finding>):
253 +nodes = Lint.Here.nodes(here)
254 Lint.done(defs(nodes, deep(nodes, False{}, []), []))
255
256def rule() -> Lint.Rule:
257 Lint.Rule{Lint.Needs{False{}, False{}}, [], Lint.OnFile{run}}