summaryrefslogtreecommitdiff
path: root/chapter-escape/README.md
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-10-24 09:38:56 -0700
committerCliff Click <cliffc@acm.org>2026-10-24 09:38:56 -0700
commitac525dc59de1ae195c40f8e21b09485d0e5d9330 (patch)
treed1af8c53dbcdf64526d0d5b5d49c8fdc53489af1 /chapter-escape/README.md
parent2a7ed6bda01f028cf13f803d89b10908d7ea7937 (diff)
downloadSimple-ac525dc59de1ae195c40f8e21b09485d0e5d9330.tar.gz
Simple-ac525dc59de1ae195c40f8e21b09485d0e5d9330.zip
module split
Diffstat (limited to 'chapter-escape/README.md')
-rw-r--r--chapter-escape/README.md53
1 files changed, 27 insertions, 26 deletions
diff --git a/chapter-escape/README.md b/chapter-escape/README.md
index 6098c72c..a1fda17b 100644
--- a/chapter-escape/README.md
+++ b/chapter-escape/README.md
@@ -2,14 +2,15 @@
English | [日本語](README.ja.md)
-[Previous: Construction and Private Memory](../chapter-construction/README.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 functions arguments.
+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
@@ -44,7 +45,7 @@ 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 caller, or passing it to a native function,
+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.
@@ -52,8 +53,8 @@ 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. The completed object has not, yet, escaped; it
-depends on what happens to the `self` pointer.
+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
@@ -67,7 +68,7 @@ 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; its uses SCCP to determine what escapes.
+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
@@ -75,7 +76,7 @@ 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 litte more, to allow more
+running SCCP. We extend and sharpen the lattice a little more, to allow more
precise tracking.
@@ -89,17 +90,15 @@ precise tracking.
`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 themselves just
-track collections of immutable bits. They have no indirection, they are just
-fly-weight wrappers around `int[]`s. The help support shared and immutable
-`int[]`s so that these bit sets follow the same invariants as the rest of the
-Types.
+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 `Types` using the formal
+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
@@ -121,8 +120,8 @@ Start -> callable functions -> returns and exposed memory -> Stop
```
Before SCCP, parsing and pessimistic iteration keep conservative callers and
-memory. During SCCP, functions lose their unknown-caller edges unless their
-function pointer escapes and become unknown entry points. Concrete calls then
+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
@@ -133,22 +132,24 @@ 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.
-If you have a `main` function, it starts discovery. Without `main`, the code
-is assumed to be a library, and all public entry points are fair game.
+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 point), or via a function pointer thats only used in a few locations,
+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 publically, its arguments can sharpen across the `meet` of all call sites
-- perhaps allowing not-null-ness to be tracked precisely and avoid some
-explicit tests.
-
-After optimization, `FinalizeIR` freezes the external summary - the escaped
-values from `Start/Stop`, for tracking if a function gets a public entry in the
-ELF file. `FinalizerIR` does a few more cleanups, including removing the
-`Stop->Start` edge SCCP uses to track escapes across external calls.
+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