Appearance
| 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 |