diff options
| author | Cliff Click <cliffc@acm.org> | 2026-10-24 15:25:40 -0700 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-10-24 15:25:40 -0700 |
| commit | c4364363dbd969c9aa27aa7c43fbe6d40544501a (patch) | |
| tree | 8b0493b3a787f98da87531058e24cf4eaab970a6 /chapter-escape/README.md | |
| parent | 6ff5bb750b8d571a48204539b90c52c1e17283d5 (diff) | |
| download | Simple-c4364363dbd969c9aa27aa7c43fbe6d40544501a.tar.gz Simple-c4364363dbd969c9aa27aa7c43fbe6d40544501a.zip | |
Rename escape analysis chapter to 27
Diffstat (limited to 'chapter-escape/README.md')
| -rw-r--r-- | chapter-escape/README.md | 178 |
1 files changed, 0 insertions, 178 deletions
diff --git a/chapter-escape/README.md b/chapter-escape/README.md deleted file mode 100644 index a1fda17b..00000000 --- a/chapter-escape/README.md +++ /dev/null @@ -1,178 +0,0 @@ -# Escape Analysis and Unknown Callers - -English | [日本語](README.ja.md) - -[Previous: Construction and Private Memory](../chapter-construction/README.md) | -[Next: Modules and Lazy Class Initialization](../chapter-modules/README.md) - - -We run a simple form of escape analysis in this chapter, tracking both -functions and some aliases across all calls and memory updates. One of the -goals here is to discover when we've found *all* the callers of a function; at -this point we can drop the conservative "unknown caller" input and run -constant propagation through a function's arguments. - -The preceding compiler kept an unknown caller for every surviving function -address. That was safe, but it lost facts even when all uses stayed within the -program. Escape analysis makes that boundary more precise. - -## A callback that stays within known code - -```simple -val run = { int n -> - val power = { int base, int exponent -> - if (exponent <= 0) return 1; - int smaller = power(base, exponent - 1); - return base * smaller; - }; - val sumPowers = { int n, {int int -> int} f -> - if (n <= 0) return 0; - return f(2,n) + sumPowers(n-1,f); - }; - return sumPowers(n,power); -}; -return run(arg); -``` - -For `arg == 3`, this computes `2 + 4 + 8 == 14`. Both functions are recursive; -their calls remain in the graph. In the prior chapters, the address passed as -`f` keeps an unknown caller for `power`, so `base` stays `i64`. - -Here the compiler follows the address through the arguments and recursive -calls. Neither function returns it, stores it where unknown code can find it, -nor passes it to an unknown callee. Every call to `power` is accounted for. -SCCP can then infer `base == 2`, including in its recursive body. The multiply -by 2 can become a shift without inlining or cloning either recursive body - and -we open ourselves up to further optimizations. - -Passing `power` to an unknown callee, or passing it to a native function, -changes the answer. The external caller may choose another base. The compiler -must retain `power` and give its parameters their declared bounds. - - -## Publishing an object and exposing it to unknown code - -The construction chapter's `EscapeNode` publishes initialized fields into -ordinary public memory. That alone does not expose the completed object to -unknown code; exposure depends on what happens to the `self` pointer. - -Escape analysis starts at the boundary with unknown code. Returning an object -across that boundary exposes its field aliases. An exposed field can contain -another object or a callback, so discovery continues through memory: - -```text -external result -> Wrap.child -> Holder.f -> callback -> unknown caller -``` - -The same rule applies to arguments passed to native code. Native code may keep -an address after the call returns, or use it on a path that never returns. -`Stop` therefore also collects exposure directly from reachable native calls. -A call to a known Simple function instead passes the values and memory to that -function's graph; SCCP follows those uses to determine what escapes. - -This is a conservative analysis by field alias. Two instances of the same -struct share their field aliases. Exposing one may therefore expose addresses -stored in the same fields of another. It does not identify each allocation -separately. - -Another way to think of this: this analysis is what we get "for free" from just -running SCCP. We extend and sharpen the lattice a little more, to allow more -precise tracking. - - -## Memory carries exposure sets - -`TypeMem` now carries two interned sets alongside its contents: - -* `_escFs`: function identities reachable through this memory. -* `_escAs`: field aliases reachable through this memory. - -`XInt`, already used for function sets, represents both. An empty set means no -address has been discovered; a full set is the conservative unknown case. -Meeting summaries unions the sets. Dual types complement them, so the existing -lattice operations and tests still apply. The `XInt` utilities operate directly -on shared, immutable `int[]` arrays; they add no wrapper object. Interning lets -these sets follow the same sharing rules as the rest of the Types. - -Stores record addresses carried by their values. Memory Phis combine those -facts over live paths. `MemMergeNode` gathers the slices whose aliases are -exposed, rather than making every locally stored callback externally callable. -If an exposed field reveals another alias, that slice participates as well. In -short, we follow escaping values through memory using the existing -`Node.compute` and `Type` machinery. - -The hidden constructor helpers still keep fields in local SSA values and use -ordinary Phis. Their private-memory arguments and results describe the object -being built; they are not external returns. Initializing Stores and -publication preserve the address facts when those fields enter ordinary memory. - - - -## Start and Stop close the cycle - -Start represents unknown callers. Stop collects what known code can expose to -them. The two form an analysis cycle: - -```text -Start -> callable functions -> returns and exposed memory -> Stop - ^ | - +---------------- newly exposed addresses ----------------+ -``` - -Before SCCP, parsing and pessimistic iteration keep conservative callers and -memory. During SCCP, functions other than the chosen entry points lose their -unknown-caller edges unless their function pointers escape. Concrete calls -rebuild the known call graph. - -Stop combines the results and memory visible to external callers. Those -callers already hold their object arguments, so writes through such arguments -also count. Start feeds the resulting alias set back into memory summaries. -If a function identity escapes, SCCP restores a Start edge and its conservative -parameter inputs. That function can reveal further addresses through its own -calls and returns. The ordinary worklist repeats until types, exposure sets, -and call edges agree - i.e. the normal SCCP reaches a fixed point. - -An explicit `main`, or one synthesized for a top-level return, starts discovery. -Without one, the code is treated as a library: ordinary named functions outside `sys` -seed the unknown entry points. Module privacy refines this rule in the next -chapter. - -Once SCCP finishes, you might have ordinary public functions callable from -anywhere, or functions called via their name (direct field load holding the -function pointer), or via a function pointer that's only used in a few locations, -or it might escape in a region and get mixed with other function pointers, or -you might never call a function at all. If a function is getting called, but -never from unknown code, its arguments can sharpen across the `meet` of all -call sites, perhaps proving an argument non-null and avoiding explicit tests. - -After optimization, `FinalizeIR` freezes the external summary from `Start/Stop` -and removes the `Stop->Start` edge SCCP uses to track escapes across external -calls. This summary says which functions need unknown callers; it does not -control ELF symbol visibility. The current ELF writer emits every retained -named function as a global symbol. - - -## Construction boundary to review - -The language still prevents a partially built object from escaping. Constructor -fields remain local values until helper exit. Exact local aliases of `self` -can access those values; passing `self` to ordinary code, storing it in an -object, or accessing fields through a merge that may choose another object -remains rejected in this snapshot. - -Discovering that a completed object stays private does not prove that an early -read of an uninitialized field is legal. These are separate checks. The runnable -construction and escape snapshots now make that border available for review. -Neither topic requires the other to be taught first. - -## Build and check - -From this directory, run `make lib tests CTAGS=`. From the repository root, run: - -```bash -make tests CHAPTERS=chapter-escape CTAGS= -``` - -`EscapeTest` checks the recursive callback, direct and indirect returned -addresses, and native exposure. The inherited construction, language, memory, -lattice, evaluator, and native/backend suites also run. |
