summaryrefslogtreecommitdiff
path: root/chapter18
diff options
context:
space:
mode:
Diffstat (limited to 'chapter18')
-rw-r--r--chapter18/README.md107
1 files changed, 63 insertions, 44 deletions
diff --git a/chapter18/README.md b/chapter18/README.md
index 7482eafb..d0ee46c6 100644
--- a/chapter18/README.md
+++ b/chapter18/README.md
@@ -173,8 +173,18 @@ When looking at the returned IR, the `StopNode` now reports one return for each
function, including `main`:
`Stop[ return find; return Phi(Region,int,-1); ]`
+### Functions and Memory
-### Calls
+Functions start with all of memory on a `Parm` (which extends `Phi`) and having
+an argument `_idx` of 1, and return all of memory on the `Return` whioh takes
+as inputs `{Control, Memory, Expr, RPC}` - except for the new `RPC`, unchanged
+from before. In the middle of the function, the normal alias slicing proceeds,
+sharpening memory information locally. A later chapter will introduce the
+notion of *escaped* aliases, and allow simple functions to not take in and
+clobber all of memory.
+
+
+## Calls
Calls have the usual syntax: `fcn(3)`. Internally a `CallNode` takes in
Control, Memory, all the normal arguments, and a hidden last argument which is
@@ -191,7 +201,7 @@ functions the call-site *knows* it will call. This will be expanded later to
be a conservative approximation to the *Call Graph*, with each `CallEnd`
*linked* to every function it *may* call; if a function is not linked it can
not be called from here. This requires a global analysis (fast, cheap,
-incremental, and global) which shows up in [Chapter24](../chapter19/README.md),
+incremental, and global) which shows up in [Chapter24](../chapter24/README.md),
[SCCP](https://en.wikipedia.org/wiki/Sparse_conditional_constant_propagation).
So for the moment we only link exact constant functions.
@@ -204,6 +214,57 @@ from GVN) then the function inlines in the IR.
Like `Start`, `CallEnd`s are followed by projections for Control, Memory and the return value.
+### Calls and Linking
+
+When Simple starts parsing code, the target of a call is generally unknown;
+likewise a function has to assume any call can reach it. This is a very weak
+knowledge about a Call Graph, and this knowledge usually gets sharper very
+quick: most calls are to known fixed functions... but not always. And for
+functions, its generally harder to know that a function pointer is never called.
+
+So taking these contrainsts together, we can imagine building a call graph by
+linking all call sites to all functions. Here *linking* means the actual call
+arguments are fed into the `Parm`s for the functions; the function `Return`
+feeds into the `CallEnd`, and we can imagine flowing type information across
+the caller/callee border. But this immediately requires `O(n^2)` edges -
+every call is linked to every function, and there are `O(n)` of each.
+
+To avoid this `O(n^2)` graph growth, Simple assumes that all calls *start*
+linked to all functions, we just don't include the edges (yet). `FunNode`
+includes a `hasUnknownCallers` call just for this situation, and this
+means all `Parms` on all functions assume the worst possible callers will
+be calling... so their arguments all default to their known types.
+
+Similarly, all `Calls` assume they call all functions, and take return
+values from all functions... so their return type is computed from
+their function signature instead of from some actual `Return`.
+
+If we later discover a sharp target for a call (i.e., any call to a named
+function, such as `fcn(3)`), we can refine the returned value to what the
+function actually returns - maybe e.g. the function ends in `return null;` and
+the CallEnd can use that information! Similar for functions, if the function
+is private, and never escapes, we might discover that its called from a limited
+set of places - and so its arguments might be more precisely known.
+
+In the same SCCP mentioned above we will refine the set of function pointers
+flowing around the graph, and thus **who** calls **what**. At that point we
+generally do NOT have an `O(n^2)` Call Graph, so we will make the prior assumed
+call edges "all-calls-all", into concrete and precise edges, building a real
+Call Graph (which we will promptly use to optimize across function/call
+borders). This all comes in [Chapter24](../chapter24/README.md). For now,
+we assume all-calls-all unless we can locally prove otherwise, and our
+calling situation remains very conservative.
+
+
+### Calls and Memory
+
+Calls take in all of memory, and return all of memory - a very conservative
+approach. Inlining can sharpen the alias information (at the cost of code
+growth), via the normal memory peepholes applying to a larger and less
+constrained graph (no Call after inlining!). Like the functions above, a later
+chapter will explore the notion of escaping aliases, and can allow call sites
+to be less conservative about memory.
+
## CodeGen - The Compile Driver
@@ -238,45 +299,3 @@ evaluate in a very straightforward way. Essentially the normal IR nodes are
treated like a special "machine instruction set" with infinite registers, and a
globally correct schedule. This evaluator supports functions and calls (and
recursive calls).
-
-
-## Graph Visualizer
-
-Run `make view`, type your program in the text box, and click **Compile**
-(or press **Ctrl+Enter**). Use the Left/Right arrow keys to step through graph
-construction and optimization. See the [shared viewer guide](../graph/README.md).
-
-Nodes use colors and shapes for their graph roles: control, data, memory, Phis,
-and scopes. This palette is distinct from the type-lattice diagrams. The viewer
-also displays `ScopeNode`s,
-which only exist for the Parser but are actual Nodes and have `use->def` edges
-into the IR.
-
-## Memory across functions
-
-Each function has one `$mem` parameter and the parser keeps one memory binding.
-Branches, loops, and multiple returns merge whole memory with BulkMemPhi. Field
-uses discover precise MemPhis, while MemMerge packages a default memory state
-and its alias overrides. MemMerge is an ordinary graph node; it no longer doubles
-as a mutable parser alias table.
-
-| Boundary | Memory convention |
-|---|---|
-| Function entry | Parm 1 carries whole memory; heap contents are unknown |
-| Call inputs | `{ctrl, $mem, arguments..., function pointer}` |
-| CallEnd outputs | `{ctrl, $mem, value}` |
-| Return inputs | `{ctrl, $mem, value, rpc}` |
-| New inputs | `{ctrl, $mem, size, field values...}` |
-| New outputs | `{ptr, $mem}`, covering only the allocated struct's aliases |
-
-A function may receive an existing object, so its entry memory cannot be treated
-as an empty heap. Calls conservatively affect every alias. The returned memory
-becomes the caller's new whole-memory state; there is no per-function alias
-summary. Trivial inlining removes this boundary and lets the ordinary memory
-rewrites see through the function body.
-
-Scheduling follows the relevant alias through MemMerge when looking for writes
-or calls that must follow a Load. Calls end their basic block, so moving the Load
-before that terminator supplies the required order without appending scheduling
-edges to the argument list. Phi factoring keeps the simple one-step Load guard;
-a direct Call user blocks that rewrite.