Skip to content
← Back

src/syntax/process/infer-effects/effect_facts.ghul

1
namespace Syntax.Process is
2
use Function = Semantic.Symbols.Function
3
use Symbol = Semantic.Symbols.Symbol
4
5
// Per-function facts the resolved-mode walk of the infer-effects
6
// pass collects — member writes, nulling writes, member reads,
7
// element access, effect-polymorphism markers — consumed by the
8
// EFFECT_SOLVERS closures and, through them, the crossing discharge.
9
// The narrowing study reads the same records for its
10
// measurements.
11
class FUNCTION_EFFECT_FACTS is
12
name: string public
13
owner_name: string public
14
location: string public
15
16
// The accessor role, derived from the structural links the
17
// accessor synthesiser stamps on each accessor function:
18
// "get" for a property read accessor, "set" for its assign
19
// accessor, "index-get" / "index-set" for an indexer's, ""
20
// for an ordinary function or method. Report display only -
21
// nothing branches on it.
22
role: string public
23
24
// The body walk saw a write to something other than a local:
25
// a member, an index, a global, or the receiver's own state.
26
own_stores: bool public
27
28
// The body walk gave up for a reason that is not a write: an
29
// unboundable callee, an unaudited node kind, an await, ...
30
own_unbounded: bool public
31
32
// Copied from the function's STORE_FREE_FACTS at fixpoint
33
// time, so the table can be checked for records aliased
34
// between two functions.
35
is_disqualified: bool public
36
aliased: bool public
37
38
// The member symbols this body writes directly, and whether it
39
// performs a write the walk could not pin to a symbol (an
40
// index, an unresolvable target) or does something unboundable
41
// that could write anything.
42
writes: Collections.SET[Symbol] public
43
writes_unbounded: bool public
44
45
// The body assigns an optional-typed (or untypeable) value to
46
// something that is, or could be, a member - excluding a
47
// constructor's writes to its own fresh instance. The seed of
48
// the never-nulls classification.
49
assigns_optional: bool public
50
51
// The member symbols the possibly-null assignments target, and
52
// whether any such assignment's target could not be resolved
53
// (in which case any member could have been nulled).
54
nulling_writes: Collections.SET[Symbol] public
55
nulling_unbounded: bool public
56
57
// The body stores through an indexer or to an array element -
58
// recorded apart from member writes because an element store
59
// cannot change what any field holds.
60
writes_elements: bool public
61
62
// The member symbols this body reads, and whether it performs
63
// a read the walk cannot pin down (an element read, an
64
// unaudited construct). The seed of the getter read-closure
65
// the crossing discharge intersects with a crossing callee's
66
// write set.
67
reads: Collections.SET[Symbol] public
68
reads_elements: bool public
69
70
// What the walk first gave up on in this body, or "" if it
71
// never did.
72
unbounded_reason: string public
73
74
// Set on a property accessor's record when the owning property
75
// is an auto-property: its getter reads only the hidden backing
76
// field, so the property's value can only change through an
77
// assignment to the property itself. A hand-written getter can
78
// read anything, so it is never stability-eligible.
79
for_auto_property: bool public
80
81
callee_count: int public
82
overrider_count: int public
83
openly_dispatchable: bool public
84
declared_pure: bool public
85
86
// The body invokes a function-typed parameter. The write-set
87
// closure then absorbs the write sets of every function value
88
// passed to this function at any call site the walk saw,
89
// instead of treating the invocation as unboundable.
90
invokes_function_param: bool public
91
92
// Known callees this body forwards one of its own
93
// function-typed parameters to: if any of them invokes its
94
// parameter, this body invokes-by-proxy, and the values are
95
// still accounted at this body's own call sites.
96
forwards_param_to: Collections.SET[Function] public
97
98
init(name: string, owner_name: string, location: string, role: string) is
99
self.name = name
100
self.owner_name = owner_name
101
self.location = location
102
self.role = role
103
writes = Collections.SET[Symbol]()
104
nulling_writes = Collections.SET[Symbol]()
105
reads = Collections.SET[Symbol]()
106
forwards_param_to = Collections.SET[Function]()
107
unbounded_reason = ""
108
si
109
si
110
111
// The function values a set of files' call sites pass into
112
// function-typed parameters, and the callees they hand a value the
113
// walk could not name, as one comparable unit.
114
class PASSED_CONTRIBUTIONS is
115
_passed: Collections.MutableMap[Function, Collections.SET[Function]]
116
_opaque: Collections.SET[Function]
117
118
init() is
119
_passed = Collections.MAP[Function, Collections.SET[Function]]()
120
_opaque = Collections.SET[Function]()
121
si
122
123
add(callee: Function, value: Function) is
124
if !_passed.contains_key(callee) then
125
_passed[callee] = Collections.SET[Function]()
126
fi
127
128
_passed[callee].add(value)
129
si
130
131
add_opaque(callee: Function) is
132
_opaque.add(callee)
133
si
134
135
// The callees whose contribution here differs from `other`'s.
136
differing_callees(other: PASSED_CONTRIBUTIONS) -> Collections.SET[Function] is
137
let result = Collections.SET[Function]()
138
139
for callee in _opaque do
140
if !other._opaque.contains(callee) then
141
result.add(callee)
142
fi
143
od
144
145
for callee in other._opaque do
146
if !_opaque.contains(callee) then
147
result.add(callee)
148
fi
149
od
150
151
for entry in _passed do
152
if !other._passed.contains_key(entry.key) \/ !_same_values(entry.value, other._passed[entry.key]) then
153
result.add(entry.key)
154
fi
155
od
156
157
for entry in other._passed do
158
if !_passed.contains_key(entry.key) then
159
result.add(entry.key)
160
fi
161
od
162
163
return result
164
si
165
166
_same_values(mine: Collections.SET[Function], theirs: Collections.SET[Function]) -> bool static is
167
if mine.count != theirs.count then
168
return false
169
fi
170
171
for value in mine do
172
if !theirs.contains(value) then
173
return false
174
fi
175
od
176
177
return true
178
si
179
si
180
181
// The registry those facts live in, plus the function values the
182
// walk saw flowing into function-typed parameters. Keyed on root
183
// symbols. Populated while `collecting` (set by the infer-effects
184
// pass) or while the measurement study's environment gates are
185
// on.
186
class EFFECT_FACTS is
187
188
// The production switch: on while the infer-effects pass is
189
// re-walking bodies with resolved types, so the per-function
190
// records collect member-granular write and nulling facts for
191
// the crossing discharge. Independent of the study's environment
192
// gates, which remain measurement-only.
193
collecting: bool public static
194
195
_records: Collections.MutableMap[Function, FUNCTION_EFFECT_FACTS]? static
196
197
records: Collections.MutableMap[Function, FUNCTION_EFFECT_FACTS] static is
198
if !_records? then
199
_records = Collections.MAP[Function, FUNCTION_EFFECT_FACTS]()
200
fi
201
202
return _records
203
si
204
205
record_for(function: Function?) -> FUNCTION_EFFECT_FACTS? static is
206
if !function? then
207
return null
208
fi
209
210
let root = cast Function?(function.root_specialized_from)
211
212
if !root? then
213
return null
214
fi
215
216
if records.contains_key(root) then
217
return records[root]
218
fi
219
220
return null
221
si
222
223
note_function(
224
function: Function?,
225
name: string,
226
owner_name: string,
227
location: string,
228
role: string
229
) static is
230
if !collecting /\ !NARROWING_STUDY.enabled then
231
return
232
fi
233
234
if !function? then
235
return
236
fi
237
238
let root = cast Function?(function.root_specialized_from)
239
240
if !root? then
241
return
242
fi
243
244
if records.contains_key(root) then
245
records[root].aliased = true
246
return
247
fi
248
249
records[root] = FUNCTION_EFFECT_FACTS(name, owner_name, location, role)
250
si
251
252
// clear the per-function records so the new walk's answers
253
// replace the old ones rather than being discarded as duplicates.
254
_next_label: string static
255
256
generation_label: string static => _next_label
257
258
begin_generation(label: string) static is
259
_next_label = label
260
_records = Collections.MAP[Function, FUNCTION_EFFECT_FACTS]()
261
_passed_functions = Collections.MAP[Function, Collections.SET[Function]]()
262
_passed_opaque = Collections.SET[Function]()
263
_passed_by_file = Collections.MAP[string, Collections.MutableList[(callee: Function, value: Function)]]()
264
_opaque_by_file = Collections.MAP[string, Collections.MutableList[Function]]()
265
si
266
267
// Drop everything the walk of one file contributed - its
268
// functions' records and the function values its call sites
269
// passed - so a re-walk of that file replaces its contribution
270
// rather than adding to it. The passed tables are rebuilt from
271
// the other files' contributions.
272
drop_file(file_name: string) static is
273
if let records = _records then
274
let stale = Collections.LIST[Function]()
275
276
for function in records.keys do
277
if function.location.file_name =~ file_name then
278
stale.add(function)
279
fi
280
od
281
282
for function in stale do
283
records.remove(function)
284
od
285
fi
286
287
let passed_by_file = _passed_by_file
288
let opaque_by_file = _opaque_by_file
289
290
let had_passed = passed_by_file? /\ passed_by_file.contains_key(file_name)
291
let had_opaque = opaque_by_file? /\ opaque_by_file.contains_key(file_name)
292
293
if !had_passed /\ !had_opaque then
294
return
295
fi
296
297
if passed_by_file? then
298
passed_by_file.remove(file_name)
299
fi
300
301
if opaque_by_file? then
302
opaque_by_file.remove(file_name)
303
fi
304
305
let passed = Collections.MAP[Function, Collections.SET[Function]]()
306
let opaque = Collections.SET[Function]()
307
308
if passed_by_file? then
309
for entry in passed_by_file do
310
for pair in entry.value do
311
_add_passed(passed, pair.callee, pair.value)
312
od
313
od
314
fi
315
316
if opaque_by_file? then
317
for entry in opaque_by_file do
318
for callee in entry.value do
319
opaque.add(callee)
320
od
321
od
322
fi
323
324
_passed_functions = passed
325
_passed_opaque = opaque
326
si
327
328
// The function values passed, and the callees handed an
329
// unnameable one, by call sites in these files: what a re-walk
330
// of the files has to reproduce for the solve inputs to be
331
// unchanged.
332
passed_from(files: Collections.Iterable[string]) -> PASSED_CONTRIBUTIONS static is
333
let result = PASSED_CONTRIBUTIONS()
334
let passed_by_file = _passed_by_file
335
let opaque_by_file = _opaque_by_file
336
337
for file_name in files do
338
if passed_by_file? /\ passed_by_file.contains_key(file_name) then
339
for pair in passed_by_file[file_name] do
340
result.add(pair.callee, pair.value)
341
od
342
fi
343
344
if opaque_by_file? /\ opaque_by_file.contains_key(file_name) then
345
for callee in opaque_by_file[file_name] do
346
result.add_opaque(callee)
347
od
348
fi
349
od
350
351
return result
352
si
353
354
// ==== function values flowing into parameters ====
355
356
// For each callee, the function values known to flow into its
357
// function-typed parameters across every call site the walk
358
// saw; and the callees handed a function value the walk could
359
// not name. Keyed on root symbols.
360
_passed_functions: Collections.MutableMap[Function, Collections.SET[Function]]? static
361
_passed_opaque: Collections.SET[Function]? static
362
363
// The same contributions by the file whose call site made
364
// them, so a file's can be dropped when it is re-walked.
365
_passed_by_file: Collections.MutableMap[string, Collections.MutableList[(callee: Function, value: Function)]]? static
366
_opaque_by_file: Collections.MutableMap[string, Collections.MutableList[Function]]? static
367
368
passed_opaque: Collections.SET[Function] static is
369
if !_passed_opaque? then
370
_passed_opaque = Collections.SET[Function]()
371
fi
372
373
return _passed_opaque
374
si
375
376
passed_functions_for(callee: Function?) -> Collections.SET[Function]? static is
377
if !callee? \/ !_passed_functions? then
378
return null
379
fi
380
381
let root = cast Function?(callee.root_specialized_from)
382
383
if root? /\ _passed_functions.contains_key(root) then
384
return _passed_functions![root]
385
fi
386
387
return null
388
si
389
390
// `from_file` is the file of the call site passing the value.
391
note_passed_function(callee: Function?, value: Function?, from_file: string) static is
392
if !callee? \/ !value? then
393
return
394
fi
395
396
let root = cast Function?(callee.root_specialized_from)
397
let value_root = cast Function?(value.root_specialized_from)
398
399
if !root? \/ !value_root? then
400
return
401
fi
402
403
let passed = _passed_functions ?? Collections.MAP[Function, Collections.SET[Function]]()
404
_passed_functions = passed
405
_add_passed(passed, root, value_root)
406
407
let by_file = _passed_by_file ?? Collections.MAP[string, Collections.MutableList[(callee: Function, value: Function)]]()
408
_passed_by_file = by_file
409
410
if !by_file.contains_key(from_file) then
411
by_file[from_file] = Collections.LIST[(callee: Function, value: Function)]()
412
fi
413
414
by_file[from_file].add((callee = root, value = value_root))
415
si
416
417
_add_passed(
418
passed: Collections.MutableMap[Function, Collections.SET[Function]],
419
root: Function,
420
value_root: Function
421
) static is
422
if !passed.contains_key(root) then
423
passed[root] = Collections.SET[Function]()
424
fi
425
426
passed[root].add(value_root)
427
si
428
429
note_passed_opaque(callee: Function?, from_file: string) static is
430
if !callee? then
431
return
432
fi
433
434
let root = cast Function?(callee.root_specialized_from)
435
436
if !root? then
437
return
438
fi
439
440
passed_opaque.add(root)
441
442
let by_file = _opaque_by_file ?? Collections.MAP[string, Collections.MutableList[Function]]()
443
_opaque_by_file = by_file
444
445
if !by_file.contains_key(from_file) then
446
by_file[from_file] = Collections.LIST[Function]()
447
fi
448
449
by_file[from_file].add(root)
450
si
451
si
452
si