performance/bang_after_fork.bend
bend-lint-rules@0.2.0.0 · 175 lines · 12 definitions
1
2
3
4
5
6
7
8
9
10
11
12
13
14import Base
15import bend-lint-api@0.2.0.0/linter/lint.bend as Lint
16import bend-lint-api@0.2.0.0/shared/lists.bend as Lists
17import bend-lint-api@0.2.0.0/shared/names.bend as Names
18import bend-lint-api@0.2.0.0/shared/sets.bend as Sets
19import bend-lint-rules@0.2.0.0/performance/deep_call_in_fork.bend as Deep
20import bend-lint-rules@0.2.0.0/performance/gpu_fit.bend as GpuFit
21
22
23
24
25
26
27type Program is Data:
28 Program{defs: Map<&2, Maybe<&2, Lint.Def>>, under: Set(), bangs: List<&2, U32>}
29
30
31
32def message(callee: String) -> String:
33 callee
34 ++ "! runs after a fork in the same evaluation (no IO step between them), so Bend runs it on the CPU pool, not on the GPU. GPU: measured 3.9 s for a numeric fork tree, against 0.55 s after the fix. CPU (`--gpu off`): no change. JS: no GPU, so no change. Confidence: high, as Bend's scheduler decides it. Put an IO step, such as `t : Nat <- IO.now()`, between the forks and this call, as Bend's slash boss demo does. If this call is short, so that the CPU pool runs it as fast, expect this finding."
35
36
37
38def host.visit(
39 +bangs: List<&2, U32>,
40 +path: List<&2, Lint.Above>,
41 +node: Lint.Node
42) -> List<&2, String>:
43 match node:
44 case Lint.Node{
45 Lint.Application{},
46 n,
47 s,
48 Con{Lint.Node{Lint.Reference{t}, rn, Some{Lint.Span{p, beg, end}}, rk, rf}, args},
49 f
50 }:
51 Bool.pick(List<&2, String>, List.contains(~U32, ~U32.is_eq, bangs, end), [], [t])
52 case _:
53 []
54
55
56
57def forks(nodes: List<&2, Lint.Node>, program: Program) -> Bool:
58 match program:
59 case Program{defs, under, bangs}:
60 Sets.has_any(Lint.visit(~List<&2, U32>, ~String, ~host.visit, bangs, nodes), under, False{})
61
62
63def let.values(+kids: List<&2, Lint.Node>) -> List<&2, Lint.Node>:
64 List.take(&2, Lint.Node, kids, Nat.sub(Lists.length(&2, Lint.Node, kids), 1n))
65
66
67
68def let.forks(last: Bool, parallel: Bool, kids: List<&2, Lint.Node>, program: Program) -> Bool:
69 match last parallel:
70 case False{} _:
71 False{}
72 case True{} True{}:
73 True{}
74 case True{} False{}:
75 forks(let.values(kids), program)
76
77
78
79def after(path: List<&2, Lint.Above>, +program: Program, found: Bool) -> Bool:
80 match path found:
81 case _ True{}:
82 True{}
83 case Nil{} False{}:
84 False{}
85 case Con{Lint.Above{Lint.Node{Lint.Lambda{b, q}, n, s, k, f}, at}, rest} False{}:
86 False{}
87 case Con{Lint.Above{Lint.Node{Lint.Body{}, n, s, k, f}, at}, rest} False{}:
88 False{}
89 case Con{Lint.Above{Lint.Node{Lint.Destructure{q}, n, s, +kids, f}, at}, rest} False{}:
90 after(
91 rest,
92 program,
93 let.forks(Deep.is_last(kids, at), Nat.is_gt(Deep.binders(kids, 0n), 1n), kids, program)
94 )
95 case Con{Lint.Above{Lint.Node{Lint.Let{}, n, s, +kids, f}, at}, rest} False{}:
96 after(rest, program, let.forks(Deep.is_last(kids, at), False{}, kids, program))
97 case Con{a, rest} False{}:
98 after(rest, program, False{})
99
100
101
102def after.if(
103 asked: Bool,
104 args: List<&2, Lint.Node>,
105 path: List<&2, Lint.Above>,
106 +program: Program
107) -> Bool:
108 match asked:
109 case True{}:
110 after(path, program, forks(args, program))
111 case False{}:
112 False{}
113
114
115def visit(
116 +program: Program,
117 +path: List<&2, Lint.Above>,
118 +node: Lint.Node
119) -> List<&2, Lint.Finding>:
120 match program node:
121 case Program{
122 +defs,
123 +under,
124 +bangs
125 } Lint.Node{
126 Lint.Application{},
127 n,
128 span,
129 Con{Lint.Node{Lint.Reference{+t}, callee, Some{Lint.Span{p, beg, end}}, k, f}, args},
130 facts
131 }:
132 Lint.finding(
133 Lint.Warning{},
134 after.if(
135 List.contains(
136 ~U32,
137 ~U32.is_eq,
138 bangs,
139 end
140 )
141 && Sets.has(under, t)
142 && Bool.not(GpuFit.is_drop(Names.find(Lint.Def, defs, t), t)),
143 args,
144 path,
145 Program{defs, under, bangs}
146 ),
147 span,
148 message(callee)
149 )
150 case _ _:
151 []
152
153
154def scanned(+here: Lint.Here, bangs: List<&2, U32>) -> Lint.Ask(List<&2, Lint.Finding>):
155 match bangs:
156 case Nil{}:
157 Lint.done([])
158 case Con{b, rest}:
159 +sources = Lint.Here.sources(here)
160 +defs = Lint.Program.defs(sources, Map.new(&2, Maybe<&2, Lint.Def>))
161 Lint.done(
162 Lint.visit(
163 ~Program,
164 ~Lint.Finding,
165 ~visit,
166 Program{defs, GpuFit.program.under(sources, defs), Con{b, rest}},
167 Lint.Here.nodes(here)
168 )
169 )
170
171def run(+here: Lint.Here) -> Lint.Ask(List<&2, Lint.Finding>):
172 scanned(here, GpuFit.bangs(Lint.Here.text(here), 0, []))
173
174def rule() -> Lint.Rule:
175 Lint.Rule{Lint.Needs{False{}, False{}}, [], Lint.OnFile{run}}