summaryrefslogtreecommitdiff
path: root/chapter25/WIP-HANDOFF.md
diff options
context:
space:
mode:
Diffstat (limited to 'chapter25/WIP-HANDOFF.md')
-rw-r--r--chapter25/WIP-HANDOFF.md387
1 files changed, 387 insertions, 0 deletions
diff --git a/chapter25/WIP-HANDOFF.md b/chapter25/WIP-HANDOFF.md
new file mode 100644
index 00000000..9c4503d8
--- /dev/null
+++ b/chapter25/WIP-HANDOFF.md
@@ -0,0 +1,387 @@
+# Chapter 25 direct-to-SSA parser rewrite handoff
+
+Updated 2026-08-04 for transfer to another AI session/machine.
+
+## Read this first
+
+- Repository: `Simple/chapter25`
+- Branch: `ch25-parser-simplification`
+- Preservation tag: `ch25-parser-before-simplification` at `4d6a59ed`
+- Last pushed commit: `4f8759b3 Rotate random seed and fix more bugs`
+- Remote branch was at the same commit when this handoff was written.
+- The working tree is dirty **after** that pushed checkpoint. Preserve it.
+- The full original design/rollback plan is in
+ `parser-simplification-plan.md`. This file supersedes the old WIP handoff.
+
+Do not reset, clean, or mechanically restore the working tree. The dirty files
+contain Cliff's next experiment. Untracked `.argsM.txt` and `.out.txt` are
+transient local files, not source changes; `.argsM.txt` was locked when the
+handoff test run was attempted.
+
+## Why this branch exists
+
+The constructor syntax rewrite exposed a deeper architectural flaw. This repo
+is an experiment in parsing a high-level language directly into SSA in one
+pass, without first constructing an AST. Forward declarations therefore remain
+unknown during parsing. The parser had nevertheless been consulting provisional
+types to choose permanent graph structure.
+
+The central rule of the rewrite is:
+
+> Syntax and resolved lexical binding choose SSA topology. Types may sharpen,
+> optimize, and reject that graph, but provisional types must not decide its
+> shape.
+
+The branch is deliberately unsquashed. Git, especially the preservation tag,
+is the “this was a giant mistake; walk it back” path. Removed mechanisms should
+be restored by named revert/cherry-pick/new commits, not by copying mysterious
+pieces out of history.
+
+## Original goals
+
+The original plan had these major parts:
+
+1. Remove parser-supplied `_con` lower-bound types from Phi, Load, Store, and
+ similar nodes. Make `compute()` total on weak inputs.
+2. Stop using inferred types for integer-versus-floating arithmetic topology.
+3. Separate unresolved lexical field binding from type inference, eventually
+ using scope-owned unresolved loads/stores resolved at scope close.
+4. Parse unresolved stores against bulk alias `#1`, then recover precise alias
+ structure monotonically after the alias is known.
+5. Keep call shape syntactic: `ptr.fld(args)` always carries `ptr` in the hidden
+ self slot, even if the loaded function is ultimately a top-level function.
+6. Separate authoritative declared function arguments from inferred return and
+ call-site information; reconsider/remove `FunNode._sig` as cached state.
+7. Replace boolean field-final knowledge with four states: unknown, mutable,
+ final, and conflicting/BAD_FINAL, including TypeTest coverage.
+8. Preserve `Type.closeOver()` and canonical cyclic named-type closure without
+ making parser topology depend on whether closure has happened yet.
+9. Advance chapter-by-chapter, keeping earlier chapters and TypeTest green.
+
+Those remain the completion goals unless explicitly revised below.
+
+## Goals and invariants learned during implementation
+
+The following refinements became equally important:
+
+- Optimization/worklist order must not affect semantics or trigger structural
+ assertions. Random seed changes are a diagnostic tool, not a way to select a
+ preferred result. Minor graph-print and register-spill goldens may vary, but
+ should eventually be hardened where practical.
+- `compute()` must be monotonic and simpler than `idealize()`. When both inspect
+ the same facts, `idealize()` should inspect them in the same order.
+- An idealization must preserve already-known type information immediately;
+ the expensive `IterPeeps.progressOnList` assertion is intentionally catching
+ one-step Church-Rosser/monotonicity failures.
+- Maintain Node keep/unkeep lifetime discipline when moving, cloning, or
+ replacing graph pieces.
+- The parser carries only one bulk memory state. Precise alias slicing and
+ `MemMergeNode` are graph semantics, not nested parser state.
+- At every program slice, alias memories are disjoint and collectively cover
+ every alias exactly once. Bulk memory means “all aliases not split out here.”
+- Calls, call ends, returns, and memory parameters deliberately use opaque bulk
+ memory to control graph size. Precision across those boundaries comes from
+ careful inlining.
+- Public concrete bulk alias-`#1` memory is never final. A single final bit
+ cannot truthfully describe an aggregate of unrelated unsplit aliases.
+ Private singleton memory and precise slices may retain finality.
+- Precise aliases can peek through a `MemMerge`. Bulk memory can peek through
+ only when its excluded-alias coverage agrees with the merge.
+- A bulk Phi carries an exclusion BitSet (`All - E`); parallel `MemPhiNode`s
+ carry the precise aliases in `E`.
+- Mode selection for unified numeric nodes is a one-shot structural event.
+ `_mode` participates in GVN identity; flipping it unlocks and reinitializes
+ the node before IterPeeps observes it.
+
+## What has been completed on this branch
+
+### Preservation and early constraint removal
+
+- Created `parser-simplification-plan.md`, the dedicated branch, and permanent
+ pre-experiment tag.
+- Fixed the `_Scan` constructor migration that initially obscured the work.
+- Removed the parser constraint from ordinary Phis. Weak loop Phi inputs are
+ tolerated. The dead ordinary-Phi type field and serialization were removed.
+- Split memory Phis out of ordinary Phi logic:
+ - `MemPhiNode` represents one precise alias.
+ - `BulkMemPhiNode` represents `All - exclusions`.
+ - Memory-specific compute/ideal behavior moved out of base Phi where useful.
+
+### Casts, guards, and conversions
+
+- Split the old overloaded Cast concept into:
+ - `GuardNode`: delayed zero/nonzero branch refinement with direction.
+ - `CheckCastNode`: proof/runtime type check which must fold or diagnose.
+ - `ConvertNode`: the one numeric/value conversion abstraction.
+- Removed the old `liftExpr`-style broad parser helper in favor of explicit,
+ smaller conversion sites.
+- Removed the obsolete `Evaluator`; `Eval2` is authoritative.
+
+### Parser memory simplification and alias recovery
+
+- Removed the parser's eager nested precise-memory slicing. Parser `ScopeNode`
+ state now tracks bulk memory rather than a parser-owned `MemMerge` bundle.
+- Alias `#1` is the unresolved Store state. A Store sharpens once to `#N`; all
+ later alias evidence must agree.
+- Store sharpening inserts/reconstructs precise memory plus a `MemMerge`,
+ preserving the bulk remainder locally.
+- Added lazy creation/widening of precise memory Phis from bulk Phis and
+ MemMerges, including recovery when a parallel precise Phi already collapsed.
+- Added checks around the exactly-once memory-slice invariant and fixed several
+ load/store push-through oscillations and missing slice cases.
+- `EscapeNode` is recognized as a precise alias consumer of a bulk memory Phi.
+- Parser constructor handling now recognizes named constructors structurally
+ and threads private constructor memory correctly; class-initializer return
+ memory handling was also repaired.
+
+### Unified integer/floating operations
+
+- Replaced split integer/FP Add/Sub/Mul/Div opcodes with unified source-level
+ operations carrying a one-shot mode: unknown, integer, or float.
+- Bool and Minus share the small `ModeNode` contract where useful; Bool output
+ remains integer even for floating inputs.
+- ULT is permanently integer-only.
+- Added mode serialization and removed obsolete commented float-node/tag code.
+- Recursive numeric SCCs are resolved after SCCP using graph evidence; float
+ wins when both families are present because integer operands can convert.
+- Mode resolution now reinitializes cached type immediately before IterPeeps.
+- Error checking for Arith/Bool/Minus was unified without allocating argument
+ arrays.
+
+### Load and memory type cleanup
+
+- `LoadNode` no longer stores a parser-provided `_con`. Its declared field type
+ is obtained via an accessor once the pointer/field exists; late type checking
+ reports missing/invalid fields.
+- Deeply read-only pointer information is honored by loads instead of being
+ widened back to the generic declared struct field type.
+- `MemPhiNode` deliberately starts weak enough that later precise field
+ knowledge remains monotonic.
+- Public concrete bulk memory is forced non-final in `TypeMem`; cyclic dual
+ construction handles its temporary null payload. `TypeTest` passed after
+ this Type change.
+
+### Calls, cloning, and optimizer order
+
+- Instance-call hidden-self shape is syntactic rather than inferred from the
+ eventual function value.
+- Function-body cloning/inlining was repaired for loop backedges; an earlier
+ bug left cloned inlined nodes pointing into the original function loop.
+- IterPeeps replacement scheduling now revisits both old and replacement inputs
+ and replacement outputs.
+- Monotonicity diagnostics now include node IDs and detailed input types/classes.
+- Worklist seeds 124, 125, and 126 exposed and drove fixes for collapsed
+ precise Phis, bulk finality, numeric-mode cached types, and Escape alias use.
+
+## Known-good pushed checkpoint
+
+At pushed commit `4f8759b3`:
+
+- Default worklist seed is `126` (also used by `TestC`).
+- `TypeTest` passed: 6 tests.
+- Chapters 1 through 20 passed: 347 tests, zero failures.
+- The seed-126 Ch5 graph shape and Ch20 x86 spill count were accepted as
+ goldens after structural failures were eliminated.
+- `git diff --check` was clean before that commit was pushed.
+
+Recent milestone commits, newest first:
+
+```text
+4f8759b3 Rotate random seed and fix more bugs
+14a1a028 Fix general memory split issues
+eea3d783 Fix cloning functions with loops
+f036a040 minor fixes, thru ch18
+eaee86b8 Handle ambiguous self-recursive int/flt conversions
+b9434562 Fixed up to ch17, plus some ch18
+18f07318 Uniform int/fp errors
+f729484c Hey ch1-13 working again
+58e012e6 Remove LoadNode dependence on _con
+0bc690ae Final cleanup around unified int/fp ops
+9720093f Finish off unified int/flt opcodes
+54e5b83b Refactor Phi into MemPhi, BulkMemPhi
+fe7ce3c4 WIP parse ordinary memory as bulk partitions
+21352840 WIP rethread stores after alias resolution
+1f7cb4ac Replace Lift with one destination ConvertNode
+bbe7ebdd Rename proof casts to CheckCast
+b3da12fd Delay branch zero refinements with GuardNode
+fa4f33e7 Remove parser type constraint from ordinary phis
+```
+
+## Current dirty WIP after `4f8759b3`
+
+At handoff, tracked modifications are:
+
+```text
+src/main/java/com/seaofnodes/simple/codegen/Opto.java
+src/main/java/com/seaofnodes/simple/node/LoadNode.java
+src/main/java/com/seaofnodes/simple/node/StoreNode.java
+src/test/java/com/seaofnodes/simple/Chapter10Test.java
+src/test/java/com/seaofnodes/simple/Chapter21Test.java
+```
+
+These appear to be an active experiment around weak Load/Store computation and
+preserving precise Store users while an unresolved bulk Store sharpens:
+
+- `LoadNode.compute()` was reordered to inspect pointer and memory weak states
+ explicitly. A non-pointer low input currently falls back to `mem._t`.
+- `StoreNode.compute()` currently comments out the `err() -> BOTTOM` fallback,
+ and `escapesFrom` was changed from the met value `t` to the stored `val`.
+- When an alias-1 Store expands into a precise Store plus MemMerge, users which
+ already require the same alias are repointed directly to the new precise
+ Store before the bulk Store is replaced.
+- Several Chapter 10 goldens currently retain a `Stop[...]` with returned
+ memory instead of expecting it to disappear.
+- Chapter 21 `testAntiDeps1` x86 spill expectation is changed from 7 to 9.
+- `Opto.linkStart` has a commented-out high-escape-set early return. There is
+ also an accidental comment typo (`asn inst`); clean that before committing.
+
+Treat all of those as WIP, not settled design. In particular, reconsider
+whether changing goldens to retain memory hides incomplete dead-memory cleanup.
+Do not discard the changes before understanding the failing case that motivated
+them.
+
+The attempted handoff test command could not run because Make reported:
+
+```text
+Makefile:111: *** open: .argsM.txt: Permission denied. Stop.
+```
+
+This is probably another active-process/Windows file-lock issue. Previously an
+IntelliJ Java debugger was conclusively found to lock JAR/class outputs; killing
+the debug process allowed compilation immediately. Close relevant Java/IDE or
+Make processes, preserve or remove only the transient `.argsM.txt` as
+appropriate, then rerun tests. Do not delete source files to work around it.
+
+## Work still required to finish the rewrite
+
+### Immediate frontier
+
+1. Recover the intent and test state of the five dirty WIP files.
+2. Clear the `.argsM.txt` lock and run, in order:
+ - the motivating focused test;
+ - `TypeTest`;
+ - Chapters 1–20;
+ - Chapter 21, especially `testAntiDeps1` and `testStringExport`;
+ - then all of `tests_raw0`.
+3. Decide whether same-alias Store users being repointed during sharpening is
+ the canonical local rewrite. Verify it preserves every user's old type in
+ one step and the exactly-once alias coverage invariant.
+4. Restore the `Opto.linkStart` high-set error handling unless a reduced test
+ proves it wrong; never iterate `XInt.next` over a high/infinite set blindly.
+5. Separate legitimate golden changes from leaked/dead returned memory.
+
+### Store `_con` and weak diagnostics
+
+`LoadNode._con` is gone, but `StoreNode._con` remains. It still sharpens from a
+Field and trims stored values. Continue the original small-step experiment:
+
+- Determine whether `_con` is authoritative declared storage width or merely a
+ cache of `ptr.field(name)._t`.
+- If it is derivable, replace uses with an accessor as was done for Load.
+- Keep error reporting in `err()`/type checking rather than manufacturing
+ parser-time graph topology or accidental BOTTOM propagation.
+- Use a diagnostic whose offending node has no BOTTOM inputs when validating
+ late error selection.
+
+### True unresolved lexical fields (original Phases 2/3)
+
+The memory alias machinery now tolerates an alias-1 Store and sharpens it
+later, but the full lexical design is not complete:
+
+- Unqualified instance/class field references before declarations must resolve
+ by closed lexical scope, not by whichever pointer type happens to sharpen.
+- A scope-owned unresolved load/store representation may still be needed.
+- Resolution must select self versus class initializer base, precise alias, and
+ constructor `init` status together.
+- Locals remain define-before-use and need no unresolved mechanism.
+- Verify loops can lazily widen precise alias runs without global O(n²)
+ reconstruction or Phi/MemMerge oscillation.
+
+### Field finality (original Phase 6)
+
+The planned two-bit four-state Field finality has not been implemented:
+
+```text
+00 unknown
+01 declared mutable
+10 declared final
+11 conflicting / BAD_FINAL
+```
+
+This still needs accessors, meet/join specification, equality, hashing, dual,
+printing/serialization as needed, representative `gather()` entries, and full
+`TypeTest`. Parser-declared final stores require the structural `init` marker;
+non-init writes to a resolved final field are errors. Cyclic named structs must
+merge finality during `Type.closeOver()`.
+
+Do not confuse Field finality with bulk-memory finality: public bulk memory is
+now always conservatively non-final.
+
+### Function contracts (original Phase 5)
+
+`FunNode._sig` still deserves an audit:
+
+- Declared argument syntax is required and authoritative.
+- Return type is graph-derived.
+- Private functions may sharpen from known call sites.
+- Escaping functions need a worst-case/default unknown caller tied to the
+ declared type binding.
+- Forward-declared argument types need a changing type source, not a true
+ ConstantNode.
+- Remove cached signature state that can be derived from Fun/Parm/Return edges;
+ retain explicit declared contracts.
+
+### Final acceptance
+
+- Sweep several deterministic worklist seeds, not just 126. Semantic graph
+ results must converge independent of order.
+- Harden graph-print and spill-count tests where harmless scheduling variation
+ remains. Do not weaken monotonicity or memory-coverage assertions.
+- Run `tests_raw0`, then `tests_raw1`, then `tests_alone`.
+- Run `TypeTest` after every Type/Field lattice change.
+- Confirm serialization/deserialization after every new semantic node field or
+ mode bit.
+- Audit the parser for remaining type-dependent structural choices, especially
+ conversions, field base selection, function self shape, and alias slicing.
+
+## Debugging guidance
+
+- Node IDs are central to debugging; refer to nodes as `Store#107`, `Phi#105`,
+ etc.
+- Stop at the earliest `IterPeeps` count where the graph becomes invalid.
+- `Opto.worklistCheck` catches missed/non-monotonic SCCP updates.
+- `IterPeeps.progressOnList` catches one-step type and idealization failures;
+ its current detailed input dump was added specifically for order bugs.
+- For memory bugs, check both:
+ 1. each program slice covers every alias exactly once; and
+ 2. a precise consumer never obtains an alias excluded from its bulk input
+ without a parallel precise slice.
+- A precise `MemPhi#N` fed by a `MemMerge` whose default bulk excludes `N` and
+ which has no explicit `N` input is malformed.
+- Avoid “fixes” which merely queue a favored node earlier. Change the seed and
+ demand order-independent semantics.
+- Golden changes are acceptable only after semantic evaluation, type
+ monotonicity, and graph invariants are known good.
+
+## Workflow and safety
+
+- Cliff wants to review every push. Ask permission immediately before pushing.
+ In this handoff Cliff said he would push the current completed checkpoint;
+ do not assume that grants future push permission.
+- Push remote checkpoints for backup once approved.
+- Commit at useful test frontiers with messages describing the architectural
+ change, not only the symptom.
+- Preserve unrelated dirty edits. Emacs temporary files are common and need
+ not be reported repeatedly.
+- Windows IntelliJ debug sessions may lock JARs/class files. Report the lock,
+ have Cliff stop the debugger, and retry before changing build machinery.
+- Prefer narrow changes; unwind experiments that cause cascading regressions.
+
+## Rollback paths
+
+- Whole experiment: branch from tag `ch25-parser-before-simplification`.
+- Individual mechanism: revert/cherry-pick the narrow branch commits.
+- Inspect old implementation without mutating this branch:
+ `git diff ch25-parser-before-simplification..HEAD` or `git show <old>:<path>`.
+- Do not squash until the rewrite is accepted; the commit sequence is part of
+ the recovery design.