summaryrefslogtreecommitdiff
path: root/chapter25/WIP-HANDOFF.md
blob: 9c4503d817b2a5e3b18142ff796d561c98ade288 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
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.