summaryrefslogtreecommitdiff
path: root/chapter29/src/main
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-11-01 10:33:55 -0800
committerCliff Click <cliffc@acm.org>2026-11-01 10:33:55 -0800
commitf4bd723d929e53475f7990374dca7b7dfcbbdc42 (patch)
tree7bb080119e7b88ad4ac9b30af019fe6a5e4ba9dd /chapter29/src/main
parent0ac0c9e10287972ac3af5a4ee43d26f0d5b318fe (diff)
downloadSimple-main.tar.gz
Simple-main.zip
Add Chapter 29 for tech debt cleanup with the parser splitmain
Keep Chapter 28b intact and carry the grammar-oriented parser package, Var move, IterPeeps move, and parser regression tests into Chapter 29. Defer privacy and exclude its experimental tests and fixture changes. Register the chapter in the indexes, IntelliJ modules and run configurations, Maven reactor, roadmap, and linear-history publisher. Windows and Linux each pass 573 tests, release builds, 199 allocation replays, and 29 native checks.
Diffstat (limited to 'chapter29/src/main')
-rw-r--r--chapter29/src/main/c/simple_crt.c61
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/README.md15
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java101
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java238
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java105
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java889
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java170
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java245
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java468
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/Encoding.java570
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java44
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/FunctionSummary.java28
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java177
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java356
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/IFG.java586
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java126
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/Iter.java101
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/IterPeeps.java349
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/LRG.java240
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java33
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java265
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/Machine.java127
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/Module.java98
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/Opto.java331
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java449
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java14
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java780
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/RegMask.java121
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java36
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/Serialize.java716
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/SourceError.java49
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/StaticData.java177
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java59
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/codegen/Unlink.java174
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/AddNode.java179
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/AndNode.java50
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ArithNode.java214
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/BoolNode.java191
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java303
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/CFGNode.java310
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/CLInitNode.java210
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/CProjNode.java85
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/CallEndMach.java38
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/CallEndNode.java321
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/CallNode.java271
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java23
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java78
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ConstantNode.java91
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ConvertNode.java104
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/CtrlNode.java17
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/DivNode.java40
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/EscapeNode.java116
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ExternNode.java38
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java19
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/FRefNode.java51
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/FldOffNode.java55
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/FunNode.java499
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java68
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/GlobalEscapeNode.java40
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/GuardMach.java17
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/GuardNode.java68
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/IfNode.java100
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/LoadNode.java338
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/LoopNode.java112
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java26
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/MachNode.java60
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java158
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/MemOpNode.java160
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java80
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/MinusNode.java79
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ModeNode.java12
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/MulNode.java65
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/MultiNode.java29
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/NeverNode.java24
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/NewNode.java106
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/Node.java779
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/NotNode.java25
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/OrNode.java45
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ParmNode.java109
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/PhiNode.java288
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/Proj.java2
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ProjNode.java71
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java17
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java36
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java19
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java27
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/RegionNode.java176
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ReturnNode.java135
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java33
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/SarNode.java38
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ScopeNode.java720
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ShlNode.java50
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ShrNode.java33
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/SplitNode.java32
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/StartCUNode.java33
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/StartNode.java81
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/StopCUNode.java134
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/StopNode.java103
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/StoreNode.java341
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/SubNode.java56
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/SummaryNode.java67
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java26
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java24
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/TypeNode.java33
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java16
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/XorNode.java44
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java21
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java23
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java28
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java22
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java25
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java19
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java32
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java80
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java50
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java36
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java23
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java22
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java41
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java20
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java20
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java31
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java21
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java45
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java22
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java26
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java39
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java34
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java20
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java34
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java20
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java29
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java81
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java20
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java19
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java25
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java39
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java28
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java21
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java25
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java17
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java15
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java16
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java36
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java60
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java21
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java11
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java38
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java20
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java19
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java29
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java51
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java49
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java48
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java20
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java27
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java503
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java42
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java18
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java22
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java21
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java13
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java16
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java95
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java62
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java35
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java20
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java17
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java32
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java19
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java48
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java23
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java24
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java55
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java49
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java31
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java33
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java65
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java51
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java18
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java17
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java21
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java49
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java22
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java13
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java16
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java17
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java15
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java22
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java34
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java21
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java20
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java13
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java17
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java61
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java15
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java17
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java13
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java17
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java56
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java18
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java16
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java56
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java44
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java49
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java14
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java16
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java450
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java33
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java24
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java23
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java24
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java11
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java11
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java11
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java33
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java55
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java24
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java38
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java44
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java28
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java24
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java25
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java29
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java22
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java39
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java32
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java21
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java32
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java43
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java65
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java38
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java44
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java28
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java89
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java24
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java27
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java24
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java20
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java24
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java21
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java11
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java11
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java17
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java15
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java22
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java15
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java32
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java21
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java44
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java32
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java21
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java27
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java21
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java25
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java45
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java48
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java23
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java10
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java51
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java39
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java41
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java11
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java11
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java596
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Allocations.java199
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/ControlFlow.java96
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Conversions.java72
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Declarations.java164
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Expressions.java394
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Functions.java346
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Lexer.java384
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Parser.java298
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/README.md26
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/References.java369
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Statements.java272
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Structs.java411
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Types.java244
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/Var.java54
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/parser/package-info.java14
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java101
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/print/IRPrinter.java60
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java80
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java35
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/sys.java25
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/Field.java154
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/Type.java667
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypeConAry.java90
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java20
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java16
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypeFloat.java132
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java341
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypeInteger.java217
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypeMem.java257
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java193
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypeNil.java61
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypePtr.java66
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypeRPC.java106
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypeScalar.java57
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypeStruct.java656
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/TypeTuple.java172
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/type/XInt.java274
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/util/Ary.java229
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/util/AryInt.java192
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/util/BAOS.java162
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/util/IntHashMap.java87
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/util/SB.java99
-rw-r--r--chapter29/src/main/java/com/seaofnodes/simple/util/Utils.java53
-rw-r--r--chapter29/src/main/smp/sys.smp11
-rw-r--r--chapter29/src/main/smp/sys/adt.smp3
-rw-r--r--chapter29/src/main/smp/sys/adt/bitset.smp57
-rw-r--r--chapter29/src/main/smp/sys/ary.smp24
-rw-r--r--chapter29/src/main/smp/sys/aryi64.smp82
-rw-r--r--chapter29/src/main/smp/sys/aryu8.smp87
-rw-r--r--chapter29/src/main/smp/sys/char.smp8
-rw-r--r--chapter29/src/main/smp/sys/io.smp64
-rw-r--r--chapter29/src/main/smp/sys/libc.smp44
-rw-r--r--chapter29/src/main/smp/sys/scan.smp39
318 files changed, 31288 insertions, 0 deletions
diff --git a/chapter29/src/main/c/simple_crt.c b/chapter29/src/main/c/simple_crt.c
new file mode 100644
index 00000000..8656a3cc
--- /dev/null
+++ b/chapter29/src/main/c/simple_crt.c
@@ -0,0 +1,61 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+// errno may be thread-local or a macro calling a platform accessor.
+int32_t simple_errno(void) { return errno; }
+
+// Native process arguments converted to ordinary Simple array layouts.
+// The outer array is mutable; every contained byte array is published as
+// immutable and non-null to Simple code.
+typedef struct SimpleBytes {
+ uint32_t len;
+ unsigned char bytes[];
+} SimpleBytes;
+
+typedef struct SimpleArgs {
+ uint32_t len;
+ uint32_t pad;
+ SimpleBytes *args[];
+} SimpleArgs;
+
+__attribute__((CALL_CONV))
+extern int64_t simple_main(SimpleArgs *args);
+
+static SimpleArgs *simple_args(int argc, char **argv) {
+ if (argc < 0 || (uint64_t)argc > UINT32_MAX)
+ return NULL;
+
+ SimpleArgs *args = calloc(1, sizeof(*args) + (size_t)argc * sizeof(args->args[0]));
+ if (args == NULL)
+ return NULL;
+ args->len = (uint32_t)argc;
+
+ for (int i = 0; i < argc; i++) {
+ size_t len = strlen(argv[i]);
+ if (len > UINT32_MAX) {
+ free(args);
+ return NULL;
+ }
+ SimpleBytes *str = malloc(sizeof(*str) + len);
+ if (str == NULL) {
+ for (int j = 0; j < i; j++) free(args->args[j]);
+ free(args);
+ return NULL;
+ }
+ str->len = (uint32_t)len;
+ memcpy(str->bytes, argv[i], len);
+ args->args[i] = str;
+ }
+ return args;
+}
+
+int main(int argc, char **argv) {
+ SimpleArgs *args = simple_args(argc, argv);
+ if (args == NULL)
+ return 1;
+ // The language-level hidden receiver is not part of the emitted ABI for
+ // free functions; source argument zero occupies native argument zero.
+ return (int)simple_main(args);
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/README.md b/chapter29/src/main/java/com/seaofnodes/simple/README.md
new file mode 100644
index 00000000..3b6a9cef
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/README.md
@@ -0,0 +1,15 @@
+# Compiler source layout
+
+| Directory | Contents |
+| --- | --- |
+| `parser/` | Recursive-descent grammar routines, lexing, name resolution, and construction of the initial IR. `Var` describes a lexical binding. See the [grammar map](../../../../../../docs/29-grammar.md). |
+| `node/` | Sea-of-Nodes IR nodes: graph edges, type computation, and local peephole rewrites. |
+| `node/cpus/` | Machine-specific nodes and instruction selection support for `arm`, `riscv`, and `x86_64_v2`. |
+| `codegen/` | Compilation driver and passes: module loading, optimization, scheduling, register allocation, serialization, and machine-code emission. `IterPeeps` runs the peephole worklist. |
+| `type/` | The type lattice, type interning, and representations of values, structs, functions, and memory. |
+| `print/` | IR/assembly printers and adapters for the shared graph viewer. |
+| `util/` | General-purpose collections, bit sets, byte buffers, and other small utilities. |
+
+The root package retains `sys.java`, which loads the default Simple namespace.
+Java tests, the command-line driver, and the IR evaluator live under
+`src/test/java`; the Simple standard library lives under `src/main/smp`.
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java
new file mode 100644
index 00000000..0b314978
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java
@@ -0,0 +1,101 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
+
+abstract public class BuildLRG {
+ // Compute live ranges in a single forwards pass. Every def is a new live
+ // range except live ranges are joined at Phi. Also, gather the
+ // intersection of register masks in each live range. Example: "free
+ // always zero" register forces register zero, and calling conventions
+ // typically force registers.
+
+ // Sets FAILED to the set of hard-conflicts (means no need for an IFG,
+ // since it will not color, just split the conflicted ranges now). Returns
+ // true if no hard-conflicts, although we still might not color.
+ public static boolean run(int round, RegAlloc alloc) {
+ FunNode fun = null;
+ for( Node bb : alloc._code._cfg ) {
+ if( bb instanceof FunNode fun2 )
+ fun = fun2;
+ for( Node n : bb.outs() ) {
+ // A synthetic never-exit value has no runtime definition.
+ if( n._type==Type.TOP ) continue;
+ if( n instanceof PhiNode phi && !(phi._type instanceof TypeMem) ) {
+ if( phi instanceof MachNode mach && mach.outregmap()==null )
+ continue;
+ // All real Phi inputs end up with the same LRG.
+ // Pass 1: find any pre-existing LRG, to avoid make-then-Union a LRG
+ LRG lrg = alloc.lrg(phi);
+ if( lrg == null )
+ for( int i=1; i<phi.nIns(); i++ )
+ if( (lrg = alloc.lrg(phi.in(i))) != null )
+ break;
+ // If none, make one.
+ if( lrg==null ) lrg = alloc.newLRG(n,fun);
+ if( phi instanceof MachNode ) defLRG(alloc,n,fun);
+ // Pass 2: everybody uses the same LRG
+ lrg=alloc.union(lrg,phi);
+ for( int i=phi instanceof ParmNode ? 2 : 1; i<n.nIns(); i++ )
+ // TOP keeps the type merge neutral, but needs no register.
+ if( n.in(i)._type!=Type.TOP )
+ lrg=alloc.union(lrg,n.in(i));
+ if( lrg._mask!=null && lrg._mask.isEmpty() )
+ alloc.fail(lrg);
+
+ } else if( n instanceof MachNode mach ) {
+ // Attempt to commute ops to keep live ranges compatible.
+ if( mach.commutes() && n.nOuts()==1 ) {
+ int uidx = n.out(0)._inputs.find(n);
+ RegMask mask1 = n.in (1) instanceof MachNode machx ? machx.outregmap() : alloc.lrg(n.in(1))._mask;
+ RegMask mask2 = n.in (2) instanceof MachNode machx ? machx.outregmap() : alloc.lrg(n.in(2))._mask;
+ RegMask masko = n.out(0) instanceof MachNode machx ? machx.regmap(uidx): mach.outregmap();
+ if( masko!=null && !mask1.overlap(masko) && mask2.overlap(masko) )
+ n.swap12();
+ }
+
+ // Define live range
+ defLRG(alloc,n,fun);
+
+ // Now, look in the opposite direction. How are incoming
+ // LRGs affected by this node: For all uses, make live lrgs
+ for( int i=1; i<n.nIns(); i++ )
+ if( n.in(i)!=null ) {
+ LRG lrg2 = alloc.lrg(n.in(i));
+ if( lrg2 != null ) { // Anti-dep or other, no LRG
+ RegMask use_mask = mach.regmap(i); // use_mask is also null for anti-dep
+ if( use_mask!=null && !lrg2.machUse(mach,(short)i,use_mask.size1()).and(use_mask) )
+ alloc.fail(lrg2); // Empty register mask, must split
+ }
+ }
+
+ }
+
+ // MultiNodes have projections which set registers
+ if( n instanceof MultiNode && !(n instanceof CFGNode) )
+ for( Node proj : n.outs() )
+ if( proj instanceof MachNode )
+ defLRG(alloc,proj,fun);
+ }
+ }
+
+ // Collect live ranges
+ alloc.unify();
+
+ return alloc.success();
+ }
+
+ private static void defLRG( RegAlloc alloc, Node n, FunNode fun ) {
+ MachNode mach = (MachNode)n;
+ RegMask def_mask = mach.outregmap();
+ if( def_mask == null ) return;
+ LRG lrg = mach.twoAddress() == 0
+ ? alloc.newLRG(n,fun) // Define a new LRG for N
+ : alloc.lrg2(n,mach.twoAddress()); // Use the matching 2-adr input
+ // Record mask and mach
+ if( !lrg.machDef(mach,def_mask.size1()).and(def_mask) )
+ alloc.fail(lrg); // Empty register mask, must split
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java
new file mode 100644
index 00000000..3547b86c
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java
@@ -0,0 +1,238 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.parser.Lexer;
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.*;
+import java.util.*;
+
+// Search for Class Initialization Cycles.
+
+// Done after Opto, so dead code does not make a cycle.
+
+// Build the inits-graph, a call-graph plus CLInits plus class parents.
+
+// (1) Scan all functions f, building _edges[f][]: the set of <clinit> fcns
+// from CompUnits from CLInitNodes, and the set of fcn call targets in f and
+// the class parent. These are all reported as fidxs (ints).
+//
+// (2) Walk the _edges graph using Tarjans SCC. Reject cycles containing more
+// than one <clinit>.
+//
+// (3) Report on <clinit> cycles, if any
+//
+public class ClassInitDependencies {
+ private final CodeGen _code;
+ // Function FIDX -> callees, touched initializers, and parent initializer.
+ public final int[][] _edgess;
+ private final int[] _pres;
+ private final int[] _lows;
+ private final int[] _stk ;
+ private final int[] _spos; // Stack position; FIDX -> stack slot + 1, or 0 when absent.
+ private int _sp;
+ private int _pre;
+
+
+ // Build once; tests can inspect the graph independently of cycle checking.
+ public ClassInitDependencies( CodeGen code ) {
+ _code = code;
+ assert code._phase.ordinal() >= CodeGen.Phase.IterPostOpto.ordinal();
+ _pres = new int[code._linker._len];
+ _lows = new int[code._linker._len];
+ _stk = new int[code._linker._len];
+ _spos = new int[code._linker._len];
+
+ //
+ BitSet visit = code.visit(); // Visit set per-fcn
+ Ary<CFGNode> work = new Ary<>(CFGNode.class); // Worklist per-fcn
+ IntHashMap iset = new IntHashMap(0); // dedup hash set per-fcn
+ _edgess = new int[code._linker._len][];
+
+ // For all functions, build the _edges array
+ for( FunNode fun : code._linker )
+ if( fun!=null && !fun.isDead() )
+ // Visit function and collect direct class touches and callees
+ _edgess[fun.sig().fidx()] = walk(code,fun,work,iset,visit,null);
+
+ // FIDX zero represents escapees - an unknown callee, including a
+ // callback supplied by another compilation. Its code can call any
+ // exposed function. Keep this edge in saved summaries so later
+ // applications contribute their callbacks too.
+
+ // Filter escapees by dead and not a class (class initializers are
+ // reached through bodies, not directly callable function pointers).
+ int[] escaped = ((TypeTuple)code._stop._type).mem()._escFs;
+ AryInt callees = new AryInt();
+ // An imported Stop can still carry FULL (only local bodies have edges)
+ // so limit by existing FIDXs.
+ int limit = code._linker._len;
+ for( int fidx = XInt.next(escaped,0,limit); fidx >= 0; fidx = XInt.next(escaped,fidx,limit) ) {
+ FunNode fun = code.link(fidx); // Discard dead bodies and external C functions.
+ if( fun!=null && !fun.isClz() ) callees.push(fidx);
+ }
+ _edgess[0] = callees.isEmpty() ? null : callees.asAry();
+ visit.clear();
+ }
+
+ // Return the outgoing init edge set for this function
+ private static int[] walk( CodeGen code, FunNode fun, Ary<CFGNode> work, IntHashMap iset, BitSet visit,
+ Ary<Lexer> sites) {
+ if( fun.summary()!=null ) return fun.summary()._classDependencyFIDXs;
+ // Walk function body, looking for outgoing init edges
+ assert iset .isEmpty(); // int hash set for de-dup
+ assert visit.isEmpty(); // needs a visit set, because loops
+ assert work .isEmpty(); // needs a worklist, because DAG, parallel explore paths
+
+ // Children require their parents to init first;
+ // add child->parent edge
+ if( fun.isClz() && fun._compunit._par != null )
+ iset.put(code.link(fun._compunit._par).sig().fidx(),1);
+
+ work.push(fun);
+ while( !work.isEmpty() ) {
+ CFGNode cfg = work.pop();
+ if( visit.get(cfg._nid) ) continue; // Been there, done that
+ visit.set(cfg._nid);
+ assert !cfg._type.isHigh();
+ // Do not walk off end of function
+ if( cfg instanceof ReturnNode ) continue;
+
+ // CLInit adds a class init requirement to this function
+ // (plus all parent classes)
+ if( cfg instanceof CLInitNode init ) {
+ FunNode clz = code.link(init.cu()); // <clinit> referred to
+ iset.put(clz.sig().fidx(),1); // Add to edge set
+ if( sites!=null && sites.atX(clz.sig().fidx())==null ) sites.setX(clz.sig().fidx(),init._loc);
+ }
+
+ // Calls union all their fptr fidxs into the callee set
+ if( cfg instanceof CallNode call && call.fptr()._type instanceof TypeFunPtr fp ) {
+ if( XInt.isHigh(fp.fidxs()) ) {
+ iset.put(0,1);
+ if( sites!=null && sites.atX(0)==null ) sites.setX(0,call._loc);
+ }
+ // Union call targets into the callee set
+ else for( int dstx = XInt.next(fp.fidxs(),0); dstx>=0; dstx=XInt.next(fp.fidxs(),dstx) ) {
+ iset.put(dstx,1);
+ if( sites!=null && sites.atX(dstx)==null ) sites.setX(dstx,call._loc);
+ }
+ }
+
+ // Forward recursion to walk the whole function
+ for( Node use : cfg._outputs ) {
+ // Do not walk into another function
+ if( !(use instanceof CFGNode cfg1) || use instanceof FunNode || use instanceof StopNode ) continue;
+ // Expect control flow edge is sensible
+ assert (use instanceof RegionNode r && r._inputs.find(cfg)>0) || use.in(0)==cfg;
+ if( !visit.get(cfg1._nid) ) // Small speed opt
+ work.add(cfg1);
+ }
+ }
+
+ visit.clear();
+ int[] edges = iset.isEmpty() ? null : iset.keySet();
+ iset.clear();
+ return edges;
+ }
+
+ // Return an error string, or null if no cycle contains multiple clinits.
+ public String check() {
+ Arrays.fill(_pres,0);
+ // A prior check may have stopped with a cycle still on the stack.
+ while( _sp>0 ) _spos[_stk[--_sp]] = 0;
+ _pre = 1;
+
+ int idx = -1;
+ for( int fidx = 0; fidx < _edgess.length; fidx++ )
+ if( _pres[fidx] == 0 && // Already visited
+ (idx = scc(fidx)) != -1 ) // And found a cycle
+ return report(idx); // Report cycle
+ return null;
+ }
+
+ // Every internal edge of an SCC belongs to a cycle. Print all of them:
+ // stack order is not a path, and enumerating all cycle paths can explode.
+ // This bookkeeping and formatting are only needed on an error.
+ private String report(int idx) {
+ BitSet members = new BitSet();
+ for( int i=idx; i<_sp; i++ ) members.set(_stk[i]);
+ SB sb = new SB().p("Cyclic class initialization:");
+ // Recover one surviving source site per edge only after finding an
+ // error. A representative touch is enough; runtime first-touch order
+ // need not be statically known. Lexer snapshots retain the source CU
+ // even when the node has been inlined into a different function.
+ Ary<CFGNode> work = new Ary<>(CFGNode.class);
+ IntHashMap iset = new IntHashMap(0);
+ BitSet visit = new BitSet();
+ Ary<Lexer> sites = new Ary<>(Lexer.class);
+ for( int i=idx; i<_sp; i++ ) {
+ int src = _stk[i];
+ FunNode fun = _code.link(src);
+ Arrays.fill(sites._es,null);
+ sites.clear();
+ if( fun!=null ) walk(_code,fun,work,iset,visit,sites);
+ if( _edgess[src]!=null )
+ for( int dst : _edgess[src] )
+ if( members.get(dst) ) {
+ sb.p("\n ").p(name(src)).p(" -> ").p(name(dst));
+ Lexer loc = sites.atX(dst);
+ if( loc!=null && loc._cu!=null )
+ sb.p('\n').p(SourceError.format(loc._cu,loc.pos(),"cyclic class initialization"));
+ else if( fun!=null && fun.isClz() && dst!=0 && fun._compunit._par==_code.link(dst)._compunit )
+ sb.p("\n parent initialization requirement");
+ }
+ }
+ return sb.toString();
+ }
+
+ private String name(int fidx) {
+ if( fidx==0 ) return "unknown call";
+ FunNode fun = _code.link(fidx);
+ String owner=fun._compunit._cname;
+ if( fun.isClz() ) return owner;
+ String label=fun.label();
+ return (label.startsWith(owner+".") ? label : owner+"."+label)+" [fidx "+fidx+"]";
+ }
+
+ // Tarjan SCC
+ int scc( int fidx ) {
+ // Assign pre-order number
+ _lows[fidx] = _pres[fidx] = _pre++;
+ _spos[fidx] = _sp+1;
+ _stk[_sp++] = fidx;
+ // walk outbound edges
+ if( _edgess[fidx]!=null ) {
+ for( int edge : _edgess[fidx] ) {
+ if( edge >= _pres.length ) continue; // Out of bounds happens when calling C fcns
+ if( _pres[edge] == 0 ) { // Not visited
+ int idx = scc(edge);
+ if( idx != -1 ) return idx; // Found a cycle
+ _lows[fidx] = Math.min(_lows[fidx],_lows[edge]);
+ } else if( _edgess[edge]!=null && _spos[edge]!=0 ) {
+ _lows[fidx] = Math.min(_lows[fidx],_pres[edge]);
+ }
+ }
+ }
+ // Found a cycle
+ if( _lows[fidx] == _pres[fidx] ) {
+ int idx = _spos[fidx]-1;
+ if( hasClzCycle(idx) )
+ return idx; // Found a class cycle
+ // Pop stack, including fidx, should be a really short loop
+ while( _sp>idx ) _spos[_stk[--_sp]] = 0;
+ }
+ return -1; // No cycle
+ }
+
+ private boolean hasClzCycle(int idx) {
+ int clinits = 0;
+ for( int i=idx; i<_sp; i++ ) {
+ FunNode fun = _code.link(_stk[i]);
+ if( fun!=null && fun.isClz() && ++clinits > 1 )
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java
new file mode 100644
index 00000000..689ceceb
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java
@@ -0,0 +1,105 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.Utils;
+import java.util.BitSet;
+import java.util.IdentityHashMap;
+
+// Coalesce copies
+abstract public class Coalesce {
+
+ public static boolean coalesce( int round, RegAlloc alloc ) {
+
+ // Walk all the splits, looking for coalesce chances
+ boolean progress = false;
+ for( CFGNode bb : alloc._code._cfg ) {
+ outer:
+ for( int j=0; j<bb.nOuts(); j++ ) {
+ Node n = bb.out(j);
+ if( !(n instanceof SplitNode) ) continue;
+ LRG v1 = alloc.lrg(n);
+ LRG v2 = alloc.lrg(n.in(1));
+ if( v1 != v2 ) {
+ LRG ov1 = v1, ov2 = v2;
+ // Get the smaller neighbor count in v1
+ if( v1.nadj() > v2.nadj() )
+ { v1 = v2; v2 = alloc.lrg(n); }
+ int v2len = v2._adj==null ? 0 : v2._adj._len;
+ // See that they do not conflict (coalescing would make a self-conflict)
+ if( v1._adj!=null && v2._adj!=null && v1._mask.overlap(v2._mask) ) {
+ // Walk the smaller neighbors, and add to the larger.
+ // Check for direct conflicts along the way.
+ for( LRG v3 : v1._adj ) {
+ if( v3==v2 ) {
+ // Direct conflict, skip this LRG pair
+ v2._adj.setLen(v2len);
+ continue outer;
+ }
+ // Add missing LRGs from V1 to V2
+ if( v2._adj.find(v3)== -1 )
+ v2._adj.push(v3);
+ }
+ }
+ // Most constrained mask
+ RegMask mask = v1._mask==v2._mask ? v1._mask : v1._mask.copy().and(v2._mask);
+ // Check for capacity
+ if( v2.nadj() >= mask.size() ) {
+ // Fails capacity, will not be trivial colorable
+ if( v2._adj!=null ) v2._adj.setLen(v2len);
+ continue;
+ }
+
+ // Union lrgs.
+ progress = true;
+
+ // Since `n` is dying, remove from the LRGS BEFORE doing
+ // the union; this will let the union code keep the
+ // matching bits from the other side, preserving some
+ // info for biased coloring
+ if( ov1. _machDef==n ) ov1. _machDef = null;
+ if( ov1._splitDef==n ) ov1._splitDef = null;
+ if( ov2. _machUse==n ) ov2. _machUse = null;
+ if( ov2._splitUse==n ) ov2._splitUse = null;
+ // Keep larger adjacency list.
+ Ary<LRG> larger = v2._adj;
+ v2._adj = null;
+
+ // Union lrgs. Swaps again, based on small lrg
+ LRG vWin = v1.union(v2);
+ vWin._adj = larger;
+ LRG vLose = v1.leader() ? v2 : v1;
+
+ // LRGs below v2len were always in v2, but maybe not in v1.
+ // LRGs above v2len are in v1 but were not in v2.
+
+ // Walk v2's neighbors, replacing vLose with vWin.
+ if( larger != null )
+ for( LRG vn : larger ) {
+ int idx = vn._adj.find(vLose);
+ if( idx != -1 )
+ vn._adj.del(idx);
+ if( vn._adj.find(vWin) == -1 )
+ vn._adj.add(vWin);
+ }
+ } else {
+
+ // Since `n` is dying, remove from the LRGS
+ if( v1. _machDef==n ) v1. _machDef=null;
+ if( v1. _machUse==n ) v1. _machUse=null;
+ if( v1._splitDef==n ) v1._splitDef=null;
+ if( v1._splitUse==n ) v1._splitUse=null;
+ }
+
+ // Remove junk split
+ n.removeSplit();
+ j--;
+ }
+ }
+
+ if( progress )
+ alloc.unify();
+
+ return true;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
new file mode 100644
index 00000000..bdb510b4
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
@@ -0,0 +1,889 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.graph.GraphObserver;
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.print.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.*;
+import java.io.File;
+import java.util.*;
+
+
+// Compile Driver for a Compilation Unit
+@SuppressWarnings("unchecked")
+public class CodeGen {
+
+ // Last created CodeGen as a global; used all over to avoid passing about a "context".
+ public static CodeGen CODE;
+
+ // Location of built-in CPU ports
+ public static final String PORTS = "com.seaofnodes.simple.node.cpus";
+
+ // Compile phases
+ public enum Phase {
+ Parse, // Parse ASCII text into Sea-of-Nodes IR
+ Iter, // Pessimistic peepholes after parse
+ Opto, // SCCP, numeric modes, seed the peephole worklist
+ IterPostOpto, // Peepholes and inlining using SCCP results
+ FinalizeIR, // Field layout, offset cleanup, function/CU roots
+ TypeCheck, // Last check for bad programs
+ Serialize, // Save ideal IR, including opaque class initialization
+ Lower, // Expand checked class initialization to ordinary IR
+ LoopTree, // Build a loop tree; break infinite loops
+ Unlink, // Unlink call sites before machine code generation
+ Select, // Convert to target hardware nodes
+ Schedule, // Global schedule (code motion) nodes
+ LocalSched, // Local schedule
+ RegAlloc, // Register allocation
+ Encoding, // Encoding
+ Export, // Export
+ LastPhase // After last phase
+ }
+ public Phase _phase;
+ // Null for normal compilation; owned by this compilation, not a global viewer.
+ public GraphObserver<Node> _obs;
+
+ /** True when tests deliberately randomize Iter worklist order. */
+ public static boolean iterSeedOverridden() { return System.getProperty("simple.iter.seed") != null; }
+
+ // Expensive assert sampling:
+ // -Dsimple.assert.expensive=0 disables these checks
+ // -Dsimple.assert.expensive=1 checks every step
+ // -Dsimple.assert.expensive=N checks once every 1<<N steps
+ private static final int EXPENSIVE_ASSERT_LOG = Integer.getInteger("simple.assert.expensive",8);
+ public static boolean expensiveAssert() { return EXPENSIVE_ASSERT_LOG > 0; }
+ public static boolean expensiveAssert( int trip ) {
+ if( EXPENSIVE_ASSERT_LOG <= 0 ) return false;
+ if( EXPENSIVE_ASSERT_LOG == 1 ) return true;
+ int shift = Math.min(EXPENSIVE_ASSERT_LOG,30);
+ return (trip & ((1<<shift)-1)) == 0;
+ }
+
+ // ---------------------------
+ // Module Source Root
+ public final String _modDir;
+ // Module Build Root
+ public final String _buildDir;
+ // Search path from CWD for `.o` files containing external symbols and types.
+ // This can contain archives, zips and other .o file containers.
+ public final Ary<String> _externPaths;
+
+ // Dotted path from module root to file containing the source, sans ".smp".
+ // e.g. module root: "sys", nested source name "sys.io".
+ public final String _srcName;
+
+ // Only available for tests, otherwise source comes from the CompUnit
+ private final String _src;
+
+ // Current Working Directory; default module base
+ private final String _cwd;
+
+ // Compilation Units used in this compile; typically one per source/object
+ // file, although you can nest structs and get more than one. These are all
+ // required to compile, and are expected to have their source timestamps
+ // ahead of their obj file. Some of these may entirely be dead or inlined
+ // in this CompUnit and thus not needed in any linking stage. All the
+ // alive ones (inlined away or not) participate in class init cycle checks.
+ public HashMap<String,CompUnit> _compunits;
+
+ // Test shortcut for only one compilation unit
+ public CompUnit compunit() {
+ assert _compunits.size()==1;
+ for( CompUnit cu : _compunits.values() )
+ return cu;
+ assert false : "no compilation unit";
+ return null;
+ }
+
+ // Frozen after ParseAll: public initializers and function-valued fields.
+ public boolean publicFIDX(int fidx) {
+ return fidx >= GlobalBits.RESERVED && XInt.bit(publicFIDXs(),fidx);
+ }
+
+ private void gatherPublicFields(TypeStruct clz, boolean classFields) {
+ boolean hasCtor = clz.field("<ctor>") != null;
+ for( Field fld : clz._fields ) {
+ // With a user constructor, class:N.N is the private <init>.
+ if( fld._fname.charAt(0)=='_' || fld._t==Type.TOP ||
+ (hasCtor && clz.hiddenInit(fld)) ) continue;
+ if( classFields ) _publicAliases = XInt.make(_publicAliases,fld._alias);
+ if( fld._t instanceof TypeFunPtr tfp )
+ _publicFIDXs = XInt.meet(_publicFIDXs,tfp._fidxs);
+ }
+ }
+
+ public static boolean hasUserConstructor(TypeStruct self) {
+ Type t = Parser.TYPES.get(Parser.addClzPrefix(self._name));
+ return t instanceof TypeStruct clz && clz.field("<ctor>") != null;
+ }
+
+ // A forward reference has a placeholder class, not a definition yet.
+ private static boolean resolvedClass(TypeStruct clz) {
+ Type t = Parser.TYPES.get(clz._name.substring(Parser.CLZ.length()));
+ return !(t instanceof TypeStruct ts) || !ts._fref;
+ }
+
+ private int[] _publicFIDXs, _publicAliases;
+
+ /**
+ * Collect every possible public escape - after ParseAll is done - in case
+ * some code calls a generic function. Example:
+ * var apply = = { {int->int} f, int x -> f(x); }; // Generic apply() call
+ * The CallEnd from `f(x)` has to use generic TypeMem.BOT, which
+ * immediately escapes *everything* including known private functions.
+ * As weak as this is, its still sharper than "everything".
+ */
+ void freezePublicInterface() {
+ assert _publicFIDXs==null && _publicAliases==null;
+ _publicFIDXs = _publicAliases = XInt.EMPTY;
+ for( Type t : Parser.TYPES.values() )
+ if( t instanceof TypeStruct clz &&
+ Parser.startsClzPrefix(clz._name) &&
+ Parser.isPublicClassName(clz._name) &&
+ resolvedClass(clz) ) {
+ // Gather function pointers from public fields of both class
+ // and instance objects.
+ gatherPublicFields(clz,true);
+ Type self = Parser.TYPES.get(clz._name.substring(Parser.CLZ.length()));
+ if( self instanceof TypeStruct ts ) gatherPublicFields(ts,false);
+ }
+ // Limit broad function types to known FIDXs, excluding reserved bits.
+ // Public class initializers are roots without needing a field reference.
+ int[] fidxs = XInt.EMPTY;
+ for( int fidx = GlobalBits.RESERVED; fidx<_linker._len; fidx++ ) {
+ FunNode fun = _linker.atX(fidx);
+ if( XInt.bit(_publicFIDXs,fidx) || (fun!=null && fun.isClz() && fun.isPublic()) )
+ fidxs = XInt.make(fidxs,fidx);
+ }
+ _publicFIDXs = fidxs;
+ }
+
+ public int[] publicFIDXs() { assert _publicFIDXs !=null; return _publicFIDXs; }
+ public int[] publicAliases(){ assert _publicAliases!=null; return _publicAliases; }
+ public boolean publicInterfaceFrozen() { return _publicFIDXs != null; }
+
+ // ---------------------------
+
+ // Very common nodes, cached here
+ public final ConstantNode ZERO;
+ public final XCtrlNode XCTRL;
+
+ public Node constant( Type t ) {
+ assert _phase==null || _phase.ordinal() <= Phase.Lower.ordinal();
+ return ConstantNode.make(t).peephole();
+ }
+ public ConstantNode con( Type t ) { return (ConstantNode)constant(t); }
+ public ConstantNode con( long con ) { return con==0 ? ZERO : con(TypeInteger.constant(con)); }
+
+
+ // ---------------------------
+ // Test setup; no module nor file with specific argument
+ public CodeGen( String src ) { this(src, 126L, true ); }
+
+ // Test setup; no module nor file; can alter seed & argument; can re-run same CodeGen
+ public CodeGen( String src, Type arg ) {
+ this(null,null,null,null, src, 126L, true, arg);
+ }
+ // Test setup; no module nor file; can alter seed & argument; can re-run same CodeGen
+ public CodeGen( String src, long workListSeed, boolean resetTypes ) {
+ this(null,null,null,null, src, workListSeed, resetTypes, TypeInteger.BOT);
+ }
+
+ // Generic CodeGen, including full module setup
+ public CodeGen( String modDir, String buildDir, Ary<String> externPaths,
+ String srcName, String src, long workListSeed, boolean resetTypes, Type arg ) {
+ // Public singleton to avoid passing about this state to a huge count
+ // of places. Probably becomes a TLS at some point.
+ CODE = this;
+ if( resetTypes ) Type.reset(); // Reset to recover from failed test
+
+ _cwd = System.getProperty("user.dir")+"/";
+ _modDir = modDir == null ? _cwd : modDir;
+ _buildDir = buildDir == null ? _cwd : buildDir;
+ _externPaths = externPaths;
+ _srcName = srcName;
+ _callingConv = null; // Calling convention
+ // Source code from test strings, not files
+ _src = src;
+ // All the compilation units
+ _compunits = new HashMap<>();
+ _phase = null;
+ // Start GVN table
+ _gvn = new HashMap<>();
+ // Allow whole-suite worklist-order sweeps without rewriting the many
+ // explicit seeds used by tests and command-line compilation.
+ String iterSeed = System.getProperty("simple.iter.seed");
+ _iter = new IterPeeps(iterSeed == null ? workListSeed : Long.parseLong(iterSeed));
+ // End points of graph
+ _stop = new StopNode().init();
+ _start = new StartNode(null,_stop,arg).init();
+ ZERO = con(TypeInteger.ZERO).keep();
+ XCTRL = new XCtrlNode().peephole().keep();
+ P = new Parser(this);
+ }
+
+
+ // Run requested phases. File export must request Serialize before Lower;
+ // requesting Export up front does that automatically. Encoding alone does
+ // not retain reloadable ideal IR. Choose combined in-memory output before
+ // Encoding; otherwise saved IR requests separate object encodings.
+
+ // No code emission, just IR generation
+ public CodeGen driver( Phase phase ) { return driver(phase,null,null,false,false,0); }
+ // No object file writing, but code generation for a specific cpu/os pair (allows emulation)
+ public CodeGen driver( Phase phase, String cpu, String callingConv ) {
+ if( phase.ordinal() < Phase.Export.ordinal() )
+ // No code outputted
+ return driver(phase,cpu,callingConv,false,false,0);
+ if( _srcName == null )
+ throw new RuntimeException("No source filename provided, so do not know how to name the obj file");
+ boolean emitEntrySymbol = !_srcName.contains(".");
+ return driver( phase, cpu, callingConv, false, emitEntrySymbol, 0 );
+ }
+ // Write an object file for a specific cpu/os pair
+
+ public CodeGen driver( String cpu, String callingConv, boolean inMemory, boolean emitEntrySymbol ) { return driver(Phase.Export,cpu,callingConv,inMemory,emitEntrySymbol,0); }
+
+ // The Generic driver
+ public CodeGen driver( Phase target, String cpu, String callingConv, boolean inMemory, boolean emitEntrySymbol, int dump ) {
+ // Parse a Simple file, and for mentioned external symbols either load
+ // from .o or recursively parse
+ step(target, Phase.Parse, this::parse, dump);
+ // Run peepholes to a fixed point
+ step(target, Phase.Iter, this::iter, dump);
+ // Run SCCP (and resolve int/flt expressions)
+ step(target, Phase.Opto, this::opto, dump);
+ // Run peepholes to a fixed point again, on the better types.
+ step(target, Phase.IterPostOpto, this::iterPostOpto, dump);
+ // Field layouts; field symbols become constant offsets
+ step(target, Phase.FinalizeIR, this::finalizeIR, dump);
+ // Final error check - bad programs fail here
+ step(target, Phase.TypeCheck, this::typeCheck, dump);
+ // Save reloadable ideal IR for a later seperate compilation
+ step(target, Phase.Serialize, () -> Serialize.forTarget(this,target,inMemory), dump);
+ // Eval2 executes ideal operations, including CLInit, directly. Only
+ // machine compilation expands them before scheduling.
+ step(target, Phase.Lower, () -> { if( cpu!=null ) lower(); }, dump);
+ // Compute the loop-tree, used for approximating exec frequencies for
+ // scheduling and register allocation
+ step(target, Phase.LoopTree, this::loopTree, dump);
+ // Detach call-graph edges; ideal execution retains unloaded summaries
+ // as dispatch targets and schedules their bodies on first use.
+ step(target, Phase.Unlink, () -> unlink(cpu!=null), dump);
+ // Instruction selection
+ step(target, Phase.Select, () -> instSelect(cpu,callingConv), dump);
+ // Global Code Motion
+ step(target, Phase.Schedule, this::GCM, dump);
+ // Local (basic block only) scheduling
+ step(target, Phase.LocalSched, this::localSched, dump);
+ // Register allocation
+ step(target, Phase.RegAlloc, this::regAlloc, dump);
+ // Encode machine instructions into bits
+ step(target, Phase.Encoding, () -> encode(inMemory), dump);
+ // Export bits (and serialized IR) into ELF files
+ step(target, Phase.Export, () -> exportELF(inMemory,emitEntrySymbol), dump);
+ return this;
+ }
+
+ // The driver names the order; optional phases decide locally whether to run.
+ private void step(Phase target, Phase next, Runnable work, int dump) {
+ if( next.ordinal() > target.ordinal() || (_phase!=null && _phase.ordinal()>=next.ordinal()) ) return;
+ Phase prior = _phase;
+ work.run();
+ if( _phase!=prior ) dump(dump);
+ }
+
+ // All phase entry points share ordering checks, timing, and viewer notification.
+ // Record elapsed time even on failure, but report completion only on success.
+ CodeGen phase(Phase next, boolean ordered, Runnable work) {
+ assert ordered : "Cannot run "+next+" after "+_phase;
+ // Serialize reads the checked graph; other phases can change it.
+ if( next!=Phase.Serialize ) _classInitDependencies = null;
+ _phase = next;
+ long t0 = System.currentTimeMillis();
+ work.run(); // Do The Phase
+ _times[next.ordinal()] = System.currentTimeMillis() - t0;
+ if( _obs!=null ) _obs.phase(next.name()); // Record for viewer
+ return this;
+ }
+
+ public String entryClinitName() {
+ return Parser.addClzPrefix(_srcName==null ? "Test" : _srcName.replace('/','.'))+".<clinit>";
+ }
+
+ // Verbose printing during compilation
+ private void dump(int dump) {
+ int p2 = _phase.ordinal();
+ if( (dump & (1<<p2)) == 0 ) return;
+ if( (dump & (1<<30)) != 0 )
+ System.err.println("After "+_phase+":");
+ System.err.println(_phase.ordinal() >= Phase.LocalSched.ordinal()
+ ? asm(new SB()).toString()
+ : IRPrinter.prettyPrint(this));
+ }
+
+
+ // Record times by phase
+ public final long[] _times = new long[Phase.LastPhase.ordinal()];
+
+ // ---------------------------
+ /**
+ * A counter, for unique node id generation. Starting with value 1, to
+ * avoid bugs confusing node ID 0 with uninitialized values.
+ */
+ public int _uid = 1;
+ public int UID() { return _uid; }
+ public int getUID() {
+ return _uid++;
+ }
+
+
+ // These next fields all 2-way map some *global* program feature to a
+ // local dense index, suitable for packing in BitSets. The mapping takes a
+ // global value - always a {source file/class} name, and an order number
+ // counting up per feature in the same file. The dense local index can be
+ // different in different compilations.
+
+ // The global info allows loading the same remote class info from different
+ // paths and aligning them. Example: Compiling A loads pre-compiled B and
+ // C, both of which load pre-compiled D. The 2 different D loads unify via
+ // this global info.
+
+ // These mappings are all trivial identities when compiling one file; they
+ // only become complex when loading separately compiled code.
+
+ // Compute local function index (FIDX) from global function info. This is
+ // called *in order* during parsing, and that order is part of the global
+ // unique mapping
+ public final GlobalBits _aliases = new GlobalBits();
+ public int alias(String clz) { return _aliases.next(clz); }
+
+ // C storage has one global alias per symbol, including across object files.
+ public int externAlias(String name) { return _aliases.next("C:"+name,GlobalBits.RESERVED); }
+
+ // External data addresses supplied by an in-memory linker (image offsets).
+ public final HashMap<String,Integer> _externDataAddresses = new HashMap<>();
+
+ // Compute local function index (FIDX) from global function info. This is
+ // called *in order* during parsing, and that order is part of the global
+ // unique mapping
+ public final GlobalBits _fidxs = new GlobalBits();
+ // Return local index for specific global file & order. Used to find fidxs for e.g. FREF class <init> fcns
+ public int fidx( String clz, int order ) { return _fidxs.next(clz, order); }
+ // New fidx in the current file
+ public int fidx( String clz ) { return fidx(clz, -1); }
+
+ // Compute local RPC index from global RPC info, one per call
+ public final GlobalBits _rpcs = new GlobalBits();
+ public int rpc( String clz ) { return _rpcs.next(clz); }
+
+
+ // Inlining can insert CFG edges and invalidate cached depth ordering.
+ private char _iDepthVersion;
+ public char iDepthVersion() { return _iDepthVersion; }
+ public void invalidateIDepthCaches() {
+ assert _iDepthVersion < Character.MAX_VALUE : "Dominator cache version exceeds 65535";
+ ++_iDepthVersion;
+ }
+
+ // Popular visit bitset, declared here, so it gets reused all over
+ public final BitSet _visit = new BitSet();
+ public BitSet visit() { assert _visit.isEmpty(); return _visit; }
+
+ // Start and Stop; end points of the generated IR
+ public StartNode _start;
+ public StopNode _stop;
+ // Iter creates this shared analysis result. Start's unused backedge slot
+ // roots it until Unlink; serialized Stops retain its escape facts instead.
+ public GlobalEscapeNode _globalEscapes;
+
+ // Global Value Numbering. Hash over opcode and inputs; hits in this table
+ // are structurally equal.
+ public final HashMap<Node,Node> _gvn;
+
+ // Reverse from a constant function pointer to the IR function being called.
+ // Error to call with a non-constant TFP
+ public FunNode link( TypeFunPtr tfp ) { return link(tfp.fidx()); }
+ // Read-only linker lookup, suitable for printers and debugger display.
+ // In particular, do not lazily remove a partially dead function.
+ public FunNode _link(int fidx) { return _linker.atX(fidx); }
+ public FunNode _link(TypeFunPtr tfp) { return _link(tfp.fidx()); }
+ // Return the FunNode from a fidx
+ public FunNode link( int fidx ) {
+ FunNode fun =_linker.atX(fidx);
+ if( fun!=null && fun.isDead() ) {
+ _linker.setX(fidx,null); fun = null; }
+ return fun;
+ }
+ // Return the <clinit> for a CompUnit
+ public FunNode link( CompUnit cu ) {
+ for( FunNode fun : _linker )
+ if( fun!=null && fun._compunit == cu && fun.isClz() )
+ return fun;
+ return null;
+ }
+
+ // Insert linker mapping from constant function signature to the function
+ // being called.
+ public void link(FunNode fun) {
+ int fidx = fun.sig().fidx();
+ _linker.setX(fidx,fun);
+ add(_globalEscapes); // A new or replacement summary may expose escapes.
+ }
+
+ // "Linker" mapping from constant TypeFunPtrs to heads of function. These
+ // TFPs all have exact single fidxs and their return is wiped to BOTTOM (so
+ // the return is not part of the match).
+ public final Ary<FunNode> _linker = new Ary<>(FunNode.class);
+
+ // Extern function declarations; input is the FIDX assigned to the name.
+ final HashMap<Integer,String> _externFunc = new HashMap<>();
+ public void externFunc(int fidx, String ex) {
+ assert !_externFunc.containsKey(fidx);
+ _externFunc.put(fidx,ex);
+ }
+ public String externFunc(int fidx) { return _externFunc.get(fidx); }
+
+ public String funcName(int fidx ) {
+ FunNode fun = _link(fidx);
+ if( fun!=null ) return fun._name;
+ return externFunc(fidx);
+ }
+
+ // Select which source CompUnit's object this encoding pass emits.
+ CompUnit _emitUnit;
+ boolean emits(CompUnit cu) { return cu!=null && cu._src!=null && (_emitUnit==null || _emitUnit==cu); }
+ boolean emits(FunNode fun) { return emits(fun._compunit); }
+ final LinkedHashMap<CompUnit,Encoding> _encodings = new LinkedHashMap<>();
+
+
+ // ---------------------------
+ // Parser object
+ public final Ary<CallNode> _calls = new Ary<>(CallNode.class);
+
+ public final Parser P;
+
+ // Parse ASCII text into Sea-of-Nodes IR
+ public CodeGen parse() { return phase(Phase.Parse, _phase==null, () -> ParseAll.parse(this,_src)); }
+
+
+ // ---------------------------
+ // Iterator peepholes.
+ public final IterPeeps _iter;
+ // Stop tracking deps while assertin
+ public boolean _midAssert;
+ // Statistics on peepholes
+ public int _iter_cnt, _iter_nop_cnt;
+ // Stat gathering
+ public void iterCnt() { if( !_midAssert ) _iter_cnt++; }
+ public void iterNop() { if( !_midAssert ) _iter_nop_cnt++; }
+
+ // Pessimistic peepholes after parsing. This lifts loaded and parsed
+ // types before the optimistic Opto pass resets types and lets them fall.
+ public CodeGen iter() { return phase(Phase.Iter, _phase==Phase.Parse, () -> Iter.run(this)); }
+
+ // SCCP and numeric-mode resolution; leave cleanup work queued for Iter.
+ public CodeGen opto() { return phase(Phase.Opto, _phase==Phase.Iter, () -> Opto.opto(this)); }
+
+ public CodeGen iterPostOpto() { return phase(Phase.IterPostOpto, _phase==Phase.Opto, () -> _iter.iterate(this)); }
+
+ // Materialize an inline candidate at an empty worklist. Loading queues
+ // the new body and affected callers before CallEnd considers inlining.
+ public void loadForInline(CompUnit cu) {
+ assert _phase==Phase.IterPostOpto && _iter._work.isEmpty();
+ ParseAll.loadCompUnit(this,cu);
+ }
+
+ // TypeCheck saves its successful analysis for Serialize. Loading bodies
+ // or entering another graph-changing phase discards it.
+ ClassInitDependencies _classInitDependencies;
+
+ public CodeGen finalizeIR() { return phase(Phase.FinalizeIR, _phase==Phase.IterPostOpto, () -> FinalizeIR.run(this)); }
+
+ // Cross-phase `keep()`s belong here! We have a few nodes which we can
+ // lose all uses of, but later find them and want to re-add output edges.
+ // If we don't `keep()` them, they'll dead-code-elimnate.
+
+ // These are mostly around adding and removal functions from other
+ // compilation units. E.g. a StartCU, when all his functions are exactly
+ // called with no escapes and no default input. Those functions will still
+ // go out in their own ELF, and will later hook on to the StartCU during
+ // code emission. Between e.g. Opto and code emission the StartCU has no
+ // direct users - but we're going to want the StartCU at elf-writer time!
+
+ // The Parser and `idealize()` `keep()`s remain locally balanced.
+
+ // We make a few collections of these cross-phase `keep()`s.
+
+ // A few more cross-phase example lifetimes:
+
+ // Until FinalizeIR: Opto and import loading retain StartCU's memory
+ // projection while unknown-caller edges disappear and may be rebuilt.
+ // CompUnit._start.proj(1) finds it; Opto.linkStart uses it for the memory
+ // Parm, found by scanning fun._outputs. FinalizeIR first holds the StartCU
+ // itself, then releases these projections and queues ordinary dead cleanup.
+
+ // Until Unlink: FinalizeIR retains StartCU even if it has no users left.
+ // CompUnit._start is the non-graph reference used by Serialize and Unlink.
+ // Unlink.run/forEval install scheduling edges before releasing these holds.
+ // A Java reference alone does not protect a node from graph deletion.
+ //
+ // Replacement is the exception to those release points: ParseAll must
+ // release our old wrapper/projection holds before subsuming the wrapper.
+ // It registers the replacement projection for the next FinalizeIR call.
+ private final Ary<Node> _keptUntilFinalize = new Ary<>(Node.class);
+ private final Ary<Node> _keptUntilUnlink = new Ary<>(Node.class);
+
+ void keepUntilFinalize(Node n) { keepOnce(_keptUntilFinalize,n); }
+ void keepUntilUnlink (Node n) { keepOnce(_keptUntilUnlink ,n); }
+ private static void keepOnce(Ary<Node> kept, Node n) {
+ if( kept.find(n)==-1 ) kept.add(n.keep());
+ }
+
+ // FinalizeIR has retained the wrappers; unused projections may now die.
+ void releaseFinalizedNodes() {
+ for( Node n : _keptUntilFinalize ) add(n.unkeep());
+ _keptUntilFinalize.clear();
+ }
+
+ // Unlink has installed the scheduling edges. No optimization is needed.
+ void releaseUnlinkedNodes() {
+ for( Node n : _keptUntilUnlink ) n.unkeep();
+ _keptUntilUnlink.clear();
+ }
+
+ // Only release our holds. A caller's temporary keep is a separate owner.
+ void releaseReplacedNode(Node n) {
+ releaseReplacedNode(_keptUntilFinalize,n);
+ releaseReplacedNode(_keptUntilUnlink,n);
+ }
+ private static void releaseReplacedNode(Ary<Node> kept, Node n) {
+ int idx = kept.find(n);
+ if( idx!=-1 ) kept.del(idx).unkeep();
+ }
+
+ // Root a surviving function without claiming that it has unknown callers.
+ public void keepInCompUnit(FunNode fun) {
+ StopCUNode stop = fun._compunit._stop;
+ if( stop._inputs.find(fun.ret()) == -1 )
+ add(stop).addDef(fun.ret());
+ }
+ public <N extends Node> N add( N n ) { return _iter.add(n); }
+ public void addAll( Ary<Node> ary ) { _iter.addAll(ary); }
+ public void addAll( Node n ) { _iter.add(n); _iter.addAll(n._inputs); }
+
+ // ---------------------------
+ // Last check for bad programs
+ public CodeGen typeCheck() { return phase(Phase.TypeCheck, _phase==Phase.FinalizeIR, () -> TypeCheck.run(this)); }
+
+ // ---------------------------
+ // TypeCheck queued surviving CLInits during its existing error walk.
+ public CodeGen lower() {
+ return phase(Phase.Lower, (_phase==Phase.TypeCheck || _phase==Phase.Serialize), () -> _iter.iterate(this));
+ }
+
+ // ---------------------------
+ // Build the loop tree; break never-exit loops
+ public CodeGen loopTree() {
+ return phase(Phase.LoopTree, (_phase==Phase.Lower || _phase==Phase.TypeCheck || _phase==Phase.Serialize),
+ () -> _start.buildLoopTree(_linker,_stop));
+ }
+
+ // ---------------------------
+ public BAOS _serial;
+ final LinkedHashMap<CompUnit,BAOS> _serials = new LinkedHashMap<>();
+ public void serialize() {
+ phase(Phase.Serialize, _phase==Phase.TypeCheck, () -> Serialize.serialize(this));
+ }
+
+ // ---------------------------
+ // Code generation CPU target
+ public Machine _mach;
+ // Chosen calling convention (usually either Win64 or SystemV)
+ public String _callingConv;
+ public String _cCallingConv;
+ // Callee save registers
+ public RegMask _callerSave;
+ public RegMask _cCallerSave;
+
+ // All returns have the following inputs:
+ // 0 - ctrl
+ // 1 - memory
+ // 2 - varies with returning GPR,FPR
+ // 3 - RPC
+ // 4+- Caller save registers
+ public RegMask[] _retMasks;
+ // Return Program Counter
+ public RegMask _rpcMask;
+
+ // Convert to target hardware nodes
+ public CodeGen instSelect( String cpu, String callingConv ) { return instSelect(cpu,callingConv,PORTS); }
+ public CodeGen instSelect( String cpu, String callingConv, String base ) {
+ return InstSelect.select(this,cpu,callingConv,base);
+ }
+
+ // ---------------------------
+ public void unlink() {
+ unlink(true);
+ }
+ private void unlink(boolean removeImports) {
+ phase(Phase.Unlink, _phase==Phase.LoopTree, () -> {
+ _start.setDef(2,null); // Releases the compiler-only escape analysis.
+ if( removeImports ) Unlink.run(this,true);
+ else Unlink.forEval(this,_start,_stop);
+ });
+ }
+
+ // ---------------------------
+ // Control Flow Graph in Reverse Post Order.
+ public Ary<CFGNode> _cfg = new Ary<>(CFGNode.class);
+
+ // Global schedule (code motion) nodes
+ public CodeGen GCM() {
+ return phase(Phase.Schedule, (_phase==Phase.Unlink || _phase==Phase.Select), () -> GlobalCodeMotion.buildCFG(this));
+ }
+
+ // ---------------------------
+ // Local (basic block) scheduler phase, a classic list scheduler
+ public CodeGen localSched() { return phase(Phase.LocalSched, _phase==Phase.Schedule, () -> ListScheduler.sched(this)); }
+
+
+ // ---------------------------
+ // Register Allocation
+ public RegAlloc _regAlloc;
+ public CodeGen regAlloc() {
+ return phase(Phase.RegAlloc, _phase==Phase.LocalSched, () -> {
+ _regAlloc = new RegAlloc(this);
+ _regAlloc.regAlloc();
+ });
+ }
+
+ // Human-readable register name
+ public String reg(Node n) {
+ if( _phase.ordinal() >= Phase.RegAlloc.ordinal() ) {
+ String s = _regAlloc.reg(n);
+ if( s!=null ) return s;
+ }
+ return "N"+ n._nid;
+ }
+
+
+ // ---------------------------
+ // Encoding
+ public Encoding _encoding;
+ public CodeGen encode() { return encode(false); }
+ private CodeGen encode(boolean inMemory) { return phase(Phase.Encoding, _phase==Phase.RegAlloc, () -> {
+ // Saved ideal IR requests one object per unit, including when the
+ // driver stopped at Serialize and resumed later. In-memory linking
+ // explicitly requests one combined image instead.
+ if( inMemory || _serials.isEmpty() ) { _encoding = new Encoding(this).encode(); return; }
+ for( CompUnit cu : _serials.keySet() ) {
+ _emitUnit = cu;
+ Encoding enc = new Encoding(this).encode();
+ _encodings.put(cu,enc);
+ if( cu._fname.equals(_srcName==null ? "Test" : _srcName) ) _encoding = enc;
+ }
+ _emitUnit = null;
+ }); }
+
+ // ---------------------------
+ // Exporting to external formats
+ ElfWriter _elf;
+ public CodeGen exportELF( boolean inMemory, boolean emitEntrySymbol ) {
+ return phase(Phase.Export, _phase==Phase.Encoding, () -> {
+ if( inMemory ) {
+ assert _encodings.isEmpty() : "In-memory output must be selected before Encoding";
+ new LinkMem(this).link(_encoding);
+ }
+ else {
+ if( _serials.isEmpty() )
+ throw new IllegalStateException("ELF export needs ideal IR: request Export from the driver, or Serialize before Lower");
+ assert !_encodings.isEmpty() : "File output must be selected before Encoding";
+ Encoding entry = _encoding;
+ BAOS serial = _serial;
+ try {
+ for( var e : _encodings.entrySet() ) {
+ _emitUnit = e.getKey();
+ _encoding = e.getValue();
+ _serial = _serials.get(_emitUnit);
+ _elf = new ElfWriter(this).export(emitEntrySymbol && _emitUnit._fname.equals(_srcName));
+ }
+ // Mutually dependent units built together must not become
+ // stale merely because their files were written in sequence.
+ long time = System.currentTimeMillis();
+ for( CompUnit cu : _encodings.keySet() ) {
+ File obj = new File(_buildDir+"/"+cu._fname+".o");
+ if( !obj.setLastModified(time) ) throw new IllegalStateException("Cannot timestamp "+obj);
+ }
+ } finally { _emitUnit=null; _encoding=entry; _serial=serial; }
+ }
+ });
+ }
+
+
+ // ---------------------------
+
+ // Search a external path list, each path is recursively searched for .o
+ // files, which are partially loaded and searched for public symbols.
+
+ // This is a state machine which lazily searches down the set of search
+ // paths until the requested module is class is found.
+
+ // Map from external Strings to either partially read Simple ElfFile or ExternNode
+ public final HashMap<String,Object> _externSymbols = new HashMap<>();
+
+ // State machine elements; the outermost element is _externPaths
+ private int _extPathIdx; // Search index into the extern path list
+ private final Ary<File> _files = new Ary<>(File.class); // Files in the current extern path being searched
+ private final HashSet<File> _seenFiles = new HashSet<>();
+ private int _extFileIdx; // Index into _files
+
+ public ElfReader findExternalSimple( String name ) {
+ return findExternalSymbol(name) instanceof ElfReader elf ? elf : null;
+ }
+
+ public ExternNode findExternal( String name ) {
+ return findExternalSymbol(name) instanceof ExternNode extern ? extern : null;
+ }
+
+ // Search the search-path for the name; return an ElfReader if found in a
+ // Simple-made ELF; return an ExternNode if from another ELF, or null if
+ // not found.
+ private Object findExternalSymbol( String name ) {
+ // State Machine!
+
+ while( true ) {
+ // Check for an immediate hit
+ switch( _externSymbols.get(name) ) {
+
+ case ExternNode extern:
+ // Name maps to an ExternNode
+ return extern;
+
+ case ElfReader elf:
+ return elf;
+
+ case null:
+ // Name is unknown. Advance the file-system search, pulling
+ // out new ELF files from the search directory list and loading
+ // their published symbols.
+
+ // The last directory was mined out, so get the next search
+ // directory and find all ELF-like files.
+ if( _extFileIdx >= _files._len ) {
+ if( _externPaths==null || _extPathIdx >= _externPaths._len )
+ return null; // A true miss in the whole search path
+ loadExtPath(); // Load next batch file of files
+ break;
+ }
+ // Load and parse an ELF header
+ ElfReader elf = getElf(_files.at(_extFileIdx++));
+ if( elf == null ) break; // Not an ELF
+ // Load public symbols from the ELF
+ elf.loadPublicSymbols();
+ if( elf._strs==null ) break; // No .simple section with strings
+ // Put public symbol in global table; first names shadow all others
+ // Map from symbol string to ELFReader; most lookups
+ // will miss and no need to unpack ElfReader types
+ _externSymbols.putIfAbsent(elf._strs[1],elf);
+
+ // Publish definitions only; referenced classes belong to other objects.
+ for( String fname : elf._units )
+ _externSymbols.putIfAbsent(Parser.addClzPrefix(fname.replace('/','.')),elf);
+ // A -L object can name dependencies alongside itself. They
+ // need metadata discovery even when no directory was supplied.
+ for( String dep : elf._deps ) {
+ File file = elf.dependencyFile(dep);
+ if( file.isFile() ) addExtFile(file);
+ }
+ break;
+
+ default: throw Utils.TODO("should not reach here");
+ }
+ }
+ }
+
+ // Load all the .o files in the next external path.
+ private void loadExtPath() {
+ _files.clear();
+ _extFileIdx = 0;
+ String extpath = _externPaths.at(_extPathIdx++);
+ File f = new File(extpath);
+ if( !f.isAbsolute() )
+ f = new File(_cwd+extpath);
+ fillExtFiles(f);
+ }
+
+ // Recursive search (TODO: gzip, archives) and gather all .o files.
+ private void fillExtFiles(File dir) {
+ if( dir.isDirectory() )
+ for( File f : dir.listFiles() )
+ fillExtFiles(f);
+ else if( dir.getName().endsWith(".o") )
+ addExtFile(dir);
+ }
+
+ private void addExtFile(File file) {
+ file=file.toPath().toAbsolutePath().normalize().toFile();
+ if( _seenFiles.add(file) ) _files.add(file);
+ }
+
+ // Map from object file names to ElfReaders
+ public final HashMap<String,ElfReader> _elfs = new HashMap<>();
+ public ElfReader getElf(File f) {
+ ElfReader elf = _elfs.get(f.getPath());
+ if( elf != null )
+ return elf;
+ _elfs.put(f.getPath(), elf = ElfReader.load(f,null));
+ return elf;
+ }
+
+ // ---------------------------
+ public boolean _asmLittle=true;
+ public String asm() { return asm(new SB()).toString(); }
+ SB asm(SB sb) {
+ ASMPrinter.print(sb,this);
+ return sb;
+ }
+
+
+ // Testing shortcuts
+ public Node ctrl() { return compunit()._stop.ret().ctrl(); }
+ public Node expr() { return compunit()._stop.ret().expr(); }
+ public String print() { return compunit()._stop.print(); }
+
+ // Debugging helper
+ @Override public String toString() {
+ if( _stop == null )
+ return "No StopNode";
+ return IRPrinter.prettyPrint(this);
+ }
+
+ // Debugging helper
+ public Node f(int idx) {
+ for( CompUnit cu : _compunits.values() ) {
+ Node n = (cu._stop == null ? _stop : cu._stop).find(idx);
+ if( n != null ) return n;
+ }
+ return null;
+ }
+
+
+ String printCFG() {
+ if( _cfg==null ) return "no CFG";
+ SB sb = new SB();
+ for( CFGNode cfg : _cfg ) {
+ sb.fix(8,""+(int)cfg._idepth);
+ IRPrinter.printLine( cfg, sb );
+ }
+ return sb.toString();
+ }
+
+ public static void print_as_hex(Encoding enc) {
+ for (byte b : enc._bits.toByteArray()) {
+ System.out.print(String.format("%02X", b));
+ }
+ System.out.println();
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java
new file mode 100644
index 00000000..2b091b3c
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java
@@ -0,0 +1,170 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeStruct;
+import com.seaofnodes.simple.util.*;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import static com.seaofnodes.simple.codegen.CodeGen.CODE;
+
+// A compilation-unit, which is an external reference and source and object
+// files. These are mirrored over a file system tree (so form a tree), both
+// the source tree and build/object tree.
+public class CompUnit {
+ public final CompUnit _par; // Parent, e.g. A/B
+ final String _fname; // Full slashed file name starting from module root, e.g. A/B/C
+ final public String _cname; // Full dotted class name starting from module root, e.g. A.B.C
+ final String _name; // Base name, e.g. C
+ final ExternNode _ext; // Found in external libs; e.g. libc
+ final File _smp; // Simple source file, e.g. module_root/A/B/C.smp
+ public final File _obj; // ELF output file, e.g. build/A/B/C.o
+ final public String _src; // Source code, from file or test case
+ // Original source path from an imported diagnostic location. Does not
+ // affect discovery/rebuild decisions, which use _smp and _src.
+ public String _sourceFile;
+
+ public String sourceFile() {
+ return _smp!=null ? _smp.getAbsolutePath() : _sourceFile;
+ }
+
+ public TypeStruct _clz; // The one TypeStruct being published
+
+ // _deps records units required for types, code, or class references,
+ // without requiring their initializers to run. These discovery/rebuild
+ // dependencies may form a cycle.
+ Ary<CompUnit> _deps;
+
+ // Per-Compilation-Unit Start/StopNodes, keeping alive all exported Nodes.
+ // Null means no code loaded (yet).
+ public StartCUNode _start;
+ public StopCUNode _stop ;
+
+
+ // Discovered source file. Source is loaded if required to parse, and null
+ // otherwise.
+ CompUnit( CompUnit par, String fname, String cname, String name ) throws IOException {
+ _par = par;
+ _fname= fname;
+ _cname= cname;
+ _name = name;
+ _ext = null;
+ _smp = new File( CODE. _modDir + "/" + fname + ".smp");
+ _obj = new File( CODE._buildDir + "/" + fname + ".o" );
+
+ boolean mustCompile = _smp.exists() && // Has source to compile
+ ( fname.equals(CODE._srcName) || // Explicit input is a compilation request
+ !_obj.exists() || // No object file
+ _smp.lastModified() > _obj.lastModified() || // Out-of-date with source?
+ !current(_obj,new HashSet<>()) ); // Out-of-date anywhere in its inputs?
+ _src = mustCompile ? new String(Files.readAllBytes(_smp.toPath())) : null;
+ }
+
+ // Test case. No source file. Source code passed in as a String. Fake
+ // class name.
+ CompUnit( String fname, String src ) {
+ _par = null;
+ _name = fname;
+ _fname= fname;
+ _cname= fname;
+ _ext = null;
+ _smp = null;
+ _obj = null;
+ _src = src;
+ }
+
+ // External CompUnit, pulled in from an external library like libc
+ CompUnit( ExternNode ext, String symbol ) {
+ _par = null;
+ _name = symbol;
+ _fname= _name;
+ _cname= null;
+ _ext = ext;
+ _smp = null;
+ _obj = null;
+ _src = null;
+ }
+
+ // External Simple object file, or namespace parent, found on an external search path.
+ CompUnit( File obj, CompUnit par, String fname, String cname, String name ) {
+ _par = par;
+ _name = name;
+ _fname= fname;
+ _cname= cname;
+ _ext = null;
+ _smp = null;
+ _obj = obj;
+ _src = null;
+ }
+
+ // Check source-backed inputs before importing any saved types or IR.
+ // A dependency's source can be newer while its object is unchanged.
+ // Use a fresh visited set for each root: encountering a cycle is safe, but
+ // caching a provisional "current" answer from inside it would not be.
+ private static boolean current(File obj, HashSet<File> visit) {
+ if( obj==null || !obj.isFile() ) return false;
+ if( !visit.add(obj.getAbsoluteFile()) ) return true;
+ ElfReader elf = CODE.getElf(obj);
+ if( elf==null ) return false;
+ elf.loadPublicSymbols();
+ if( elf._units==null ) return false;
+ long time = obj.lastModified();
+ for( String unit : elf._units ) {
+ // Never import another copy of a unit explicitly being compiled.
+ CompUnit cu = CODE._compunits.get(unit);
+ if( unit.equals(CODE._srcName) || (cu!=null && cu._src!=null) ) return false;
+ File src = new File(CODE._modDir+"/"+unit+".smp");
+ if( !src.isFile() || src.lastModified()>time ) return false;
+ }
+ for( String dep : elf._deps ) {
+ if( Arrays.asList(elf._units).contains(dep) ) continue; // Owned by this object.
+ File file = dependencyObj(dep,true);
+ if( file==null || !file.isFile() || file.lastModified()>time ) return false;
+ // Object-only libraries are supplied as a coherent build. Their
+ // input versions cannot be inferred from installation timestamps.
+ if( file.equals(new File(CODE._buildDir+"/"+dep+".o")) && !current(file,visit) ) return false;
+ }
+ return true;
+ }
+
+ // Resolve dependencies the same way for freshness checks and actual loads.
+ static File dependencyObj(String fname, boolean local) {
+ File obj = new File(CODE._buildDir+"/"+fname+".o");
+ if( local && (obj.isFile() || new File(CODE._modDir+"/"+fname+".smp").isFile()) ) return obj;
+ ElfReader elf = CODE.findExternalSimple(Parser.addClzPrefix(fname.replace('/','.')));
+ return elf==null ? null : elf._file;
+ }
+
+ // Add a discovered dependency, for later writing out in an ELF file, ".simple" section
+ void addDep(CompUnit dep) {
+ if( _deps == null ) _deps = new Ary<>(CompUnit.class);
+ _deps.add(dep);
+ }
+
+ public void addFun(CodeGen code, FunNode fun) {
+ assert fun.in(1)==_start;
+ _stop.addDef(fun.ret());
+ code.add(_stop);
+ _stop.addDep(fun);
+ }
+
+ void setStart(CodeGen code) {
+ _stop = new StopCUNode().init();
+ _start = new StartCUNode(code._start,_stop, Type.BOTTOM, _fname).init();
+ code._stop.addDef(_stop);
+ }
+
+ @Override public String toString() {
+ String x = "{"+_cname;
+ if( _smp!=null && _smp.exists()) x += ",hasSMP";
+ if( _obj!=null && _obj.exists()) x += ",hasObj";
+ if( _src!=null ) x += ",hasSrc";
+ if( _ext != null ) x += ",isExt";
+ x += "}";
+ return x;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java
new file mode 100644
index 00000000..1dced3a8
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java
@@ -0,0 +1,245 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.type.TypeMemPtr;
+import com.seaofnodes.simple.type.TypeStruct;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
+import java.io.*;
+import java.nio.file.Files;
+import java.util.HashMap;
+
+public class ElfReader {
+
+ public final File _file;
+ public final byte[] _buf;
+ int _x; // Cursor into buffer
+ // ELF Header
+ public final ElfHeader _header;
+ // All sections
+ public final Section[] _sections;
+ // Simple section, if available
+ public final Section _simple;
+ // Byte array in/out stream - used as a reader
+ public final BAOS _bais;
+ // Compilation unit; written into FunNodes
+ public final CompUnit _compunit;
+
+ // Strings from the Simple section, either small public set or whole set
+ public String[] _strs;
+ // Dependent file names
+ public String[] _deps;
+ // Compilation units defined by this object, independent of init analysis.
+ public String[] _units;
+ // Only published symbol this file
+ public TypeStruct _clz;
+ // Canonical named types, distinct from optimized same-named graph states.
+ public TypeStruct[] _pubTypes;
+ // Nodes read in this Elf
+ public Ary<Node> _nodes;
+ Serialize.TypeRead _types;
+ FunctionSummary[] _functions;
+
+ // Load an Elf file (mostly lazily)
+ public static ElfReader load( File f, CompUnit cu ) {
+ try { return new ElfReader(f,cu); }
+ catch( IOException ioe ) { return null; }
+ }
+
+ // Load the Simple section from the ELF file
+ private ElfReader( File f, CompUnit cu ) throws IOException {
+ _file = f;
+ _compunit = cu;
+ _buf = Files.readAllBytes(f.toPath());
+ _header = new ElfHeader();
+ // No attempt is made to parse the program header table.
+ _sections = new Section[_header._shnum];
+ for( int i=0; i<_header._shnum; i++ )
+ _sections[i] = new Section(i);
+
+ // Load section names
+ Section secstr = _sections[_header._shstrndx];
+ for( Section sec : _sections )
+ if( sec._type != 0 )
+ sec._name = secstr.str(sec._noff);
+
+ Section simple = null;
+ for( Section sec : _sections )
+ if( ".simple".equals(sec._name) )
+ simple = sec;
+ _simple = simple;
+ _bais = _simple==null ? null : new BAOS(_buf, (int)_simple._offset);
+ }
+
+ // For Serialize self-test
+ ElfReader( BAOS bais ) {
+ _file = null;
+ _compunit = null;
+ _buf = bais.buf();
+ _header = null;
+ _sections = null;
+ _simple = null;
+ _bais = bais;
+ }
+
+ // Load the public symbols and dependent objs, but not the whole IR
+ void loadPublicSymbols() {
+ // Check already loaded and cached
+ if( _strs == null && _simple != null )
+ // Read only the published public strings, for speed purposes when
+ // scanning a large count of ELF files but getting hits in only a
+ // few of them. i.e. optimizing (prematurely?) for the traditional
+ // case of linking a small file against a large library
+ Serialize.read_public_strs(this);
+ }
+
+ File dependencyFile(String unit) {
+ File root = _file;
+ for( String part : _units[0].split("/") ) root=root.getParentFile();
+ return new File(root,unit+".o");
+ }
+
+ // Loads the entire IR
+ public TypeStruct loadSimple(CodeGen code) {
+ code._classInitDependencies = null; // Loaded bodies can change init edges.
+ Serialize.readAll(code, this, code._aliases, code._fidxs, code._rpcs);
+ return _clz;
+ }
+
+ // Loads strings, mappings, and types, but not Nodes. Used after the
+ // public-string scan finds a matching Simple ELF, so parsing can resolve
+ // imported field/array aliases before the full graph is linked in later.
+ public TypeStruct loadPublicTypes(CodeGen code) {
+ Serialize.readTypes(code, this, code._aliases, code._fidxs, code._rpcs);
+ return _clz;
+ }
+
+ int u1() { return _buf[_x++]&0xFF; }
+ int magic() { // Big endian
+ return (u1() << 24) | (u1() << 16) | (u1() << 8) | u1();
+ }
+ int u2() { return u1() | (u1()<<8); } // Little endian
+ int u4() { // Little endian
+ return u1() | (u1() << 8) | (u1() << 16) | (u1() << 24);
+ }
+ long u8() { return u4() | ((long)u4()<<32); }
+ void skip(int skip) { _x += skip; }
+
+
+ public class ElfHeader {
+ final int _magic;
+ final boolean _format32; // Format32 or format64
+ final byte _osabi;
+ final boolean _little; // True if little endian, false if big endian
+ final char _type;
+ final char _machine;
+ final long _entry; // Process entry point
+ final long _phoff; // Program header table offset, from file start
+ final long _shoff; // Section header table offset, from file start
+ final int _flags; // Flags
+ final int _phnum; // Number of program header entries
+ final int _shentsize;
+ final int _shnum; // Number of section header entries
+ final int _shstrndx; // Section header index contains section names
+
+
+ ElfHeader() throws IOException {
+ _x = 0;
+ if( (_magic = magic()) != 0x7F454C46 ) bad("magic number",_magic);
+ byte EI_CLASS = (byte)u1();
+ if( EI_CLASS != 1 && EI_CLASS != 2 ) bad("EI_CLASS",EI_CLASS);
+ _format32 = (EI_CLASS==1);
+ byte EI_DATA = (byte)u1();
+ if( EI_DATA != 1 && EI_DATA != 2 ) bad("EI_DATA",EI_DATA);
+ _little = EI_DATA == 1;
+ if( u1() != 1 ) bad("version",1);
+ if( (_osabi=(byte)u1()) != 0 ) bad("EI_VERSION",_osabi);
+ byte abiversion = (byte)u1();
+ if( abiversion != 0 ) bad("EI_ABIVERSION",abiversion);
+ skip(7); // Pad
+
+ // Endian matters now
+ if( !_little ) throw new IOException("TODO: Big endian");
+ if( (_type = (char)u2()) != 1 ) bad("ET_REL",_type);
+ if( (_machine = (char)u2()) != 0x3E ) bad("AMD x86-64",_machine);
+ int ver = u4();
+ if( ver != 1 ) bad("Version",ver);
+ // Format32 matters now
+ if( _format32 ) bad("Format",1);
+ _entry = u8(); // Process entry point
+ _phoff = u8(); // Program header table offset
+ _shoff = u8(); // Section header table offset
+ _flags = u4(); // Flags
+ int ehsize = u2();
+ if( ehsize != 0x40 ) bad("EHSIZE",ehsize);
+ int phentsize = u2();
+ if( phentsize != 0x38 && phentsize != 0 ) bad("PHENTSIZE",phentsize);
+ _phnum = u2();
+ _shentsize = u2();
+ if( _shentsize != 0x40 ) bad("SHENTSIZE",_shentsize);
+ _shnum = u2();
+ // String section
+ _shstrndx = u2();
+ assert _x == ehsize;
+ }
+ }
+
+ public class Section {
+ final int _noff; // Offset in string section for name
+ final int _type; // Section type
+ final long _flags;
+ final long _addr; // Offset in virtual mem, if loaded
+ final long _offset; // Offset in file
+ final long _size; // Size in bytes
+ final int _link; // Associated section index, varies
+ final int _info; // Varies
+ final byte _align; // Log2 alignment
+ final int _esize; // Element size, if non-zero
+ String _name;
+
+ Section(int sidx) throws IOException {
+ _x = (int)(_header._shoff + (sidx * _header._shentsize));
+ _noff = u4();
+ _type = u4();
+ _flags = u8();
+ _addr = u8();
+ _offset= u8();
+ _size = u8();
+ _link = u4();
+ _info = u4();
+ long align = u8();
+ if( align==0 ) align=1;
+ _align = (byte)Long.numberOfTrailingZeros(align);
+ if( (1L << _align ) != align )
+ throw new IOException("Bad section alignment: "+align);
+ _esize = (int)u8();
+ }
+
+ String str(int off) {
+ assert _type == 3/*SHT_STRTAB*/; // String table section header
+ int start = (int)(_offset+off);
+ int x = start;
+ while( _buf[x++] !=0 ) ;
+ return new String(_buf,start,x-start-1);
+ }
+ }
+
+ private static void bad(String msg, int val) throws IOException {
+ throw new IOException("Bad "+msg+": "+val);
+ }
+
+
+
+ //public TypeMemPtr lookup( String name ) {
+ // if( _simple == null ) return null;
+ // BAOS baos = new BAOS(_buf, (int)_simple._offset);
+ // Ary<String> strs = Serialize.read_strs(baos);
+ //
+ //
+ //
+ // Ary<Node> nodes = Serialize.read_types(baos);
+ //
+ // throw Utils.TODO("Elf load symbol "+name);
+ //}
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
new file mode 100644
index 00000000..fcf5c287
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
@@ -0,0 +1,468 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.type.TypeStruct;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.BAOS;
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.HashMap;
+
+public class ElfWriter {
+
+ final CodeGen _code;
+ Ary<Section> _sections;
+ StringSection _strtab;
+
+ ElfWriter( CodeGen code ) {
+ _code = code;
+ }
+
+ // ------------------------------------------------------------------------
+ abstract public class Section {
+ static final int SHT_NULL = 0;
+ static final int SHT_PROGBITS= 1;
+ static final int SHT_SYMTAB = 2;
+ static final int SHT_STRTAB = 3;
+ static final int SHT_RELA = 4;
+ static final int SHT_SIMPLE = 10; // Simple section
+
+ final String _name;
+ final int _type, _entsize, _index, _name_pos, _flags;
+ int _link, _info;
+ int _file_offset;
+
+ Section(String name, int type, int entsize, int flags) {
+ _name = name;
+ _type = type;
+ _entsize = entsize;
+ _flags = flags;
+ _index = _sections._len;
+ _sections.push(this);
+ _name_pos = name.isEmpty()
+ ? 0 // Null section
+ : _strtab == null
+ ? 1 // StringSection
+ // All the other sections
+ : _strtab.writeCString(name);
+ }
+
+ void writeHeader(ByteBuffer out) {
+ out.putInt(_name_pos); // name
+ out.putInt(_type); // type
+ out.putLong(_flags); // flags
+ out.putLong(0); // addr
+ out.putLong(_file_offset); // offset
+ out.putLong(size()); // size
+ out.putInt(_link); // link
+ out.putInt(_info); // info
+ out.putLong(16); // addralign
+ out.putLong(_entsize); // Fixed-size element size
+ }
+
+ abstract void write(ByteBuffer out);
+ abstract int size();
+ }
+
+ // ------------------------------------------------------------------------
+ // Generic data sections; carries about a BAOS for bits
+ public class DataSection extends Section {
+ static final int SHF_WRITE = 1;
+ static final int SHF_ALLOC = 2;
+ static final int SHF_EXECINSTR = 4;
+ static final int SHF_INFO_LINK = 64;
+
+ final BAOS _contents;
+
+ DataSection( String name, int type, int flags, int entsize ) { this(name, type, flags, entsize, new BAOS() ); }
+ DataSection( String name, int type, BAOS contents, int flags ) { this(name, type, flags, 0, contents ); }
+ private DataSection( String name, int type, int flags, int entsize, BAOS contents ) {
+ super(name, type, entsize, flags);
+ _contents = contents;
+ }
+
+ @Override void write(ByteBuffer out) { out.put(_contents.toByteArray()); }
+ @Override int size() { return _contents.size(); }
+ public void write2( int op ) {
+ _contents.write(op );
+ _contents.write(op>> 8);
+ }
+ public void write4( int op ) {
+ _contents.write(op );
+ _contents.write(op>> 8);
+ _contents.write(op>>16);
+ _contents.write(op>>24);
+ }
+ public void write8( long i64 ) {
+ write4( (int) i64 );
+ write4( (int)(i64>>32));
+ }
+
+ }
+
+ // ------------------------------------------------------------------------
+ // Section to collect and de-dup strings
+ public class StringSection extends DataSection {
+ final HashMap<String,Integer> _dedup = new HashMap<>();
+
+ StringSection() {
+ super(".strtab",SHT_STRTAB, 0, 0 );
+ int emptyoff = writeCString(""); // Always empty string at offset 0
+ assert emptyoff == 0;
+ int namepos = writeCString(".strtab");
+ assert namepos == _name_pos;
+ }
+
+ public int writeCString(String name) {
+ if( name==null ) return 0; // Anonymous functions have no name
+ Integer ii = _dedup.get(name);
+ if( ii != null ) return ii;
+ // place name in the string table
+ int pos = _contents.size();
+ _dedup.put(name,pos);
+ byte[] name_bytes = name.getBytes();
+ _contents.write(name_bytes);
+ // the strings are null terminated
+ _contents.write(0);
+ return pos;
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // The relocations; 3 words per relocation
+ public class ReloSection extends DataSection {
+ ReloSection( int info ) {
+ this(".rela.text",info);
+ }
+ ReloSection(String name,int info) {
+ super(name, SHT_RELA, SHF_INFO_LINK, 24/*entsize*/);
+ _link = 2;
+ _info = info;
+ }
+
+ void writeReloOffPlus(int offset, int symidx, byte relo) {
+ writeRelo(offset,symidx,relo,-4);
+ }
+ void writeRelo(int offset,int symidx,byte relo,long addend) {
+ assert symidx > 0;
+ write8(offset);
+ write8(((long)symidx << 32L) | relo);
+ write8(addend);
+ }
+ }
+
+
+ // ------------------------------------------------------------------------
+ // Symbols section; emit the symbols into the data section directly
+ public class SymbolSection extends DataSection {
+ public static final int SYMBOL_SIZE = 4+1+1+2+8+8;
+ final BAOS _globals;
+ final int _nlocals;
+
+ SymbolSection(int nlocals) {
+ super(".symtab", SHT_SYMTAB, 0/*flags*/, SYMBOL_SIZE);
+ _globals = new BAOS();
+ _nlocals = nlocals;
+ }
+
+ // Global symbol index
+ int gidx() { return _nlocals+(_globals.size()/SYMBOL_SIZE); }
+
+ public int symbol( String name, int shndx, int bind, int type, long value, long size ) {
+ int gidx = gidx(); // Global index before I write to _globals
+ BAOS baos = bind==SYM_BIND_LOCAL ? _contents : _globals;
+ write4(baos,_strtab.writeCString(name));
+ int info = (bind << 4) | (type & 0xF);
+ baos.write(info);
+ baos.write(0); // other/visibility
+ write2(baos,shndx); // section index
+ write8(baos,value);
+ write8(baos,size);
+ return bind==SYM_BIND_LOCAL ? -99 : gidx;
+ }
+
+ public int symbol( String name, int shndx, int bind, int type ) {
+ return symbol(name,shndx,bind,type,0,0);
+ }
+
+ // creates function and stores where it starts.
+ // Filters to just this class.obj file.
+ void encodeFunctions(StopNode stop, Encoding enc, int text_idx, boolean emitEntrySymbol) {
+ for( Node stopcu : stop._inputs ) {
+ for( Node ret : stopcu._inputs ) {
+ FunNode fun = ((ReturnNode)ret).fun();
+ if( !_code.emits(fun) )
+ continue;
+ int end = enc.opStart(fun.ret()) + enc.opLen(fun.ret());
+ long value = enc.opStart(fun);
+ long size = end - value;
+ // Stable FIDX names are exported separately below.
+ if( fun._name != null && !fun._name.equals(_code._fidxs.symbol(fun.sig().fidx())) ) {
+ // The C runtime owns `main` and adapts argc/argv into
+ // a normal Simple array. Keep the Simple function on
+ // its native ABI under a private entry symbol.
+ String name = emitEntrySymbol && "main".equals(fun._name)
+ ? "simple_main"
+ : fun._name;
+ symbol(name, text_idx, SYM_BIND_GLOBAL, SYM_TYPE_FUNC, value, size);
+ }
+ }
+ }
+ }
+
+ // Insert a default-main for top-level compilations, if the top-level
+ // does not declare a main.
+ void symbolEntryClinit(int text_idx, Encoding enc) {
+ FunNode clinit = null;
+ String entryName = _code.entryClinitName();
+ for( FunNode fun : _code._linker )
+ if( fun != null && _code.emits(fun) ) {
+ if( "main".equals(fun._name) )
+ return; // encodeFunctions exported this as simple_main
+ if( entryName.equals(fun._name) )
+ clinit = fun; // Keep top-level entry, if we don't find an explicit main
+ }
+ // No top-level main, so the top-level clinit becomes a default main
+ if( clinit != null ) {
+ assert enc.opStart(clinit) == 0;
+ symbol("simple_main",text_idx, SYM_BIND_GLOBAL, SYM_TYPE_FUNC, enc.opStart(clinit), 0);
+ return;
+ }
+ throw new IllegalStateException("Missing entry <clinit> "+entryName);
+ }
+
+ static void write2( BAOS bits, int op ) {
+ bits.write(op );
+ bits.write(op>> 8);
+ }
+ static void write4( BAOS bits, int op ) {
+ bits.write(op );
+ bits.write(op>> 8);
+ bits.write(op>>16);
+ bits.write(op>>24);
+ }
+ static void write8( BAOS bits, long i64 ) {
+ write4(bits, (int) i64 );
+ write4(bits, (int)(i64>>32));
+ }
+
+ @Override
+ void writeHeader(ByteBuffer out) {
+ // points to string table section
+ _link = _strtab._index;
+ _info = _nlocals;
+ super.writeHeader(out);
+ }
+
+ @Override void write(ByteBuffer out) {
+ out.put(_contents.toByteArray());
+ out.put(_globals .toByteArray());
+ }
+ @Override int size() {
+ return _contents.size() + _globals.size();
+ }
+ }
+
+ public static final int SYM_BIND_LOCAL = 0;
+ public static final int SYM_BIND_GLOBAL = 1;
+ public static final int SYM_BIND_WEAK = 2;
+
+ public static final int SYM_TYPE_NOTYPE = 0;
+ public static final int SYM_TYPE_OBJECT = 1;
+ public static final int SYM_TYPE_FUNC = 2;
+ public static final int SYM_TYPE_SECTION = 3;
+
+ public static final int SECTION_HDR_SIZE = 64;
+
+ // ------------------------------------------------------------------------
+ public class SimpleSection extends DataSection {
+ SimpleSection(BAOS serial) { super(".simple", SHT_SIMPLE, serial, 0); }
+ }
+
+
+ // ------------------------------------------------------------------------
+
+ // Native code can live above 4G, outside our four-byte function pointers.
+ // Give address-taken natives a low entry which tail-jumps through the PLT.
+ // A weak, stable name makes references from separate objects agree on one
+ // entry. Do not patch references straight to the local copy: that would
+ // bypass the linker's choice and break pointer equality across objects.
+ private int nativeEntry(String name, HashMap<String,Integer> entries,
+ DataSection text, SymbolSection symbols, ReloSection relos) {
+ Integer old = entries.get(name);
+ if( old!=null ) return old;
+ int offset = text.size();
+ text._contents.write(0xE9); // jmp rel32; preserve arguments and return address
+ text.write4(0);
+ int target = symbols.symbol(name,0,SYM_BIND_GLOBAL,SYM_TYPE_FUNC);
+ relos.writeReloOffPlus(offset+1,target,(byte)4/*PLT32*/);
+ int entry = symbols.symbol("__simple_native$"+name,text._index,
+ SYM_BIND_WEAK,SYM_TYPE_FUNC,offset,5);
+ entries.put(name,entry);
+ return entry;
+ }
+
+ public ElfWriter export(boolean emitEntrySymbol) {
+ if( _code._serial==null )
+ throw new IllegalStateException("ELF export needs ideal IR: request Export from the driver, or Serialize before Lower");
+ // Sections are created in the order they are emitted.
+ _sections = new Ary<>(Section.class);
+
+ // Null section
+ Section nullSection = new DataSection("",Section.SHT_NULL,0,0);
+
+ // String section next; written out early and available to record/dedup
+ // strings for all remaining sections
+ _strtab = new StringSection();
+
+ // place all the symbols
+ int nlocals = 1/*null*/+9/*number of sections*/+1/*Simple section*/;
+ SymbolSection symbols = new SymbolSection(nlocals);
+
+ // we've already constructed these entire sections in the encoding phase
+ Encoding enc = _code._encoding;
+ // Append ELF-only thunks to a copy. Export must not change instruction
+ // offsets or the image used by the in-memory linker and emulators.
+ BAOS textBits = new BAOS(enc._bits.toByteArray(),enc._bits.size());
+ DataSection text = new DataSection(".text" , Section.SHT_PROGBITS, textBits, DataSection.SHF_ALLOC | DataSection.SHF_WRITE | DataSection.SHF_EXECINSTR );
+ // Constant pool
+ DataSection rodata = new DataSection(".rodata", Section.SHT_PROGBITS, enc._cpool, DataSection.SHF_ALLOC );
+ // Static/global pool
+ DataSection rwdata = new DataSection(".data" , Section.SHT_PROGBITS, enc._sdata, DataSection.SHF_ALLOC | DataSection.SHF_WRITE );
+
+
+ // populate function symbols
+ symbols.encodeFunctions(_code._stop, _code._encoding, text._index, emitEntrySymbol);
+ // Export the source compilation unit <clinit> as the C runtime "main".
+ // Encoding keeps this function at text offset zero.
+ if( emitEntrySymbol )
+ symbols.symbolEntryClinit(text._index,enc);
+
+ // create external .text relocations
+ ReloSection relocations = new ReloSection(text._index);
+ HashMap<String,Integer> nativeEntries = new HashMap<>();
+
+ for( Node n : enc._externals.keySet()) {
+ String extern = enc._externals.get(n);
+ boolean nativeAddress = n instanceof FunPtrNode &&
+ _code.externFunc(((TypeFunPtr)n._type).fidx())!=null;
+ int symidx = nativeAddress
+ ? nativeEntry(extern,nativeEntries,text,symbols,relocations)
+ : symbols.symbol(extern,0,SYM_BIND_GLOBAL,SYM_TYPE_FUNC);
+ int offset = enc.opStart(n) + enc.opLen(n) - 4;
+ // Address materialization and static fields name the same thunk;
+ // direct calls still go straight to the native symbol's PLT entry.
+ relocations.writeReloOffPlus(offset,symidx,(byte)(nativeAddress ? 2/*PC32*/ : 4/*PLT32*/));
+ }
+
+ // Native ELF output is AMD64. Data addresses use PC32, not PLT calls.
+ for( Node n : enc._externData.keySet() ) {
+ String extern = enc._externData.get(n);
+ int symidx = symbols.symbol(extern, 0, SYM_BIND_GLOBAL, SYM_TYPE_OBJECT);
+ int offset = enc.opStart(n) + enc.opLen(n) - 4;
+ relocations.writeReloOffPlus(offset, symidx, (byte)2/*PC32*/);
+ }
+
+ // Stable function identities are also addressable from data, including
+ // anonymous functions which have no source-level exported name.
+ HashMap<String,Integer> dataSymbols=new HashMap<>();
+ for( FunNode fun : _code._linker )
+ if( fun!=null && !fun.isDead() && _code.emits(fun) && enc.opStart(fun)>=0 ) {
+ String name=_code._fidxs.symbol(fun.sig().fidx());
+ dataSymbols.put(name,symbols.symbol(name,text._index,SYM_BIND_GLOBAL,SYM_TYPE_FUNC,enc.opStart(fun),0));
+ }
+ for( StaticData.ObjectData obj : enc._data.objects.values() ) {
+ int section=obj.external ? 0 : obj.readOnly ? rodata._index : rwdata._index;
+ dataSymbols.put(obj.symbol,symbols.symbol(obj.symbol,section,SYM_BIND_GLOBAL,SYM_TYPE_OBJECT,
+ obj.external ? 0 : obj.offset,obj.external ? 0 : obj.size));
+ }
+ for( Encoding.Relo relo : enc._bigCons.values() ) {
+ StaticData.ObjectData obj=enc._data.object(relo._t);
+ relocations.writeReloOffPlus(relo._opStart+relo._off,dataSymbols.get(obj.symbol),relo._elf);
+ }
+ ReloSection roRelos=new ReloSection(".rela.rodata",rodata._index);
+ ReloSection rwRelos=new ReloSection(".rela.data",rwdata._index);
+ for( StaticData.Relocation relo : enc._data.relocations ) {
+ String name=enc._data.targetSymbol(relo.value());
+ String nativeName=relo.value() instanceof TypeFunPtr fp ? _code.externFunc(fp.fidx()) : null;
+ Integer sym=nativeName==null ? dataSymbols.get(name)
+ : nativeEntry(nativeName,nativeEntries,text,symbols,relocations);
+ if( sym==null ) {
+ sym=symbols.symbol(name,0,SYM_BIND_GLOBAL,SYM_TYPE_NOTYPE);
+ dataSymbols.put(name,sym);
+ }
+ // AMD64 absolute 32/64-bit addresses; linker rejects overflow.
+ byte kind=(byte)(relo.logSize()==2 ? 10 : 1);
+ (relo.object().readOnly ? roRelos : rwRelos).writeRelo(relo.object().offset+relo.offset(),sym,kind,0);
+ }
+
+ // Create a "Simple" section for Simple types and ir
+ SimpleSection simple = new SimpleSection(_code._serial);
+
+ // Write the local section header symbols
+ assert nlocals == 1+_sections._len;
+ symbols.symbol("",0,0,0); // Null symbol at index 0
+ for( Section s : _sections )
+ symbols.symbol(s._name, s._index, SYM_BIND_LOCAL, SYM_TYPE_SECTION, 0, s.size());
+
+ int size = 64; // ELF header size
+ // size of all section data
+ for( Section s : _sections ) {
+ s._file_offset = size;
+ size += s.size();
+ }
+ // headers go at the very end
+ int shdr_offset = size;
+ size += SECTION_HDR_SIZE*_sections.len();
+
+ ByteBuffer out = ByteBuffer.allocate(size);
+ out.order(ByteOrder.LITTLE_ENDIAN);
+
+ // ELF header
+ out.put(new byte[]{ 0x7F, 'E', 'L', 'F', 2, 1, 1, 0 });
+ out.put(new byte[8]);
+ out.putShort((short)1 /* ET_REL */); // type
+ out.putShort((short)62 /* AMD64 */); // machine
+ out.putInt(1); // version
+ out.putLong(0); // entry
+ out.putLong(0); // phoff
+ out.putLong(shdr_offset); // shoff
+ out.putInt(0); // flags
+ out.putShort((short)64); // ehsize
+ out.putShort((short)0); // phentsize
+ out.putShort((short)0); // phnum
+ out.putShort((short)64); // shentsize
+ out.putShort((short) _sections.len()); // shnum
+ out.putShort((short) _strtab._index); // shstrndx
+
+ // raw section data
+ for( Section s : _sections )
+ s.write(out);
+
+ // Section headers
+ for( Section s : _sections )
+ s.writeHeader(out);
+
+ assert out.position() == size;
+
+ String objName = _code._buildDir + "/" + (_code._emitUnit==null ? _code._srcName : _code._emitUnit._fname) + ".o";
+ File file = new File(objName);
+ if( file.getParentFile()!=null )
+ file.getParentFile().mkdirs();
+ try {
+ BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
+ bos.write(out.array());
+ bos.close();
+ } catch( IOException ioe ) {
+ // Caller does not want to deal, but it is a failed compilation in any case
+ throw new RuntimeException(ioe);
+ }
+ // Reset for next ELF
+ _sections = null;
+ _strtab = null;
+ return this;
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/Encoding.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Encoding.java
new file mode 100644
index 00000000..8b868218
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Encoding.java
@@ -0,0 +1,570 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.print.IRPrinter;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.*;
+import java.util.*;
+
+
+/**
+ * Instruction encodings
+ * <p>
+ * This class holds the encoding bits, plus the relocation information needed
+ * to move the code to a non-zero offset, or write to an external file (ELF).
+ * <p>
+ * There are also a bunch of generic utilities for managing bits and bytes
+ * common in all encodings.
+ */
+public class Encoding implements com.seaofnodes.isa.CodeSink {
+
+ // Top-level program graph structure
+ public final CodeGen _code;
+
+ // CFG Nodes in RPO order, per-encoding.
+ public Ary<CFGNode> _cfg;
+
+ // Instruction bytes. The registers are encoded already. Relocatable data
+ // is in a fixed format depending on the kind of relocation.
+
+ // - RIP-relative offsets into the same chunk are just encoded with their
+ // chunk relative offsets; no relocation info is required.
+
+ // - Fixed address targets in the same encoding will be correct for a zero
+ // base; moving the entire encoding requires adding the new base address.
+
+ // - RIP-relative to external chunks have a zero offset; the matching
+ // relocation info will be used to patch the correct value.
+
+ public final BAOS _bits = new BAOS(); // Instructions / encodings
+ public final BAOS _cpool = new BAOS(); // Constant r/o pool
+ public final BAOS _sdata = new BAOS(); // Static r/w pool
+ public final HashMap<Node,Relo> _bigCons = new HashMap<>();
+ public final StaticData _data;
+ public final HashMap<String,Integer> _externalAddresses = new HashMap<>();
+
+ // Big Constant relocation info.
+ public static class Relo {
+ public final Node _op;
+ public final Type _t; // Constant type
+ public final int _align; // Log alignment, captured for diagnostic display
+ public final int _structSize; // Capture layout during encoding, not printing
+ public final byte _off; // Offset from start of opcode
+ public final byte _elf; // ELF relocation type, e.g. 2/PC32
+ public int _target; // Where constant is finally placed
+ public int _opStart; // Opcode start
+ Relo( Node op, Type t, byte off, byte elf ) {
+ _op=op; _t=t; _off=off; _elf=elf;
+ _align = t.alignment();
+ _structSize = t instanceof TypeStruct ts ? ts.size() : 0;
+ _readOnly = readOnly(t);
+ }
+ public boolean readOnly() { return _readOnly; }
+ private final boolean _readOnly;
+ private static boolean readOnly(Type t) {
+ // Class initializers still write their fields, even when every
+ // final value is constant. Their storage must remain writable.
+ return !(t instanceof TypeStruct ts) ||
+ (!Parser.startsClzPrefix(ts._name) && ts.isConstant());
+ }
+ }
+
+ Encoding( CodeGen code ) {
+ _code = code;
+ _data = new StaticData(this);
+ }
+
+ // Shortcut to the defining register
+ public short reg(Node n) {
+ return _code._regAlloc.regnum(n);
+ }
+
+ public int read1(int idx) {
+ byte[] buf = _bits.buf();
+ return (buf[idx]&0xFF);
+ }
+ public int read2(int idx) {
+ byte[] buf = _bits.buf();
+ return (buf[idx]&0xFF) | (buf[idx+1]&0xFF) <<8;
+ }
+ public int read4(int idx) {
+ byte[] buf = _bits.buf();
+ return (buf[idx]&0xFF) | (buf[idx+1]&0xFF) <<8 | (buf[idx+2]&0xFF)<<16 | (buf[idx+3]&0xFF)<<24;
+ }
+ public long read8(int idx) {
+ return (read4(idx) & 0xFFFFFFFFL) | read4(idx+4);
+ }
+
+ // This buffer is invalid/moving until after all encodings are written
+ public byte[] bits() { return _bits.buf(); }
+
+ // 4 byte little-endian write
+ public void patch4( int idx, int val ) {
+ byte[] buf = _bits.buf();
+ buf[idx ] = (byte)(val );
+ buf[idx+1] = (byte)(val>> 8);
+ buf[idx+2] = (byte)(val>>16);
+ buf[idx+3] = (byte)(val>>24);
+ }
+
+ static void padN(int n, BAOS bits) {
+ while( (bits.size()+n-1 & -n) > bits.size() )
+ bits.write(0);
+ }
+
+ // Convenience for writing log-N
+ static void addN( int log, long x, BAOS bits ) {
+ for( int i=0; i < 1<<log; i++ ) {
+ bits.write((int)x);
+ x >>= 8;
+ }
+ }
+
+ public Encoding add1( int op ) { addN(0,op,_bits); return this; }
+ public Encoding add2( int op ) { addN(1,op,_bits); return this; }
+ public Encoding add4( int op ) { addN(2,op,_bits); return this; }
+ public Encoding add8(long op ) { addN(3,op,_bits); return this; }
+
+ private final static int SENTINEL = -1;
+ private final IntHashMap _opStart = new IntHashMap(SENTINEL);
+ private final IntHashMap _opLen = new IntHashMap(SENTINEL);
+
+
+ // Read op start for n
+ public int opStart(Node n) { return _opStart.get(n._nid); }
+ // Read op length for n
+ public byte opLen (Node n) { return (byte)_opLen.get(n._nid); }
+
+ // Set op start for n
+ public void opStart(Node n, int off) {
+ assert off != SENTINEL;
+ _opStart.put(n._nid,off);
+ }
+
+ // Set op start for n
+ public void opStartAdd(Node n, int off) {
+ if( off==0 ) return;
+ int old = _opStart.get(n._nid);
+ assert old != SENTINEL && old+off != SENTINEL;
+ _opStart.put(n._nid,old+off);
+ }
+
+ // Set op length for n
+ public void opLen(Node n, byte len) {
+ assert len != SENTINEL;
+ _opLen.put(n._nid,len);
+ }
+
+ // Nodes need "relocation" patching; things done after code is placed.
+ // Record src and dst Nodes.
+ private final HashMap<Node,CFGNode> _internals = new HashMap<>();
+ // Source is a Call, destination in the Fun.
+ public Encoding relo( CallNode call ) {
+ FunNode fun = _code.link(call.tfp());
+ if( fun != null && !_code.emits(fun) )
+ _externals.put(call,fun._name);
+ else
+ _internals.put(call,fun);
+ return this;
+ }
+ // Code address as a constant
+ public Encoding relo( FunPtrNode fptr ) {
+ TypeFunPtr tfp = (TypeFunPtr)fptr._type;
+ // Native functions have no local FunNode, but are still callable.
+ // Do not treat their addresses as identities of deleted functions.
+ String nativeName = _code.externFunc(tfp.fidx());
+ if( nativeName!=null ) return external(fptr,nativeName);
+ FunNode fun = _code.link(tfp);
+ if( fun != null && !_code.emits(fun) )
+ _externals.put(fptr,fun._name);
+ else
+ _internals.put(fptr,fun);
+ return this;
+ }
+ // Local jump
+ public void jump( CFGNode jmp, CFGNode dst ) {
+ _internals.put(jmp,dst.uctrlSkipEmpty());
+ }
+
+ // External references only located by Strings a link-time
+ public final HashMap<Node,String> _externals = new HashMap<>();
+ public Encoding external( Node n, String extern ) {
+ _externals.put(n,extern);
+ return this;
+ }
+
+ public final HashMap<Node,String> _externData = new HashMap<>();
+ public void externalData(Node n, String name) { _externData.put(n,name); }
+
+ // Store t as a 32/64 bit constant in the code space; generate RIP-relative
+ // addressing to load it. Type is stored in either the .rodata or .data.
+ public void largeConstant( Node relo, Type t, int off, int elf ) {
+ // TODO: Any-old struct in the cpool
+ assert t.isConstant() || (t instanceof TypeStruct ts && Parser.startsClzPrefix(ts._name));
+ assert (byte)off == off;
+ assert (byte)elf == elf;
+ _bigCons.put(relo,new Relo(relo,t,(byte)off,(byte)elf));
+ }
+
+ // --------------------------------------------------
+ Encoding encode( ) {
+ // Basic block layout: negate branches to keep blocks in-order; insert
+ // unconditional jumps. Attempt to keep backwards branches taken,
+ // forwards not-taken (this is the default prediction on most
+ // hardware). Layout is still Reverse Post Order but with more
+ // restrictions.
+ basicBlockLayout( );
+
+ // Write encoding bits in order into a big byte array.
+ // Record opcode start and length.
+ writeEncodings();
+
+ // Short-form RIP-relative support: replace long encodings with short
+ // encodings and compact the code, changing all the offsets.
+ compactShortForm();
+
+ // Place pool objects and record relocations within their contents.
+ _data.write();
+
+ // Patch RIP-relative and local encodings now.
+ patchLocalRelocations();
+
+ return this;
+ }
+
+ // --------------------------------------------------
+ // Basic block layout: negate branches to keep blocks in-order; insert
+ // unconditional jumps. Attempt to keep backwards branches taken, forwards
+ // not-taken (this is the default prediction on most hardware). Layout is
+ // still Reverse Post Order but with more restrictions.
+ private void basicBlockLayout( ) {
+ IdentityHashMap<LoopNode,Ary<CFGNode>> rpos = new IdentityHashMap<>();
+ _cfg = new Ary<>(CFGNode.class);
+ rpos.put(_code._start.loop(),_cfg);
+ BitSet visit = _code.visit();
+
+ // Do them all except the <clinit>s
+ Ary<FunNode> clinits = new Ary<>(FunNode.class);
+ FunNode entry = null;
+ String entryName = _code.entryClinitName();
+ for( FunNode fun : _code._linker ) {
+ if( fun != null && !fun.isDead() && _code.emits(fun) ) {
+ if( fun.isClz() ) {
+ if( fun._name.equals(entryName) )
+ entry = fun;
+ else
+ clinits.add(fun);
+ } else {
+ int x = _cfg._len;
+ _rpo_cfg(fun,visit,rpos);
+ assert _cfg.at(x) instanceof ReturnNode;
+ }
+ }
+ }
+ // Now the <clinit>s last, so when reversed they become first. The
+ // program entry <clinit> is last of all, so it lands at offset zero.
+ for( FunNode clinit : clinits ) {
+ int x = _cfg._len;
+ _rpo_cfg(clinit,visit,rpos);
+ assert _cfg.at(x) instanceof ReturnNode;
+ }
+ if( entry != null ) {
+ int x = _cfg._len;
+ _rpo_cfg(entry,visit,rpos);
+ assert _cfg.at(x) instanceof ReturnNode;
+ }
+
+
+ // Reverse in-place
+ for( int i=0; i< _cfg.size()>>1; i++ )
+ _cfg.swap(i,_cfg.size()-1-i);
+ visit.clear();
+ }
+
+
+ private void _rpo_cfg(CFGNode bb, BitSet visit, IdentityHashMap<LoopNode,Ary<CFGNode>> rpos ) {
+ if( bb==null || visit.get(bb._nid) ) return;
+ visit.set(bb._nid);
+ CFGNode next = bb.uctrl();
+
+ // Loops run an inner "rpo_cfg", then append the entire loop body in
+ // place. This keeps loop bodies completely contained, although if
+ // Some Day Later we have real profile data we ought to arrange the
+ // branch orderings based on frequency.
+ if( bb instanceof LoopNode loop ) {
+ Ary<CFGNode> body = new Ary<>(CFGNode.class); // Private RPO for the loop
+ rpos.put(loop.loop(),body); // Find it via loop tree
+ _rpo_cfg(next,visit,rpos); // RPO the loop
+ body.add(loop); // Include loop last (first in RPO)
+ Ary<CFGNode> outer = rpos.get(loop.cfg(1).loop());
+ outer.addAll(body); // Append to original CFG
+ return;
+ }
+
+ // IfNodes visit 2 sides, and may choose to reorder them in the RPO
+ if( bb instanceof IfNode iff ) {
+ // Pick out the T/F projections
+ CProjNode t = iff.cproj(0);
+ CProjNode f = iff.cproj(1);
+ // Invert the branch or not
+ if( shouldInvert(t,f,iff.loopDepth()) ) {
+ iff.negate();
+ t.invert();
+ f.invert();
+ CProjNode tmp=f; f=t; t=tmp; // Swap t/f
+ }
+
+ // Whichever side is visited first becomes last in the RPO. With
+ // no loops, visit the False side last (so True side first) so that
+ // when the False RPO visit returns, the IF is immediately next.
+ // When the RPO is reversed, the fall-through path will always be
+ // following the IF.
+ _rpo_cfg(t,visit,rpos); // True side first
+ next = f; // False side last
+ }
+
+ // If the *next* BB has already been visited, and we are not already a
+ // jump, we may need an unconditional forwards jump here
+ Ary<CFGNode> rpo = rpos.get(bb.loop());
+ if( next!=null && visit.get(next._nid) && !(bb instanceof IfNode) ) {
+ boolean needJump = next instanceof LoopNode
+ // Empty blocks from an IF will invert the IF and backwards
+ // branch to the loop head. If not empty, or not an IF
+ // will need a jump.
+ ? (bb.nOuts()>1 || !(bb.cfg0() instanceof IfNode) )
+ // Forwards jump. If all the blocks, in RPO order, to our
+ // target are empty, we will fall in and not need a jump.
+ : !backwardsEmptyScan(rpo,next);
+ if( needJump ) {
+ CFGNode jmp = _code._mach.jump();
+ jmp._ltree = bb._ltree;
+ jmp.setDefX(0,bb);
+ next.setDef(next._inputs.find(bb),jmp);
+ rpo.add(jmp);
+ }
+ }
+
+ _rpo_cfg(next,visit,rpos);
+ rpo.add(bb);
+ }
+
+ // Should this test be inverted?
+ private static boolean shouldInvert(CFGNode t, CFGNode f, int bld) {
+ int tld = t.loopDepth(), fld = f.loopDepth();
+ // These next two are symmetric and can happen in any order; if `tld <
+ // bld` is true, then `fld < bld` must be false, or else both directions
+ // exit the loop... and the IF test would not be in the loop.
+
+ // true to exit a loop usually (and false falls into Yet Another Loop
+ // Block), invert if false is taking an empty backwards branch.
+ if( tld < bld ) return forwardsEmptyScan(f,bld);
+ // false to exit a loop, normally invert to true (except empty back)
+ if( fld < bld ) return !forwardsEmptyScan(t,bld);
+
+ // Not exiting the loop; staying at this depth (or going deeper loop).
+
+ // Jump/true to an empty backwards block
+ if( forwardsEmptyScan(t,bld) ) return false;
+ if( forwardsEmptyScan(f,bld) ) return true ;
+
+ // Fall/false into a full block, Jump/true to an empty block.
+ if( f.nOuts()>1 && t.nOuts()==1 ) return false;
+ if( t.nOuts()>1 && f.nOuts()==1 ) return true ;
+ // Jump to a merge point, assuming other things are jumping there as well
+ if( f.out(0) instanceof RegionNode ) return true ;
+ if( t.out(0) instanceof RegionNode ) return false;
+ // Everything else equal, use pre-order
+ return t._pre > f._pre;
+ }
+
+ // Do we continue forwards from 'c' to the Loop back edge, without doing
+ // anything else? Then this branch can directly jump to the loop head.
+ private static boolean forwardsEmptyScan( CFGNode c, int bld ) {
+ if( c.nOuts()!=1 || c.loopDepth()!=bld ) return false;
+ return c.uctrl() instanceof RegionNode cfg &&
+ ((cfg instanceof LoopNode && cfg._ltree==c._ltree) || forwardsEmptyScan(cfg,bld));
+ }
+
+ // Is the CFG from "next" to the end empty? This means jumping to "next"
+ // will naturally fall into the end.
+ private static boolean backwardsEmptyScan(Ary<CFGNode> rpo, CFGNode next) {
+ for( int i=rpo._len-1; rpo.at(i)!=next; i-- )
+ if( rpo.at(i).nOuts()!=1 )
+ return false;
+ return true;
+ }
+
+ // --------------------------------------------------
+ // Write encoding bits in order into a big byte array.
+ // Record opcode start and length.
+
+ // Current function is used by the spill-op encodings to query the stack
+ // frame layout and get the spill offsets.
+ private void writeEncodings() {
+ for( CFGNode bb : _cfg ) {
+ if( !(bb instanceof MachNode mach0) )
+ opStart(bb, _bits.size());
+ else if( bb instanceof FunNode ) {
+ opStart(bb, _bits.size());
+ mach0.encoding( this );
+ opLen(bb, (byte) (_bits.size() - opStart(bb)));
+ }
+ for( Node n : bb._outputs ) {
+ if( n instanceof MachNode mach && !(n instanceof FunNode) &&
+ // FunPtrs are encoding in the block of their control, not
+ // the block of the ReturnNode input - which is also a CFG.
+ !(n instanceof FunPtrNode && bb instanceof ReturnNode) ) {
+ opStart(n, _bits.size());
+ mach.encoding( this );
+ opLen(n, (byte) (_bits.size() - opStart(n)));
+ }
+ }
+ }
+ }
+
+ // --------------------------------------------------
+ // Short-form RIP-relative support: replace short encodings with long
+ // encodings and expand the code, changing all the offsets.
+ private void compactShortForm() {
+ int len = _cfg._len;
+ int[] oldStarts = new int[len];
+ for( int i=0; i<len; i++ )
+ oldStarts[i] = opStart(_cfg.at(i));
+
+ // TODO: Rewrite this algo to use the small "_jmps" list of just the
+ // jumps instead of walking all blocks.
+ // TODO2: Allow short encodings on non-jumps. This algo doesn't
+ // really care jumps vs other rip-relative things.
+
+ // Check the size of short jumps, adjust the block starts for a whole
+ // pass over all blocks. If some jumps go long, they might stretch the
+ // distance for other jumps, so another pass is needed.
+ int slide = -1;
+ while( slide != 0) { // While no fails
+ slide = 0;
+ for( int i=0; i<len; i++ ) {
+ CFGNode bb = _cfg.at(i);
+ opStartAdd(bb, slide);
+ // Slide down all other (non-CFG) ops in the block
+ for( Node n : bb._outputs )
+ if( n instanceof MachNode && !(n instanceof CFGNode) &&
+ // Match writeEncodings(): FunPtrs are encoded in the
+ // block of their control, not again at the Return.
+ !(n instanceof FunPtrNode && bb instanceof ReturnNode) )
+ opStartAdd(n, slide);
+ if( bb instanceof RIPRelSize riprel ) {
+ CFGNode target = (bb instanceof IfNode iff ? iff.cproj(0) : (CFGNode)bb.out(0)).uctrlSkipEmpty();
+ // Delta is from opStart to opStart. X86 at least counts
+ // the delta from the opEnd, but we don't have the end until
+ // we decide the size - so the encSize has to deal
+ int delta = opStart(target) - opStart(bb);
+ byte opLen = riprel.encSize(delta);
+ // Recorded size is smaller than the current size?
+ if( opLen(bb) < opLen ) {
+ // Start sliding the code down; record slide amount and new size
+ slide += opLen - opLen(bb);
+ opLen(bb, opLen);
+ }
+ }
+ }
+ }
+
+
+ // Go again, inserting padding on function headers. Since no
+ // short-jumps span function headers, the padding will not make any
+ // short jumps fail.
+ for( int i=0; i<len; i++ ) {
+ CFGNode bb = _cfg.at(i);
+ // Functions pad to align 16
+ if( bb instanceof FunNode ) {
+ int newStart = opStart(bb)+slide;
+ slide += (newStart+15 & -16)-newStart;
+ }
+ opStartAdd(bb, slide);
+ for( Node n : bb._outputs )
+ if( n instanceof MachNode && !(n instanceof CFGNode) &&
+ !(n instanceof FunPtrNode && bb instanceof ReturnNode) )
+ opStartAdd(n, slide);
+ }
+
+ // Copy/slide the bits to make space for all the longer branches
+ int grow = opStart(_cfg.at(len-1)) - oldStarts[len-1];
+ if( grow > 0 ) { // If no short-form ops, nothing to do here
+ int end = _bits.size();
+ byte[] bits = new byte[end+grow];
+ for( int i=len-1; i>=0; i-- ) {
+ int start = oldStarts[i];
+ if( start==0 && i>1 ) continue;
+ int oldStart = oldStarts[i];
+ int newStart = opStart(_cfg.at(i));
+ System.arraycopy(_bits.buf(),oldStart,bits,newStart,end-start);
+ end = start;
+ }
+ _bits.set(bits,bits.length);
+ }
+
+ }
+
+
+ // --------------------------------------------------
+ // Patch local encodings now
+ void patchLocalRelocations() {
+ // Walk the local code-address relocations
+ for( Node src : _internals.keySet() ) {
+ int start = opStart(src);
+ Node dst = _internals.get(src);
+ // If function is entirely dead, only the function pointer remains
+ // and, it can only be used to test against zero or equals to
+ // another function pointer... i.e., there Is No Code Here.
+ int target = dst == null ? start : opStart(dst);
+ ((RIPRelSize)src).patch(this, start, opLen(src), target - start);
+ }
+ }
+
+ // Emit a single scalar as bits
+ static void addN( int log, Type t, BAOS bits ) {
+ long x = switch( t ) {
+ case TypeInteger ti -> ti.value();
+ case TypeFloat tf -> log==3
+ ? Double.doubleToRawLongBits( tf.value())
+ : Float.floatToRawIntBits((float)tf.value());
+ case TypeFunPtr tfp -> 0; // These need to be relocated
+ case TypeMemPtr tmp -> 0; // These need to be relocated
+ default -> { if( t==Type.NIL ) yield 0; else throw Utils.TODO(); }
+ };
+ addN(log,x,bits);
+ }
+
+ // A series of libc/external calls that Simple can link against in a JIT.
+ // Since no runtime in the JVM process, using magic numbers for the CPU
+ // emulators to pick up on.
+ public static int SENTINEL_CALLOC = -4;
+ public static int SENTINEL_WRITE = -8;
+
+ int externalAddress(String name) {
+ Integer address=_externalAddresses.get(name);
+ if( address!=null ) return address;
+ return switch(name) {
+ case "calloc" -> SENTINEL_CALLOC;
+ case "write" -> SENTINEL_WRITE;
+ default -> throw new IllegalArgumentException("Unresolved external function '"+name+"'");
+ };
+ }
+
+ void patchGlobalRelocations() {
+ for( Node src : _externData.keySet() ) {
+ String name = _externData.get(src);
+ Integer target = _code._externDataAddresses.get(name);
+ if( target==null ) throw new IllegalArgumentException("Unresolved C data symbol '"+name+"'");
+ int start = opStart(src);
+ ((RIPRelSize)src).patch(this,start,opLen(src),target-start);
+ }
+ for( Node src : _externals.keySet() ) {
+ int start = opStart(src);
+ String dst = _externals.get(src);
+ int target = externalAddress(dst);
+ ((RIPRelSize)src).patch(this, start, opLen(src), target - start);
+ }
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java
new file mode 100644
index 00000000..69e0104d
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java
@@ -0,0 +1,44 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+import java.util.Arrays;
+
+// Comnpute field layouts only after SCCP and its peepholes converge.
+// Keep surviving functions rooted without introducing unknown callers.
+abstract class FinalizeIR {
+ static void run(CodeGen code) {
+ // Compute field layouts and replace symbolic field offsets by constants.
+ for( int i=0; i<code._start.nOuts(); i++ ) {
+ Node use = code._start.out(i);
+ if( use instanceof ConFldOffNode off ) {
+ Node con = off.asOffset();
+ assert con != null : off._ts._name+"."+off._fname;
+ off.subsume(con);
+ i--;
+ }
+ }
+ code._iter.iterate(code); // Propagate field offsets and clean up.
+
+ // Retain surviving functions for emission without adding unknown callers.
+ for( FunNode fun : code._linker )
+ if( fun != null ) {
+ // A folded static function field can leave a detached body
+ // with internal cycles. It has no callers; later phases must
+ // not revive it merely because its nodes are still allocated.
+ if( fun.isDead() || fun._type.isHigh() ) {
+ code._linker.set(fun.sig().fidx(),null);
+ continue;
+ }
+ code.keepInCompUnit(fun);
+ }
+ // Hand off the wrapper lifetime before releasing its memory projection.
+ for( CompUnit cu : code._compunits.values() )
+ code.keepUntilUnlink(cu._start);
+ code.releaseFinalizedNodes();
+ // Stable print order: initializers first, then other Returns by NID.
+ for( Node stop : code._stop._inputs )
+ Arrays.sort(stop._inputs._es,0,stop.nIns(),
+ (x,y) -> ((((ReturnNode)x).fun().isClz() ? -1 : x._nid) -
+ (((ReturnNode)y).fun().isClz() ? -1 : y._nid) ));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/FunctionSummary.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/FunctionSummary.java
new file mode 100644
index 00000000..6cd4035d
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/FunctionSummary.java
@@ -0,0 +1,28 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+
+// The small, eagerly readable part of a function's serialized implementation.
+record FunctionSummary(TypeFunPtr sig, TypeTuple state, String name, int size, int[] classDependencyFIDXs) {
+ void install(CodeGen code, CompUnit cu) {
+ FunNode fun = new FunNode(null,sig,name,cu,null,cu._start);
+ fun._approxUIDs=size;
+ fun.init();
+ // First user stays in output slot zero until the summary is removed.
+ SummaryNode body = new SummaryNode(state,classDependencyFIDXs,fun);
+ ParmNode rpc = new ParmNode("$rpc",0,TypeRPC.BOT,fun,code.con(TypeRPC.BOT)).init();
+ ParmNode mem = new ParmNode("$mem",1,TypeMem.BOT,fun,cu._start.proj(1)).init();
+ body.addDef(mem);
+ for( int i = 0; i<sig.nargs(); i++ )
+ body.addDef(new ParmNode("arg"+i,i+2,sig.arg(i),fun,code.con(sig.arg(i))).init());
+ body.init();
+ Node ctrl = new CProjNode(body,0,"ctrl").init();
+ Node memory = new ProjNode(body,1,"mem").init();
+ Node value = new ProjNode(body,2,"value").init();
+ ReturnNode ret = new ReturnNode(ctrl,memory,value,rpc,fun).init();
+ fun.setRet(ret);
+ cu._stop.addDef(ret);
+ code.link(fun);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java
new file mode 100644
index 00000000..bc9c9d39
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java
@@ -0,0 +1,177 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.util.*;
+import java.util.HashMap;
+import java.util.Arrays;
+import java.util.Objects;
+
+
+// This utility 2-way maps some *global* program feature to a local dense
+// index, suitable for packing in BitSets. The mapping takes a global value -
+// always a {source file/class} name and an order number counting up per
+// feature in the same file. The dense local index can be different in
+// different compilations.
+
+// The global info allows loading the same remote class info from different
+// paths and aligning them. Example: Compiling A loads pre-compiled B and C,
+// both of which load pre-compiled D. The 2 different D loads unify via this
+// global info.
+
+// These mappings are all trivial identities when compiling one file; they
+// only become complex when loading seperately compiled code.
+
+public class GlobalBits {
+ public static final int RESERVED = 2;
+ private final HashMap<String,int[]> _clzOrder2Local = new HashMap<>();
+ private final Ary<String> _local2Clz = new Ary<>(String.class);
+ private final AryInt _local2Order = new AryInt();
+ // Local dense index
+ private int _local;
+
+ GlobalBits() {
+ _local = RESERVED;
+ // Reserved null-class bits are global identities and are never
+ // remapped.
+ int[] xs = new int[RESERVED];
+ xs[0] = RESERVED; // Per-class next order counter
+ for( int i=0; i<RESERVED; i++ ) {
+ if( i > 0 ) xs[i] = i;
+ _local2Clz .setX(i,null);
+ _local2Order.setX(i,i);
+ }
+ _clzOrder2Local.put(null,xs);
+ }
+
+ // Get a next local index for this clz
+ int next( String clz ) { return next(clz,-1); }
+
+ // Given a clz and order, return an existing local index or make a new
+ // mapping and return that.
+ int next(String clz, int order ) {
+ // Get the per-clz Order->Local mapping
+ int[] xs = _clzOrder2Local.get(clz);
+ if( xs == null )
+ xs = new int[]{RESERVED,1}; // Make, if it does not exist
+ // Caller does not have an order in mind, just take the next one stored
+ // in xs[0]
+ if( order == -1 )
+ order = xs[0]++;
+ // Order exists from prior compilation, but not recorded locally
+ else if( order >= xs[0] )
+ xs[0] = order+1;
+ // Grow as needed
+ while( xs.length <= order )
+ xs = Arrays.copyOf(xs,xs.length*2);
+ // Have a local mapping already?
+ if( xs[order] != 0 )
+ return xs[order];
+ int local = _local++;
+ bind(local,clz,order);
+ return local; // The dense local index
+ }
+
+ // Bind a specific dense local index to {clz,order}. Used by both fresh
+ // allocation and deserialization, where local indexes must not compact
+ // across holes because XInt bitsets are serialized against this space.
+ private void bind( int local, String clz, int order ) {
+ if( _local <= local )
+ _local = local+1;
+ _local2Clz .setX(local,clz );
+ _local2Order.setX(local,order);
+
+ // Inline-only fidx holes are represented as {null,0}. They need the
+ // reverse mapping above, but no forward map because xs[0] is the
+ // per-class next-order counter.
+ if( clz==null && order==0 )
+ return;
+
+ int[] xs = _clzOrder2Local.get(clz);
+ if( xs == null )
+ xs = new int[]{RESERVED,1};
+ if( order >= xs[0] )
+ xs[0] = order+1;
+ while( xs.length <= order )
+ xs = Arrays.copyOf(xs,xs.length*2);
+ _clzOrder2Local.put(clz,xs);
+ xs[order] = local;
+ }
+
+ // A file-local function that will inline, and never write to disk
+ public int nextInline( ) {
+ int local = _local++;
+ _local2Clz .setX(local,null);
+ _local2Order.setX(local,0);
+ return local;
+ }
+ // Get a next local index for the same clz as this old index
+ public int next( int old ) { return next(_local2Clz.at(old)); }
+
+ // Map a file-local dense index to this compilation unit's local dense index.
+ public int map( GlobalBits fileBits, int fileLocal ) {
+ if( fileLocal >= fileBits._local2Clz.size() )
+ throw new ArrayIndexOutOfBoundsException(""+fileLocal+" >= "+fileBits._local2Clz.size());
+ return findOrNext(fileBits._local2Clz.at(fileLocal), fileBits._local2Order.at(fileLocal));
+ }
+
+ // Map a global {clz,order} to a local one; keep any existing mapping or
+ // make a new one as needed.
+ private int findOrNext(String clz, int order ) {
+ // Reserved null-class identities are not represented in xs[order],
+ // because xs[0] is the next-order counter.
+ if( clz==null && order < _local2Clz.size() && _local2Order.at(order)==order && _local2Clz.at(order)==null )
+ return order;
+ int[] xs = _clzOrder2Local.get(clz);
+ if( xs != null && order < xs.length ) {
+ int local = xs[order];
+ if( local < _local2Clz.size() && _local2Order.at(local)==order && Objects.equals(_local2Clz.at(local),clz) )
+ return local;
+ }
+ return next(clz,order);
+ }
+
+ // Stable native symbol identity, independent of this compilation's dense IDs.
+ String owner(int local) { return _local2Clz.at(local); }
+ String symbol(int local) {
+ return _local2Clz.at(local)+"$F$"+_local2Order.at(local);
+ }
+
+ // Write out to BAOS enough bits to unwind the local back to global index
+ void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ // Unwind a local index to the clz.
+ baos.packed2(_local2Clz.size());
+ for( int i=RESERVED; i<_local2Clz.size(); i++ ) {
+ String clz = _local2Clz.at(i);
+ baos.packed2(clz==null ? 0 : strs.get(clz));
+ baos.packed2( _local2Order.at(i) );
+ }
+ }
+
+ void gather( HashMap<String,Integer> strs ) {
+ for( int i=RESERVED; i<_local2Clz.size(); i++ )
+ Serialize.gather(strs,_local2Clz.at(i));
+ }
+
+ // Read in the BAIS bits needed to make a file-local -> global mapping
+ static GlobalBits packed( BAOS bais, String[] strs ) {
+ var gb = new GlobalBits();
+ int n = bais.packed2();
+ for( int i=RESERVED; i<n; i++ ) {
+ int sx = bais.packed2();
+ String clz = strs[sx];
+ if( clz.isEmpty() ) clz = null;
+ int order = bais.packed2();
+ gb.bind(i,clz,order);
+ }
+ return gb;
+ }
+
+ @Override public String toString() {
+ SB sb = new SB().p('[');
+ for( int i=0; i<_local2Clz.size(); i++ ) {
+ if( i > 0 ) sb.p(',');
+ sb.p(i).p(" <-> ").p(_local2Clz.at(i)).p('#').p(_local2Order.at(i));
+ }
+ return sb.p(']').toString();
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
new file mode 100644
index 00000000..86cb2e98
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -0,0 +1,356 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.codegen.IterPeeps.WorkList;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.AryInt;
+import com.seaofnodes.simple.util.Utils;
+import java.util.*;
+
+public abstract class GlobalCodeMotion {
+
+ // Ideal execution needs a schedule, but neither CLInit lowering nor
+ // interprocedural optimization. Each loaded object is prepared once.
+ public static Ary<CFGNode> forEval(CodeGen code, StartNode start, StopNode stop) {
+ Ary<FunNode> funs = Unlink.forEval(code,start,stop);
+ start.buildLoopTree(funs,stop);
+ return buildCFG(code,start,stop);
+ }
+
+ // Arrange that the existing isCFG() Nodes form a valid CFG. The
+ // Node.use(0) is always a block tail (either IfNode or head of the
+ // following block). There are no unreachable infinite loops.
+ public static void buildCFG( CodeGen code ) {
+ code._cfg = buildCFG(code,code._start,code._stop);
+ }
+
+ // Schedule one graph, including an object loaded while Eval2 is running.
+ // Other graphs (and their active evaluation frames) are left untouched.
+ public static Ary<CFGNode> buildCFG(CodeGen code, StartNode start, StopNode stop) {
+ Ary<CFGNode> rpo = new Ary<>(CFGNode.class);
+ _rpo_cfg(null, start, code.visit(), rpo);
+ // Reverse in-place
+ for( int i=0; i< rpo.size()>>1; i++ )
+ rpo.swap(i,rpo.size()-1-i);
+ schedEarly(code,start,rpo);
+
+ // Break up shared global constants by functions
+ breakUpGlobalConstants(start);
+
+ code._visit.clear();
+ schedLate(code,stop);
+ return rpo;
+ }
+
+ // Post-Order of CFG
+ private static void _rpo_cfg(CFGNode def, Node use, BitSet visit, Ary<CFGNode> rpo) {
+ if( !(use instanceof CFGNode cfg) || visit.get(cfg._nid) )
+ return; // Been there, done that
+ // A shared dead-control node can also reach an unloaded function's
+ // Return. Its wrapper is a dispatch target, not executable code.
+ if( cfg instanceof FunNode fun && fun.summary()!=null ||
+ cfg instanceof ReturnNode ret && ret.fun().summary()!=null ) return;
+ assert !( def instanceof ReturnNode && use instanceof CallEndNode ); // All calls unwired now
+ assert !( def instanceof CallNode && use instanceof FunNode ); // All calls unwired now
+ visit.set(cfg._nid);
+ for( Node useuse : cfg._outputs )
+ _rpo_cfg(cfg,useuse,visit,rpo);
+ rpo.add(cfg);
+ }
+
+ // After early scheduling, every global chain member has Start in slot 0.
+ // Give each function one private copy of the entire dependency graph.
+ private static void breakUpGlobalConstants( StartNode start ) {
+ var globals = new IdentityHashMap<Node,Boolean>();
+ var cons = new ArrayList<Node>();
+ for( Node con : start._outputs )
+ if( con!=null && !(con instanceof CFGNode) &&
+ (con.isConst() || con instanceof MachNode mach && mach.outregmap()!=null) ) {
+ globals.put(con,true);
+ cons.add(con.keep()); // Preserve original inputs while rewiring users.
+ }
+
+ var copies = new IdentityHashMap<FunNode,IdentityHashMap<Node,Node>>();
+ for( Node con : cons )
+ for( Node use : con._outputs.asAry() ) {
+ if( use==null || globals.containsKey(use) ) continue;
+ FunNode fun = useFun(use);
+ if( fun==null || fun.summary()!=null ) continue; // Metadata has no executable owner.
+ assert CodeGen.CODE._mach==null || fun.isFromSource(); // Ideal evaluation includes imported bodies.
+ var local = copies.computeIfAbsent(fun,f -> new IdentityHashMap<>());
+ Node copy = cloneGlobal(con,fun,globals,local);
+ for( int i=0; i<use.nIns(); i++ )
+ if( use.in(i)==con )
+ use.setDef(i,copy);
+ }
+ for( Node con : cons ) con.unkill();
+ }
+
+ private static Node cloneGlobal(Node con, FunNode fun, IdentityHashMap<Node,Boolean> globals,
+ IdentityHashMap<Node,Node> copies) {
+ Node copy = copies.get(con);
+ if( copy!=null ) return copy;
+ copies.put(con,copy=con.copyEmpty());
+ copy.addDef(fun);
+ for( int i=1; i<con.nIns(); i++ ) {
+ Node def = con.in(i);
+ copy.addDef(globals.containsKey(def) ? cloneGlobal(def,fun,globals,copies) : def);
+ }
+ return copy;
+ }
+
+ private static FunNode useFun(Node use) {
+ if( use instanceof ReturnNode ret ) return ret.fun();
+ if( use instanceof ParmNode parm ) return parm.fun();
+ CFGNode cfg = use.cfg0();
+ while( cfg!=null && !(cfg instanceof FunNode) )
+ cfg = cfg.idom();
+ return (FunNode)cfg;
+ }
+
+
+ // ------------------------------------------------------------------------
+ // Visit all nodes in CFG Reverse Post-Order, essentially defs before uses
+ // (except at loops). Since defs are visited first - and hoisted as early
+ // as possible, when we come to a use we place it just after its deepest
+ // input.
+ private static void schedEarly(CodeGen code, StartNode start, Ary<CFGNode> cfgs) {
+ // Reverse Post-Order on CFG
+ for( CFGNode cfg : cfgs ) {
+ cfg.loopDepth();
+ for( Node n : cfg._inputs )
+ _schedEarly(n,code,start );
+ // In dead infinite loops, entire code blocks may be unreachable
+ // from below. Reach down from the CFG to their Phis so their
+ // inputs are scheduled too.
+ if( cfg instanceof RegionNode )
+ for( Node phi : cfg._outputs )
+ if( phi instanceof PhiNode )
+ _schedEarly(phi,code,start );
+ }
+ }
+
+ private static void _schedEarly(Node n, CodeGen code, StartNode start) {
+ if( n==null || code._visit.get(n._nid) ) return; // Been there, done that
+ assert !(n instanceof CFGNode) : n.getClass().getSimpleName()+"#"+n._nid+" outside scheduled CFG";
+ code._visit.set(n._nid);
+ // Schedule inputs first, except Phis: following their backedges would
+ // enter a data cycle before its control has been scheduled.
+ for( Node def : n._inputs )
+ if( def!=null && !(def instanceof PhiNode) )
+ _schedEarly(def,code,start);
+
+ // An existing edge 0 already supplies control (or a Phi/Proj binding).
+ if( n.in(0)==null ) {
+ // Schedule at deepest input
+ CFGNode early = start; // Maximally early, lowest idepth
+ for( int i=1; i<n.nIns(); i++ )
+ if( n.in(i)!=null && n.in(i).cfg0().idepth() > early.idepth() )
+ early = n.in(i).cfg0(); // Latest/deepest input
+ n.setDef(0,early);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Scheduling is not recursive. Retain scratch capacity between objects,
+ // but release graph references and reset only the entries we used.
+ private static CFGNode[] LATE = new CFGNode[1];
+ // Load NIDs at their CFG block choices, checked against Store blocks.
+ private static int[] ANTI = new int[1];
+ private static final AryInt TOUCHED = new AryInt();
+ private static final Ary<Node> PLACED = new Ary<>(Node.class);
+ private static final Ary<Node> ANTIDEPS = new Ary<>(Node.class);
+ private static final WorkList<Node> WORK = new WorkList<>();
+
+ private static void schedLate( CodeGen code, StopNode stop ) {
+ assert TOUCHED.isEmpty() && PLACED.isEmpty() && WORK.isEmpty();
+ if( code.UID() > LATE.length ) {
+ int len = Math.max(code.UID(),LATE.length<<1);
+ LATE = new CFGNode[len];
+ ANTI = new int[len];
+ }
+ WORK.clear();
+ // Breadth-first scheduling
+ // Early scheduling has released the shared visit bits.
+ breadth(stop,PLACED,LATE,ANTI,ANTIDEPS,code.visit());
+
+ // Copy the best placement choice into the control slot
+ for( Node n : PLACED )
+ if( !(n instanceof ProjNode) )
+ n.setDef(0,LATE[n._nid]);
+ for( int i=0; i<TOUCHED.len(); i++ )
+ LATE[TOUCHED.at(i)] = null;
+ TOUCHED.clear();
+ clear(PLACED);
+ clear(ANTIDEPS);
+ }
+
+ // Ary.clear retains references; static scratch must release them as well.
+ private static void clear(Ary<Node> nodes) {
+ Arrays.fill(nodes._es,0,nodes._len,null);
+ nodes.clear();
+ }
+
+ private static Ary<Node> antiDeps(MemOpNode load, Ary<Node> antideps) {
+ clear(antideps);
+ return load.antiDeps(antideps);
+ }
+
+ private static void breadth(Node stop, Ary<Node> placed, CFGNode[] late, int[] anti, Ary<Node> antideps, BitSet visit) {
+ // Things on the worklist have some (but perhaps not all) uses done.
+ WorkList<Node> work = WORK;
+ work.push(stop);
+ Node n;
+ outer:
+ while( (n = work.pop()) != null ) {
+ assert late[n._nid]==null; // No double visit
+ // These I know the late schedule of, and need to set early for loops
+ if( n instanceof CFGNode cfg ) late[n._nid] = cfg.blockHead() ? cfg : cfg.cfg(0);
+ else if( n instanceof PhiNode phi ) late[n._nid] = phi.region();
+ else if( n instanceof ProjNode && n.in(0) instanceof CFGNode cfg ) late[n._nid] = cfg;
+ else {
+
+ // All uses done?
+ for( Node use : n._outputs )
+ if( use!=null && late[use._nid]==null )
+ continue outer; // Nope, await all uses done
+
+ // Loads need their memory inputs' uses also done
+ if( n instanceof MemOpNode ld && ld._isLoad )
+ for( Node memuse : antiDeps(ld,antideps) )
+ if( late[memuse._nid]==null )
+ continue outer;
+
+ // All uses done, schedule
+ _doSchedLate(n,placed,late,anti,antideps);
+ }
+ TOUCHED.push(n._nid);
+
+ // A use just finished; reconsider its inputs and waiting loads,
+ // even when the shared memory input was already scheduled.
+ for( Node def : n._inputs ) {
+ if( def==null ) continue;
+ if( late[def._nid]==null ) work.push(def);
+ wakeLoads(def,late,work,visit);
+ }
+ visit.clear();
+ if( n instanceof LoopNode loop )
+ for( Node phi : loop._outputs )
+ if( phi instanceof PhiNode && late[phi._nid]==null )
+ work.push(phi);
+ }
+ }
+
+ private static void _doSchedLate(Node n, Ary<Node> placed, CFGNode[] late, int[] anti, Ary<Node> antideps) {
+ // Walk uses, gathering the LCA (Least Common Ancestor) of uses
+ CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
+ assert early != null;
+ CFGNode lca = null;
+ for( Node use : n._outputs )
+ if( use != null )
+ lca = use_block(n,use, late).domLCA(lca,null);
+
+ // Loads may need anti-dependencies, raising their LCA
+ if( n instanceof MemOpNode load && load._isLoad )
+ lca = find_anti_dep(lca,load,early,late,anti, antideps);
+
+ // Walk up from the LCA to the early, looking for best place. This is
+ // the lowest execution frequency, approximated by least loop depth and
+ // deepest control flow.
+ CFGNode best = lca;
+ lca = lca.idom(); // Already found best for starting LCA
+ for( ; lca != early.idom(); lca = lca.idom() )
+ if( better(lca,best) )
+ best = lca;
+ assert !(best instanceof IfNode);
+ placed.push(n);
+ late[n._nid] = best;
+ }
+
+ // Block of use. Normally from late[] schedule, except for Phis, which go
+ // to the matching Region input.
+ private static CFGNode use_block(Node n, Node use, CFGNode[] late) {
+ if( use instanceof ParmNode parm )
+ return late[use._nid];
+ if( !(use instanceof PhiNode phi) )
+ return late[use._nid];
+ CFGNode found=null;
+ for( int i=1; i<phi.nIns(); i++ )
+ if( phi.in(i)==n )
+ found = phi.region().cfg(i).domLCA(found,null); // Can be more than one matching input.
+
+ assert found!=null;
+ return found;
+ }
+
+
+ // Least loop depth first, then largest idepth
+ private static boolean better( CFGNode lca, CFGNode best ) {
+ return lca.loopDepth() < best.loopDepth() ||
+ lca instanceof NeverNode ||
+ lca.idepth() > best.idepth() ||
+ best instanceof IfNode;
+ }
+
+ // A writer may consume a load's memory through an aggregate.
+ private static void wakeLoads(Node def, CFGNode[] late, WorkList<Node> work, BitSet visit) {
+ if( visit.get(def._nid) ) return;
+ visit.set(def._nid);
+ for( Node out : def._outputs )
+ if( out instanceof MemOpNode ld && ld._isLoad && late[ld._nid]==null ) work.push(ld);
+ if( def instanceof MemMergeNode )
+ for( int i=1; i<def.nIns(); i++ )
+ if( def.in(i)!=null ) wakeLoads(def.in(i),late,work,visit);
+ }
+
+ private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti, Ary<Node> antideps) {
+ // We could skip final-field loads here.
+ // Walk LCA->early, flagging Load's block location choices
+ CFGNode marked = lca;
+ for( CFGNode cfg = marked; cfg!=early.idom(); cfg = cfg.idom() )
+ anti[cfg._nid] = load._nid;
+ // Walk load->mem uses, looking for Stores causing an anti-dep
+ for( Node mem : antiDeps(load,antideps) ) {
+ if( mem instanceof PhiNode phi ) {
+ // Inputs carrying different memory might overwrite this alias.
+ // Raise the LCA for those paths; the unchanged-memory paths
+ // do not clobber the load. No scheduling edge to the Phi.
+ for( int i=1; i<phi.nIns(); i++ )
+ if( phi.in(i)!=load.mem() )
+ lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null,anti);
+ } else {
+ // Hunt for clobbering stores
+ assert mem instanceof MemOpNode || mem instanceof EscapeNode ||
+ mem instanceof CallNode || mem instanceof CLInitNode;
+ assert late[mem._nid]!=null;
+ lca = anti_dep(load,late[mem._nid],mem.cfg0(),lca,mem,anti);
+ }
+ }
+ // This load's marks are no longer needed; leave scratch clean even
+ // when the next compilation reuses these node IDs.
+ for( CFGNode cfg = marked; cfg!=early.idom(); cfg = cfg.idom() )
+ anti[cfg._nid] = 0;
+ return lca;
+ }
+
+ //
+ private static CFGNode anti_dep( MemOpNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st, int[] anti ) {
+ // A conditional writer can precede the load without dominating its late block.
+ // Walk back to the first overlap with the load's placement range.
+ // A different Phi input need not be dominated by this memory's
+ // definition. Stop at its depth rather than looking for that ancestor.
+ // Retain the sentinel too: a function entry has a null idom.
+ for( ; stblk != defblk.idom() && stblk.idepth() >= defblk.idepth(); stblk = stblk.idom() ) {
+ // Store and Load overlap, need anti-dependence
+ if( anti[stblk._nid]==load._nid ) {
+ lca = stblk.domLCA(lca,null); // Raise Loads LCA
+ if( lca == stblk && st != null && !(st instanceof CallNode) && st._inputs.find(load) == -1 ) // And if something moved,
+ st.addDef(load); // Add anti-dep as well (calls already end their block)
+ return lca; // Cap this stores' anti-dep to here
+ }
+ }
+ return lca;
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/IFG.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/IFG.java
new file mode 100644
index 00000000..d15e66da
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/IFG.java
@@ -0,0 +1,586 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.Utils;
+import java.util.BitSet;
+import java.util.IdentityHashMap;
+
+// Interference Graph
+abstract public class IFG {
+ // Interference Graph Support
+
+ // Map from a Basic Block to Live-Out: {a map from a Live Range to a Def}
+ private static final IdentityHashMap<CFGNode,IdentityHashMap<LRG,Node>> BBOUTS = new IdentityHashMap<>();
+ static void resetBBLiveOut() {
+ for( IdentityHashMap<LRG,Node> bbout : BBOUTS.values() )
+ bbout.clear();
+ }
+ static final IdentityHashMap<LRG,Node> TMP = new IdentityHashMap<>();
+
+ // Inteference Graph: Array of Bitsets
+ static final Ary<BitSet> IFG = new Ary<>(BitSet.class);
+ static void resetIFG() {
+ for( BitSet bs : IFG )
+ if( bs!=null ) bs.clear();
+ }
+
+ // Set matching bit
+ private static void addIFG( LRG lrg0, LRG lrg1 ) {
+ short x0 = lrg0._lrg, x1 = lrg1._lrg;
+ // Triangulate
+ if( x0 < x1 ) _addIFG(x0,x1);
+ else _addIFG(x1,x0);
+ }
+ // Add lrg1 to lrg0's conflict set
+ private static void _addIFG( short x0, short x1 ) {
+ BitSet ifg = IFG.atX(x0);
+ if( ifg==null )
+ IFG.setX(x0,ifg = new BitSet());
+ ifg.set(x1);
+ }
+
+
+ private static final Ary<CFGNode> WORK = new Ary<>(CFGNode.class);
+ static void pushWork(CFGNode bb) {
+ if( WORK.find(bb) == -1 )
+ WORK.push(bb);
+ }
+
+ // ------------------------------------------------------------------------
+ // Visit all blocks, using the live-out LRGs per-block and doing a backwards
+ // walk over each block. At the end of the walk, push the live-out sets to
+ // prior blocks. Due to loops, no single visitation order suffices. This
+ // problem can be fairly efficiently solved with a proper LIVE calculation
+ // and bitsets. In the interests of simplicity (and assumption of smaller
+ // programs) I am skipping LIVE and directly computing it during the IFG
+ // building.
+
+ // Start by setting the live-out of the exit block (to empty), and putting
+ // it on a worklist.
+
+ // - Pull from worklist a block who's live-out has changed
+ // - Walk backwards, adding interferences and computing live.
+ // - At block head, "push" the new live-out to prior blocks.
+ // - - If they pick up new live-outs, put them on the worklist.
+
+ public static boolean build( int round, RegAlloc alloc ) {
+ // Reset all to empty
+ resetBBLiveOut();
+ resetIFG();
+ WORK.clear();
+
+ // Last block has nothing live out
+ for( CFGNode bb : alloc._code._cfg )
+ if( bb.blockHead())
+ WORK.push( bb );
+
+ // Process blocks until no more changes
+ while( !WORK.isEmpty() )
+ do_block(round,alloc,WORK.pop());
+
+ if( alloc.success() )
+ convert2DAdjacency(alloc);
+ return alloc.success();
+ }
+
+ // Walk one block backwards, compute live-in from live-out, and build IFG
+ static void do_block( int round, RegAlloc alloc, CFGNode bb ) {
+ assert bb.blockHead();
+ IdentityHashMap<LRG,Node> live_out = BBOUTS.get(bb);
+ TMP.clear();
+ if( live_out != null )
+ TMP.putAll(live_out); // Copy bits to temp
+
+ // A backwards walk over instructions in the basic block
+ for( int inum = bb.nOuts()-1; inum >= 0; inum-- ) {
+ Node n = bb.out(inum);
+ if( n.in(0) != bb ) continue;
+ // In a backwards walk, proj users come before the node itself
+ if( n instanceof MultiNode )
+ for( Node proj : n.outs() )
+ if( proj instanceof ProjNode )
+ do_node(alloc,proj);
+ if( n instanceof MachNode )
+ do_node(alloc,n);
+ }
+
+ // The bb head kills register, e.g. a CallEnd and caller-save registers
+ if( bb instanceof MachNode mach )
+ kills(alloc,mach);
+
+ // Push live-sets backwards to priors in CFG.
+ if( bb instanceof RegionNode )
+ for( int i=1; i<bb.nIns(); i++ )
+ mergeLiveOut(alloc,bb,i);
+ else
+ mergeLiveOut(alloc,bb,0);
+ }
+
+ private static void do_node(RegAlloc alloc, Node n) {
+ // Defining means killing live LRG
+ LRG lrg = alloc.lrg(n);
+ if( lrg != null ) {
+ // Check for def-side self-conflict live ranges. These must split,
+ // and only happens during the first round a particular LRG splits.
+ selfConflict(alloc,n,lrg);
+ TMP.remove(lrg); // Kill def
+ }
+
+ // Phis use and define the same live range, i.e. these LRGs already
+ // marked conflicted, no need to mark again
+ if( n instanceof PhiNode )
+ return;
+
+ // Kill any killed registers; milli-code routines like New can kill
+ // will not being a CFG.
+ if( n instanceof MachNode m )
+ kills(alloc,m);
+
+ // Interfere n with all live
+ if( lrg!=null ) {
+ // Interfere n with all live
+ for( LRG tlrg : TMP.keySet() ) {
+ assert tlrg.leader();
+ // Skip self
+ if( lrg != tlrg &&
+ // And register sets overlap
+ lrg._mask.overlap(tlrg._mask) )
+ // Then tlrg and lrg interfere. If lrg needs the single
+ // last tlrg register at some point, either tlrg or lrg
+ // must fail. If *n* (a subset of lrg) needs the single
+ // last tlrg register then only tlrg must fail.
+ if( ((MachNode)n).outregmap().size1() ) {
+ if( !tlrg.clr(lrg._mask.firstReg()) ) // Clear bit, no interference
+ alloc.fail(tlrg); // Clearing drives mask to empty
+ } else addIFG(lrg,tlrg); // Add interference
+ }
+ }
+
+
+ // Check for self-conflict live ranges. These must split, and only
+ // happens during the first round when a particular LRG splits.
+ // Also record use-side spills for biased coloring.
+ // Then make all inputs live.
+ for( int i=1; i<n.nIns(); i++ ) {
+ Node def = n.in(i);
+ if( def==null ) continue;
+ LRG lrg1 = alloc.lrg(def);
+ if( lrg1==null ) continue; // Anti-dependence, etc, no register
+
+ // Check use-side for self-conflict; if so we MUST split
+ selfConflict(alloc,def,lrg1);
+
+ // All operands are live together, regardless of their input order.
+ TMP.put(lrg1,def);
+ }
+
+ // Check fixed-register uses after gathering every operand. An indirect
+ // call's target comes after its arguments, but must also avoid their
+ // fixed registers (and may need to move from its incoming register).
+ for( int i=1; i<n.nIns(); i++ ) {
+ Node def = n.in(i);
+ if( def==null || alloc.lrg(def)==null ) continue;
+ // Look for a must-use single register conflicting with some other must-def.
+ if( n instanceof MachNode m ) {
+ RegMask ni_mask = m.regmap(i);
+ if( ni_mask!=null && ni_mask.size1() ) { // Must-use single register
+ // Search all current live
+ for( LRG tlrg : TMP.keySet() ) {
+ assert tlrg.leader();
+ Node live = TMP.get(tlrg);
+ if( live != def && live instanceof MachNode lmach && lmach.outregmap().overlap(ni_mask) ) {
+ // Deny the register, since it absolutely must be used here
+ if( !tlrg.clr(ni_mask.firstReg()) )
+ // Then direct reg-reg conflict between use here (at n.in(i)) and def (of tlrg) there.
+ // Fail the older live range, it must move its register.
+ alloc.fail( tlrg );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Single-register defines *must* have their register; other live ranges
+ // *must* avoid this register, so instead of interfering we remove the
+ // single register from the other rmask.
+ private static void kills( RegAlloc alloc, MachNode m ) {
+ RegMask killMask = m.killmap();
+ if( killMask==null ) return;
+ // Kill registers with all live
+ for( LRG tlrg : TMP.keySet() ) {
+ assert tlrg.leader();
+ // Always, tlrg cannot use kills
+ if( tlrg._mask.overlap(killMask) ) {
+ Node live = TMP.get(tlrg);
+ // Disallow clone-ables from living across killed registers.
+ // Fail them and let splitting re-clone closer to the target use.
+ if( live instanceof MachNode mach && mach.isClone() )
+ alloc.fail(tlrg);
+ else if( !tlrg.sub(killMask) )
+ alloc.fail(tlrg);
+ }
+ }
+ }
+
+ // Check for self-conflict live ranges. These must split, and only happens
+ // during the first round a particular LRG splits.
+ private static void selfConflict(RegAlloc alloc, Node n, LRG lrg) {
+ selfConflict(alloc,n,lrg,TMP.get(lrg));
+ }
+ private static void selfConflict(RegAlloc alloc, Node n, LRG lrg, Node prior) {
+ if( prior!=null && prior != n ) {
+ lrg.selfConflict(prior);
+ lrg.selfConflict(n);
+ alloc.fail(lrg); // 2 unrelated values live at once same live range; self-conflict
+ }
+ }
+
+ // Merge TMP into bb's live-out set; if changes put bb on WORK
+ private static void mergeLiveOut( RegAlloc alloc, CFGNode priorbb, int i ) {
+ CFGNode bb = priorbb.cfg(i);
+ if( bb == null ) return; // Start has no prior
+ while( !bb.blockHead() ) bb = bb.cfg0();
+
+ // Lazy get live-out set for bb
+ IdentityHashMap<LRG, Node> lrgs = BBOUTS.computeIfAbsent( bb, k -> new IdentityHashMap<>() );
+
+ for( LRG lrg : TMP.keySet() ) {
+ Node def = TMP.get(lrg);
+ // Effective def comes from phi input from prior block
+ if( def instanceof PhiNode phi && phi.cfg0()==priorbb ) {
+ assert i!=0;
+ def = phi.in( i );
+ }
+ // The synthetic exit carries no value on this Phi arm.
+ if( def._type==Type.TOP ) continue;
+ Node def_bb = lrgs.get(lrg);
+ if( def_bb==null ) {
+ lrgs.put(lrg,def);
+ pushWork(bb);
+ } else {
+ // Alive twice with different definitions; self-conflict
+ selfConflict(alloc,def,lrg,def_bb);
+ }
+ }
+ }
+
+
+ static void convert2DAdjacency( RegAlloc alloc ) {
+ // Convert the 2-D array of bits (a 1-D array of BitSets) into an
+ // adjacency matrix.
+ int maxlrg = alloc._LRGS.length;
+ for( int i=1; i<maxlrg; i++ ) {
+ BitSet ifg = IFG.atX(i);
+ if( ifg != null ) {
+ LRG lrg0 = alloc._LRGS[i];
+ for( int lrg = ifg.nextSetBit(0); lrg>=0; lrg=ifg.nextSetBit(lrg+1) ) {
+ LRG lrg1 = alloc._LRGS[lrg];
+ lrg0.addNeighbor(lrg1);
+ lrg1.addNeighbor(lrg0);
+ }
+ }
+ }
+ }
+
+
+ // ------------------------------------------------------------------------
+ // Color the inference graph.
+
+ // Coloring works by removing "trivial" live ranges from the IFG - those
+ // live ranges with fewer neighbors than available colors (registers).
+ // These are trivial because even if every neighbor takes a unique color,
+ // there's at least one more available to color this live range.
+
+ // If we hit a clique of non-trivial live ranges, we pick one to be "at
+ // risk" of not-coloring. Good choices include live ranges with a large
+ // area and few hot uses.
+
+ // Then we reverse and put-back live ranges - picking a color as we go.
+ // If there's no spare color we'll have to spill this at-risk live range.
+
+ public static boolean color(int round, RegAlloc alloc) {
+ int maxlrg = alloc._LRGS.length, nlrgs=0;
+ for( int i=1; i<maxlrg; i++ )
+ if( alloc._LRGS[i] != null )
+ nlrgs++;
+
+ // Simplify
+
+ // Walk all the LRGS looking for some trivial ones to start with.
+ // During this pass all LRGS are broken into 3 disjoint sets:
+ // - trivial, removed from IFG; color_stack[0 to sptr]
+ // - trivial, not (yet) removed from IFG; color_stack[sptr to swork]
+ // - unknown; color_stack[work to maxlrg]
+
+ // Gather all not-unified (not-null); separate trivial and non-trivial set.
+ int sptr = 0, swork = 0;
+ LRG[] color_stack = new LRG[nlrgs];
+ for( int i=0, j=0; i<maxlrg; i++ ) {
+ LRG lrg = alloc._LRGS[i];
+ if( lrg==null ) continue; // Unified lrgs are null here
+ color_stack[j++] = lrg;
+ if( lrg.lowDegree() ) swap(color_stack, swork++, j-1);
+ }
+
+ // Pull all lrgs from IFG, in trivial order if possible
+ while( sptr < color_stack.length ) {
+ // Swap best color up front
+ pickColor(color_stack,sptr,swork,alloc);
+
+ // Pick a trivial lrg, and (temporarily) remove from the IFG.
+ LRG lrg = color_stack[sptr++];
+ // If sptr was swork, then pulled an at-risk lrg
+ if( sptr > swork )
+ swork = sptr;
+
+ // Walk all neighbors and remove
+ if( lrg._adj != null ) {
+ for( LRG nlrg : lrg._adj ) {
+ // Remove and compress out neighbor
+ if( nlrg.removeCompress(lrg) ) {
+ // Neighbor is just exactly going trivial as 'lrg' is removed from IFG
+ // Find "j" position in the color_stack
+ int jx = swork; while( color_stack[jx] != nlrg ) jx++;
+ // Add trivial neighbor to trivial list. Pull lrg j out of
+ // unknown set, since its now in the trivial set
+ swap(color_stack, swork++, jx);
+ }
+ }
+ }
+ }
+
+ // Reverse simplify (unstack the color stack), and set colors (registers) for live ranges
+ while( sptr > 0 ) {
+ LRG lrg = color_stack[--sptr];
+ if( lrg==null ) continue;
+ RegMaskRW rmask = lrg._mask.copy();
+ // Walk neighbors and remove adjacent colors
+ if( lrg._adj!=null ) {
+ for( LRG nlrg : lrg._adj ) {
+ nlrg.reinsert(lrg);
+ int reg = nlrg._reg;
+ if( reg!= -1 ) // Failed neighbors do not count
+ rmask.clr(reg); // Remove neighbor from my choices
+ }
+ }
+ // At-risk live-range did not color?
+ if( rmask.isEmpty() ) {
+ alloc.fail(lrg);
+ lrg._reg = -1;
+ } else {
+ // Pick first available register
+ short reg = rmask.firstReg();
+ // Pick a "good" color from the choices. Typically, biased-coloring
+ // removes some over-spilling.
+ if( rmask.size() > 1 ) reg = biasColor(alloc,lrg,reg,rmask);
+ lrg._reg = reg; // Assign the register
+ }
+ }
+
+ return alloc.success();
+ }
+
+ // Pick LRG from color stack
+ private static void pickColor(LRG[] color_stack, int sptr, int swork, RegAlloc alloc) {
+ // Out of trivial colorable, pick an at-risk to pull
+ if( sptr==swork )
+ swap(color_stack,sptr,pickRisky(color_stack,sptr,alloc));
+ // When coloring, we'd like to give more choices; so when coloring we'd
+ // like to see the single-def first (since no choices anyway), then
+ // non-split related (so more live ranges get colored), then
+ // split-related last, so they have more colors to bias towards.
+
+ // Working in reverse, pick first split-related with many regs, then
+ // those with some regs, then single-def.
+ int bidx=sptr;
+ LRG best=color_stack[bidx];
+ for( int idx = sptr+1; idx < swork; idx++ )
+ if( betterLRG(best,color_stack[idx]) )
+ best = color_stack[bidx=idx];
+ if( bidx != sptr )
+ swap(color_stack,sptr,bidx); // Pick best at sptr
+ }
+
+ // Returns true if 'lrg' is better than 'best'
+ private static boolean betterLRG( LRG best, LRG lrg ) {
+ // If single-def varies, keep the not-single-def
+ if( best.size1() != lrg.size1() )
+ return best.size1();
+ // If hasSplit varies, keep the hasSplit
+ if( best.hasSplit() != lrg.hasSplit() )
+ return lrg.hasSplit();
+ // If both are single register (so both sizes are 1),
+ // return a multi-use over a multi-def
+ if( best.size1() )
+ return !best._multiUse && lrg._multiUse;
+ // Keep large register count
+ return best.size() < lrg.size();
+ }
+
+ // Pick a live range that hasn't already spilled, or has a single-def-
+ // single-use that are not adjacent.
+ private static int pickRisky( LRG[] color_stack, int sptr, RegAlloc alloc ) {
+ int best=sptr;
+ int bestScore = pickRiskyScore(color_stack[best],alloc);
+ for( int i=sptr+1; i<color_stack.length; i++ ) {
+ if( bestScore == 1000000 ) return best; // Already max score
+ int iScore = pickRiskyScore(color_stack[i],alloc);
+ if( iScore > bestScore )
+ { best = i; bestScore = iScore; }
+ }
+ return best;
+ }
+
+ // Pick a live range to pull, that might not color.
+ //
+ // Picking a live range with a very large span, with defs and uses outside
+ // loops means spilling a relative cheap live range and getting that
+ // register over a large area.
+ //
+ // Picking a live range that is very close to coloring might allow it to
+ // color despite being risky.
+ private static int pickRiskyScore( LRG lrg, RegAlloc alloc ) {
+ // Callee-save live ranges cover the whole function and are cheap to
+ // split. Rank scheduled area recovered against the loop-scaled cost
+ // of the def- and use-side moves.
+ if( lrg._machDef instanceof CalleeSaveNode )
+ return areaScore(functionArea(lrg._fun,alloc),
+ splitCost((Node)lrg._machDef,(Node)lrg._machUse));
+ if( lrg._splitDef != null && lrg._splitDef. in(1) instanceof CalleeSaveNode &&
+ lrg._splitUse != null && lrg._splitUse.out(0) instanceof ReturnNode )
+ return areaScore(functionArea(lrg._fun,alloc),
+ splitCost(lrg._splitDef,lrg._splitUse));
+
+ // Pick single-def clonables that are not right next to their single-use.
+ // Rank by the area recovered by cloning closer to the use.
+ if( !lrg._multiDef && lrg._machDef!=null && lrg._machUse!=null && lrg._machDef.isClone() ) {
+ Node def = ((Node)lrg._machDef);
+ Node use = ((Node)lrg._machUse);
+ CFGNode cfg = def.cfg0();
+ int area = cfg != use.cfg0()
+ ? functionArea(lrg._fun,alloc)
+ : cfg._outputs.find(use)-cfg._outputs.find(def)-1;
+ // A single-use function address can move next to its use without
+ // adding an instruction. Prefer that to saving a callee-save
+ // register just to hold the address. Stay in the same block and
+ // require a gap so the clone makes progress.
+ if( def instanceof FunPtrNode && !lrg._multiUse &&
+ cfg == use.cfg0() && area > 0 )
+ return 1000000;
+ if( area > 0 )
+ return areaScore(area,loopCost(use));
+ }
+
+ // Default for ranges without a specific area/cost estimate.
+ return 1000 + (lrg._multiUse ? -100 : 0);
+ }
+
+ private static int functionArea(FunNode fun, RegAlloc alloc) {
+ int area=0;
+ for( CFGNode bb : alloc._code._cfg )
+ if( bb.fun()==fun )
+ area += bb.nOuts();
+ return area;
+ }
+
+ private static int areaScore(int area, int cost) {
+ return 1000 + (area<<10)/cost;
+ }
+
+ private static int splitCost(Node def, Node use) {
+ return loopCost(def) + (use==null ? 0 : loopCost(use));
+ }
+
+ private static int loopCost(Node n) {
+ return 1 << (n.cfg0().loopDepth()*3);
+ }
+
+ private static short biasColor( RegAlloc alloc, LRG lrg, short reg, RegMask mask ) {
+ if( mask.size1() ) return reg;
+ // Check chain of splits up the def-chain. Take first allocated
+ // register, and if it's available in the mask, take it.
+ Node def = lrg._splitDef, use = lrg._splitUse;
+ int tidx=0, cnt=0;
+
+ while( def != null || use != null ) {
+ if( cnt++ > 10 ) break;
+
+ if( def != null ) {
+ short bias = biasColor( alloc, def, mask );
+ if( bias >= 0 ) return bias; // Good bias
+ if( bias == -2 ) def = null; // Kill this side, no more searching
+ else if( (tidx=biasable(def)) == 0 ) def = null;
+ }
+
+ if( use != null ) {
+ short bias = biasColor( alloc, use, mask );
+ if( bias >= 0 ) return bias; // Good bias
+ if( bias == -2 ) use = null; // Kill this side, no more searching
+ else if( biasable(use)==0 ) use = null;
+ }
+
+ if( def != null ) {
+ short bias = biasColorNeighbors( alloc, def, mask );
+ if( bias >= 0 ) return bias;
+ // Advance def side
+ def = def.in(tidx);
+ if( alloc.lrg(def)==null ) def=null;
+ }
+
+ if( use != null ) {
+ short bias = biasColorNeighbors( alloc, use, mask );
+ if( bias >= 0 ) return bias;
+ use = use.out(0);
+ if( biasable(use)==0 ) use=null;
+ }
+
+ }
+ return mask.firstReg();
+ }
+
+ private static int biasable(Node split) {
+ if( split instanceof SplitNode ) return 1; // Yes biasable, advance is slot 1
+ if( split instanceof PhiNode phi ) return phi.region() instanceof LoopNode ? 2 : 1; // Prefer the backedge in loops
+ if( !(split instanceof MachNode mach) ) return 0; // Not biasable
+ return mach.twoAddress(); // Only biasable if 2-addr
+ }
+
+ // 3-way return:
+ // - good bias reg, take it & exit
+ // - this path is cutoff; do not search here anymore
+ // - advance this side
+ private static short biasColor( RegAlloc alloc, Node split, RegMask mask ) {
+ short bias = alloc.lrg(split)._reg;
+ if( bias != -1 ) {
+ if( mask.test(bias) ) return bias; // Good bias
+ else return -2; // Kill this side
+ } else return -1; // Advance this side
+ }
+
+ // Check if we can match "split" color, or else trim "mask" to colors
+ // "split" might get.
+ private static short biasColorNeighbors( RegAlloc alloc, Node split, RegMask mask ) {
+ LRG slrg = alloc.lrg(split);
+ if( slrg._adj == null ) return -1; // No trimming
+
+ // Can I limit my own choices to valid neighbor choices?
+ for( LRG alrg : slrg._adj ) {
+ short reg = alrg._reg;
+ if( reg == -1 && alrg._mask.size1() )
+ reg = alrg._mask.firstReg();
+ if( reg != -1 ) {
+ mask.clr(reg);
+ if( mask.size1() )
+ return mask.firstReg();
+ }
+ }
+ return -1; // No obvious color choice, but mask got trimmed
+ }
+
+ private static void swap( LRG[] ary, int x, int y ) {
+ LRG tmp = ary[x]; ary[x] = ary[y]; ary[y] = tmp;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java
new file mode 100644
index 00000000..5fc002a1
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java
@@ -0,0 +1,126 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import java.util.BitSet;
+import java.util.IdentityHashMap;
+
+// Replace ideal nodes with machine nodes while preserving CFG/loop metadata.
+// Selection also owns target ABI masks and rebuilding the copied graph's uses.
+class InstSelect {
+ private final CodeGen _code;
+ private InstSelect(CodeGen code) { _code=code; }
+
+ static CodeGen select(CodeGen code, String cpu, String callingConv, String base) {
+ // Ideal evaluation schedules the ideal graph without a CPU port.
+ if( cpu==null ) return code;
+ return code.phase(CodeGen.Phase.Select, code._phase==CodeGen.Phase.Unlink,
+ () -> new InstSelect(code).run(cpu,callingConv,base));
+ }
+
+ private void run(String cpu, String callingConv, String base) {
+ _code._callingConv = callingConv;
+
+ // Look for CPU in fixed named place:
+ // com.seaofnodes.simple.node.cpus."cpu"."cpu.class"
+ String clzFile = base+"."+cpu+"."+cpu;
+ try { _code._mach = Class.forName(clzFile).asSubclass(Machine.class).getDeclaredConstructor(CodeGen.class).newInstance(_code); }
+ catch( Exception e ) { throw new RuntimeException(e); }
+
+ _code._cCallingConv = _code._mach.cCallingConv(callingConv);
+
+ // Build global copies of common register masks.
+ long callerSave = _code._mach.callerSave();
+ long cCallerSave= _code._mach.callerSave(_code._cCallingConv);
+ long neverSave = _code._mach. neverSave();
+ int maxReg = Math.min(64,_code._mach.regs().length);
+ assert maxReg>=64 || (-1L << maxReg & callerSave)==0; // No stack slots in callerSave
+ assert maxReg>=64 || (-1L << maxReg & cCallerSave)==0; // No stack slots in cCallerSave
+ _code._callerSave = new RegMask(callerSave);
+ _code._cCallerSave= new RegMask(cCallerSave);
+
+ // Build a Return RegMask array. All returns have the following inputs:
+ // 0 - ctrl
+ // 1 - memory
+ // 2 - varies with returning GPR,FPR
+ // 3 - RPC
+ // 4+- Caller save registers
+ _code._retMasks = new RegMask[(maxReg-_code._callerSave.size()-Long.bitCount( neverSave ))+4];
+ for( int reg=0, i=4; reg<maxReg; reg++ )
+ if( !_code._callerSave.test(reg) && ((1L<<reg)&neverSave)==0 )
+ _code._retMasks[i++] = new RegMask(reg);
+ _code._rpcMask = new RegMask(_code._mach.rpc());
+ _code._retMasks[3] = _code._rpcMask;
+
+ // Convert to machine ops
+ _code._uid = 1; // All new machine nodes reset numbering
+ var map = new IdentityHashMap<Node,Node>();
+ _instSelect( _code._stop, map );
+ _code._stop = (StopNode)map.get(_code._stop );
+ StartNode start = (StartNode)map.get(_code._start);
+ _code._start = start==null ? new StartNode(_code._start) : start;
+ _instOuts(_code._stop,_code.visit());
+ _code._visit.clear();
+
+ // Replace the CompUnit stop (and list of functions)
+ // with hardware-specific ones
+ for( CompUnit cu : _code._compunits.values() ) {
+ cu._start= (StartCUNode)map.get(cu._start);
+ cu._stop = (StopCUNode )map.get(cu._stop );
+ }
+ }
+
+ private Node _instSelect( Node n, IdentityHashMap<Node,Node> map ) {
+ if( n==null ) return null;
+ Node x = map.get(n);
+ if( x !=null ) return x; // Been there, done that
+
+ // If n is a MachNode already, then its part of a multi-node expansion.
+ // It does not need instruction selection (already selected!)
+ // but it does need its inputs walked.
+ if( n instanceof MachNode ) {
+ for( int i=0; i < n.nIns(); i++ )
+ n._inputs.set(i, _instSelect(n.in(i),map) );
+ return n;
+ }
+
+ // Produce a machine node from n; map it to flag as done so stops cycles.
+ if( n instanceof MemOpNode mem && mem.off() instanceof ExternOffsetNode off ) {
+ // External fields replace base+offset with symbol+0. Keep the ideal
+ // declaring type on the address for machine load/store width selection.
+ TypeMemPtr ptr = (TypeMemPtr)mem.ptr()._type;
+ n._inputs.set(2,new ExternNode(TypeMemPtr.make((byte)2,ptr._obj,true),off._extern));
+ n._inputs.set(3,ConstantNode.raw(TypeInteger.ZERO));
+ }
+ map.put(n, x=_code._mach.instSelect(n) );
+ // Carry loop-tree and pre-order info across the ideal->mach transition
+ if( n instanceof CFGNode ncfg && x instanceof CFGNode xcfg ) {
+ xcfg._ltree = ncfg._ltree;
+ xcfg._pre = ncfg._pre;
+ }
+ // Walk machine op and replace inputs with mapped inputs
+ for( int i=0; i < x.nIns(); i++ )
+ x._inputs.set(i, _instSelect(x.in(i),map) );
+ // Post selection action
+ if( x instanceof MachNode mach ) {
+ if( n instanceof ReturnNode ret )
+ ((ReturnNode)mach)._fun = (FunNode)map.get(ret._fun);
+ mach.postSelect(_code); // Post selection action
+ }
+
+ return x;
+ }
+
+ private void _instOuts( Node n, BitSet visit ) {
+ if( visit.get(n._nid) ) return;
+ visit.set(n._nid);
+ for( Node in : n._inputs )
+ if( in!=null ) {
+ in._outputs.push(n);
+ // CallNode special: outputs are partially ordered; CallEnd in slot 0
+ if( in instanceof CallNode call && n instanceof CallEndNode cend && call.out(0) != cend )
+ call._outputs.swap(0,call.nOuts()-1);
+ _instOuts(in,visit);
+ }
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/Iter.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Iter.java
new file mode 100644
index 00000000..4085ebff
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Iter.java
@@ -0,0 +1,101 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Ary;
+
+// Establish a pessimistic fixed point after parsing/loading. Later Iter runs
+// reuse the worklist and types produced by the preceding phase.
+public abstract class Iter {
+ static void run(CodeGen code) {
+ assert code._globalEscapes==null;
+ code._globalEscapes = new GlobalEscapeNode();
+ code._start.setDef(2,code._globalEscapes);
+ // The phase shift closes all parser-in-progress states and freezes
+ // unknown external inputs. Imported nodes carry post-Opto escape
+ // precision, but Iter is a fresh pessimistic solve: memory escape
+ // summaries must restart at FULL so newly discovered private escapes
+ // can only be removed, never added. Seed the complete graph once.
+ boolean hasImports = code._externPaths != null;
+ code._start.walk(n -> {
+ // Type import can reconcile a constant's declaration.
+ n._type = n instanceof ConstantNode ? n.compute()
+ : hasImports ? iterImportReset(n._type) : iterEscapeReset(n._type);
+ code.add(n);
+ return null;
+ });
+ code._iter.iterate(code);
+ // Expensive assert.
+ assert !CodeGen.expensiveAssert() || Opto.fixedPointCheck(code);
+ }
+
+ private static Type iterEscapeReset(Type t) {
+ if( t instanceof TypeMem mem )
+ return mem._one ? mem : mem.makeFrom(XInt.FULL,XInt.FULL);
+ if( t instanceof TypeTuple tt ) {
+ TypeTuple rez = tt;
+ for( int i=0; i<tt._types.length; i++ ) {
+ Type tx = iterEscapeReset(tt._types[i]);
+ if( tx != tt._types[i] ) rez = rez.makeFrom(i,tx);
+ }
+ return rez;
+ }
+ return t;
+ }
+
+ private static Type iterImportReset(Type t) {
+ if( t.isHigh() )
+ return t.dual();
+ if( t instanceof TypeMem mem )
+ return TypeMem.make(mem._alias,iterImportReset(mem._t),mem._one,mem._clz,mem._final,XInt.FULL,XInt.FULL);
+ if( t instanceof TypeMemPtr )
+ return t.makeStorage();
+ if( t instanceof TypeStruct )
+ return t.makeStorage();
+ if( t instanceof TypeTuple tt ) {
+ TypeTuple rez = tt;
+ for( int i=0; i<tt._types.length; i++ ) {
+ Type tx = iterImportReset(tt._types[i]);
+ if( tx != tt._types[i] ) rez = rez.makeFrom(i,tx);
+ }
+ return rez;
+ }
+ return t;
+ }
+
+ // Saved types belong to a different caller context. Reset only the new
+ // graph, then repair any affected bounds before peepholes consume them.
+ static void imports(CodeGen code, Ary<Node> nodes) {
+ for( Node n : nodes )
+ if( !n.isDead() )
+ n._type = n instanceof ConstantNode ? n.compute() : iterImportReset(n._type);
+ // Stop normalizes unknown memory into the unit's visible escapes.
+ // Do not publish a transient FULL Stop merely because we reset its body.
+ for( Node n : nodes )
+ if( n instanceof StopCUNode && !n.isDead() ) n._type = n.compute();
+ code.addAll(nodes);
+ repair(code);
+ }
+
+ // Repair the monotonicity assert after loading new nodes (from a file via
+ // Serialize). Only widening types here makes it safe to resume the usual
+ // lifting peeps. The worklist already contains changed edges and their
+ // neighbors.
+ public static void repair(CodeGen code) {
+ // Gather changes for a normal IterPeeps once the graph has its
+ // monotonicity property back.
+ Ary<Node> peeps = new Ary<>(Node.class);
+ Node n;
+ while( (n = code._iter._work.pop())!=null ) {
+ if( n.isDead() ) continue;
+ peeps.add(n);
+ // Meet over existing and new; only drops
+ Type t = n._type.meet(n.compute());
+ if( t==n._type ) continue;
+ n._type = t;
+ code.addAll(n._outputs);
+ n.moveDepsToWorklist();
+ }
+ code.addAll(peeps);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/IterPeeps.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/IterPeeps.java
new file mode 100644
index 00000000..543a69df
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/IterPeeps.java
@@ -0,0 +1,349 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.node.*;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.IdentityHashMap;
+import java.util.Random;
+
+/**
+ * The IterPeeps runs after parsing. It iterates the peepholes to a fixed point
+ * so that no more peepholes apply. This should be linear because peepholes rarely
+ * (never?) increase code size. The graph should monotonically reduce in some
+ * dimension, which is usually size. It might also reduce in e.g. number of
+ * MulNodes or Load/Store nodes, swapping out more "expensive" Nodes for cheaper
+ * ones.
+ * <br>
+ * The theoretical overall worklist is mindless just grabbing the next thing and
+ * doing it. If the graph changes, put the neighbors on the worklist.
+ * <br>
+ * Lather, Rinse, Repeat until the worklist runs dry.
+ * <p>
+ * The main issues we have to deal with:
+ *
+ * <ul>
+ * <li>Nodes have uses; replacing some set of Nodes with another requires more graph
+ * reworking. Not rocket science, but it can be fiddly. Its helpful to have a
+ * small set of graph munging utilities, and the strong invariant that the graph
+ * is stable and correct between peepholes. In our case `Node.subsume` does
+ * most of the munging, building on our prior stable Node utilities.</li>
+ *
+ * <li>Changing a Node also changes the graph "neighborhood". The neighbors need to
+ * be checked to see if THEY can also peephole, and so on. After any peephole
+ * or graph update we put a Nodes uses and defs on the worklist.</li>
+ *
+ * <li>Our strong invariant is that for all Nodes, either they are on the worklist
+ * OR no peephole applies. This invariant is easy to check, although expensive.
+ * Basically the normal "iterate peepholes to a fixed point" is linear, and this
+ * check is linear at each peephole step... so quadratic overall. It's a useful
+ * assert, but one we can disable once the overall algorithm is stable - and
+ * then turn it back on again when some new set of peepholes is misbehaving.
+ * The code for this is turned on in `IterPeeps.iterate` as `assert
+ * progressOnList(stop);`</li>
+ * </ul>
+ */
+public class IterPeeps {
+
+ public final WorkList<Node> _work;
+ final WorkList<CallEndNode> _workInline;
+ private int _cnt;
+
+ public IterPeeps( long seed ) {
+ _work = new WorkList<>(seed);
+ _workInline = new WorkList<>(seed);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <N extends Node> N add( N n ) { return (N)_work.push(n); }
+
+ public void addAll( Ary<Node> ary ) { _work.addAll(ary); }
+
+ /**
+ * Iterate peepholes and inlining to a fixed point
+ */
+ public void iterate( CodeGen code ) {
+ boolean didInline = false;
+ Ary<Node> defer = new Ary<>(Node.class);
+ while( true ) {
+ // Clean up everything that does not grow the code
+ iteratePeeps(code);
+
+ // Pick an inline candidate, no real heuristic, first come, first served
+ boolean inlined = false;
+ CallEndNode cend;
+ while( (cend = _workInline.pop()) != null) {
+ if( cend.isDead() ) continue;
+ if( cend.loadForInline() ) { inlined = true; break; }
+ byte inline = cend.maybeInline();
+ if( inline == -1 ) defer.add(cend);
+ if( inline > 0 ) {
+ inlined = true;
+ break;
+ }
+ }
+ // Inlined, run peeps until clean again
+ if( inlined ) { didInline = true; continue; }
+ // No more candidates, check the defer list
+ if( !didInline ) break; // No more progress, so all the "maybe inline after cleanup" do not progress
+ // Some inlining happened, retry all the "try again after cleanup" calls
+ didInline = false;
+ code.addAll(defer);
+ defer.clear();
+ }
+ }
+
+ // Run all the code-reduction and type-lifting peeps as possible
+ private void iteratePeeps( CodeGen code ) {
+ assert !CodeGen.expensiveAssert(0) || (progressOnList(code, _work) && schedulableUses(code));
+
+ Node n;
+ while( (n = _work.pop()) != null ) {
+ if( n.isDead() ) continue;
+ _cnt++; // Useful for debugging, searching which peephole broke things
+ var obs = code._obs; // Collect peephole neighbors for graph display
+ if( obs != null ) obs.before(n);
+ Node x = n.peepholeOpt();
+ if( n instanceof CallEndNode cend )
+ _workInline.push(cend);
+ if( x != null ) {
+ assert !x.isDead(); // Peepholes return alive answers
+ // peepholeOpt can return brand-new nodes, needing an initial type set
+ if( x._type==null ) x.setType(x.compute());
+ // Changes require neighbors onto the worklist
+ if( x != n || !(x instanceof ConstantNode) ) {
+ // All outputs of n (changing node) not x (prior existing node).
+ for( Node z : n._outputs ) _work.push(z);
+ // Everybody gets a free "go again" in case they didn't get
+ // made in their final form.
+ _work.push(x);
+ // A self-returning peephole can have rewritten its input
+ // edges. The new defs gained a user and may have
+ // backwards, user-sensitive peepholes of their own.
+ for( Node z : x._inputs ) _work.push(z);
+ // If the result is not self, revisit all inputs (because
+ // there's a new user), and replace in the graph.
+ if( x != n ) {
+ //for( Node z : n. _inputs ) _work.push(z);
+ for( Node z : x._outputs ) _work.push(z);
+ n.subsume(x);
+ }
+ }
+ // If there are distant neighbors, move to worklist
+ n.moveDepsToWorklist();
+ // Very expensive assert.
+ assert !CodeGen.expensiveAssert(_cnt) || (progressOnList(code, _work) && schedulableUses(code));
+ }
+ boolean unused = n.isUnused();
+ if( unused ) {
+ assert !(n instanceof StopNode); // StopNodes can die if all code in the compunit dies
+ n.kill(); // Just plain dead
+ }
+ if( obs != null ) obs.after(n, x!=null ? x : unused ? n : null, true);
+ }
+ assert !CodeGen.expensiveAssert(0) || schedulableUses(code);
+
+ }
+
+ // Visit ALL nodes and confirm the invariant:
+ // Either you are on the _work worklist OR running `iter()` makes no progress.
+
+ // This invariant ensures that no progress is missed, i.e., when the
+ // worklist is empty we have indeed done all that can be done. To help
+ // with debugging, the {@code assert} is broken out in a place where it is easy to
+ // stop if a change is found.
+
+ // Also, the normal usage of `iter()` may attempt peepholes with distance
+ // neighbors and these should fail, but will then try to add dependencies
+ // {@link #Node.addDep} which is a side effect in an assert. The {@link
+ // #midAssert} is used to stop this side effect.
+ // Pessimistic solver assert
+ public static boolean progressOnList(CodeGen code, WorkList<Node> list ) {
+ code._midAssert = true;
+ Node changed = code._stop.walk( n -> {
+ // Ignore most in-progress things
+ if( n.iskeep() ) return null;
+ // Types must be forwards, even if on the worklist.
+ Type nval = n.compute();
+ assert nval.isa(n._type) : "Non-monotonic peep: "+n+"#"+n._nid+" old="+n._type+" new="+nval+" inputs="+inputTypes(n)+" peep="+n;
+ if( list.on(n) )
+ return null; // On worklist is ok!
+ if( n instanceof CallEndNode cend ) {
+ if( code._iter._workInline.on(cend) )
+ return null; // On inline worklist is ok!
+ assert cend.maybeInline() <= 0 : "Inline fired and not on worklist, CallEndNode#"+cend._nid;
+ }
+ // Off list: must be live, in GVN table, and no peeps.
+ assert n.nOuts() > 0 : "Unused live node: "+n;
+ assert code._gvn.get(n)==n : "Not in GVN table: "+n;
+ Node m = n.peepholeOpt();
+ assert m==null : "Peep fired and not on worklist, "+n.getClass().getSimpleName()+"#"+n._nid+" -> "+m;
+ return m;
+ });
+ code._midAssert = false;
+ return changed==null;
+ }
+
+ private static String inputTypes(Node n) {
+ StringBuilder sb = new StringBuilder("[");
+ for( Node in : n._inputs )
+ sb.append(in==null ? "null" : in.getClass().getSimpleName()+"#"+in._nid+"="+in+":"+in._type+":keep="+in.iskeep()).append(',');
+ return sb.append(']').toString();
+ }
+
+ // GCM assumes every movable node can be placed no earlier than its inputs
+ // and no later than the LCA of its uses. Catch graphs where a use escapes
+ // above an input-defined branch before the later scheduling pass walks off
+ // the top of the idom tree.
+ private static final IdentityHashMap<Node,CFGNode> earlyCache = new IdentityHashMap<>();
+ private static final BitSet VISIT = new BitSet();
+ public static boolean schedulableUses(CodeGen code) {
+ earlyCache.clear();
+ Node bad = code._stop.walk( n -> {
+ if( n.iskeep() || n.isDead() || n.isConst() ||
+ n instanceof CFGNode || n instanceof PhiNode || n instanceof ProjNode )
+ return null;
+ assert VISIT.isEmpty();
+ CFGNode early = CFGNode.earlyCFG(n,earlyCache,VISIT);
+ VISIT.clear();
+ if( early == null ) return null;
+ CFGNode lca = null;
+ for( Node use : n._outputs ) {
+ CFGNode ublk = useBlock(n,use);
+ if( ublk != null )
+ lca = ublk.domLCA(lca,null);
+ }
+ if( lca == null ) return null;
+ if( !early.sameFun(lca) ) return null;
+ assert early.dominates(lca,null) : badSchedule(n,early,lca);
+ return null;
+ });
+ return bad == null;
+ }
+
+ private static CFGNode useBlock(Node n, Node use) {
+ if( use == null )
+ return null;
+ if( use instanceof PhiNode phi ) {
+ CFGNode found = null;
+ for( int i = 1; i<phi.nIns(); i++ )
+ if( phi.in(i)==n ) {
+ if( i >= phi.region().nIns() )
+ return null;
+ found = phi.region().cfg(i).domLCA(found,null);
+ }
+ return found;
+ }
+ return CFGNode.safeCFG(use);
+ }
+
+ private static String badSchedule(Node n, CFGNode early, CFGNode lca) {
+ StringBuilder sb = new StringBuilder("Unschedulable data node ")
+ .append(n.getClass().getSimpleName()).append('#').append(n._nid)
+ .append(" early=").append(cfg(early))
+ .append(" use-lca=").append(cfg(lca))
+ .append(" node=").append(n).append("\ninputs:");
+ for( int i = 0; i<n.nIns(); i++ ) {
+ Node in = n.in(i);
+ sb.append("\n in").append(i).append(": ").append(node(in))
+ .append(" cfg=").append(cfg(CFGNode.safeCFG(in)));
+ }
+ sb.append("\noutputs:");
+ for( Node use : n._outputs )
+ if( use != null )
+ sb.append("\n use ").append(node(use))
+ .append(" block=").append(cfg(useBlock(n,use)));
+ return sb.toString();
+ }
+
+ private static String node(Node n) {
+ return n==null ? "null" : n.getClass().getSimpleName()+"#"+n._nid+" "+n;
+ }
+
+ private static String cfg(CFGNode cfg) {
+ return cfg==null ? "null" : cfg.getClass().getSimpleName()+"#"+cfg._nid+" "+cfg;
+ }
+
+ /**
+ * Classic WorkList, with a fast add/remove, dup removal, random pull.
+ * The Node's nid is used to check membership in the worklist.
+ */
+ @SuppressWarnings("unchecked")
+ public static class WorkList<E extends Node> {
+
+ private Node[] _es;
+ private int _len;
+ private final BitSet _on; // Bit set if Node._nid is on WorkList
+ private final Random _R; // For randomizing pull from the WorkList
+ private final long _seed;
+
+ /* Useful stat - how many nodes are processed in the post parse iterative opt */
+ private long _totalWork = 0;
+
+ public WorkList() { this(123); }
+ WorkList(long seed) {
+ _es = new Node[1];
+ _len=0;
+ _on = new BitSet();
+ _seed = seed;
+ _R = new Random();
+ _R.setSeed(_seed);
+ }
+
+ /**
+ * Pushes a Node on the WorkList, ensuring no duplicates
+ * If Node is null it will not be added.
+ */
+ public E push( E x ) {
+ if( x==null ) return null;
+ int idx = x._nid;
+ if( !_on.get(idx) ) {
+ _on.set(idx);
+ if( _len==_es.length )
+ _es = Arrays.copyOf(_es,_len<<1);
+ _es[_len++] = x;
+ _totalWork++;
+ }
+ return x;
+ }
+
+ public void addAll( Ary<E> ary ) {
+ for( E n : ary )
+ push(n);
+ }
+ public void addAll( E[] es ) {
+ for( E n : es )
+ push(n);
+ }
+
+
+ /**
+ * True if Node is on the WorkList
+ */
+ public boolean on( E x ) { return _on.get(x._nid); }
+
+ /**
+ * Removes a random Node from the WorkList; null if WorkList is empty
+ */
+ public E pop() {
+ if( _len == 0 ) return null;
+ int idx = _R.nextInt(_len);
+ E x = (E)_es[idx];
+ _es[idx] = _es[--_len]; // Compress array
+ _es[_len] = null;
+ _on.clear(x._nid);
+ return x;
+ }
+
+ public boolean isEmpty() { return _len==0; }
+ public void clear() {
+ Arrays.fill(_es,0,_len,null);
+ _len = 0;
+ _on.clear();
+ _R.setSeed(_seed);
+ _totalWork = 0;
+ }
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/LRG.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/LRG.java
new file mode 100644
index 00000000..fdde8838
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/LRG.java
@@ -0,0 +1,240 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+import java.util.IdentityHashMap;
+
+/** A Live Range
+ * <p>
+ * A live range is a set of nodes and edges which must get the same register.
+ * Live ranges form an interconnected web with almost no limits on their
+ * shape. Live ranges also gather the set of register constraints from all
+ * their parts.
+ * <p>
+ * Most of the fields in this class are for spill heuristics, but there are a
+ * few key ones that define what a LRG is. LRGs have a unique dense integer
+ * `_lrg` number which names the LRG. New `_lrg` numbers come from the
+ * `RegAlloc._lrg_num` counter. LRGs can be unioned together -
+ * (<a href="https://en.wikipedia.org/wiki/Disjoint-set_data_structure">this is the Union-Find algorithm</a>)
+ * - and when this happens the lower numbered `_lrg` wins. Unioning only
+ * happens during `BuildLRG` and happens because either a `Phi` is forcing all
+ * its inputs and outputs into the same register, or because of a 2-address
+ * instruction. LRGs have matching `union` and `find` calls, and a set
+ * `_leader` field.
+ */
+public class LRG {
+
+ // Dense live range numbers
+ final short _lrg;
+
+ // U-F leader; null if leader
+ LRG _leader;
+
+ // Choosen register
+ short _reg;
+
+ // Count of single-register defs and uses
+ short _1regDefCnt, _1regUseCnt;
+ // Count of all defs, uses. Mostly interested 1 vs many
+ boolean _multiDef, _multiUse;
+
+ // A sample MachNode def in the live range
+ public MachNode _machDef, _machUse;
+ short _uidx; // _machUse input
+
+ // Some splits used in biased coloring
+ MachConcreteNode _splitDef, _splitUse;
+
+ // All the self-conflicting defs for this live range
+ IdentityHashMap<Node,String> _selfConflicts;
+
+ // AND of all masks involved; null if none have been applied yet
+ RegMask _mask;
+
+ // Adjacent Live Range neighbors. Only valid during coloring
+ Ary<LRG> _adj;
+
+ // Function fully containing the live range; used to find the stack frame
+ // layout, for spill op offsets.
+ final public FunNode _fun;
+
+ public int nadj() { return _adj==null ? 0 : _adj._len; }
+
+ public RegMask useMask() { return _machUse==null ? null : _machUse.regmap(_uidx); }
+
+ void addNeighbor(LRG lrg) {
+ if( _adj==null ) _adj = new Ary<>(LRG.class);
+ _adj.push(lrg);
+ }
+
+ // Remove and compress neighbor list; store ex-neighbor past
+ // end for unwinding Simplify. True if exactly going low-degree
+ boolean removeCompress( LRG lrg ) {
+ _adj.swap(_adj.find(lrg),_adj._len-1);
+ return _adj._len-- == _mask.size();
+ }
+
+ void reinsert( LRG lrg ) {
+ assert _adj._es[_adj._len]==lrg;
+ _adj._len++;
+ }
+
+ // More registers than neighbors
+ boolean lowDegree() { return nadj() < _mask.size(); }
+
+ LRG( short lrg, FunNode fun ) { _lrg = lrg; _reg = -1; _fun = fun; }
+
+ boolean leader() { return _leader == null; }
+
+ // Fast-path Find from the Union-Find algorithm
+ LRG find() {
+ if( _leader==null ) return this; // I am the leader
+ if( _leader._leader==null ) // I point to the leader
+ return _leader;
+ return _rollup();
+ }
+ // Slow-path rollup of U-F
+ private LRG _rollup() {
+ LRG ldr = _leader._leader;
+ // Roll-up
+ while( ldr._leader!=null ) ldr = ldr._leader;
+ LRG l2 = this;
+ while( l2 != ldr ) {
+ LRG l3 = l2._leader;
+ l2._leader = ldr;
+ l2 = l3;
+ }
+ return ldr;
+ }
+
+ // Union `this` and `lrg`, keeping the lower numbered _lrg.
+ // Includes a number of fast-path cutouts.
+ LRG union( LRG lrg ) {
+ assert leader();
+ if( lrg==null ) return this;
+ lrg = lrg.find();
+ if( lrg==this ) return this;
+ return _lrg < lrg._lrg ? _union(lrg) : lrg._union(this);
+ }
+ // Union `this` and `lrg`, folding together all stats.
+ private LRG _union( LRG lrg ) {
+ assert _fun == lrg._fun;
+ // Set U-F leader
+ lrg._leader = this;
+ // Fold together stats
+ if( _machDef==null ) {
+ _machDef = lrg._machDef;
+ } else if( lrg._machDef!=null ) {
+ if( _machDef != lrg._machDef ) _multiDef=true;
+ if( _1regDefCnt==0 )
+ _machDef = lrg._machDef;
+ else if( _machDef==lrg._machDef )
+ _1regDefCnt--;
+ }
+ _1regDefCnt += lrg._1regDefCnt;
+ _multiDef |= lrg._multiDef;
+
+ if( _machUse==null ) {
+ _machUse = lrg._machUse;
+ _uidx = lrg._uidx;
+ } else if( lrg._machUse!=null ) {
+ if( _machUse != lrg._machUse ) _multiUse=true;
+ if( _1regUseCnt==0 ) {
+ _machUse = lrg._machUse;
+ _uidx = lrg._uidx;
+ }
+ else if( _machUse==lrg._machUse )
+ _1regUseCnt--;
+ }
+ _1regUseCnt += lrg._1regUseCnt;
+ _multiUse |= lrg._multiUse;
+
+ // Fold deepest Split
+ _splitDef = deepSplit(_splitDef,lrg._splitDef);
+ _splitUse = deepSplit(_splitUse,lrg._splitUse);
+
+ // Fold together masks
+ RegMask mask = _mask.and(lrg._mask);
+ if( mask==null )
+ mask = _mask.copy().and(lrg._mask);
+ _mask = mask;
+ return this;
+ }
+
+ private static MachConcreteNode deepSplit( MachConcreteNode s0, MachConcreteNode s1 ) {
+ return s0==null || (s1!=null && s0.cfg0().loopDepth() < s1.cfg0().loopDepth()) ? s1 : s0;
+ }
+
+ // Record any Mach def for spilling heuristics
+ LRG machDef( MachNode def, boolean size1 ) {
+ if( _machDef!=null && _machDef!=def )
+ _multiDef = true;
+ if( _machDef==null || size1 )
+ _machDef = def;
+ if( size1 )
+ _1regDefCnt++;
+ if( def instanceof SplitNode split )
+ _splitDef = deepSplit(_splitDef,split);
+ return this;
+ }
+
+ // Record any Mach use for spilling heuristics
+ LRG machUse( MachNode use, short uidx, boolean size1 ) {
+ if( _machUse!=null && _machUse!=use )
+ _multiUse = true;
+ if( _machUse==null || size1 )
+ { _machUse = use; _uidx = uidx; }
+ if( size1 )
+ _1regUseCnt++;
+ if( use instanceof SplitNode split )
+ _splitUse = deepSplit(_splitUse,split);
+ return this;
+ }
+
+ boolean hasSplit() { return _splitDef != null || _splitUse != null; }
+ short size() { return _mask.size(); }
+ boolean size1() { return _mask.size1(); }
+
+
+ void selfConflict( Node def ) {
+ if( _selfConflicts == null ) _selfConflicts = new IdentityHashMap<>();
+ _selfConflicts.put(def,"");
+ }
+
+
+ // Record intersection of all register masks.
+ // True if still has registers
+ boolean and( RegMask mask ) {
+ RegMask mask2 = mask.and(_mask);
+ if( mask2==null )
+ mask2 = _mask.copy().and(mask);
+ _mask = mask2;
+ return !_mask.isEmpty();
+ }
+ // Remove this singular register
+ // True if still has registers
+ boolean clr( int reg ) {
+ if( _mask.clr(reg) ) return true;
+ _mask = _mask.copy(); // Need a mutable copy
+ return _mask.clr(reg);
+ }
+ // Subtract mask (AND complement)
+ // True if still has registers
+ boolean sub( RegMask mask ) {
+ RegMask mask2 = _mask.sub(mask);
+ if( mask2==null )
+ mask2 = _mask.copy().sub(mask);
+ _mask = mask2;
+ return !_mask.isEmpty();
+ }
+
+ @Override public String toString() { return toString(new SB()).toString(); }
+
+ public SB toString( SB sb ) {
+ sb.p("V").p(_lrg);
+ if( _mask!=null ) _mask.toString(sb);
+ return sb;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java
new file mode 100644
index 00000000..252b5bf0
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.type.TypeStruct;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// In-memory linking
+public class LinkMem {
+ final CodeGen _code;
+ LinkMem( CodeGen code ) { _code = code; }
+
+ public CodeGen link(Encoding enc) {
+ // Patch external calls internally
+ enc.patchGlobalRelocations();
+
+ // Place constant pool after code, static data after constants
+ int code = 0; // Code start
+ int cpool = (enc._bits .size() + code + 15) & -16; // pad to 16
+ int sdata = (enc._cpool.size() + cpool + 15) & -16;
+
+
+ // Patch local ops, e.g. loading float constants from the constant pool
+ for( Node op : enc._bigCons.keySet() ) {
+ Encoding.Relo relo = enc._bigCons.get(op);
+ int target = enc._data.address(enc._data.object(relo._t),cpool,sdata);
+ ((RIPRelSize)relo._op).patch(enc, relo._opStart, enc.opLen(relo._op), target - relo._opStart);
+ }
+
+ enc._data.link(cpool,sdata);
+ return _code;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java
new file mode 100644
index 00000000..c3b603e5
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java
@@ -0,0 +1,265 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.codegen.IterPeeps.WorkList;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.Utils;
+import java.util.*;
+
+public abstract class ListScheduler {
+
+ // eXtra per-node stuff for scheduling.
+ // Nodes are all block-local, and projections fold into their parent.
+ private static final IdentityHashMap<Node,XSched> XS = new IdentityHashMap<>();
+ private static class XSched {
+
+ Node _n; // Node this extra is for
+ int _bcnt; // Not-ready not-scheduled inputs
+ int _rcnt; // Ready not-scheduled inputs
+ boolean _ruse; // Node IS a "remote use", uses a other-block value
+ boolean _rdef; // Node IS a "remote def" of a value used in other block
+ boolean _single; // Defines a single register, likely to conflict
+
+ static final Ary<XSched> FREE = new Ary<>(XSched.class);
+ static void alloc(CFGNode bb, Node n) {
+ XSched x = FREE.pop();
+ XS.put(n, (x==null ? new XSched() : x).init(bb,n));
+ }
+ void free() { _n = null; FREE.push(this); }
+
+ static XSched get(Node n) { return n==null ? null : XS.get(n instanceof ProjNode && !(n.in(0) instanceof CFGNode) ? n.in(0) : n); }
+
+ private XSched init(CFGNode bb, Node n) {
+ _n = n;
+ _bcnt = 0;
+ _ruse = _rdef = _single = false;
+
+ // Count block-locals and remote inputs
+ if( !(n instanceof PhiNode) )
+ for( Node def : n._inputs )
+ if( def != null && def != bb ) {
+ if( def.cfg0()==bb ) _bcnt++; // Raise block-local input count
+ else if( !def.isMem() ) _ruse = true; // Remote register user
+ }
+ // Count remote outputs and allowed registers
+ if( n instanceof MultiNode )
+ for( Node use : n._outputs )
+ computeSingleRDef(bb,use);
+ else
+ computeSingleRDef(bb,n);
+
+ if( n instanceof NewNode || n instanceof CallNode )
+ bb.fun()._hasCalls = true;
+ return this;
+ }
+
+ private void computeSingleRDef(CFGNode bb, Node n) {
+ // See if this is 2-input, and that input is single-def
+ if( n instanceof MachNode mach && mach.twoAddress() != 0 ) {
+ XSched xs = XS.get(n.in(mach.twoAddress()));
+ if( xs != null )
+ _single = xs._single;
+ }
+ // Visit all outputs
+ RegMaskRW rmask = !_single && n instanceof MachNode mach && mach.outregmap() != null ? mach.outregmap().copy() : null;
+ for( Node use : n._outputs ) {
+ // Remote use, so this is a remote def
+ if( use!=null && use.cfg0()!=bb ) _rdef = true;
+ // And mask between n's def mask and all n's users masks'
+ if( use instanceof MachNode mach && rmask != null )
+ for( int i=1; i<use.nIns(); i++ )
+ if( use.in(i)==n )
+ rmask.and(mach.regmap(i));
+ }
+ // Defines in a single register
+ _single |= rmask!=null && rmask.size1();
+ }
+
+ // If _bcnt==0, declare ready; move user bcnts into rcnts.
+ boolean isReady() {
+ if( _bcnt > 0 || _rcnt > 0 ) return false;
+ if( _n instanceof MultiNode )
+ for( Node use : _n._outputs )
+ if( use instanceof ProjNode ) readyUp(use);
+ // anti-dep from a multireg directly
+ else _readyUp(use);
+ else
+ readyUp(_n);
+ return true;
+ }
+ private static void readyUp(Node n) {
+ for( Node use : n._outputs )
+ _readyUp(use);
+ }
+ private static void _readyUp(Node use) {
+ XSched xs = get(use);
+ if( xs!=null && !(use instanceof PhiNode) )
+ { assert xs._bcnt>0; xs._bcnt--; xs._rcnt++; }
+ }
+
+ boolean decIsReady() {
+ assert _rcnt > 0;
+ _rcnt--;
+ return isReady();
+ }
+ }
+
+
+ // List schedule every block
+ public static void sched( CodeGen code ) {
+ sched(code._cfg);
+ }
+
+ public static void sched(Ary<CFGNode> cfgs) {
+ XS.clear();
+ for( CFGNode bb : cfgs )
+ if( bb.blockHead() )
+ local(bb,false);
+ }
+
+ // Classic list scheduler
+ private static void local( CFGNode bb, boolean trace ) {
+ assert XS.isEmpty();
+ int len = bb.nOuts();
+
+ // Count block-locals, remote inputs and outputs
+ for( int i=0; i<len; i++ )
+ XSched.alloc(bb,bb.out(i));
+
+ // Nodes are ready if they are not used by other nodes in this block.
+ // Move ready up front.
+ int ready = 0;
+ for( int i=0; i<len; i++ )
+ if( XSched.get(bb.out(i)).isReady() ) // No block-local inputs?
+ bb._outputs.swap(ready++,i); // Move into ready set
+ assert len==0 || ready > 0;
+
+ // Classic list scheduler. Behind sched is scheduled. Between sched
+ // and ready have zero counts and are ready to schedule. Ahead of
+ // ready is undefined.
+ //
+ // As nodes are scheduled, the nodes they use decrement their bcnts[]. Once
+ // that gets to 0, all the nodes using it are scheduled, so it's ready.
+ int sched = 0;
+ while( sched < len ) {
+ assert sched < ready;
+ int pick = best(bb._outputs,sched,ready,trace); // Pick best
+ Node best = bb._outputs.swap(pick,sched++); // Swap best to head of ready and move into the scheduled set
+ // Lower ready count of users, and since projections are ready as
+ // soon as their multi is ready, lower those now too.
+ for( Node use : best._outputs )
+ if( use instanceof ProjNode )
+ for( Node useuse : use._outputs )
+ ready = ready(bb,useuse,ready);
+ else
+ ready = ready(bb,use,ready);
+ }
+
+ // Reset for next block
+ for( XSched x : XS.values() )
+ x.free();
+ XS.clear();
+ }
+
+ private static int ready(CFGNode bb, Node use, int ready) {
+ if( use!=null && use.in(0)==bb && !(use instanceof PhiNode) && XSched.get(use).decIsReady() )
+ bb._outputs.set(ready++, use); // Became ready, move into ready set
+ //bb._outputs.swap(ready++,use); // Became ready, move into ready set
+ return ready;
+ }
+
+
+ // Pick best between sched and ready.
+ private static int best( Ary<Node> blk, int sched, int ready, boolean trace ) {
+ int pick = sched;
+ Node p = blk.at(pick);
+ int score = score(p);
+ if( trace ) { System.out.printf("%4d N%d ",score,p._nid); System.out.println(p); }
+ for( int i=sched+1; i<ready; i++ ) {
+ Node n = blk.at(i);
+ int nscore = score(n);
+ if( trace ) { System.out.printf("%4d N%d ",nscore,n._nid); System.out.println(n); }
+ if( nscore > score )
+ { score = nscore; pick = i; p = n; }
+ }
+ if( trace ) { System.out.printf("Pick: N%d %s\n\n",p._nid,p); }
+ return pick;
+ }
+
+ // Highest score wins. Max at 1000, min at 10, except specials.
+ private static final int[] CNT = new int[3];
+ static int score( Node n ) {
+ if( n instanceof ProjNode ) return 1001; // Pinned at block entry
+ if( n instanceof CProjNode ) return 1001; // Pinned at block entry
+ if( n instanceof PhiNode ) return 1000;
+ if( n instanceof CFGNode ) return 1; // Pinned at block exit
+
+ int score = 500;
+ // Ideal nodes ignore register pressure scheduling
+ if( !(n instanceof MachNode) )
+ return score;
+
+ // Register pressure local scheduling: avoid overlapping specialized
+ // registers usages.
+
+ // Subtract 10 (delay) if this op forces a live range to exist that
+ // cannot be resolved immediately; i.e., live count goes up. Scale to
+ // 20 if multi-def.
+
+ // Subtract 100 if this op forces a single-def live range to exist
+ // which might conflict on the same register with other live ranges.
+ // Defines a single register based on def & uses, and the output is not
+ // ready. Scale to 200 for multi-def.
+ CNT[1]=CNT[2]=0;
+ XSched xn = XSched.get(n);
+ // If defining a remote value, just generically stall alot. Value is
+ // used in a later block, can we delay until the end of this block?
+ if( xn._rdef ) score = 200; // If defining a remote value, just generically stall alot
+ boolean flags = false;
+ if( n instanceof MultiNode ) {
+ for( Node use : n._outputs )
+ flags |= singleUseNotReady( use, xn._single );
+ } else
+ flags |= singleUseNotReady( n, xn._single );
+ score += -10 * Math.min( CNT[1], 2 );
+ score += -100 * Math.min( CNT[2], 2 );
+ if( flags ) return 10;
+
+ // Add 10 if this op ends a single-def live range, add 20 for 2 or more.
+ // Scale to 100,200 if the single-def is also single-register.
+ CNT[1]=CNT[2]=0;
+ for( int i=1; i<n.nIns(); i++ ) {
+ XSched xd = XSched.get( n.in(i) );
+ if( xd != null && n.in(i).nOuts()==1 )
+ CNT[xd._single || ((MachNode)n).regmap(i)!=null && ((MachNode)n).regmap(i).size1() ? 2 : 1]++;
+ }
+ score += 10 * Math.min( CNT[1], 2 );
+ score += 100 * Math.min( CNT[2], 2 );
+
+ // Nothing special
+ assert 10 <= score && score <= 990;
+ return score;
+ }
+
+ // Single use, and that use is not ready to fire once this one does. If
+ // true, stalling 'n' might reduce 'n's lifetime. Stall by 1 (2 if single)
+ // per not-ready.
+
+ // If CFG user in this block, it must be last, so definitely stall
+ // single-def (which stalls flags on x86,arm until the jmp).
+ private static boolean singleUseNotReady( Node n, boolean single ) {
+ // If n can rematerialize, assume allocator will split into private uses
+ // as needed. No impact on local scheduling.
+ if( n.nOuts()>1 && n instanceof MachNode mach && mach.isClone() )
+ return false;
+ for( Node use : n.outs() ) {
+ XSched xu = XSched.get(use);
+ if( xu != null && xu._n instanceof CFGNode ) return true;
+ if( xu != null && xu._bcnt > 0 )
+ CNT[single ? 2 : 1]++; // Since bcnt>0 or CFG, stall until user is more ready
+ }
+ return false;
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/Machine.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Machine.java
new file mode 100644
index 00000000..3eee8fc3
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Machine.java
@@ -0,0 +1,127 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.type.TypeInteger;
+
+abstract public class Machine {
+ // Human readable machine name. Something like "x86-64" or "arm" or "risc5"
+ public abstract String name();
+
+ // Default opcode size for printing; 4 bytes for most 32-bit risc chips
+ public int defaultOpSize() { return 4; }
+
+ // Human-readable name for a register number, e.g. "RAX" or "R0"
+ public abstract String[] regs();
+
+ // Create a split op; any register to any register, including stack slots.
+ // The FunNode holds the stack layout after RegAlloc, which in turn
+ // dictates the stack slot (the frame offset) for stack splits.
+ public abstract SplitNode split( LRG lrg, String kind, byte round );
+
+ // List of caller-save registers, as a 64bit mask
+ public abstract long callerSave();
+ public long callerSave(String conv) { return callerSave(); }
+
+ // List of never-save registers, e.g. RSP or a ZERO register if you have one
+ public abstract long neverSave();
+
+ // Call Argument Mask. Passed in the function signature and argument
+ // number (2-based; 0 is for control and 1 for memory). Also passed in a 0
+ // for the function itself, or for *outgoing* calls, the maximum stack slot
+ // given to the incoming function arguments (stack slots reserved for
+ // incoming arguments).
+ public abstract RegMask callArgMask(TypeFunPtr tfp, int arg, int maxArgSlot);
+ public RegMask callArgMask(TypeFunPtr tfp, int arg, int maxArgSlot, String conv) { return callArgMask(tfp,arg,maxArgSlot); }
+
+ // Return register mask, based on signature (GPR vs FPR)
+ public abstract RegMask retMask(TypeFunPtr tfp);
+
+ // Return PC register
+ public abstract int rpc();
+
+ // Return a MachNode unconditional branch
+ public abstract CFGNode jump();
+
+ // Instruction select from ideal nodes
+ public abstract Node instSelect( Node n );
+
+ /** Stack Slot Numbering
+
+RA gives all registers a number, starting at 0. Stack slots continue this
+numbering going up from the last register number. Common register numbers are
+0-15 for GPRs, 16-31 for FPRs, 32 for flags, and stack slots starting at
+register#33 going up. These numbers are machine-specific, YMMV, etc; e.g. RPC
+is only in stack slot 0 on X86; other cpus start with the rpc in a register
+which may spill into any generic spill slot.
+
+For ease of reading and printing, stack slots start again at slot#0 - but
+during RA they are actually biased by CPU.MAX_REG.
+
+Stack Layout during Reg Alloc:
+
+ Slot Value Description
+ ---- ------- -----------
+MAXREG+ R+1 argR maxArgSlot(calleR tfp), can be 0
+ ...
+MAXREG+ 1 arg0
+ ---- -- Caller ------
+MAXREG+ 0 RPC
+MAXREG+ R+E+N PAD optional alignment
+
+MAXREG+ + Spills Caller Frame Space; N spills
+MAXREG+ R+E+1 Spills
+
+MAXREG+ R+E argE maxArgSlot(calleE tfp), can be 0
+ ...
+MAXREG+ R+2 arg0
+ ---- -- Callee -------
+ ...
+
+Slots defined by maxArgSlot do not have to contain the actual passed argument;
+they can be "shadow slots". If they exist, they are property of the frame /
+FunNode where the TFP comes from.
+
+For a given calleR, there are generally many calleE's and they all share the
+low-range stack slots.
+
+Example:, Win64, main(int arg) { calloc(1,size); }
+
+ Post-Warp: SP+56 shadow-main-arg
+Pre-alloc Post-Alloc SP+48 RPC
+--- ------- ---------- -------------
+40 spill#3 SP+40 SP+40 spill#3
+39 spill#2 SP+32 ""
+38 spill#1 SP+24 ""
+37 spill#0 SP+16 "" spill#0
+36 shadow-calloc#size SP+ 8 SP+ 8 shadow-calloc#size
+35 shadow-calloc#1 SP+ 0 SP+ 0 shadow-calloc#1
+--- --- FRAME BASE ---------- ---- FRAME BASE -------
+34 shadow-main-arg SP+56
+33 RPC SP+48
+
+
+ */
+
+
+ // Maximum stack slot (or 0) for the args in this TFP. This will include
+ // shadow slots if defined in the ABI, even if all arguments are passed in
+ // registers.
+ public abstract short maxArgSlot(TypeFunPtr tfp);
+ public short maxArgSlot(TypeFunPtr tfp, String conv) { return maxArgSlot(tfp); }
+
+ // Calling convention used for external C symbols.
+ public String cCallingConv(String callingConv) { return callingConv; }
+
+ // Only the declared C result width describes the physical return register.
+ // Simple functions already return canonical full-width integer values.
+ protected static TypeInteger cReturn(ProjNode prj) {
+ if( prj._idx==2 && prj.in(0) instanceof CallEndNode cend &&
+ cend.call().fptr()._type instanceof TypeFunPtr tfp && tfp.isConstant() &&
+ CodeGen.CODE.externFunc(tfp.fidx())!=null &&
+ tfp.ret() instanceof TypeInteger ret && ret.log_size()<3 )
+ return ret;
+ return null;
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/Module.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Module.java
new file mode 100644
index 00000000..a5d85dbc
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Module.java
@@ -0,0 +1,98 @@
+package com.seaofnodes.simple.codegen;
+
+
+/***
+ Modules!?!?!?
+
+ Goal: under no circumstances can you load from an uninitialized variable
+ (although you can see default inits for fields that have defaults).
+ Goal: nested tree-structured name spaces, to control complexity.
+ Goal: field decl order within a file is fairly flexible.
+
+ A Module a root-level SMP file, and the full file path must be provided on
+ the command line. All other Simple structs are relative to this file.
+
+ Example: "simple /user/Dave/hello.smp"
+
+ Defines a module "hello" in directory "/user/Dave", compiles and produces
+ a `hello` or `hello.exe` output file, and runs it.
+
+ A SMP file has some <finit> file-init code which is static-global.
+ This code is executed on "first touch" of the file (defined below).
+
+ Example: hello.smp contains:
+ sys.io.p("Hello World!");
+ On first-touch, will print "Hello World!".
+
+ A SMP file is a lexical scope, and code inside it can reference those names.
+
+ Example: hello.smp contains:
+ val greet = "Hello World!"; // Declare file-scoped variable
+ sys.io.p(greet); // Reference file-scoped variable
+
+ Static code is NOT allowed to have forward-references AND be executed
+ before the <finit> finishes initializing all variables. After var
+ initialization, a singleton instance of the implied file struct is made,
+ and then <finit> code can call anything.
+
+ Example:
+ sys.io.p(greet); // Illegal forward reference
+ val greet = "Oops!";
+
+ Example:
+ val delay = { -> greet }; // Legal forward ref, but cannot execute (yet)
+ val greet = "Hello World!"; // Ref is resolved
+ sys.io.p(delay()); // Allowed to call delay()
+ val nprimes = { N ->
+ // compute number of primes < N
+ }
+ // Can otherwise execute any code in a <finit>
+ sys.io.p(nprimes(100));
+
+ Inside a directory, you can have sibling SMP files:
+ File hello.smp contains:
+ sys.io.p(I9A.GREETING); // File hello references sibling file I9A static globals
+ File I9A.smp contains:
+ val GREETING = translate("Hello World!");
+ val translate = { str msg -> ... };
+
+ <finit> code cannot reference sibling files (nor child files, see below)
+ until all static variables are initialized. This prevents cyclic
+ initialization bugs, where file A uses vars from file B and vice-versa.
+
+ File Chicken.smp contains:
+ val lay = Egg.hatch; // Illegal, defining var 'lay' after calling sibling Egg
+ File Egg.smp contains:
+ val hatch = Chicken.lay; // Illegal
+
+ You can make cross references, if they get delayed until after all fields
+ are declared.
+
+ File Chicken.smp contains:
+ int lay = 2;
+ lay = Egg.egg;
+
+ File Egg.smp contains:
+ int egg = 3;
+ egg = Chicken.lay;
+
+ The problem here, of course, is that you need to touch one of Chicken or
+ Egg first.
+
+
+ Inside a SMP file, you can define structs; structs are also a lexical
+ scope. Bare code in the struct is part of the struct's <init> and only
+ runs when an instance is created. Structs do not have static <clinit> (nor
+ <finit>). Structs <init> cannot have any forward references, and locally
+ declared functions cannot have both forward references and be executed (or
+ passed along to a possibly-executing function).
+
+ In the same directory as a SMP file, Simple allows other directories with
+ names matching SMP files. Inside these dirs can be more SMP files that are
+ lexically nested, similar to embedded structs.
+
+
+*/
+
+public abstract class Module {
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/Opto.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Opto.java
new file mode 100644
index 00000000..aa997204
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Opto.java
@@ -0,0 +1,331 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.codegen.IterPeeps.WorkList;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Ary;
+
+import java.util.BitSet;
+
+// Optimistic Algorithm
+abstract public class Opto {
+
+ // Optimistic analysis - extended SCCP
+
+ // Start all Types at TOP, and propagate changes until stable. Unlike a
+ // normal Sparse Conditional Constant Propagation, this version is
+ // interprocedural, computing effectively CFA0 in linear time using the
+ // normal SCCP algorithm.
+
+ // This algo also builds a Call Graph; as Calls become reachable (CONTROL
+ // not TOP nor XCONTROL), they "link" against the reaching function
+ // pointers - adding edges from the Call to the Fun, and from the Return to
+ // the CallEnd. This allows the normal algorithm to use the normal edge
+ // propagation rules as function argument types stabilize. In the end, we
+ // get a fairly precise set of Call Graph edges between CallNodes and
+ // FunNodes.
+ public static void opto(CodeGen code) {
+ // Reset Types before running the worklist algorithm
+ Ary<Type> oldTypes = new Ary<>(Type.class);
+ WorkList<Node> deps = new WorkList<>();
+ WorkList<Node> changes = new WorkList<>();
+ Ary<Node> modes = new Ary<>(Node.class);
+ resetToTop(code,oldTypes,modes,deps);
+ unlinkStart(code);
+
+ // OPTIMISTIC INTERPROCEDURAL SCCP
+ sccp(code, oldTypes, changes, deps);
+
+ // Settle unresolved modes using SCCP's int/float evidence. Some
+ // computes wait at TOP until a mode is known; others already return
+ // BOTTOM and stay there. Resume SCCP only on the affected nodes;
+ // types are not reset, and any further changes must still fall.
+ resolveNumericModes(code,modes);
+
+ if( !code._iter._work.isEmpty() )
+ sccp(code, oldTypes, changes, deps);
+ Node dep;
+ while( (dep=deps.pop())!=null ) code.add(dep);
+
+ // After SCCP add everything changed to the worklist and see if
+ // peepholes can make more progress.
+ moveChangesToWorklist(code, oldTypes, changes);
+ }
+
+ // Reset Types before running the worklist algorithm
+ private static void resetToTop(CodeGen code, Ary<Type> oldTypes, Ary<Node> modes, WorkList<Node> deps ) {
+ code._start.walk( x -> {
+ // Opto ends Parm.inProgress(), enabling constant folding even
+ // when a parameter's type was already constant before SCCP.
+ if( x instanceof ParmNode && x._type.isHighOrConst() ) deps.push(x);
+ // SCCP adds call edges and default arguments, not numeric
+ // operations. Gather unresolved modes on this required walk.
+ if( x instanceof ModeNode mode && mode.mode()==0 ) modes.push(x);
+ oldTypes.setX(x._nid,x._type); // Compare the final SCCP type with Iter's type
+ x._type = Type.TOP; // Reset all Nodes to TOP
+ code._iter.add(x); // Visit everybody at least once; most Nodes produce better than TOP
+ return null;
+ } );
+ }
+
+
+ // Only entry initializers start with unknown callers. Discover every
+ // other function through calls or escaping values during SCCP.
+ private static void unlinkStart(CodeGen code) {
+ // CodeGen owns these until FinalizeIR; linkStart may need them again.
+ for( CompUnit cu : code._compunits.values() ) {
+ ProjNode mem = cu._start.proj(1);
+ if( mem!=null ) code.keepUntilFinalize(mem);
+ }
+
+ // Iterate all functions.
+ // TODO: use CodeGen _link list
+ for( Node stop : code._stop._inputs ) {
+ for( int i=0; i<stop.nIns(); i++ ) {
+ ReturnNode ret = (ReturnNode)stop.in(i);
+ FunNode fun = ret.fun();
+ if( !fun.isRoot() &&
+ fun.in(1) instanceof StartNode ) {
+ fun.removeDeadPath(1);
+ // Unhook from Stop without treating the ReturnNode as
+ // DEAD. It stays in limbo until Opto links when a caller
+ // is discovered (or not, and the function is really dead).
+ stop._inputs.del(i--);
+ ret.delUse(stop);
+ }
+ // Unlink the existing conservative call linkages.
+ for( int j=1; j<fun.nIns(); j++ )
+ if( fun.in(j) instanceof CallNode call )
+ call.unlink(fun,j--);
+ }
+ }
+ }
+
+ // As part of building a CallGraph, when opto finds a function ptr flowing
+ // into a Call, link the Call and Fun.
+ private static void linkCG(CodeGen code, TypeFunPtr tfp, CallNode call) {
+ if( tfp.nargs() != call.nargs() ) return; // Error calls hit this
+ int[] fidxs = tfp.fidxs();
+ // An unresolved forward call can still carry the infinite, inferred
+ // function set into Opto. There is no finite call graph to link yet;
+ // type checking will report the unresolved reference afterwards. This
+ // can yet be valid if the call is eventually dead.
+ // `if( false ) frefCall(args);`
+ if( XInt.isHigh(fidxs) ) return;
+ for( int fidx = XInt.next(fidxs,0); fidx >=0; fidx = XInt.next(fidxs,fidx) ) {
+ // unlinkStart deliberately leaves uncalled functions in a
+ // temporarily dead limbo. A newly discovered call revives them;
+ // do not use the cleanup lookup which removes dead linker entries.
+ FunNode fun = code._link(fidx);
+ // null here means an external function; i.e. this Call
+ // calls to an outside library and all its arguments escape.
+ if( fun != null && !call.linked(fun) ) {
+ call.link( fun );
+ code._iter.add(fun);
+ code._iter.addAll(fun._outputs);
+ }
+ }
+ }
+
+ // Start added some FIDXs, the same functions now can be reached from start
+ // and called by anybody. Add the CG edges.
+ private static void linkStart( CodeGen code, TypeTuple tt ) {
+ TypeMem tmem = (TypeMem)tt._types[1];
+ int[] fidxs = tmem._escFs;
+ if( XInt.isHigh(fidxs) ) {
+ // A scalar BOTTOM can flow into memory while compiling an error
+ // program. It exposes all public functions, not private fields.
+ for( FunNode fun : code._linker )
+ if( fun != null && code.publicFIDX(fun.sig().fidx()) )
+ linkStart(code,fun);
+ return;
+ }
+ for( int fidx = XInt.next(fidxs,0); fidx >=0; fidx = XInt.next(fidxs,fidx) ) {
+ FunNode fun = code._linker.atX(fidx);
+ if( fun==null ) assert code._externFunc.containsKey(fidx);
+ else linkStart(code,fun);
+ }
+ }
+
+ // Function is now reachable by unknown callers through Start. The
+ // default inputs must therefore use conservative declared types.
+ static void linkStart( CodeGen code, FunNode fun ) {
+ assert !fun.isDead();
+ StartCUNode start = fun._compunit._start;
+ if( !fun.unknownCallers() ) {
+ assert !start.isDead();
+ code.keepInCompUnit(fun);
+ // Function is reachable by any *remote* caller who gets the pointer!
+ fun.insertDef(1,start);
+ code._iter.add(fun);
+ for( Node p : fun._outputs )
+ if( p instanceof ParmNode parm ) {
+ Node defalt = parm._idx==1 ? start.proj(1) : ConstantNode.seed(parm._declaredType).peephole();
+ parm.insertDef(1,defalt);
+ code.add(parm);
+ }
+ }
+ }
+
+
+ // The core SCCP algorithm
+ private static void sccp( CodeGen code, Ary<Type> oldTypes, WorkList<Node> changes, WorkList<Node> deps ) {
+ // Iter workList is empty at this point
+ // OPTIMISTIC PASS
+ Node n;
+ int count = 0; // Debug counter
+ while( (n = code._iter._work.pop()) != null ) {
+ if( n.isDead() ) continue;
+ count++;
+ Type oval = n._type, nval = n.compute();
+ if( n instanceof CLInitNode init ) init.link();
+ // Remember possible peephole work without walking the graph again.
+ // SCCP can still fall back to the old type, so filter at the end.
+ if( nval!=oldTypes.atX(n._nid) || n.isUnlocked() ) changes.push(n);
+ if( oval == nval ) continue;
+ assert oval.isa(nval) : "Non-monotonic SCCP: "+n.getClass().getSimpleName()+"#"+n._nid+" "+n+" old="+oval+" new="+nval; // Types start high and always fall
+ Type pesiVal = oldTypes.atX(n._nid);
+ assert pesiVal==null || nval.isa(pesiVal) : "SCCP below pessimistic type: "+n.getClass().getSimpleName()+"#"+n._nid+" "+n+" pessimistic="+pesiVal+" new="+nval; // Never fall worse than the pessimistic pass
+ n._type = nval;
+
+ // Now we have a series of stanzas where we lazily create the Call
+ // Graph - adding graph edges between every call site and called
+ // function, including external (unknown) callers. These edges are
+ // not in from the start because they are *too many*; we would get
+ // O(n^2) edges. So all along we have been treating them as
+ // "virtual" CFG edges - a FunNode taking a Start input is treated
+ // as if every call on the planet might call it. Now we make these
+ // CG edges *concrete*, adding them back as we discover that a call
+ // calls a particular function.
+
+ // If a TFP adds a new function input to a call, link to the new
+ // Fun. This adds new edges to the graph allowing argument values
+ // to flow from calls into functions. The added edges are
+ // effectively a refined Call Graph.
+ if( nval instanceof TypeFunPtr tfp ) {
+ for( Node use : n._outputs )
+ if( !use._type.isHigh() && use instanceof CallNode call && call.fptr() == n )
+ linkCG(code,tfp,call);
+ }
+
+ // Link a Call which becomes alive. Like above, adds new Call
+ // Graph edges to the graph.
+ if( n instanceof CallNode call && nval == Type.CONTROL && call.fptr()._type instanceof TypeFunPtr )
+ linkCG(code,call.tfp(),call);
+
+ // If an otherwise-dead function pointer escapes, any future linked
+ // caller might find and call it. Force the function to be alive
+ // and called by Start.
+ if( n instanceof StartNode )
+ linkStart(code,(TypeTuple)n._type);
+
+ // Since n._type changed, visit all output neighbors
+ code._iter.addAll(n._outputs);
+ // SCCP consumes these without running peepholes. Save them for
+ // the following Iter pass before moving clears the dependency list.
+ for( int i=0; i<n.nDeps(); i++ ) deps.push(n.dep(i));
+ n.moveDepsToWorklist(code._iter);
+ // Quadratic (expensive) small-step assert.
+ assert !CodeGen.expensiveAssert(count) || worklistCheck(code);
+ }
+ }
+
+ // Quadratic (expensive) fixed-point assert
+ public static boolean fixedPointCheck(CodeGen code) {
+ return fixedPointCheck(code,code._stop);
+ }
+ public static boolean fixedPointCheck(CodeGen code, Node root) {
+ root.walk( x -> {
+ Type xval = x.compute();
+ assert xval == x._type;
+ return null;
+ });
+ return true;
+ }
+
+ // Quadratic (expensive) small-step assert for SCCP, where changed nodes
+ // may already be on the worklist.
+ private static boolean worklistCheck(CodeGen code) {
+ code._midAssert = true;
+ Node root = code._start;
+ root.walk( x -> {
+ Type xval = x.compute();
+ assert xval == x._type || (x._type.isa( xval ) && code._iter._work.on( x ))
+ : "Non-monotonic or missed SCCP work: "+x.getClass().getSimpleName()+"#"+x._nid+" "+x+" old="+x._type+" new="+xval;
+ return null;
+ });
+ code._midAssert = false;
+ return true;
+ }
+
+ // Types can stay unchanged while SCCP edits edges or numeric modes. Such
+ // edits unlock the node from GVN. Revisit both kinds of change and their
+ // neighbors; distant dependencies consumed by SCCP were saved separately.
+ private static void moveChangesToWorklist(CodeGen code, Ary<Type> oldTypes, WorkList<Node> changes) {
+ Node n;
+ while( (n=changes.pop())!=null ) {
+ if( n.isDead() ) continue;
+ assert n.compute() == n._type; // Hit the fixed point.
+ Type old = oldTypes.atX(n._nid);
+ assert old==null || n._type.isa(old);
+ if( n._type!=old || n.isUnlocked() ) {
+ code.addAll(n);
+ code.addAll(n._outputs);
+ n.moveDepsToWorklist();
+ }
+ }
+ }
+
+ // Resolve modes left undecided by SCCP, following recursive inputs for
+ // evidence. Float wins because integer operands can be converted to float.
+ private static void resolveNumericModes(CodeGen code, Ary<Node> modes) {
+ final BitSet visited = new BitSet();
+ for( Node n : modes ) {
+ if( !n.isDead() && n instanceof ModeNode modeNode && modeNode.mode()==0 ) {
+ visited.clear();
+ visited.set(n._nid); // Inspect inputs, not Bool's integer result
+ int mode = 0;
+ for( int i=1; i<n.nIns(); i++ )
+ mode |= numericEvidence(n.in(i),visited);
+ // A closed recursive numeric component can have no remaining
+ // concrete evidence after SCCP, e.g. dead all-integer loops.
+ // With no float evidence, settle to integer.
+ modeNode.setMode((byte)((mode&2)==2 ? 2 : 1));
+ code.add(n);
+ code.addAll(n.outs());
+ }
+ }
+ }
+
+ // Gather numeric evidence through Phi and interprocedural return cycles.
+ // The visited set makes self- and mutually-recursive calls finite.
+ private static int numericEvidence(Node n, BitSet visited) {
+ if( n == null ) return 0;
+ if( visited.get(n._nid) ) return 0;
+ visited.set(n._nid);
+
+ Type t = n._type;
+ if( t instanceof TypeFloat ) return 2;
+ if( t instanceof TypeInteger ) return 1;
+ if( t!=Type.BOTTOM && !(t instanceof TypeTuple) )
+ return 0; // Something unrelated, like a ptr
+
+ int evidence = 0;
+ assert !(n instanceof ConvertNode); // No test case
+ switch( n ) {
+ case ModeNode modeNode when modeNode.mode() != 0 -> { return modeNode.mode(); }
+ case ModeNode modeNode -> {}
+ case PhiNode phi -> {}
+ case ReturnNode ret -> {}
+ case ProjNode proj when proj.in(0) instanceof CallEndNode cend -> {
+ for( int i=1; i<cend.nIns(); i++ )
+ evidence |= numericEvidence(cend.in(i),visited);
+ }
+ default -> { return 0; }
+ }
+ for( int i=1; i<n.nIns(); i++ )
+ evidence |= numericEvidence(n.in(i),visited);
+
+ return evidence;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
new file mode 100644
index 00000000..1218c956
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
@@ -0,0 +1,449 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.print.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.*;
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+// Parse a given file, and all its dependencies
+public abstract class ParseAll {
+
+ static void parse(CodeGen code, String src) {
+ Parser.TYPES.clear();
+ Parser.TYPES.putAll(Parser.INIT_TYPES);
+ if( src != null ) {
+ // No source file, just the source itself.
+ parseSource(code,code._srcName==null ? "Test" : code._srcName,src);
+ } else {
+ // Path from module root to source file
+ parsePath(code,code._srcName);
+ }
+ }
+
+ // Worklist of symbols to search and files to parse
+ private static final Ary<CompUnit> WORK = new Ary<>(CompUnit.class);
+
+ private static final Ary<CompUnit> NEEDS_LOAD = new Ary<>(CompUnit.class);
+
+ // Return a compilation unit (cached or new). If new, it does not necessarily
+ // need to be compiled.
+ public static CompUnit makeCUnit( CodeGen code, CompUnit par, String name ) throws IOException {
+ return makeCUnit(code,par,name,null,false);
+ }
+
+ private static CompUnit makeCUnit( CodeGen code, CompUnit par, String name, File obj ) throws IOException {
+ return makeCUnit(code,par,name,obj,obj!=null);
+ }
+
+ private static CompUnit makeCUnit( CodeGen code, CompUnit par, String name, File obj, boolean external ) throws IOException {
+ // Slashed file name? Move leading parts into parent CUs.
+ int idx = name.indexOf("/");
+ if( idx >= 0 ) {
+ String head = name.substring(0,idx);
+ CompUnit sub = makeCUnit(code,par,head,external ? externalObj(code,par,head) : null,external);
+ return makeCUnit(code,sub,name.substring(idx+1).intern(),obj,external);
+ }
+
+ String fname = par==null ? name : (par._fname + "/" + name).intern();
+ CompUnit cunit = code._compunits.get(fname);
+ if( cunit != null ) return cunit;
+ String cname = par==null ? name : (par._cname + "." + name).intern();
+ code._compunits.put(fname, cunit = external ? new CompUnit(obj,par,fname,cname,name) : new CompUnit(par,fname,cname,name));
+ return cunit;
+ }
+
+ // Parse a top-level string, used for e.g. testing
+ static void parseSource( CodeGen code, String src ) {
+ parseSource(code,"Test",src);
+ }
+
+ // Parse a top-level string with an explicit compilation-unit name.
+ static void parseSource( CodeGen code, String fname, String src ) {
+ CompUnit cunit = new CompUnit(fname,src);
+ code._compunits.put(cunit._fname,cunit);
+ parseAll(code,cunit);
+ }
+
+ // module_root/.../src.smp is source file name.
+ static void parsePath(CodeGen code, String fname) {
+ try {
+ // Split and build the parent-path down to the source file.
+ CompUnit cu = makeCUnit( code, null, fname );
+ // Compile this CompUnit
+ parseAll(code,cu);
+ } catch( IOException ioe ) {
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ private static void enqueue(CompUnit cu) {
+ if( cu._par!=null && (cu._par._src!=null || (cu._par._obj!=null && cu._par._obj.exists())) ) enqueue(cu._par);
+ if( cu._start==null && WORK.find(cu)==-1 ) WORK.add(cu);
+ }
+
+ private static void parseAll(CodeGen code, CompUnit cunit) {
+ WORK.clear(); // Cleanup from prior tests
+ NEEDS_LOAD.clear();
+ // Prime the pump with the first CompUnit
+ enqueue(cunit);
+ // Work through all unparsed compilation units and parse
+ while( !WORK.isEmpty() ) {
+ CompUnit cu = WORK.remove(0);
+ // No source code? Means we do not need to parse, but instead need to load the symbols as-if we parsed
+ if( cu._src==null || cu._src.isEmpty() )
+ loadDeps(code,cu); // Load recursive dependents
+ else
+ parseOne( code, cu ); // Parse
+ }
+
+ // Load all pre-compiled CompUnits
+ for( CompUnit cu : NEEDS_LOAD )
+ loadSummaries(code,cu);
+
+ // Many types will have been declared cyclically either internally or
+ // across multiple compilation units.
+
+ // Close over all recursive types, and upgrade TYPES
+ Ary<TypeStruct> ary = new Ary<>(TypeStruct.class);
+ for( Type t : Parser.TYPES.values() )
+ if( t instanceof TypeStruct ts )
+ ary.add(ts);
+ TypeStruct[] tss = Type.closeOver(ary.asAry(),Parser.TYPES);
+ for( TypeStruct ts : tss )
+ Parser.TYPES.put(ts._name,ts);
+ for( CompUnit cu : code._compunits.values() )
+ if( cu._ext==null )
+ cu._clz = (TypeStruct)Parser.TYPES.get(Parser.addClzPrefix(cu._cname));
+
+ // Walk over all Nodes, and upgrade the internal constants to the
+ // closed-over types.
+ code._stop.walk( (Node n) -> {
+ n.upgradeType(Parser.TYPES);
+ if( n instanceof FRefNode fref ) {
+ boolean alloc = fref._name.endsWith(".<new>");
+ String clzName = alloc
+ ? fref._name.substring(0,fref._name.length()-".<new>".length()).intern()
+ : fref._name;
+ TypeStruct clz = (TypeStruct)Parser.TYPES.get(clzName);
+ if( clz == null )
+ return null;
+ Node con;
+ if( alloc ) {
+ Field ctor = clz.field("<new>");
+ if( ctor == null || !(ctor._t instanceof TypeFunPtr tfp) ) {
+ String name = clzName.startsWith(Parser.CLZ)
+ ? clzName.substring(Parser.CLZ.length()) : clzName;
+ int dot = name.lastIndexOf('.');
+ throw Parser.error("Unknown struct type '"+(dot==-1 ? name : name.substring(dot+1))+"'",fref._loc);
+ }
+ FunNode fun = code.link(tfp);
+ if( fun == null ) return null;
+ con = new FunPtrNode(tfp,code._start,fun.ret()).peephole();
+ } else {
+ // Class pointer constant:
+ TypeMemPtr clzptr = TypeMemPtr.make((byte)2,clz,true);
+ con = code.con(clzptr);
+ }
+ // Replace and optimize
+ fref.addDef(con);
+ code.add(fref);
+ }
+ // Adds sort by NIDs, which just got shuffled
+ if( n instanceof AddNode add )
+ code.add(add);
+ // All imported bodies are loaded; link them before Iter checks types.
+ if( n instanceof CallNode call && !NEEDS_LOAD.isEmpty() )
+ code.add(call).idealize();
+ return null;
+ } );
+
+ // The complete public interface is now known. Keep this independent
+ // of later graph/linker liveness so SCCP computations remain local and
+ // monotonic.
+ code.freezePublicInterface();
+ }
+
+ // Parse one Simple source code file. Add all the FRefs produced to the worklist.
+ private static void parseOne( CodeGen code, CompUnit cunit ) {
+ cunit.setStart(code);
+ // Parse a source file, return missing external references
+ Ary<FRefNode> frefs = code.P.parse(cunit);
+
+ // Find all missing external references, or complain. This can trigger more parses.
+ while( !frefs.isEmpty() ) {
+ FRefNode fref = frefs.remove(0);
+ // Symbol not found in source code (or we would not be here).
+ // Search the module for the fref
+ try {
+ CompUnit xcunit = findCUnitModule(code,cunit,fref._name);
+ if( xcunit == null )
+ xcunit = findCUnitExternalSimple(code,fref._name);
+ if( xcunit != null ) {
+ // Replace the FRef with the discovered class name.
+ fref._name = Parser.addClzPrefix(xcunit._cname);
+ cunit.addDep(xcunit);
+ } else {
+ throw new RuntimeException("Undefined name '" + fref._name +"'");
+ }
+ } catch( IOException ioe ) {
+ throw new RuntimeException(ioe);
+ }
+ }
+ addRequiredTypes(code,cunit);
+ }
+
+ private static void addRequiredTypes(CodeGen code, CompUnit cunit) {
+ for( String typeName : code.P._requiredTypes ) {
+ // A forward use can have been resolved later in this source file.
+ if( Parser.TYPES.get(typeName) instanceof TypeStruct ts && !ts._fref ) continue;
+ try {
+ CompUnit xcunit = findRequiredType(code,cunit,typeName);
+ if( xcunit != null )
+ cunit.addDep(xcunit);
+ else
+ assert false : "expected to find required type";
+ } catch( IOException ioe ) {
+ throw new RuntimeException(ioe);
+ }
+ }
+ }
+
+ private static CompUnit findRequiredType(CodeGen code, CompUnit cunit, String typeName) throws IOException {
+ for( CompUnit cu = cunit; cu != null; cu = cu._par ) {
+ String symbol = typeName.startsWith(cu._cname+".")
+ ? typeName.substring(cu._cname.length()+1)
+ : typeName;
+ CompUnit xcunit = findCUnitModule(code,cu,symbol);
+ if( xcunit != null )
+ return xcunit;
+ }
+ return findCUnitExternalSimple(code,typeName);
+ }
+
+ // Load the public symbols for this CompUnit, and add dependent CompUnits to the NEEDS_LOAD list.
+ private static void loadDeps(CodeGen code, CompUnit cunit) {
+ // Load IR after parsing
+ if( NEEDS_LOAD.find(cunit) != -1 )
+ return; // Already on the to-be-loaded list?
+ for( CompUnit loaded : NEEDS_LOAD )
+ if( loaded._obj.equals(cunit._obj) ) return; // Another class in the same object.
+ try {
+ NEEDS_LOAD.add(cunit);
+ // Load dependent classes
+ ElfReader elf = code.getElf(cunit._obj);
+ elf.loadPublicSymbols();
+ // Register bundled definitions before resolving dependencies or IR.
+ for( String fname : elf._units )
+ makeCUnit(code,null,fname,cunit._obj);
+ for( String fname : elf._deps ) {
+ // Check CompUnit for being up to date
+ CompUnit sub = code._compunits.get(fname);
+ if( sub==null ) sub=makeCUnit(code,null,fname,depObj(code,cunit,fname));
+ // If not seen before or out of date, parse the new nested cunit
+ if( sub._clz == null && WORK.find(sub)==-1 && NEEDS_LOAD.find(sub)==-1 )
+ enqueue(sub);
+ }
+ } catch( IOException ioe ) {
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ private static File depObj(CodeGen code, CompUnit cunit, String fname) {
+ File obj = CompUnit.dependencyObj(fname,cunit._smp!=null);
+ if( obj == null )
+ throw new RuntimeException("Cannot find Simple dependency '"+fname+"'");
+ return cunit._smp!=null && obj.equals(new File(code._buildDir+"/"+fname+".o")) ? null : obj;
+ }
+
+ private static File externalObj(CodeGen code, CompUnit par, String name) {
+ String cname = par==null ? name : par._cname + "." + name;
+ ElfReader elf = code.findExternalSimple(Parser.addClzPrefix(cname));
+ return elf == null ? null : elf._file;
+ }
+
+ private static void loadSummaries(CodeGen code, CompUnit cu) {
+ ElfReader elf = code.getElf(cu._obj);
+ elf.loadPublicTypes(code);
+ cu._clz = elf._clz;
+ cu.setStart(code);
+ new ProjNode(cu._start,1,"$mem").init();
+ for( FunctionSummary summary : elf._functions ) summary.install(code,cu);
+ }
+
+ public static void loadCompUnit(CodeGen code, CompUnit cunit) {
+ // Expecting a previously compiled object file
+ assert cunit._obj != null;
+ // Extract published symbols and put them in the internal tables;
+ // cross-references will pick them up there.
+ ElfReader elf = code.getElf(cunit._obj);
+ if( elf._nodes!=null ) return;
+ StartCUNode oldStart = cunit._start;
+ StopCUNode oldStop = cunit._stop;
+ ProjNode oldMem = oldStart==null ? null : oldStart.proj(1);
+ if( oldMem!=null ) code.releaseReplacedNode(oldMem);
+ elf.loadSimple(code);
+ cunit._clz = (TypeStruct)Parser.TYPES.get(Parser.addClzPrefix(cunit._cname));
+ StartNode lstart = (StartNode)elf._nodes.at(0);
+ StopNode lstop = ( StopNode)elf._nodes.last();
+
+ // Add all new loaded functions to the linker table
+ for( Node n : lstop._inputs ) {
+ StopCUNode stop = (StopCUNode)n;
+ StartCUNode start = stop.start();
+ CompUnit cu = code._compunits.get(start._fname);
+ cu._start = start;
+ cu._stop = stop ;
+
+ for( Node ret : stop._inputs ) {
+ FunNode fun = ((ReturnNode)ret).fun();
+ FunNode old = code._link(fun.sig().fidx());
+ code.link(fun);
+ if( old!=null && old.summary()!=null && !old.isDead() ) {
+ old.ret().keep();
+ for( int i = 1; i<old.nIns(); i++ )
+ if( old.in(i) instanceof CallNode call ) {
+ call.unlink(old,i--);
+ call.link(fun);
+ } else if( old.in(i) instanceof CProjNode proj && proj.in(0) instanceof CLInitNode init ) {
+ init.unlink();
+ init.link();
+ i--;
+ }
+ old.ret().unkeep().subsume(fun.ret());
+ }
+ }
+ }
+
+ // Hook the new code into the existing code graph
+ for( Node lstopcu : lstop._inputs )
+ code._stop.addDef(lstopcu);
+ lstart.subsume(code._start);
+ if( oldStart!=null && oldStart!=cunit._start ) {
+ code.releaseReplacedNode(oldStart);
+ int idx = code._stop._inputs.find(oldStop);
+ if( idx!=-1 ) code._stop.delDef(idx);
+ oldStart.subsume(cunit._start);
+ }
+ // Saved types describe the library's original compilation. Repair
+ // only the newly loaded graph; ordinary dependencies carry changes
+ // into its callers without restarting whole-program analysis.
+ ProjNode startMem = cunit._start.proj(1);
+ if( startMem!=null ) code.keepUntilFinalize(startMem);
+ Iter.imports(code,elf._nodes);
+
+ // Add the new top-level loaded class
+ Parser.TYPES.put(cunit._clz._name,cunit._clz);
+ Field inst = cunit._clz.field(cunit._name);
+ if( inst != null && inst._t instanceof TypeFunPtr tfp && tfp._ret instanceof TypeMem mem )
+ Parser.TYPES.put(cunit._cname,mem._t);
+ }
+
+ // Symbol not found in source code (or we would not be here).
+ // Search the module for the fref
+ public static CompUnit findCompUnitOrThrow(CodeGen code, CompUnit cu, String symbol) {
+ CompUnit cu2 = findCompUnit(code,cu,symbol);
+ if( cu2 != null ) return cu2;
+ throw new RuntimeException("Undefined name '" + symbol +"'");
+ }
+
+ public static CompUnit findCompUnit(CodeGen code, CompUnit cu, String symbol) {
+ // Symbol not found in source code (or we would not be here).
+ // Search the module for the fref
+ try {
+ CompUnit local = findCUnitModule(code,cu,symbol);
+ if( local != null )
+ return local;
+ return findCUnitExternalSimple(code,symbol);
+ } catch( IOException ioe ) {
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ // Find the symbol in module, starting from cunit. This searches up the nested
+ // class chain and sideways 1 step, so that any prefix of the class chain can
+ // be skipped as a shortcut. Symbol can have dotted names, which then go
+ // down into classes. e.g.
+ // A/X/Y exists.
+ // A/B contains X.Y, which is a shortcut for A.X.Y
+
+ // However if A/B contains "Y", the search would be for A.B.Y then A.Y both of
+ // which do not exist.
+ private static CompUnit findCUnitModule( CodeGen code, CompUnit cunit, String symbol ) throws IOException {
+ if( cunit == null ) // Ran off top of module scope?
+ return null; // Failed search
+
+ // Check self
+ if( cunit._name.equals(symbol) )
+ return cunit;
+
+ // If symbol is itself nested (has embedded dots), move the leading part
+ // over to the CompUnit path. So e.g.:
+ // CompUnit{A.B}, Symbol="X.Y"
+ // becomes
+ // CompUnit{A.B.X}, Symbol="Y"
+ int dotIdx = symbol.indexOf(".");
+ if( dotIdx != -1 ) {
+ String pre = symbol.substring(0,dotIdx);
+ String rest = symbol.substring(dotIdx+1 );
+ String fname = cunit._fname + "/" + pre;
+ File f = new File( code._modDir + "/" + fname + ".smp" );
+ if( f.exists() ) {
+ CompUnit sub = makeCUnit(code,cunit,pre);
+ // Note that "sub" itself is not put on the to-be-parsed-list. We're
+ // referring to a child class and do not need to parse the middles.
+ CompUnit dcunit = findCUnitModule(code,sub,rest);
+ if( dcunit != null )
+ return dcunit; // Nested dot search found cunit
+ }
+ }
+
+ // Look in mod / fname / name / symbol.smp
+ String fname = cunit._fname + "/" + symbol;
+ File f = new File( code._modDir + "/" + fname + ".smp" );
+ if( f.exists() ) {
+ CompUnit sub = makeCUnit(code,cunit,symbol);
+ // If not seen before or out of date, parse the new nested cunit
+ if( sub._clz == null && WORK.find(sub) == -1 )
+ enqueue(sub);
+ return sub;
+ }
+
+ // Look up the scope
+ return findCUnitModule( code, cunit._par, symbol );
+ }
+
+ // Search external Simple objects only. C linkage uses explicit "C"
+ // declarations, which produce ExternNodes and are resolved by the linker.
+ public static CompUnit findCUnitExternalSimple( CodeGen code, String symbol ) {
+ String clzName = symbol.startsWith(Parser.CLZ) ? symbol : Parser.addClzPrefix(symbol);
+ String cname = clzName.substring(Parser.CLZ.length());
+ String fname = cname.replace('.','/');
+
+ CompUnit cunit = code._compunits.get(fname);
+ if( cunit != null )
+ return cunit;
+
+ ElfReader elf = code.findExternalSimple(clzName);
+ if( elf == null )
+ return null;
+
+ try {
+ cunit = makeCUnit(code,null,fname,elf._file);
+ } catch( IOException ioe ) {
+ throw new RuntimeException(ioe);
+ }
+ ElfReader typedElf = code.getElf(cunit._obj);
+ if( typedElf != null ) typedElf.loadPublicTypes(code);
+ TypeStruct clz = (TypeStruct)Parser.TYPES.get(clzName);
+ if( clz == null )
+ Parser.TYPES.put(clzName,clz = TypeStruct.make(clzName,true));
+ cunit._clz = clz;
+ if( WORK.find(cunit) == -1 && NEEDS_LOAD.find(cunit) == -1 )
+ enqueue(cunit);
+ return cunit;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java
new file mode 100644
index 00000000..48319006
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java
@@ -0,0 +1,14 @@
+package com.seaofnodes.simple.codegen;
+
+// This Node rip-relative encodings has sizes that vary by delta
+public interface RIPRelSize {
+ // delta is the distance from the opcode *start* to the target start. Each
+ // hardware target adjusts as needed. X86 measures from the opcode *end*
+ // and small jumps are 2 bytes, so they'll need to subtract 2.
+ byte encSize(int delta);
+
+ // Patch full encoding in. Used for both short and long forms. delta
+ // again is from the opcode *start*.
+ void patch( Encoding enc, int opStart, int opLen, int delta );
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
new file mode 100644
index 00000000..ac792988
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
@@ -0,0 +1,780 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.Type;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.IdentityHashMap;
+
+/**
+ * "Briggs/Chaitin/Click".
+ * Graph coloring.
+ * <p>
+ * Every def and every use have a bit-set of allowed registers.
+ * Fully general to bizarre chips.
+ * Multiple outputs fully supported, e.g. add-with-carry or combined div/rem or "xor rax,rax" setting both rax and flags.
+ * <p>
+ * Intel 2-address accumulator-style ops fully supported.
+ * Op-to-stack-spill supported.
+ * All addressing modes supported.
+ * Register pairs could be supported with some grief.
+ * <p>
+ * Splitting instead of spilling (simpler implementation).
+ * Stack slots are "just another register" (tiny stack frames, simpler implementation).
+ * Stack/unstack during coloring with a marvelous trick (simpler implementation).
+ * <p>
+ * Both bitset and adjacency list formats for the interference graph; one of
+ * the few times it's faster to change data structures mid-flight rather than
+ * just wrap one of the two.
+ * <p>
+ * Liveness computation and interference graph built in the same one pass (one
+ * fewer passes per round of coloring).
+ * <p>
+ * Single-register def or use live ranges deny neighbors their required
+ * register and thus do not interfere, vs interfering and denying the color
+ * and coloring time. Basically all function calls do this, but many oddball
+ * registers also, e.g. older div/mod/mul ops. (5x smaller IFG, the only
+ * O(n^2) part of this operation).
+ */
+
+public class RegAlloc {
+ // Main Coloring Algorithm:
+ // Repeat until colored:
+ // Build Live Ranges (LRGs)
+ // - Intersect allowed registers
+ // If hard conflicts (LRGs with no allowed registers)
+ // - Pre-split conflicted LRGs, and repeat
+ // Build Interference Graph (IFG)
+ // - Self conflicts split now
+ // Color (remove trivial first then any until empty; reverse assign colors
+ // - If color fails:
+ // - - Split uncolorable LRGs
+
+ // Top-level program graph structure
+ final CodeGen _code;
+
+ public int _spills, _spillScaled;
+
+ private boolean _done;
+
+ // -----------------------
+ // Live ranges with self-conflicts or no allowed registers
+ private final IdentityHashMap<LRG,String> _failed = new IdentityHashMap<>();
+ // Loop Phis get one chance to color after only cold self-conflict splits.
+ // If the same Phi self-conflicts again, split its hot def/backedge also.
+ private final IdentityHashMap<PhiNode,String> _deferredLoopSelf = new IdentityHashMap<>();
+ void fail( LRG lrg ) {
+ assert lrg.leader();
+ _failed.put(lrg,"");
+ }
+ boolean success() { return _failed.isEmpty(); }
+ public boolean done() { return _done; }
+
+
+ // -----------------------
+ // Map from Nodes to Live Ranges
+ private final IdentityHashMap<Node,LRG> _lrgs = new IdentityHashMap<>();
+ short _lrg_num;
+
+ // Define a new LRG, and assign n
+ LRG newLRG( Node n, FunNode fun ) {
+ LRG lrg = lrg(n);
+ if( lrg!=null ) return lrg;
+ lrg = new LRG(_lrg_num++, fun);
+ LRG old = _lrgs.put(n,lrg); assert old==null;
+ return lrg;
+ }
+
+ // LRG for n
+ public LRG lrg( Node n ) {
+ LRG lrg = _lrgs.get(n);
+ if( lrg==null ) return null;
+ LRG lrg2 = lrg.find();
+ if( lrg != lrg2 )
+ _lrgs.put(n,lrg2);
+ return lrg2;
+ }
+
+ // Find LRG for n.in(idx), and also map n to it
+ LRG lrg2( Node n, int idx ) {
+ LRG lrg = lrg(n.in(idx));
+ return union(lrg,n);
+ }
+
+ // Union any lrg for n with lrg and map to the union
+ LRG union( LRG lrg, Node n ) {
+ LRG lrgn = _lrgs.get(n);
+ LRG lrg3 = lrg.union(lrgn);
+ _lrgs.put(n,lrg3);
+ return lrg3;
+ }
+
+ // Force all unified to roll up; collect live ranges
+ LRG[] _LRGS;
+ void unify() {
+ Ary<LRG> lrgs = new Ary<>(LRG.class);
+ for( Node n : _lrgs.keySet() ) {
+ LRG lrg = lrg(n);
+ lrgs.setX(lrg._lrg,lrg);
+ }
+ _LRGS = lrgs.asAry();
+ // Remove unified lrgs from failed set also
+ for( LRG lrg : _failed.keySet().toArray(new LRG[0]) ) {
+ if( !lrg.leader() ) {
+ LRG lrg2 = lrg.find();
+ _failed.remove(lrg);
+ _failed.put(lrg2,"");
+ }
+ }
+ }
+
+
+ // Diagnostic lookup: do not compress leaders or rewrite the node mapping.
+ private LRG _lrg(Node n) {
+ LRG lrg = _lrgs.get(n);
+ if( lrg != null )
+ while( lrg._leader != null ) lrg = lrg._leader;
+ return lrg;
+ }
+
+ public short regnum( Node n ) {
+ LRG lrg = _lrg(n);
+ return lrg==null ? -1 : lrg._reg;
+ }
+
+ // Printable register number for node n
+ String reg( Node n ) {
+ LRG lrg = _lrg(n);
+ return lrg==null ? null : reg(lrg);
+ }
+ String reg( LRG lrg ) {
+ // No register yet, use LRG
+ if( lrg._reg == -1 ) return "V"+lrg._lrg;
+ // Chosen machine register unless stack-slot and past RA
+ String[] regs = _code._mach.regs();
+ if( lrg._reg < regs.length || _code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() || lrg._fun==null )
+ return RegMask.reg(regs,lrg._reg);
+ // Stack-slot past RA uses the frame layout logic
+ return "[rsp+"+lrg._fun.computeStackOffset(_code,lrg._reg)+"]";
+ }
+
+ // -----------------------
+ RegAlloc( CodeGen code ) { _code = code; }
+
+ public void regAlloc() {
+ // Insert callee-save registers
+ String[] regs = _code._mach.regs();
+ long neverSave= _code._mach.neverSave();
+ for( CFGNode bb : _code._cfg )
+ if( bb instanceof FunNode fun ) {
+ ReturnNode ret = fun.ret();
+ int len = Math.min(regs.length,64);
+ for( int reg=0; reg<len; reg++ )
+ if( !_code._callerSave.test(reg) && ((1L<<reg)&neverSave)==0 ) {
+ ret.addDef(new CalleeSaveNode(fun,reg,regs[reg]));
+ assert ret.regmap(ret.nIns()-1).firstReg()==reg;
+ }
+ }
+ // Cache reg masks for New and Call
+ for( CFGNode bb : _code._cfg ) {
+ if( bb instanceof CallEndMach cend ) cend.cacheRegs(_code);
+ for( Node n : bb._outputs )
+ if( n instanceof NewNode nnn ) nnn.cacheRegs(_code);
+ }
+ // Expensive assert: useful when chasing cross-function allocator edges.
+ //assert verifyFunctionLocalEdges();
+
+ // Top driver: repeated rounds of coloring and splitting.
+ byte round=0;
+ while( !graphColor(round) ) {
+ split(round);
+ if( round >= 7 ) // Really expect to be done soon
+ throw new IllegalStateException("Register allocation made no progress after eight rounds");
+ round++;
+ }
+ postColor(); // Remove no-op spills
+ _done = true;
+ }
+
+ boolean verifyFunctionLocalEdges() {
+ for( CFGNode bb : _code._cfg )
+ for( Node use : bb._outputs ) {
+ FunNode useFun = funOf(use);
+ for( int i=1; i<use.nIns(); i++ ) {
+ Node def = use.in(i);
+ if( def==null || def.isConst() || allowedCrossFunctionEdge(use,i,def) )
+ continue;
+ FunNode defFun = funOf(def);
+ if( defFun != null && useFun != null && defFun != useFun )
+ throw new AssertionError(crossFunctionMessage(def,defFun,use,useFun,i));
+ }
+ }
+ return true;
+ }
+
+ private static String crossFunctionMessage(Node def, FunNode defFun, Node use, FunNode useFun, int idx) {
+ StringBuilder sb = new StringBuilder("Cross-function value edge: ");
+ sb.append(def.uniqueName()).append("(").append(defFun.label()).append(") -> ")
+ .append(use.uniqueName()).append("(").append(useFun.label()).append(") input ").append(idx)
+ .append(" def=").append(def).append(" use=").append(use).append("\n all uses:");
+ for( Node out : def.outs() ) {
+ FunNode outFun = funOf(out);
+ sb.append("\n ").append(out.uniqueName()).append("(")
+ .append(outFun==null ? "?" : outFun.label()).append(")");
+ }
+ return sb.toString();
+ }
+
+ private static boolean allowedCrossFunctionEdge(Node use, int idx, Node def) {
+ // Function metadata/control edges are not value live ranges.
+ if( use instanceof FunNode || use instanceof StopNode || use instanceof StopCUNode )
+ return true;
+ // Return-linked function pointers carry function identity/lifetime, not
+ // an allocator value flowing from the callee body into this function.
+ if( use instanceof FunPtrNode && def instanceof ReturnNode )
+ return true;
+ // Parm defaults and call-linked argument edges are the entry protocol;
+ // BuildLRG also skips Parm input 1.
+ if( use instanceof ParmNode )
+ return true;
+ // Linked CallEnd -> Return edges should be gone before RegAlloc, but
+ // diagnose allocator value edges rather than call graph metadata here.
+ if( use instanceof CallEndNode && def instanceof ReturnNode )
+ return true;
+ return false;
+ }
+
+ private static FunNode funOf(Node n) {
+ if( n instanceof ParmNode parm ) return parm.fun();
+ if( n instanceof ReturnNode ret ) return ret.fun();
+ if( n instanceof FunNode fun ) return fun;
+ if( n instanceof CFGNode cfg ) return cfg.fun();
+ CFGNode cfg = n.cfg0();
+ return cfg==null ? null : cfg.fun();
+ }
+
+ private boolean graphColor(byte round) {
+ _failed.clear();
+ _lrgs.clear();
+ _lrg_num = 1;
+ _LRGS=null;
+
+ return
+ // Build Live Ranges
+ BuildLRG.run(round,this) && // if no hard register conflicts
+ // Build Interference Graph
+ IFG.build(round,this) && // If no self conflicts or uncolorable
+ // Conservative coalesce copies
+ Coalesce.coalesce(round,this) &&
+ // Color attempt
+ IFG.color(round,this); // If colorable
+ }
+
+ // -----------------------
+ // Split conflicted live ranges.
+ void split(byte round) {
+
+ // In C2, all splits are handling in one pass over the program. Here,
+ // in the name of clarity, we'll handle each failing live range
+ // independently... which generally requires a full pass over the
+ // program for each failing live range. i.e., might be a lot of
+ // passes.
+
+ // Sort, to avoid non-deterministic HashMap ordering
+ LRG[] splits = _failed.keySet().toArray(new LRG[0]);
+ Arrays.sort(splits, (x,y) -> x._lrg - y._lrg );
+ for( LRG lrg : splits )
+ split(round,lrg);
+ }
+
+ // Split this live range, top level heuristic
+ boolean split( byte round, LRG lrg ) {
+ assert lrg.leader(); // Already rolled up
+
+ if( lrg._selfConflicts != null )
+ return splitSelfConflict(round,lrg);
+
+ // Register mask when empty; split around defs and uses with limited
+ // register masks.
+ if( lrg._mask.isEmpty() ) {
+ if( lrg._1regDefCnt <= 1 &&
+ lrg._1regUseCnt <= 1 &&
+ (lrg._1regDefCnt + lrg._1regUseCnt) > 0 &&
+ // A fixed-register clone with flexible uses must split at the uses.
+ !(lrg._1regDefCnt==1 && lrg._machDef.isClone() && lrg._1regUseCnt==0) )
+ return splitEmptyMaskSimple(round,lrg);
+ // Repeated single-reg uses from a single def. Special for archs
+ // with more fixed regs.
+ if( !lrg._multiDef && lrg._1regDefCnt <= 1 && lrg._1regUseCnt > 2 )
+ if( splitEmptyMaskByUse(round,lrg) )
+ return true;
+ }
+ if( lrg._mask.isEmpty() && lrg._multiDef && lrg._1regUseCnt > 1 && lrg._1regDefCnt <= 1 ) {
+ if( splitEmptyMaskSingleUses(round,lrg) )
+ return true;
+ }
+
+ // Generic split-by-loop depth.
+ return splitByLoop(round,lrg);
+ }
+
+ // Split live range with an empty mask. Specifically forces splits at
+ // single-register defs or uses and not elsewhere.
+ boolean splitEmptyMaskSimple( byte round, LRG lrg ) {
+ // Live range has a single-def single-register, and/or a single-use
+ // single-register. Split after the def and before the use. Does not
+ // require a full pass.
+
+ // Compute def nOuts *before* replacing those uses with a split
+ int defNouts = ((Node)lrg._machDef).nOuts();
+ // Split just after def
+ if( lrg._1regDefCnt==1 && !lrg._machDef.isClone() )
+ // Force must-split, even if a prior split same block because register
+ // conflicts. Example:
+ // alloc
+ // V1/rax - forced by alloc
+ // alloc
+ // V2/rax - kills prior RAX
+ // st4 [V1],len - No good, must split around
+ insertAfterAndReplace( makeSplit("def/empty1",round,lrg), (Node)lrg._machDef, false/*true*/);
+ // Split just before use
+ if( lrg._machUse!=null && (lrg._1regUseCnt==1 || (lrg._1regDefCnt==1 && defNouts==1)) )
+ insertBefore((Node)lrg._machUse,lrg._uidx,"use/empty1",round,lrg);
+ return true;
+ }
+
+ // Popular single-def value: share a split among uses with compatible masks.
+ boolean splitEmptyMaskByUse( byte round, LRG lrg ) {
+ Node def = (Node)lrg._machDef;
+ // Snapshot distinct users: rewriting an input changes def's output list,
+ // and a call can use the same value in more than one argument.
+ Ary<Node> uses = new Ary<>(Node.class);
+ for( Node use : def._outputs )
+ if( uses.find(use)==-1 ) uses.push(use);
+
+ Ary<RegMask> rclass = new Ary<>(RegMask.class);
+ int ncalls=0;
+ for( Node use : uses )
+ if( use instanceof MachNode mach ) {
+ // Sharing copies across several calls usually loses to their kills.
+ if( use instanceof CallNode && ++ncalls > 1 ) return false;
+ for( int i=1; i<use.nIns(); i++ )
+ if( use.in(i)==def && mach.regmap(i)!=null )
+ putIntoRegClass(rclass,mach.regmap(i));
+ }
+ if( rclass._len <= 1 ) return false;
+
+ for( RegMask rmask : rclass ) {
+ Node split = makeSplit(def,"popular",round,lrg,rmask);
+ split.insertAfter(def);
+ if( split.nIns()>1 ) split.setDef(1,def);
+ for( Node use : uses )
+ if( use instanceof MachNode mach )
+ for( int i=1; i<use.nIns(); i++ ) {
+ RegMask mask = mach.regmap(i);
+ if( use.in(i)==def && mask!=null && mask.overlap(rmask) ) {
+ assert rmask.and(mask)==rmask;
+ use.setDefOrdered(i,split);
+ }
+ }
+ }
+ return true;
+ }
+
+ // Empty-mask live range with several direct single-register uses, often
+ // through a Phi with multiple defs. Split those uses directly instead of
+ // doing a loop-depth split, which might keep isolating the wrong edge.
+ boolean splitEmptyMaskSingleUses( byte round, LRG lrg ) {
+ findAllLRG(lrg);
+ if( _ns._len <= 5 )
+ return false;
+ boolean progress = false;
+ for( Node n : _ns ) {
+ if( !(n instanceof MachNode mach) ) continue;
+ for( int i=1; i<n.nIns(); i++ ) {
+ RegMask use = mach.regmap(i);
+ if( use != null && use.size1() && lrg(n.in(i))==lrg ) {
+ insertBefore(n,i,"use/emptyN",round,lrg);
+ progress = true;
+ }
+ }
+ }
+ return progress;
+ }
+
+ // Narrowing preserves all earlier users of a class; new classes are disjoint.
+ // Each use contains its first overlapping class, so one pass suffices.
+ private static void putIntoRegClass( Ary<RegMask> rclass, RegMask rmask ) {
+ for( int i=0; i<rclass._len; i++ ) {
+ RegMask omask = rclass.at(i);
+ if( !omask.overlap(rmask) ) continue;
+ if( omask.and(rmask)!=omask )
+ rclass.set(i,new RegMask(omask.copy().and(rmask)));
+ return;
+ }
+ rclass.push(rmask);
+ }
+
+
+ // Self conflicts require Phis (or two-address).
+ // Insert a split after every def.
+ boolean splitSelfConflict( byte round, LRG lrg ) {
+ // Sort conflict set, so we're deterministic
+ Node[] conflicts = lrg._selfConflicts.keySet().toArray(new Node[0]);
+ Arrays.sort(conflicts, (x,y) -> x._nid - y._nid );
+
+ // First encounter with a loop Phi: try only cold splits this round.
+ // Remember the Phi across rounds so a persistent self-conflict forces
+ // the hot def/backedge splits on the next encounter.
+ IdentityHashMap<PhiNode,String> deferred = new IdentityHashMap<>();
+ for( Node def : conflicts ) {
+ if( def instanceof PhiNode phi && phi.region() instanceof LoopNode )
+ deferLoopSelf(phi,deferred);
+ for( Node use : def._outputs )
+ if( use instanceof PhiNode phi && phi.region() instanceof LoopNode &&
+ phi._inputs.find(def)==2 )
+ deferLoopSelf(phi,deferred);
+ }
+
+ // For all conflicts
+ for( Node def : conflicts ) {
+ assert lrg(def)==lrg; // Might be conflict use-side
+ // Split before each use that extends the live range; i.e. is a
+ // Phi or two-address
+ for( int i=0; i<def._outputs._len; i++ ) {
+ Node use = def.out(i);
+ if( (use instanceof PhiNode phi &&
+ !(deferred.containsKey(phi) && phi.region() instanceof LoopNode && phi._inputs.find(def)==2) &&
+ !(phi.region() instanceof LoopNode loop && phi.in(2)==def && def.cfg0().idepth() > loop.idepth() ) ) ||
+ (use instanceof MachNode mach && mach.twoAddress()!=0 && use.in(mach.twoAddress())==def) )
+ insertBefore( use, use._inputs.find(def), "use/self/use",round,lrg );
+ }
+ // Split after the Phi and before its entry. Also isolate inputs
+ // shared with another Phi: otherwise the shared input keeps the
+ // conflicting live ranges joined, even after splitting the entry.
+ if( def instanceof PhiNode phi && !(def instanceof ParmNode) ) {
+ if( !deferred.containsKey(phi) ) {
+ SplitNode split = makeSplit("def/self",round,lrg);
+ insertAfterAndReplace(split,def,false);
+ if( split.nOuts()==0 )
+ split.killOrdered();
+ }
+ for( int i=1; i<(deferred.containsKey(phi) ? 2 : phi.nIns()); i++ )
+ if( i==1 || sharedPhiInput(phi,i) )
+ insertBefore(phi,i,"use/self/phi",round,lrg);
+ }
+ // Split before two-address ops which extend the live range
+ if( def instanceof MachNode mach && mach.twoAddress()!= 0 )
+ insertBefore(def,mach.twoAddress(),"use/self/two",round,lrg);
+ }
+ return true;
+ }
+
+ private void deferLoopSelf(PhiNode phi, IdentityHashMap<PhiNode,String> deferred) {
+ if( !_deferredLoopSelf.containsKey(phi) ) {
+ _deferredLoopSelf.put(phi,"");
+ deferred.put(phi,"");
+ }
+ }
+
+
+ private static boolean sharedPhiInput(PhiNode phi, int i) {
+ for( Node use : phi.in(i)._outputs )
+ if( use instanceof PhiNode && use!=phi ) return true;
+ return false;
+ }
+
+ // Generic: split around the outermost loop with non-split def/uses. This
+ // covers both self-conflicts (once we split deep enough) and register
+ // pressure issues.
+ boolean splitByLoop( byte round, LRG lrg ) {
+ findAllLRG(lrg);
+
+ // Find min loop depth for all non-split defs and uses.
+ long ld = (-1L<<32) | 9999;
+ for( Node n : _ns ) {
+ if( lrg(n)==lrg ) // This is a LRG def
+ ld = ldepth(ld,n,n.cfg0());
+ // PhiNodes check all CFG inputs
+ if( n instanceof PhiNode phi ) {
+ for( int i=1; i<n.nIns(); i++ )
+ if( phi.in(i)._type!=Type.TOP )
+ ld = ldepth(ld, phi.in(i), phi.region().cfg(i));
+ } else {
+ // Others check uses
+ for( int i=1; i<n.nIns(); i++ )
+ if( lrgSame(n.in(i),lrg) ) // This is a LRG use
+ ld = ldepth(ld,n,n.cfg0());
+ }
+ }
+ int min = (int)ld;
+ int max = (int)(ld>>32);
+
+ boolean emptyMulti = lrg._mask.isEmpty() && lrg._multiDef && lrg._1regDefCnt <= 1 &&
+ (lrg._1regUseCnt > 1 || _ns._len > 5);
+ // Multi-def empty-mask conflicts need the hot/deep side isolated from
+ // fixed register uses. Ordinary pressure splits keep the old shallow
+ // boundary behavior.
+ for( Node n : _ns ) {
+ if( n instanceof SplitNode split && !split.isNonStackSplit() && min!=max ) continue; // Ordinary splits already reach stack storage.
+ if( n.isDead() ) continue; // Some Cloneable went dead by other spill changes
+ // If this is a 2-address commutable op (e.g. AddX86, MulX86) and the rhs has only a single user,
+ // commute the inputs... which chops the LHS live ranges' upper bound to just the RHS.
+ if( n instanceof MachNode mach && lrg(n)==lrg && mach.twoAddress()==1 && mach.commutes() && n.in(2).nOuts()==1 )
+ n.swap12();
+
+ if( lrg(n)==lrg && // This is a LRG def
+ // At loop boundary, or splitting in inner loop
+ (min==max || (emptyMulti ? n.cfg0().loopDepth() >= max : n.cfg0().loopDepth() <= min)) ) {
+ // Cloneable constants will be cloned at uses, not after def
+ if( !(n instanceof MachNode mach && mach.isClone()) &&
+ // Single user is already a split adjacent
+ !(n.nOuts()==1 && n.out(0) instanceof SplitNode split && sameBlockNoClobber(split) ) )
+ // Split after def in min loop nest
+ insertAfterAndReplace( makeSplit("def/loop",round,lrg), n,true);
+ }
+
+ // PhiNodes check all CFG inputs
+ if( n instanceof PhiNode phi && !(n instanceof ParmNode)) {
+ for( int i=1; i<n.nIns(); i++ )
+ // No split in front of a split
+ if( !(n.in(i) instanceof SplitNode) &&
+ // splitting in inner loop or at loop border
+ (min==max || (emptyMulti ? phi.region().cfg(i).loopDepth() >= max : phi.region().cfg(i).loopDepth() <= min)) &&
+ // and not around the backedge of a loop (bad place to force a split, hard to remove)
+ !(phi.region() instanceof LoopNode && i==2 && (phi.in(i) instanceof PhiNode pp && pp.region()==phi.region())) )
+ // Split before phi-use in prior block
+ insertBefore(phi,i, "use/loop/phi",round,lrg);
+
+ } else {
+ // Others check uses
+ for( int i=1; i<n.nIns(); i++ ) {
+ // This is a LRG use
+ // splitting in inner loop or at loop border
+ if( lrgSame( n.in( i ), lrg ) &&
+ (min == max || (n.in(i) instanceof MachNode mach && mach.isClone()) ||
+ (emptyMulti ? n.cfg0().loopDepth() >= max : n.cfg0().loopDepth() <= min)) )
+ // Split before in this block
+ insertBefore( n, i, "use/loop/use", round,lrg, false );
+ }
+ }
+ }
+ return true;
+ }
+
+ private boolean lrgSame(Node x, LRG lrg) {
+ LRG xlrg = lrg(x);
+ while( xlrg==null && x instanceof SplitNode )
+ xlrg=lrg(x = x.in(1));
+ return xlrg==lrg;
+ }
+
+ private static long ldepth( long ld, Node n, CFGNode cfg ) {
+ // Ordinary splits already reach stack storage; register-only transfers do not.
+ if( n instanceof SplitNode split && !split.isNonStackSplit() ) return ld;
+ // Collect min/max loop depth
+ int min = (int)ld;
+ int max = (int)(ld>>32);
+ int d = cfg.loopDepth();
+ // if n will lower the min loop and is in the tail end of the loop
+ // header, splitting "around" the loop will not help. Treat n as being
+ // in the loop.
+ if( d < min ) {
+ if( cfg.uctrl() instanceof LoopNode loop && loop.entry()==cfg ) {
+ for( int i=cfg.nOuts()-2; i>=0; i-- ) {
+ Node out = cfg.out(i);
+ if( n==out )
+ { d = loop.loopDepth(); break; } // Treat n as being "in the loop"
+ if( !((out instanceof MachNode mach && mach.isClone()) || out instanceof SplitNode ) )
+ break; // Treat b as "normal", out of loop
+ }
+ }
+ }
+
+ // lower min, raise max, and re-fold
+ min = Math.min(min,d);
+ max = Math.max(max,d);
+ return ((long)max<<32) | min;
+ }
+
+ // Find all members of a live range, both defs and uses
+ private final Ary<Node> _ns = new Ary<>(Node.class);
+ void findAllLRG( LRG lrg ) {
+ _ns.clear();
+ int wd = 0;
+ if( lrg._machDef != null ) _ns.push((Node)lrg._machDef);
+ if( lrg._machUse != null ) _ns.push((Node)lrg._machUse);
+ while( wd < _ns._len ) {
+ Node n = _ns.at(wd++);
+ if( lrg(n)!=lrg ) continue;
+ for( Node def : n._inputs )
+ if( lrg(def)==lrg && _ns.find(def)== -1 )
+ _ns.push(def);
+ for( Node use : n._outputs )
+ if( _ns.find(use)== -1 )
+ _ns.push(use);
+ }
+ for( Node n : _ns ) assert !n.isDead();
+ }
+
+ void insertBefore(Node n, int i, String kind, byte round, LRG lrg, boolean skip) {
+ Node def = n.in(i);
+ // Phi splitting may visit the synthetic never-exit arm.
+ if( def._type==Type.TOP ) return;
+ // Effective block for use
+ CFGNode cfg = n instanceof PhiNode phi ? phi.region().cfg(i) : n.cfg0();
+ // Use-side RegMask, if available
+ RegMask umask = n instanceof MachNode mach ? mach.regmap(i) : null;
+ // Def is a split ?
+ if( skip && def instanceof SplitNode ) {
+ boolean singleReg = umask!=null && umask.size1();
+ // Same block, multiple registers, split is only used by n,
+ // assume this is good enough and do not split again.
+ if( cfg==def.cfg0() && def.nOuts()==1 && !singleReg )
+ return;
+ }
+ makeSplit(def,kind,round,lrg,umask).insertBefore(n, i);
+ // Skip split-of-split same block
+ if( skip && def instanceof SplitNode && cfg==def.cfg0() )
+ n.in(i).setDefOrdered(1,def.in(1));
+ }
+ void insertBefore(Node n, int i, String kind, byte round, LRG lrg) {
+ insertBefore(n,i,kind,round,lrg,true);
+ }
+
+ // Replace uses of `def` with `split`, and insert `split` immediately after
+ // `def` in the basic block.
+ public void insertAfterAndReplace( Node split, Node def, boolean must ) {
+ split.insertAfter(def);
+ if( split.nIns()>1 ) split.setDef(1,def);
+ for( int j=def.nOuts()-1; j>=0; j-- ) {
+ Node use = def.out(j);
+ if( use==split ) continue; // Skip self
+ // Can we avoid a split of a split? 'this' split is used by
+ // another split in the same block.
+ if( !must && use instanceof SplitNode split2 && sameBlockNoClobber(split2) )
+ continue;
+ int idx = use._inputs.find(def);
+ use.setDefOrdered(idx,split);
+ if( j < def.nOuts() ) j++;
+ }
+ }
+
+ private Node makeSplit( Node def, String kind, byte round, LRG lrg, RegMask umask ) {
+ // Clone simple constants if possible
+ Node split = def instanceof MachNode mach && mach.isClone() && (umask==null || mach.outregmap().overlap(umask))
+ ? mach.copy()
+ : _code._mach.split(lrg,kind,round);
+ _lrgs.put(split,lrg);
+ return split;
+ }
+ private SplitNode makeSplit( String kind, byte round, LRG lrg ) {
+ SplitNode split = _code._mach.split(lrg,kind,round);
+ _lrgs.put(split,lrg);
+ return split;
+ }
+
+
+ // -----------------------
+ // POST PASS: Remove empty spills that biased-coloring made
+ private void postColor() {
+ int maxReg = -1;
+ for( CFGNode bb : _code._cfg ) { // For all ops
+ if( bb instanceof FunNode )
+ maxReg = -1; // Reset for new function
+ // Compute frame size, based on arguments and largest reg seen
+ if( bb instanceof ReturnNode ret )
+ ret.fun().computeFrameAdjust(_code,maxReg);
+ // Raise frame size by max stack args passed, even if ignored
+ if( bb instanceof CallEndMach cend )
+ maxReg = Math.max(maxReg,cend._xslot);
+
+ for( int j=0; j<bb.nOuts(); j++ ) {
+ Node n = bb.out(j);
+ if( lrg(n)!=null )
+ maxReg = Math.max(maxReg,lrg(n)._reg+1);
+ // Raise frame size by max stack args passed to New
+ if( n instanceof NewNode nnn )
+ maxReg = Math.max(maxReg,nnn._xslot);
+
+ if( !(n instanceof SplitNode ) ) continue;
+ int defreg = lrg(n )._reg;
+ int usereg = lrg(n.in(1))._reg;
+ // Attempt to bypass split
+ if( defreg != usereg && splitBypass(bb,j,n,defreg) )
+ usereg = lrg(n.in(1))._reg;
+
+ // Same register, or the next instruction can use the source directly.
+ if( defreg == usereg || splitForward(bb,j,n,usereg) ) {
+ n.removeSplit();
+ j--;
+ continue;
+ }
+ // Split is kept; count against split score
+ _spills++;
+ _spillScaled += (1<<bb.loopDepth()*3);
+ assert _spillScaled >= 0;
+ }
+ }
+ }
+
+ // A sole, adjacent use can read the source register directly. Nothing can
+ // clobber it in between; only the operand mask and two-address tie remain.
+ private boolean splitForward( CFGNode bb, int j, Node split, int reg ) {
+ if( split.nOuts()!=1 || j+1==bb.nOuts() ) return false;
+ Node use = split.out(0);
+ if( use!=bb.out(j+1) || use instanceof PhiNode || use instanceof CFGNode ||
+ !(use instanceof MachNode mach) ) return false;
+ int i = use._inputs.find(split);
+ RegMask mask = mach.regmap(i);
+ return mask!=null && mask.test(reg) && mach.twoAddress()!=i;
+ }
+
+ private boolean splitBypass( CFGNode bb, int j, Node lo, int defreg ) {
+ // Attempt to bypass split
+ Node hi = lo.in(1);
+ while( true ) {
+ if( !(hi instanceof SplitNode && lo.cfg0() == hi.cfg0()) )
+ return false;
+ if( lrg(hi.in(1))._reg==defreg )
+ break;
+ hi = hi.in(1);
+ }
+ // Walk backward from the later copy, including instructions without results.
+ for( int idx = j-1; bb.out(idx) != hi; idx--) {
+ Node n = bb.out(idx);
+ if( clobbers(n,defreg) )
+ return false; // Clobbered
+ }
+ lo.setDefOrdered(1,hi.in(1));
+ return true;
+ }
+
+
+ private boolean clobbers(Node n, int reg) {
+ LRG lrg = lrg(n);
+ return lrg!=null && (lrg._reg==reg || lrg._reg==-1 && lrg._mask.size1() && lrg._mask.firstReg()==reg) ||
+ n instanceof MachNode mach && mach.killmap()!=null && mach.killmap().test(reg);
+ }
+
+ public boolean sameBlockNoClobber( SplitNode split ) {
+ Node def = split.in(1);
+ CFGNode cfg = def.cfg0();
+ if( cfg != split.cfg0() ) return false; // Not same block
+ // Get multinode head
+ Node def0 = def instanceof ProjNode ? def.in(0) : def;
+ int defreg = lrg(def)._reg;
+ if( defreg == -1 ) defreg = lrg(def)._mask.firstReg();
+ if( defreg == -1 ) return false; // no allowed registers -> clobbered
+ for( int idx = cfg._outputs.find(split) -1; idx >= 0; idx-- ) {
+ Node n = cfg.out(idx);
+ if( n==def0 ) return true; // No clobbers
+ if( lrg(n) == lrg(def) ) return false; // Self conflict
+ if( clobbers(n,defreg) )
+ return false; // Clobbered
+ }
+ throw Utils.TODO("Should not reach here");
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/RegMask.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/RegMask.java
new file mode 100644
index 00000000..9cb9ec16
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/RegMask.java
@@ -0,0 +1,121 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+/** RegMask
+ * A "register mask" - 1 bit set for each allowed register. In addition,
+ * "stack slot" registers may be allowed, effectively making the set infinite.
+ * <p>
+ * For smaller and simpler machines it suffices to make such masks an i64 or
+ * i128 (64- or 128-bit integers), and this presentation is by far the better
+ * way to go... if all register allocations can fit in this bit limitation.
+ * The allocator will need bits for stack-based parameters and for splits
+ * which cannot get a register. For a 32-register machine like the X86, add 1
+ * for flags - gives 33 registers. Using a Java `long` has 64 bits, leaving
+ * 31 for spills and stack passing. This is adequate for nearly all
+ * allocations; only the largest allocations will run this out. However, if
+ * we move to a chip with 64 registers we'll immediately run out, and need at
+ * least a 128 bit mask. Since you cannot *return* a 128 bit value directly
+ * in Java, Simple will pick up a `RegMask` class object.
+*/
+public class RegMask {
+
+ long _bits0, _bits1;
+
+ private static final RegMask EMPTY = new RegMask(0L);
+ public static final RegMask FULL = new RegMask(-1L);
+
+ public RegMask(int bit) {
+ if( bit < 64 ) _bits0 = 1L<<bit;
+ else _bits1 = 1L<<(bit-64);
+ }
+ public RegMask(long bits ) { _bits0 = bits; }
+ public RegMask(long bits0, long bits1 ) { _bits0 = bits0; _bits1 = bits1; }
+ public RegMask(RegMask r) { _bits0 = r._bits0; _bits1 = r._bits1; }
+
+ // AND, with copy-on-write if changing
+ RegMask and( RegMask mask ) {
+ if( mask==null ) return this;
+ long bits0 = _bits0 & mask._bits0;
+ long bits1 = _bits1 & mask._bits1;
+ if( bits0==_bits0 && bits1==_bits1 ) return this;
+ if( bits0==mask._bits0 && bits1==mask._bits1 ) return mask;
+ if( bits0==0 && bits1==0 ) return EMPTY;
+ // Update-in-place a mutable mask, or make a defensive copy
+ return null;
+ }
+ RegMask sub( RegMask mask ) {
+ if( mask==null ) return this;
+ long bits0 = _bits0 & ~mask._bits0;
+ long bits1 = _bits1 & ~mask._bits1;
+ if( bits0==_bits0 && bits1==_bits1 ) return this;
+ if( bits0==mask._bits0 && bits1==mask._bits1 ) return mask;
+ if( bits0==0 && bits1==0 ) return EMPTY;
+ // Update-in-place a mutable mask, or make a defensive copy
+ return null;
+ }
+
+ // Fails if bit is set, because this is immutable
+ public boolean clr( int reg ) {
+ if( reg < 64 ) return ((_bits0 >> reg)&1)==0;
+ if( reg < 128 ) return ((_bits1 >> reg)&1)==0;
+ return true;
+ }
+
+
+ public short firstReg() {
+ if( isEmpty() ) return -1;
+ return (short)(_bits0 != 0
+ ? Long.numberOfTrailingZeros(_bits0)
+ : Long.numberOfTrailingZeros(_bits1)+64);
+ }
+ public short nextReg(short reg) {
+ if( reg < 63 ) {
+ long bits0 = _bits0 & (-1L << (reg+1));
+ if( bits0!=0 ) return (short)Long.numberOfTrailingZeros(bits0);
+ }
+ long bits1 = reg < 64 ? _bits1 : reg < 127 ? _bits1 & (-1L << (reg-63)) : 0;
+ return bits1==0 ? -1 : (short)(64+Long.numberOfTrailingZeros(bits1));
+ }
+
+ boolean isEmpty() { return _bits0==0 && _bits1==0; }
+
+ public boolean test( int reg ) {
+ return ((reg<64 ? (_bits0 >> reg) : (_bits1 >> (reg-64))) & 1) != 0;
+ }
+
+ // checks if the 2 masks have at least 1 bit in common
+ public boolean overlap( RegMask mask ) { return (_bits0 & mask._bits0)!=0 || (_bits1 & mask._bits1)!=0; }
+
+ // Defensive writable copy
+ public RegMaskRW copy() { return new RegMaskRW( _bits0, _bits1 ); }
+
+ // Has exactly 1 bit set
+ public boolean size1() {
+ return !isEmpty() && (((_bits0 & -_bits0)==_bits0 && _bits1==0) ||
+ ((_bits1 & -_bits1)==_bits1 && _bits0==0));
+ }
+
+ // Cardinality
+ public short size() { return (short)(Long.bitCount(_bits0)+Long.bitCount(_bits1)); }
+
+ @Override public String toString() { return toString(new SB()).toString(); }
+ public SB toString(SB sb) {
+ Machine mach = CodeGen.CODE._mach;
+ if( _bits0==0 && _bits1==0 ) return sb.p("[]");
+ sb.p("[");
+ String[] regs = mach.regs();
+ for( int i=0; i<64; i++ )
+ if( ((_bits0 >> i)&1) != 0 )
+ sb.p(reg(regs,i)).p(",");
+ for( int i=0; i<64; i++ )
+ if( ((_bits1 >> i)&1) != 0 )
+ sb.p(reg(regs,i+64)).p(",");
+ return sb.unchar().p("]");
+ }
+
+ public static String reg(String[] regs, int i) {
+ return i<regs.length ? regs[i] : "[stk#"+i+"]";
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java
new file mode 100644
index 00000000..1da9a3b1
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java
@@ -0,0 +1,36 @@
+package com.seaofnodes.simple.codegen;
+
+// Mutable regmask(writable)
+public class RegMaskRW extends RegMask {
+ public RegMaskRW(long x, long y) { super(x,y); }
+ // clears bit at position r. Returns true if the mask is still not empty.
+ public boolean clr(int r) {
+ if( r < 64 ) _bits0 &= ~(1L<<(r ));
+ else _bits1 &= ~(1L<<(r-64));
+ return _bits0!=0 || _bits1!=0;
+ }
+ // sets bit at position r.
+ public void set(int r) {
+ if( r < 64 ) _bits0 |= (1L<<(r ));
+ else _bits1 |= (1L<<(r-64));
+ }
+ @Override RegMaskRW and( RegMask mask ) {
+ if( mask==null ) return this;
+ _bits0 &= mask._bits0;
+ _bits1 &= mask._bits1;
+ return this;
+ }
+ @Override RegMaskRW sub( RegMask mask ) {
+ if( mask==null ) return this;
+ _bits0 &= ~mask._bits0;
+ _bits1 &= ~mask._bits1;
+ return this;
+ }
+ public RegMaskRW or( RegMask mask ) {
+ if( mask!=null ) {
+ _bits0 |= mask._bits0;
+ _bits1 |= mask._bits1;
+ }
+ return this;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/Serialize.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
new file mode 100644
index 00000000..76829151
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
@@ -0,0 +1,716 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.*;
+import java.util.*;
+
+public class Serialize {
+
+ // Shared by all objects in this serialization pass. The graph and its
+ // declarations stay unchanged while each compilation unit is written.
+ private final ClassInitDependencies _init;
+ private final HashSet<TypeStruct> _pubTypes = new HashSet<>();
+ private final HashMap<CompUnit,Ary<TypeStruct>> _classes = new HashMap<>();
+
+ private Serialize(CodeGen code) {
+ _init = code._classInitDependencies;
+ assert _init!=null; // TypeCheck checked this same, still-unmodified graph.
+ // Preserve the canonical declarations, distinct from sharper graph
+ // types with the same names. Gather/sort them once for all objects.
+ TreeMap<String,TypeStruct> named = new TreeMap<>();
+ for( Type type : Parser.TYPES.values() )
+ if( type instanceof TypeStruct ts ) named.put(ts._name,ts);
+ for( TypeStruct ts : named.values() ) {
+ _pubTypes.add(ts);
+ CompUnit cu = classOwner(code,ts);
+ if( cu!=null && cu._src!=null )
+ _classes.computeIfAbsent(cu,k -> new Ary<>(TypeStruct.class)).add(ts);
+ }
+ }
+
+ // Encoding/emulation need only machine code. File export also needs ideal
+ // IR for a later compiler to import; an explicit Serialize request stops here.
+ static void forTarget(CodeGen code, CodeGen.Phase target, boolean inMemory) {
+ if( target==CodeGen.Phase.Serialize || (target.ordinal()>=CodeGen.Phase.Export.ordinal() && !inMemory) )
+ code.serialize();
+ }
+
+ static void serialize( CodeGen code ) {
+ // Anonymous functions also need names when another object refers to them.
+ for( FunNode fun : code._linker )
+ if( fun!=null && !fun.isDead() && fun._name==null )
+ fun._name=code._fidxs.symbol(fun.sig().fidx());
+ ArrayList<CompUnit> units = new ArrayList<>(code._compunits.values());
+ units.sort(Comparator.comparing(cu -> cu._fname));
+ Serialize writer = new Serialize(code);
+ for( CompUnit cu : units ) {
+ if( cu._src==null ) continue;
+ BAOS serial = writer.serialize(code,cu);
+ code._serials.put(cu,serial);
+ if( cu._fname.equals(code._srcName==null ? "Test" : code._srcName) ) code._serial=serial;
+ }
+ }
+
+ private BAOS serialize(CodeGen code, CompUnit top) {
+ Ary<Node> nodes = nodeOrder(code,top);
+ HashSet<String> xdeps = new HashSet<>();
+ // Retain build dependencies even when their calls were inlined away.
+ if( top._deps!=null ) for( CompUnit dep : top._deps )
+ if( dep!=top ) xdeps.add(dep._fname);
+ if( top._par!=null ) xdeps.add(top._par._fname);
+ String[] deps = xdeps.toArray(new String[0]);
+ Arrays.sort(deps);
+ String[] units = {top._fname};
+ TypeStruct clz = top._clz;
+
+ // Compress into bytes
+ Ary<TypeStruct> classes = _classes.get(top);
+ BAOS baos = write(nodes, clz, classes, deps, units, code._aliases, code._fidxs, code._rpcs, code._externFunc);
+
+ // --- Expensive bijection assert
+ if( false ) {
+ // Inflate into POJOs; renumbers everything
+ ElfReader elf = new ElfReader(new BAOS(baos.toByteArray()));
+ readAll(code,elf, code._aliases, code._fidxs, code._rpcs);
+ BAOS baos2 = write(elf._nodes,elf._clz,classes,elf._deps,elf._units, code._aliases, code._fidxs, code._rpcs, code._externFunc);
+
+ // Bi-jection
+ for( int i=0; i<baos.size(); i++ )
+ assert baos.buf()[i]==baos2.buf()[i] : "bijection mismatch at "+i+": "+(baos.buf()[i]&0xFF)+" != "+(baos2.buf()[i]&0xFF);
+ assert baos.size()==baos2.size() : "bijection size mismatch: "+baos.size()+" != "+baos2.size();
+ }
+ // --- Expensive bijection assert
+
+ // Record serialized IR for later ELF writing
+ return baos;
+ }
+
+ // --------------------------------------------------
+ private BAOS write(Ary<Node> nodes, TypeStruct clz, Ary<TypeStruct> classes, String[] depobjs, String[] units, GlobalBits aliases, GlobalBits fidxs, GlobalBits rpcs, HashMap<Integer,String> externFunc) {
+ // Initialize the mapping from bits/tags to types
+ Type.TAGOFFS();
+
+ IdentityHashMap<Node,Integer> anodes = new IdentityHashMap<>();
+ for( Node n : nodes ) anodes.put(n,anodes.size()+1); // 0 is null.
+ // The saved wrappers summarize this object's units only. Keep the
+ // live whole-program graph intact for subsequent lowering.
+ TypeTuple stopType = TypeTuple.STOP_HIGH;
+ for( Node n : nodes.last()._inputs )
+ if( anodes.containsKey(n) ) stopType = (TypeTuple)stopType.meet(n._type);
+ Type startType = TypeTuple.make(Type.CONTROL,stopType.mem(),((StartNode)nodes.at(0)).arg());
+
+ BAOS baos = new BAOS();
+ // A - Print the C0DE header. The format is not versioned yet.
+ baos.write('C').write('0').write('D').write('E');
+
+ // Count unique Types
+ var types = new HashMap<Type,Integer>();
+ IdentityHashMap<FunNode,TypeTuple> summaries = new IdentityHashMap<>();
+ // Count published types *first*, so can align with published strings
+ types.put(Type.TOP,0);
+ types.put( clz, types.size() );
+ // Have to visit the innards specially, since they did not get the
+ // normal recursive visit when first touching a type, because the
+ // published types are packed early to align with their string names so
+ // lookup in ElfReader can be fast.
+ for( int i=0; i<clz.nkids(); i++ )
+ clz.at(i).gather(types);
+ // Gather types from all sources
+ for( Node n : nodes ) {
+ n._type.gather(types);
+ if( n instanceof CallEndNode cend ) cend._rpc.gather(types);
+ if( n instanceof FunNode fun ) {
+ fun.sig().gather(types);
+ TypeTuple state = (TypeTuple)fun.ret()._type;
+ TypeMem mem = (TypeMem)state.mem();
+ // Retention can happen on a path that never returns. The
+ // unit's escape summary includes those native-call effects.
+ TypeMem escaped = (TypeMem)((TypeTuple)fun._compunit._stop._type).mem();
+ mem=mem.makeFrom(XInt.meet(mem._escFs,escaped._escFs),XInt.meet(mem._escAs,escaped._escAs));
+ state=state.makeFrom(1,mem);
+ summaries.put(fun,state);
+ state.gather(types);
+ }
+ if( n instanceof ParmNode parm ) parm._declaredType.gather(types);
+ if( n instanceof TypeNode con ) con._con .gather(types);
+ }
+ stopType.gather(types);
+ startType.gather(types);
+ // A defined class remains importable even when its initializer body dies.
+ // Nested structs also need their class declaration: <new> and <ctor>
+ // can be absent from graph types even while their function bodies live.
+ // Keep the nearest file owner, so an imported child is not re-exported
+ // merely because its parent was compiled here.
+ if( classes!=null )
+ for( TypeStruct ts : classes )
+ ts.gather(types);
+
+ // Only record canonical declarations already reachable from this
+ // object's graph. Pulling unrelated parser types into this closure
+ // can include open or partially built types from other units.
+ Ary<TypeStruct> writtenPubs = new Ary<>(TypeStruct.class);
+ for( Type type : types.keySet() )
+ if( type instanceof TypeStruct ts && _pubTypes.contains(ts) )
+ writtenPubs.add(ts);
+ Arrays.sort(writtenPubs._es,0,writtenPubs._len,Comparator.comparing(ts -> ts._name));
+ // Radix sort by Type ID#
+ Type[] atypes = new Type[types.size()];
+ for( Type t : types.keySet() )
+ atypes[types.get(t)] = t;
+
+ // Count all unique Strings
+ var strs = new HashMap<String,Integer>();
+ strs.put("",0); // Null string is always 0
+ // Find and count *published* Strings first
+ gather(strs,clz._name);
+ // Find and count dependent object file string names
+ if( depobjs != null )
+ for( String fname : depobjs )
+ gather(strs,fname);
+ for( String fname : units ) gather(strs,fname);
+ // Count strings from all types
+ for( Type t : atypes ) {
+ if( t instanceof Field fld ) gather(strs,fld._fname);
+ if( t instanceof TypeStruct ts ) gather(strs,ts . _name);
+ }
+ // Count strings used by local->global bit mappings.
+ aliases.gather(strs);
+ fidxs .gather(strs);
+ rpcs .gather(strs);
+ for( String extern : externFunc.values() )
+ gather(strs,extern);
+ // Count strings from nodes
+ for( Node n : nodes )
+ n.gather(strs);
+ // Radix sort by string ID#
+ String[] astrs = new String[strs.size()];
+ for( String s : strs.keySet() )
+ astrs[strs.get(s)] = s;
+
+ // Check that published strings and types align
+ assert astrs[1] == ((TypeStruct)atypes[1])._name;
+ if( depobjs != null )
+ for( int i=0; i<depobjs.length; i++ )
+ assert astrs[1+i+1] == depobjs[i];
+
+
+ // B - Write unique strings
+ baos.packed4(depobjs==null ? 0 : depobjs.length);
+ baos.packed4(strs.size());
+ // Write in ID# order; the first depobjs.length of these are dependent strings
+ for( String s : astrs )
+ baos.packedS(s);
+
+ // Defined units are loader metadata, not initialization dependencies.
+ baos.packed4(units.length);
+ for( String fname : units ) baos.packed4(strs.get(fname));
+
+ // B.2 - Write the local->global mappings
+ aliases.packed(baos,strs);
+ fidxs .packed(baos,strs);
+ rpcs .packed(baos,strs);
+ ArrayList<Integer> externFidxs = new ArrayList<>(externFunc.keySet());
+ Collections.sort(externFidxs);
+ baos.packed2(externFidxs.size());
+ for( int fidx : externFidxs ) {
+ baos.packed2(fidx);
+ baos.packed2(strs.get(externFunc.get(fidx)));
+ }
+
+ // C - Write unique Types
+ baos.packed4(atypes.length);
+ baos.packed2(writtenPubs._len);
+ for( TypeStruct ts : writtenPubs )
+ baos.packed4(types.get(ts));
+ // Write Types in ID# order, no children
+ for( Type t : atypes )
+ t.packed(baos,strs);
+ // Write Types in ID# order, only child IDs
+ for( Type t : atypes ) {
+ int nkids = t.nkids();
+ for( int i=0; i<nkids; i++ )
+ baos.packed4(types.get(t.at(i)));
+ }
+
+
+ // Function summaries precede the graph, so declarations and call
+ // behavior are available without allocating the implementation nodes.
+ int nfuncs = 0;
+ for( Node n : nodes ) if( n instanceof FunNode ) nfuncs++;
+ baos.packed4(nfuncs);
+ for( Node n : nodes ) if( n instanceof FunNode fun ) {
+ baos.packed2(types.get(fun.sig()));
+ baos.packed2(types.get(summaries.get(fun)));
+ baos.packed2(fun._name==null ? 0 : strs.get(fun._name));
+ baos.packed4(fun._approxUIDs);
+ int[] edges = _init._edgess[fun.sig().fidx()];
+ baos.packed4(edges==null ? 0 : edges.length);
+ if( edges!=null ) for( int edge : edges ) baos.packed4(edge);
+ }
+
+ // D - Write the nodes. Since this is expected to be the bulk of the
+ // data, we might want to explore various options.
+
+ // How many nodes?
+ baos.packed4(nodes._len);
+
+ // First cut: 1 byte opcode, optional per-node info, includes variable nIns
+ for( Node n : nodes ) {
+ baos.packed1(n.serialTag().ordinal());
+ baos.packed2(types.get(n==nodes.at(0) ? startType : n==nodes.last() ? stopType : n._type));
+ n.packed(baos,strs,types,anodes);
+ }
+ // Write out the input indices packed.
+ for( Node n : nodes ) {
+ for( int j=0; j<n.nIns(); j++ ) {
+ if( !writeInput(n,j,anodes) ) continue;
+ Node def = n.in(j);
+ Integer idx = def==null ? Integer.valueOf(0) : anodes.get(def);
+ // Imported function constants retain their symbol, not a body.
+ if( idx==null && n instanceof FunPtrNode && j==1 ) idx=0;
+ // Shared escape analysis belongs to this compilation only.
+ if( def instanceof GlobalEscapeNode ) idx=0;
+ assert idx != null : "Missing serialized input for "+n+" input "+j+" def "+def;
+ baos.packed2(idx);
+ }
+ if( n instanceof FunNode fun )
+ baos.packed2(anodes.get(fun.ret()));
+ }
+
+ return baos;
+ }
+
+ // Omit cross-object call-graph links without unlinking the live IR.
+ // A parameter follows exactly the same retained callers as its function.
+ private static boolean writeInput(Node n, int i, IdentityHashMap<Node,Integer> nodes) {
+ if( n instanceof ParmNode parm ) n=parm.fun();
+ if( (n instanceof FunNode || n instanceof CallEndNode) && i>0 )
+ return nodes.containsKey(n.in(i));
+ if( n instanceof StopNode ) return nodes.containsKey(n.in(i));
+ return true;
+ }
+
+ public static int nIns(Node n, IdentityHashMap<Node,Integer> nodes) {
+ int len=0;
+ for( int i=0; i<n.nIns(); i++ ) if( writeInput(n,i,nodes) ) len++;
+ return len;
+ }
+ public static <T> void gather( HashMap<T,Integer> strs, T s) {
+ if( s!=null && !strs.containsKey(s) )
+ strs.put(s,strs.size());
+ }
+ public static void gather( AryInt is, int s) {
+ if( is.atX(s)==0 ) {
+ int cnt=0;
+ for( int i=1; i<is._len; i++ )
+ if( is._es[i] != 0 )
+ cnt++;
+ is.setX(s,cnt+1);
+ }
+ }
+
+ // A nested declaration belongs to its nearest enclosing file.
+ private static CompUnit classOwner(CodeGen code, TypeStruct ts) {
+ if( !Parser.startsClzPrefix(ts._name) ) return null;
+ String name = ts._name.substring(Parser.CLZ.length()).replace('.','/');
+ while( true ) {
+ CompUnit cu = code._compunits.get(name);
+ if( cu!=null ) return cu;
+ int slash = name.lastIndexOf('/');
+ if( slash<0 ) return null;
+ name = name.substring(0,slash);
+ }
+ }
+
+ static void read_public_strs(ElfReader elf) {
+ BAOS bais = elf._bais;
+ Type.TAGOFFS();
+
+ // A - Read a header
+ if( bais.read()!='C' || bais.read()!='0' || bais.read()!='D' || bais.read()!='E' )
+ throw new IllegalArgumentException("Unsupported serialized IR format; rebuild the object file");
+
+ // B - Packed read of #strings, then strings
+ int ndependents = bais.packed4(); // Number of dependent object files
+ int nstrs = bais.packed4(); // Total number of strings
+ String[] strs = new String[nstrs];
+ for( int i=0; i<nstrs; i++ )
+ strs[i] = bais.packedS();
+ elf._strs = strs;
+
+ // Collect dependent file names
+ elf._deps = new String[ndependents];
+ for( int i=0; i<ndependents; i++ )
+ elf._deps[i] = strs[1+i+1];
+ elf._units = new String[bais.packed4()];
+ for( int i=0; i<elf._units.length; i++ )
+ elf._units[i] = strs[bais.packed4()];
+ }
+
+ // --------------------------------------------------
+ record TypeRead(Type[] types, String[] strs, GlobalBits fileAliases, GlobalBits fileFidxs, GlobalBits fileRpcs) {}
+
+ static TypeRead readTypes( CodeGen code, ElfReader elf, GlobalBits aliases, GlobalBits fidxs, GlobalBits rpcs ) {
+ if( elf._types!=null ) return elf._types;
+ BAOS bais = elf._bais;
+ // Initialize the mapping from bits/tags to types
+ Type.TAGOFFS();
+
+ // If not read the strings already, read them now
+ String[] strs = elf._strs;
+ if( strs == null ) {
+ // A - Read a header
+ if( bais.read() != 'C' || bais.read() != '0' || bais.read() != 'D' || bais.read() != 'E' )
+ throw new IllegalArgumentException( "Unsupported serialized IR format; rebuild the object file" );
+
+ // B - Packed read of #strings, then strings
+ int ndependents = bais.packed4(); // Number of dependent object files
+ int nstrs = bais.packed4(); // Total number of strings
+ elf._strs = strs = new String[nstrs];
+ for( int i = 0; i < nstrs; i++ )
+ strs[i] = bais.packedS();
+
+ // Collect dependent file names
+ elf._deps = new String[ndependents];
+ for( int i = 0; i < ndependents; i++ )
+ elf._deps[i] = strs[1 + i + 1];
+ elf._units = new String[bais.packed4()];
+ for( int i=0; i<elf._units.length; i++ )
+ elf._units[i] = strs[bais.packed4()];
+ }
+
+ // B.2 - Read the local->global mappings. Further down we will map
+ // Elf file-local -> global, then global -> CodeGen.CODE local
+ GlobalBits fileAliases = GlobalBits.packed(bais,strs);
+ GlobalBits fileFidxs = GlobalBits.packed(bais,strs);
+ GlobalBits fileRpcs = GlobalBits.packed(bais,strs);
+ int nexterns = bais.packed2();
+ for( int i=0; i<nexterns; i++ ) {
+ int fileFidx = bais.packed2();
+ String extern = strs[bais.packed2()];
+ int fidx = fileFidx < GlobalBits.RESERVED ? fileFidx : fidxs.map(fileFidxs,fileFidx);
+ String old = code.externFunc(fidx);
+ if( old == null )
+ code.externFunc(fidx,extern);
+ else
+ assert old.equals(extern);
+ }
+
+
+ // C - Packed read of #types, then types
+ int ntypes = bais.packed4();
+ int npubTypes = bais.packed2();
+ int[] pubTypeIds = new int[npubTypes];
+ for( int i=0; i<npubTypes; i++ )
+ pubTypeIds[i] = bais.packed4();
+ Type[] types = Type.packed(bais,strs,ntypes, Parser.TYPES,
+ fileAliases,aliases,
+ fileFidxs ,fidxs,
+ fileRpcs ,rpcs);
+
+ // aliases.set(0,0);
+ // Check and collect first published symbols map to TypeStructs
+ elf._clz = (TypeStruct)types[1/*skip zero*/];
+ assert elf._clz._name==strs[1/*skip null ptr*/];
+ for( Type type : types )
+ if( type instanceof TypeStruct ts )
+ Parser.TYPES.put(ts._name,ts);
+ for( Type type : types ) {
+ if( !(type instanceof TypeStruct ts) || !Parser.startsClzPrefix(ts._name) )
+ continue;
+ String cname = ts._name.substring(Parser.CLZ.length());
+ int dot = cname.lastIndexOf('.');
+ String name = dot == -1 ? cname : cname.substring(dot+1);
+ Field inst = ts.field(name.intern());
+ if( inst != null && inst._t instanceof TypeFunPtr tfp && tfp._ret instanceof TypeMem mem && mem._t instanceof TypeStruct its )
+ Parser.TYPES.put(cname,its);
+ }
+ elf._pubTypes = new TypeStruct[npubTypes];
+ for( int i=0; i<npubTypes; i++ ) {
+ TypeStruct ts = elf._pubTypes[i] = (TypeStruct)types[pubTypeIds[i]];
+ Parser.TYPES.put(ts._name,ts);
+ }
+ elf._functions = new FunctionSummary[bais.packed4()];
+ for( int i = 0; i<elf._functions.length; i++ ) {
+ TypeFunPtr sig = (TypeFunPtr)types[bais.packed2()];
+ TypeTuple state = (TypeTuple)types[bais.packed2()];
+ String name = strs[bais.packed2()];
+ int size = bais.packed4();
+ int[] edges = new int[bais.packed4()];
+ for( int j = 0; j<edges.length; j++ ) edges[j]=fidxs.map(fileFidxs,bais.packed4());
+ elf._functions[i]=new FunctionSummary(sig,state,name.isEmpty() ? fidxs.symbol(sig.fidx()) : name,size,edges);
+ }
+ return elf._types = new TypeRead(types,strs,fileAliases,fileFidxs,fileRpcs);
+ }
+
+ static void readAll( CodeGen code, ElfReader elf, GlobalBits aliases, GlobalBits fidxs, GlobalBits rpcs ) {
+ BAOS bais = elf._bais;
+ TypeRead tread = readTypes(code, elf, aliases, fidxs, rpcs);
+ Type[] types = tread.types();
+ String[] strs = tread.strs();
+ GlobalBits fileAliases = tread.fileAliases();
+
+ // D - Read the nodes
+
+ // Number of nodes
+ int num = bais.packed4();
+ // Packed array of nodes
+ Ary<Node> nodes = new Ary<>(Node.class);
+ CompUnit cu = null;
+ for( int i = 0; i<num; i++ ) {
+ Node.Tag tag = Node.Tag.VALS[bais.packed1()];
+ Type t = types[bais.packed2()];
+ Node n = tag.make(bais,strs,types,fileAliases,aliases);
+ n._type = t;
+ nodes.push(n);
+ // A little extra for FunNodes
+ if( n instanceof StartCUNode startcu ) {
+ cu = code._compunits.get(startcu._fname==null ? (code._srcName==null ? "Test" : code._srcName) : startcu._fname);
+ assert cu != null;
+ }
+ if( n instanceof StopCUNode stopcu ) {
+ cu = null;
+ }
+ if( n instanceof FunNode fun ) {
+ assert cu != null;
+ fun._compunit = cu;
+ if( fun._name==null || fun._name.isEmpty() ) fun._name=fidxs.symbol(fun.sig().fidx());
+ }
+ if( n instanceof ExternNode ext && ext._con instanceof TypeFunPtr tfp && tfp.isConstant() ) {
+ String old = code.externFunc(tfp.fidx());
+ if( old == null )
+ code.externFunc(tfp.fidx(), ext._extern);
+ else
+ assert old.equals(ext._extern);
+ }
+ }
+
+ // Node edges
+ for( Node n : nodes ) {
+ int len = n.nIns();
+ for( int i = 0; i<len; i++ ) {
+ int idx = bais.packed2();
+ Node def = idx==0 ? null : nodes.at(idx-1);
+ // MemMerge precise aliases are encoded by their input slot.
+ // Remap populated file-local slots into this compilation's
+ // local alias numbering; null holes carry no identity.
+ int local = i;
+ if( n instanceof MemMergeNode && i >= GlobalBits.RESERVED ) {
+ if( def == null ) continue;
+ local = aliases.map(fileAliases,i);
+ }
+ n.setDefX(local,def);
+ // Call's output[0] is always the CallEnd
+ if( n instanceof CallEndNode cend && i==0 ) {
+ var outs = cend.call()._outputs;
+ outs.swap(0,outs._len-1);
+ }
+ }
+ // A little extra for FunNodes
+ if( n instanceof FunNode fun ) {
+ // Return is hooked like an edge
+ ReturnNode ret = (ReturnNode)nodes.at(bais.packed2()-1);
+ fun.setRet(ret);
+ ret._fun = fun;
+ }
+ }
+ // Add edge from Start to Stop
+ StartNode start = (StartNode)nodes.at(0);
+ StopNode stop = (StopNode)nodes.last();
+ start.setDef(1,stop);
+
+ elf._nodes = nodes;
+ }
+
+ // --------------------------------------------------
+ public static Ary<Node> nodeOrder( CodeGen code, CompUnit cu ) {
+ BitSet visit = code.visit();
+ Ary<Node> nodes = new Ary<>(Node.class);
+ visit.set(code._stop._nid);
+ // FinalizeIR attached every surviving function to its unit's Stop.
+ for( Node ret : cu._stop._inputs )
+ _funWalk(((ReturnNode)ret).fun(),nodes,visit);
+
+ // Gather constants and shared expressions through this unit's inputs.
+ // Walking their users instead would search other units and miss new
+ // kinds of shared expressions unless we special-cased each one.
+ Ary<Node> cons = new Ary<>(Node.class);
+ cons.add(code._start);
+ visit.set(code._start._nid);
+ visit.set(cu._start._nid);
+ for( Node n : cu._start._outputs )
+ if( n instanceof ProjNode ) visit.set(n._nid);
+ for( Node n : nodes )
+ for( int i = 0; i<n.nIns(); i++ ) {
+ // Parameters omit arguments from callers in other objects,
+ // just as writeInput omits those call-graph edges.
+ if( n instanceof ParmNode parm && i>0 && !visit.get(parm.fun().in(i)._nid) ) continue;
+ constantInputs(n.in(i),cons,visit);
+ }
+ // StartCU and memory projection
+ cons.add(cu._start);
+ for( Node n : cu._start._outputs )
+ if( n instanceof ProjNode ) cons.add(n);
+ // All the innards of functions
+ cons.addAll(nodes);
+ // StopCU, Stop
+ cons.add(cu._stop);
+ cons.add(code._stop);
+ visit.clear();
+ return cons;
+ }
+
+ private static void constantInputs(Node n, Ary<Node> cons, BitSet visit) {
+ if( n==null || visit.get(n._nid) ) return;
+ // Call graph edges lead to other functions, not shared values.
+ if( n instanceof CFGNode && !n.isConst() ) return;
+ visit.set(n._nid);
+ cons.add(n);
+ // A FunPtr's Return edge must not pull in the callee's body.
+ if( !n.isConst() )
+ for( Node def : n._inputs ) constantInputs(def,cons,visit);
+ }
+
+ // --------------------------------------------------
+ public static Ary<Node> nodeOrder( CodeGen code ) {
+ Ary<Node> nodes = new Ary<>(Node.class);
+ BitSet visit = code.visit();
+ nodes.add(code._start);
+
+ // All the global constants
+ for( Node n : code._start._outputs ) {
+ if( n.isConst() ) {
+ visit.set( n._nid );
+ nodes.add( n );
+ // Special for Cast:BOT
+ for( Node cast : n._outputs )
+ if( cast != null && cast.nIns() == 2 && cast.in( 0 ) == null ) {
+ visit.set( cast._nid );
+ nodes.add( cast );
+ // Array pointer conversion adds the body offset after
+ // the zero-code PtrToInt. Both nodes are global-value
+ // descendants of the constant and otherwise have no
+ // function-local root from which to walk them.
+ if( cast instanceof PtrToIntNode )
+ for( Node add : cast.outs() )
+ if( add instanceof AddNode && !visit.get(add._nid) ) {
+ visit.set(add._nid);
+ nodes.add(add);
+ }
+ }
+ }
+ }
+
+ // All the CompUnits
+ for( CompUnit cu : code._compunits.values() ) {
+ // Whole CompUnits have no users, went dead
+ if( cu._src!=null && cu._start != null && !cu._start.isDead() ) {
+ nodes.add(cu._start);
+ // A memory-free, nonreturning unit can lose its memory projection.
+ ProjNode mem=cu._start.proj(1);
+ if( mem!=null ) nodes.add(mem);
+ // All the functions, including internal ones, in comp-unit order
+ for( FunNode fun : code._linker )
+ if( fun!=null && !fun.isDead() && fun._compunit == cu )
+ _funWalk(fun,nodes,visit);
+ nodes.add(cu._stop);
+ }
+ }
+
+ nodes.add(code._stop);
+ visit.clear();
+ return nodes;
+ }
+
+ // Walk and print whole functions at a time, in CG order
+ private static void _funWalk( FunNode fun, Ary<Node> rpo, BitSet visit ) {
+ int start = rpo._len;
+ _funRPO(fun,rpo,visit);
+ // A nonreturning function still owns a Return, even before LoopTree
+ // inserts synthetic exits. No loop-depth ordering is needed on disk.
+ _funRPO(fun.ret(),rpo,visit);
+ int len = rpo._len - start;
+ for( int i=0; i< len>>1; i++ )
+ rpo.swap(start+i,start+len-1-i);
+ }
+
+ // Walk and gather RPO nodes.
+ private static void _funRPO(Node n, Ary<Node> rpo, BitSet visit ) {
+ // Return-linked constants can have users in other compilation units.
+ if( n.isConst() ) return;
+ if( visit.get(n._nid) ) return; // Been there, done that
+ visit.set(n._nid); // Stop recursion
+ // Walk outputs ordered
+ if( n instanceof CFGNode ncfg ) {
+ if( n instanceof IfNode iff ) {
+ CProjNode c0 = iff.cproj(0);
+ CProjNode c1 = iff.cproj(1);
+ _funRPO(c0,rpo,visit);
+ _funRPO(c1,rpo,visit);
+ } else if( n instanceof StopNode ) {
+ // nothing
+ } else {
+ // CFG to CFG before CFG to data
+ for( Node use : n._outputs )
+ if( use instanceof CFGNode usecfg && !ncfg.skip(usecfg) ) // Do not walk from Call to Fun
+ _funRPO(use,rpo,visit);
+ }
+ // Walk CFG to data eventually
+ for( Node use : n._outputs )
+ if( !(use instanceof CFGNode) )
+ _funRPO(use,rpo,visit);
+ } else {
+ // Do not walk from a non-CFG to a CFG; CFGs walk to CFGs to
+ // preserve CFG order. Do not walk *into* Phis; wait for the
+ // Region to walk the Phis (otherwise you visit some of the
+ // Phis ahead of others forcing the Phis to be spread around).
+ for( Node use : n._outputs ) {
+ if( use!=null && !(use instanceof CFGNode) && !(use instanceof PhiNode) )
+ _funRPO(use,rpo,visit);
+ }
+ }
+
+ // If parent is a Multi, do not add (yet).
+ // If self is a Multi, add self and children.
+ if( multiChild(n) || n instanceof CallEndNode ) {
+ // nothing; delay until Multi-head or CallNode
+ } else if( n instanceof MultiNode ) {
+ // Now lump all multi/projections together
+ printMulti(n,rpo);
+ } else if( n instanceof CallNode call ) {
+ printMulti(call.cend(),rpo);
+ rpo.add(call);
+ } else if( n instanceof RegionNode ) {
+ // Now lump all Phis together
+ int old = rpo._len;
+ for( Node use : n._outputs )
+ if( use instanceof PhiNode )
+ rpo.add(use);
+ // Sort by label? Want Phis before control outputs
+ Arrays.sort( rpo._es, old, rpo._len, (x,y) -> y.label().compareTo(x.label()) );
+ rpo.add(n);
+ } else if( n instanceof StopNode ) {
+ // nothing
+ } else {
+ rpo.add(n); // Post-order add
+ }
+ }
+
+ private static boolean multiChild(Node n) {
+ return n instanceof Proj || n instanceof PhiNode;
+ }
+
+ private static void printMulti(Node n, Ary<Node> rpo) {
+ // Now lump all multi/projections together
+ for( Node use : n._outputs )
+ rpo.add(use);
+ // Sort by projection order
+ if( !(n instanceof StartNode) )
+ Arrays.sort( rpo._es,rpo._len-n.nOuts(),rpo._len,
+ (x,y) -> x instanceof ProjNode px && y instanceof ProjNode py ? (py.idx() - px.idx()) : (y._nid - x._nid) );
+ rpo.add(n);
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/SourceError.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/SourceError.java
new file mode 100644
index 00000000..f2f46146
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/SourceError.java
@@ -0,0 +1,49 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.util.SB;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+/** Format one parser byte offset. Source is read and text built only on error. */
+public abstract class SourceError {
+ public static String format(CompUnit cu, int pos, String message) {
+ String file=cu.sourceFile();
+ String name=file==null ? cu._fname+".smp" : file;
+ byte[] source;
+ try {
+ source=cu._src!=null ? cu._src.getBytes(StandardCharsets.UTF_8)
+ : file==null ? null : Files.readAllBytes(Path.of(file));
+ } catch( IOException ex ) { source=null; }
+ if( source==null || pos<0 || pos>source.length )
+ return name+": error: "+message+" (source unavailable at byte offset "+pos+")";
+
+ int line=1, start=0;
+ for( int i=0; i<pos; i++ )
+ if( source[i]=='\n' ) { line++; start=i+1; }
+ int end=pos;
+ while( end<source.length && source[end]!='\n' ) end++;
+ if( end>start && source[end-1]=='\r' ) end--;
+ int point=Math.min(pos,end);
+ String before=new String(source,start,point-start,StandardCharsets.UTF_8);
+ String text=new String(source,start,end-start,StandardCharsets.UTF_8);
+ int column=before.codePointCount(0,before.length())+1;
+ SB sb=new SB().p(name).p(':').p(line).p(':').p(column).p(": error: ").p(message).p("\n ");
+ int width=0, caret=0;
+ for( int i=0; i<text.length(); ) {
+ if( i==before.length() ) caret=width;
+ int ch=text.codePointAt(i);
+ if( ch=='\t' ) {
+ int spaces=8-(width%8);
+ for( int j=0; j<spaces; j++ ) sb.p(' ');
+ width+=spaces;
+ } else { sb.p(new String(Character.toChars(ch))); width++; }
+ i+=Character.charCount(ch);
+ }
+ if( before.length()==text.length() ) caret=width;
+ sb.p("\n ");
+ for( int i=0; i<caret; i++ ) sb.p('-');
+ return sb.p('^').toString();
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/StaticData.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/StaticData.java
new file mode 100644
index 00000000..f813fab5
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/StaticData.java
@@ -0,0 +1,177 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+import java.util.*;
+
+// Pool objects have identity independently of their initial field values.
+// References in their contents use the same symbols in ELF and emulator images.
+public final class StaticData {
+ public static final class ObjectData {
+ public final Type type;
+ public final String symbol;
+ public final boolean readOnly, external;
+ public final int alignment, size;
+ public int offset;
+ ObjectData(Type t, String name, boolean ro, boolean ext) {
+ type=t; symbol=name; readOnly=ro; external=ext;
+ alignment=t.alignment();
+ int align=1<<alignment;
+ size=t instanceof TypeStruct ts ? (Math.max(1,ts.size())+align-1)&-align : align;
+ }
+ }
+ public record Relocation(ObjectData object, int offset, int logSize, Type value) {}
+
+ final Encoding enc;
+ final LinkedHashMap<Object,ObjectData> objects = new LinkedHashMap<>();
+ final ArrayList<Relocation> relocations = new ArrayList<>();
+ final String prefix;
+
+ public ArrayList<ObjectData> entries() {
+ ArrayList<ObjectData> entries=new ArrayList<>(objects.values());
+ entries.sort(Comparator.comparingInt((ObjectData obj) -> obj.alignment).reversed());
+ return entries;
+ }
+
+ StaticData(Encoding enc) {
+ this.enc=enc;
+ prefix=(enc._code._emitUnit==null ? enc._code.entryClinitName() : enc._code._emitUnit._cname)+"$DATA$";
+ }
+
+ static boolean isClass(Type t) {
+ return t instanceof TypeStruct ts && Parser.startsClzPrefix(ts._name);
+ }
+
+ static CompUnit owner(CodeGen code,TypeStruct ts) {
+ return owner(code,ts._name.substring(Parser.CLZ.length()));
+ }
+ static CompUnit owner(CodeGen code,String name) {
+ if(name==null) return null;
+ CompUnit owner=null;
+ for( CompUnit cu : code._compunits.values() )
+ if( cu._cname!=null && (name.equals(cu._cname) || name.startsWith(cu._cname+".")) &&
+ (owner==null || cu._cname.length()>owner._cname.length()) ) owner=cu;
+ return owner;
+ }
+
+ ObjectData object(Type t) {
+ Object key=isClass(t) ? ((TypeStruct)t)._name
+ : t instanceof TypeFunPtr fp ? functionSymbol(fp) : t;
+ ObjectData old=objects.get(key);
+ if( old!=null ) return old;
+ boolean clz=isClass(t);
+ if( clz && Parser.TYPES.get(((TypeStruct)t)._name) instanceof TypeStruct canonical ) t=canonical;
+ CompUnit owner=clz ? owner(enc._code,(TypeStruct)t)
+ : t instanceof TypeFunPtr fp ? owner(enc._code,enc._code._fidxs.owner(fp.fidx())) : null;
+ String symbol=clz ? ((TypeStruct)t)._name.substring(Parser.CLZ.length())+".$class"
+ : t instanceof TypeFunPtr fp ? functionSymbol(fp) : prefix+objects.size();
+ ObjectData obj=new ObjectData(t,symbol,!clz && (!(t instanceof TypeStruct ts) || ts.isConstant()),
+ owner!=null && !enc._code.emits(owner));
+ objects.put(key,obj);
+ // Register before following fields: class objects may refer to each other.
+ if( !obj.external && t instanceof TypeStruct ts )
+ for( Field f : ts._fields )
+ if( !f._extern && f._t.isConstant() ) target(f._t);
+ return obj;
+ }
+
+ private static boolean pointerConstant(TypeMemPtr ptr) {
+ return ptr.notNull() && (isClass(ptr._obj) || (ptr._obj.isConstant() && (!ptr._obj.isAry() || ptr._obj.isConAry())));
+ }
+
+ private void target(Type t) {
+ if( t instanceof TypeMemPtr ptr && pointerConstant(ptr) ) object(ptr._obj);
+ if( t instanceof TypeFunPtr fp && fp.notNull() &&
+ enc._code.link(fp)==null && !enc._code._externFunc.containsKey(fp.fidx()) )
+ // A dead function still needs a unique non-null identity for comparisons.
+ object(fp);
+ }
+
+ void write() {
+ ArrayList<Encoding.Relo> refs=new ArrayList<>(enc._bigCons.values());
+ refs.sort(Comparator.comparingInt(r -> r._op._nid));
+ for( Encoding.Relo ref : refs ) object(ref._t);
+ // Definitions belong to their compilation unit, even if every local use folded.
+ ArrayList<CompUnit> units=new ArrayList<>(enc._code._compunits.values());
+ units.sort(Comparator.comparing(cu -> cu._cname==null ? "" : cu._cname));
+ for( CompUnit cu : units ) if( enc._code.emits(cu) && cu._clz!=null ) object(cu._clz);
+ for( ObjectData obj : entries() ) {
+ if( obj.external ) continue;
+ BAOS bits=obj.readOnly ? enc._cpool : enc._sdata;
+ Encoding.padN(1<<obj.alignment,bits);
+ obj.offset=bits.size();
+ if( obj.type instanceof TypeStruct ts ) writeStruct(obj,ts,bits);
+ else Encoding.addN(obj.alignment,obj.type,bits);
+ while( bits.size()<obj.offset+obj.size ) bits.write(0);
+ }
+ for( Encoding.Relo ref : refs ) {
+ ObjectData obj=object(ref._t);
+ ref._target=obj.offset;
+ ref._opStart=enc.opStart(ref._op);
+ }
+ }
+
+ private void writeStruct(ObjectData obj, TypeStruct ts, BAOS bits) {
+ int[] layout=ts.layout();
+ for( int idx : layout ) {
+ if( idx==ts._fields.length ) continue;
+ Field f=ts._fields[idx];
+ if( f._extern ) continue;
+ int off=ts.offset(idx);
+ while( bits.size()<obj.offset+off ) bits.write(0);
+ if( f._fname.equals("[]") ) ((TypeConAry)f._t).write(bits);
+ else {
+ Type value=f._t.isConstant() ? f._t : f._t.makeZero();
+ int log=f._t.log_size();
+ Encoding.addN(log,value,bits);
+ if( (value instanceof TypeFunPtr fp && fp.notNull()) ||
+ (value instanceof TypeMemPtr ptr && pointerConstant(ptr)) )
+ relocations.add(new Relocation(obj,off,log,value));
+ }
+ }
+ while( bits.size()<obj.offset+ts.size() ) bits.write(0);
+ }
+
+ String functionSymbol(TypeFunPtr fp) {
+ String ext=enc._code._externFunc.get(fp.fidx());
+ return ext!=null ? ext : enc._code._fidxs.symbol(fp.fidx());
+ }
+
+ String targetSymbol(Type value) {
+ if( value instanceof TypeMemPtr ptr ) return object(ptr._obj).symbol;
+ TypeFunPtr fp=(TypeFunPtr)value;
+ return enc._code.link(fp)==null && !enc._code._externFunc.containsKey(fp.fidx())
+ ? object(fp).symbol : functionSymbol(fp);
+ }
+
+ void link(int cpool, int sdata) {
+ for( Relocation rel : relocations ) {
+ long address;
+ if( rel.value instanceof TypeMemPtr ptr ) address=address(object(ptr._obj),cpool,sdata);
+ else {
+ TypeFunPtr fp=(TypeFunPtr)rel.value;
+ String ext=enc._code._externFunc.get(fp.fidx());
+ FunNode fun=enc._code.link(fp);
+ if( ext!=null ) address=enc.externalAddress(ext);
+ else if( fun==null ) address=address(object(fp),cpool,sdata);
+ else {
+ if( !fun.isFromSource() ) throw new IllegalArgumentException("Unlinked function "+functionSymbol(fp));
+ address=enc.opStart(fun);
+ }
+ }
+ // Emulator native-call sentinels are signed 32-bit addresses.
+ if( rel.logSize==2 && (address<Integer.MIN_VALUE || address>0xffff_ffffL) )
+ throw new IllegalArgumentException("Function address outside 32 bits: "+address);
+ BAOS bits=rel.object.readOnly ? enc._cpool : enc._sdata;
+ int off=rel.object.offset+rel.offset;
+ for( int i=0; i<(1<<rel.logSize); i++ ) bits.buf()[off+i]=(byte)(address>>>(8*i));
+ }
+ }
+
+ int address(ObjectData obj,int cpool,int sdata) {
+ if( obj.external ) throw new IllegalArgumentException("Unlinked class object "+obj.symbol);
+ return obj.offset+(obj.readOnly ? cpool : sdata);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
new file mode 100644
index 00000000..c9e53311
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
@@ -0,0 +1,59 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.Ary;
+
+// Reject invalid optimized programs before lowering erases their source-level
+// obligations. Queue surviving CLInits during the same walk for Lower's Iter.
+abstract class TypeCheck {
+ static void run(CodeGen code) {
+ // Check for class-init cycles first; such a cycle can prevent other
+ // things from resolving types. Use the graph and summaries left by
+ // optimization; do not load more bodies to disprove a possible cycle.
+ ClassInitDependencies deps = new ClassInitDependencies(code);
+ String cycle = deps.check();
+ if( cycle!=null ) throw Parser.error(cycle,null);
+ code._classInitDependencies = deps; // Serialize reuses this checked graph.
+
+ Ary<Node> errs = new Ary<>(Node.class);
+ code._stop.walk( n -> {
+ if( n.err() != null )
+ errs.add(n);
+ if( n instanceof CLInitNode ) code.add(n);
+ return null;
+ });
+
+ if( !errs.isEmpty() ) {
+ // Prefer an originating error over a secondary error caused by a
+ // BOTTOM input. If all errors are downstream-poisoned, retain
+ // the old first-error behavior.
+ Node min = null;
+ for( Node n : errs )
+ if( noBottomInputs(n) )
+ { min = n; break; }
+ if( min==null )
+ min = errs.at(0);
+ for( Node n : errs )
+ if( n!=min &&
+ noBottomInputs(n) &&
+ ((n instanceof MemOpNode nmop &&
+ min instanceof MemOpNode mmop &&
+ nmop._loc.before(mmop._loc)) ||
+ (n instanceof CFGNode ncfg &&
+ min instanceof CFGNode mincfg &&
+ ncfg.idepth() < mincfg.idepth())) )
+ min = n;
+ if( code._obs != null ) code._obs.error(min, min.err().getMessage());
+ throw min.err();
+ }
+ }
+
+ private static boolean noBottomInputs(Node n) {
+ for( Node def : n._inputs )
+ if( def!=null && !(def instanceof FldOffNode) && def._type==Type.BOTTOM )
+ return false;
+ return true;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/codegen/Unlink.java b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Unlink.java
new file mode 100644
index 00000000..bb9a6196
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/codegen/Unlink.java
@@ -0,0 +1,174 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.Ary;
+
+// Discard call-graph edges after LoopTree. Machine selection keeps only this
+// object's bodies; ideal evaluation keeps imports so their calls can execute.
+abstract class Unlink {
+ // Disconnect compiler-only interprocedural edges before scheduling. Keep
+ // signatures in the linker; runtime calls resolve FIDXs, not graph edges.
+ static Ary<FunNode> forEval(CodeGen code, StartNode start, StopNode stop) {
+ Ary<Node> nodes = new Ary<>(Node.class);
+ start.walk(n -> { nodes.add(n); return null; });
+ Ary<FunNode> funs = new Ary<>(FunNode.class);
+ for( Node n : nodes )
+ if( n instanceof FunNode fun ) {
+ fun.ret().keep();
+ if( fun.summary()==null ) funs.add(fun);
+ }
+ for( Node n : nodes ) {
+ if( n instanceof CallNode call ) call.unlink_all();
+ if( n instanceof CLInitNode init ) init.unlink();
+ if( n instanceof FunPtrNode ptr && ptr.ret()!=null ) ptr.setDef(1,null);
+ }
+ for( FunNode fun : funs ) {
+ // Keep bodies rooted for scheduling after their callers detach.
+ if( fun.nIns()==1 ) {
+ fun.addDef(fun._compunit._start);
+ for( Node use : fun._outputs )
+ if( use instanceof ParmNode parm ) {
+ ConstantNode con = ConstantNode.raw(parm._type);
+ con.setDef(0,start);
+ parm.addDef(con);
+ }
+ }
+ restoreRPC(fun);
+ }
+ code.releaseUnlinkedNodes();
+ for( Node n : nodes )
+ if( n instanceof StartCUNode unitStart ) detachEmptyStart(unitStart);
+ // A summary is a dispatch target, not a schedulable implementation.
+ // Retain its own wrapper graph without including it in this schedule.
+ for( int i = 0; i<stop.nIns(); i++ ) {
+ StopCUNode unit = (StopCUNode)stop.in(i);
+ boolean summary = false;
+ for( Node r : unit._inputs )
+ summary |= ((ReturnNode)r).fun().summary()!=null;
+ if( summary || unit.nIns()==0 ) {
+ unit.start().setDef(0,null);
+ stop.delDef(i--);
+ }
+ }
+ for( Node n : nodes )
+ if( n instanceof FunNode fun ) fun.ret().unkeep();
+ return funs;
+ }
+
+ private static void unlinkImports(CodeGen code) {
+ // Imported function constants keep their symbolic identity, not an
+ // embedded copy of the other object's Return and body.
+ for( Node n : code._start._outputs.asAry() )
+ if( n instanceof FunPtrNode ptr && ptr.ret()!=null && !ptr.fun().isFromSource() ) {
+ ptr.liftType(ptr._type);
+ ptr.setDef(1,null);
+ }
+ for( FunNode fun : code._linker ) {
+ if( fun==null || fun.isDead() || fun.isFromSource() )
+ continue;
+ for( int i=1; i<fun.nIns(); i++ )
+ if( fun.in(i) instanceof CallNode call )
+ call.unlink(fun,i--);
+ }
+ for( int i=0; i<code._stop.nIns(); i++ ) {
+ StopCUNode stop=(StopCUNode)code._stop.in(i);
+ CompUnit cu=code._compunits.get(stop.start()._fname);
+ if( cu._src==null ) code._stop.delDef(i--);
+ }
+ // The whole-object wrappers now summarize only the emitted units.
+ code._stop ._type=code._stop .compute();
+ code._start._type=code._start.compute();
+ }
+
+ static void run(CodeGen code, boolean removeImports) {
+ if( removeImports ) unlinkImports(code);
+
+ // The remaining passes assume all calls are unlinked; i.e. we are throwing
+ // away the Call Graph here. Functions only reachable from internal calls
+ // need to be re-hooked to stop/start less they go dead.
+ for( FunNode fun : code._linker ) {
+ // Already linked to start, not going dead
+ if( fun==null || fun.isDead() )
+ continue;
+
+ // Imported functions are not emitted by this object. Ideal
+ // evaluation retains them so their calls can actually execute.
+ if( removeImports && !fun.isFromSource() ) {
+ while( fun.nIns() > 1 ) {
+ if( fun.in(1) instanceof CallNode call )
+ call.unlink(fun,1);
+ else
+ fun.removeDeadPath(1);
+ }
+ StartCUNode start = fun._compunit._start;
+ StopCUNode stop = fun._compunit._stop;
+ // Unhook the Stop->Return edge also
+ ReturnNode ret = fun.ret();
+ int idx = stop._inputs.find(ret);
+ if( idx != -1 ) {
+ stop._inputs.del(idx);
+ ret.delUse(stop);
+ }
+ //
+ if( stop.nIns()==0 ) {
+ idx = code._stop._inputs.find(stop);
+ if( idx != -1 )
+ code._stop.delDef(idx);
+ if( start.nIns() > 0 && start.in(0) != null )
+ start.setDef(0,null);
+ if( start.nIns() > 1 && start.in(1) != null )
+ start.setDef(1,null);
+ }
+ continue;
+ }
+
+ // Insert the post-Opto code-generation hook. Unlike an Opto-time
+ // Start input, this no longer carries unknown-caller semantics.
+ ReturnNode ret = fun.ret();
+ StartNode start = fun._compunit._start;
+ assert start!=null;
+ if( fun.nIns() <= 1 || fun.in(1) != start ) {
+ fun.insertDef(1,start);
+ for( Node use : fun._outputs )
+ if( use instanceof ParmNode parm )
+ parm.insertDef(1,ConstantNode.raw(parm._type));
+ if( fun._compunit._stop._inputs.find(ret) == -1 )
+ fun._compunit._stop.addDef(ret);
+ }
+ // Unlink from Call
+ for( int i=2; i<fun.nIns(); i++ )
+ ((CallNode)fun.in(i)).unlink(fun,i--);
+ restoreRPC(fun);
+ }
+ code.releaseUnlinkedNodes();
+ for( CompUnit cu : code._compunits.values() )
+ if( cu._start!=null && !cu._start.isDead() ) detachEmptyStart(cu._start);
+
+ code._stop.walk(n -> {
+ if( n instanceof CallEndNode cend )
+ for( int i=1; i<cend.nIns(); i++ )
+ if( cend.in(i) instanceof ReturnNode )
+ cend.delDef(i--);
+ if( n instanceof CallNode call )
+ call.unlink_all();
+ return null;
+ });
+ }
+
+ private static void detachEmptyStart(StartCUNode start) {
+ // An unused unit can lose its outer Stop edge during optimization.
+ // Detach its empty wrapper from the scheduling root as well.
+ if( start.in(1) instanceof StopCUNode stop && stop.nIns()==0 ) start.setDef(0,null);
+ }
+
+ private static void restoreRPC(FunNode fun) {
+ if( fun.rpc()!=null ) return;
+ // A single-call target can have a folded RPC even when a multi-target
+ // call cannot inline it. Restore the parameter for codegen and for
+ // Eval2's continuation, now that calls no longer supply graph edges.
+ ReturnNode ret = fun.ret();
+ ParmNode rpc = new ParmNode("$rpc",0,ret.rpc()._type,fun);
+ rpc.addDef(ret.rpc());
+ ret.setDef(3,rpc.init());
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/AddNode.java
new file mode 100644
index 00000000..a29ba73b
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -0,0 +1,179 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+
+public class AddNode extends ArithNode {
+ @Override boolean allowFloat() { return true; }
+ public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+ public AddNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs, mode); }
+ @Override public Tag serialTag() { return Tag.Add; }
+
+ @Override public String label() { return "Add"; }
+ @Override public String op() { return "+"; }
+
+ @Override long doOp( long x, long y ) { return x + y; }
+ @Override double doOp(double x, double y ) { return x + y; }
+ @Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
+ // Fold ranges like {0-1} + {2-3} into {2-4}.
+ if( !overflow(x._min,y._min) &&
+ !overflow(x._max,y._max) )
+ return TypeInteger.make(x._min+y._min,x._max+y._max);
+ return TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize () {
+ // Settle the one-shot arithmetic mode before applying family-specific
+ // algebraic identities.
+ if( _mode==0 )
+ return super.idealize();
+
+ Node lhs = in(1);
+ Node rhs = in(2);
+ if( rhs instanceof AddNode add && add.err()!=null )
+ return null;
+ Type t2 = rhs._type;
+
+ // Add of 0. We do not check for (0+x) because this will already
+ // canonicalize to (x+0)
+ if( t2 == TypeInteger.ZERO && lhs._type.isa(compute()) )
+ return lhs;
+
+ // Add of same to a multiply by 2
+ if( lhs==rhs && _mode==1 )
+ return new ShlNode(null,lhs,CodeGen.CODE.con(1));
+
+ // Goal: a left-spine set of adds, with constants on the rhs (which then fold).
+
+ // Move non-adds to RHS
+ if( !(lhs instanceof AddNode) && rhs instanceof AddNode )
+ return swap12();
+
+ // x+(-y) becomes x-y
+ if( rhs instanceof MinusNode minus )
+ return new SubNode(lhs,minus.in(1),_mode);
+
+ // Now we might see (add add non) or (add non non) or (add add add) but never (add non add)
+
+ // Do we have x + (y + z) ?
+ // Swap to (x + y) + z
+ // Rotate (add add add) to remove the add on RHS
+ if( rhs instanceof AddNode add )
+ return copy(copy(lhs,add.in(1)).peephole(), add.in(2));
+
+ // Now we might see (add add non) or (add non non) but never (add non add) nor (add add add)
+ if( !(lhs instanceof AddNode) ) {
+ // Rotate; look for (add (phi cons) con/(phi cons))
+ if( spine_cmp(lhs,rhs,this) ) return swap12();
+ Node n = phiCon(this,true);
+ return n == null ? super.idealize() : n;
+ }
+
+ // Now we only see (add add non)
+
+ // Dead data cycle; comes about from dead infinite loops. Do nothing,
+ // the loop will peep as dead after a bit.
+ if( lhs.nIns()>=2 && lhs.in(1) == lhs )
+ return null;
+
+ // Do we have (x + con1) + con2?
+ // Replace with (x + (con1+con2) which then fold the constants
+ // lhs.in(2) is con1 here
+ // If lhs.in(2) is not a constant, we add ourselves as a dependency
+ // because if it later became a constant then we could make this
+ // transformation.
+ if( addDep(lhs.in(2))._type.isConstant() && rhs._type.isConstant() )
+ return copy(lhs.in(1),copy(lhs.in(2),rhs).peephole());
+
+
+ // Do we have ((x + (phi cons)) + con) ?
+ // Do we have ((x + (phi cons)) + (phi cons)) ?
+ // Push constant up through the phi: x + (phi con0+con0 con1+con1...)
+ Node phicon = phiCon(this,true);
+ if( phicon!=null ) return phicon;
+
+ // Now we sort along the spine via rotates, to gather similar things together.
+
+ // Do we rotate (x + y) + z
+ // into (x + z) + y ?
+ if( spine_cmp(lhs.in(2),rhs,this) )
+ return copy(copy(lhs.in(1),rhs).peephole(),lhs.in(2));
+
+ return super.idealize();
+ }
+
+ // Rotation is only valid for associative ops, e.g. Add, Mul, And, Or, Xor.
+ // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)|(op y con)) ?
+ // Push constant up through the phi: x + (phi con0+con0 con1+con1...)
+ static Node phiCon(Node op, boolean rotate) {
+ Node lhs = op.in(1);
+ Node rhs = op.in(2);
+ if( rhs._type==TypeInteger.TOP ) return null;
+ // LHS is either a Phi of constants, or another op with Phi of constants
+ PhiNode lphi = pcon(lhs,op);
+ if( rotate && lphi==null && lhs.nIns() > 2 ) {
+ // Only valid to rotate constants if both are same associative ops
+ if( lhs.getClass() != op.getClass() ) return null;
+ lphi = pcon(lhs.in(2),op); // Will rotate with the Phi push
+ }
+ if( lphi==null ) return null;
+ if( lphi.region().nIns() <=2 ) return null; // Phi is collapsing
+
+ // RHS is a constant or a Phi of constants
+ if( !(rhs instanceof ConstantNode) && pcon(rhs,op)==null )
+ return null;
+
+ // If both are Phis, must be same Region
+ if( rhs instanceof PhiNode && lphi.in(0) != rhs.in(0) )
+ return null;
+
+ // Note that this is the exact reverse of Phi pulling a common op down
+ // to reduce total op-count. We don't get in an endless push-up
+ // push-down peephole cycle because the constants all fold first.
+ Node[] ns = new Node[lphi.nIns()];
+ ns[0] = lphi.in(0);
+ // Push constant up through the phi: x + (phi con0+con0 con1+con1...)
+ for( int i=1; i<ns.length; i++ )
+ ns[i] = op.copy(lphi.in(i), rhs instanceof PhiNode ? rhs.in(i) : rhs).peephole();
+ String label = lphi._label + (rhs instanceof PhiNode rphi ? rphi._label : "");
+ Node phi = new PhiNode(label,ns).peephole();
+ // Rotate needs another op, otherwise just the phi
+ return lhs==lphi ? phi : op.copy(lhs.in(1),phi);
+ }
+
+ /**
+ * Tests if the op is a phi and has all constant inputs.
+ * If not, returns null.
+ * If op is a phi, but its inputs are not all constants, then dep is added as
+ * a dependency to the phi's non-const input, because if later the input turn into a constant
+ * dep can make progress.
+ */
+ static PhiNode pcon(Node op, Node dep) {
+ return op instanceof PhiNode phi && !(phi instanceof ParmNode) && phi.allCons(dep) ? phi : null;
+ }
+
+ // Compare two off-spine nodes and decide what order they should be in.
+ // Do we rotate ((x + hi) + lo) into ((x + lo) + hi) ?
+ // Generally constants always go right, then Phi-of-constants, then muls, then others.
+ // Ties with in a category sort by node ID.
+ // TRUE if swapping hi and lo.
+ static boolean spine_cmp( Node hi, Node lo, Node dep ) {
+ if( lo._type.isConstant() || lo instanceof ConFldOffNode ) return false;
+ if( hi._type.isConstant() || hi instanceof ConFldOffNode ) return true ;
+
+ if( lo instanceof PhiNode lphi && lphi.region()._type==Type.XCONTROL ) return false;
+ if( hi instanceof PhiNode hphi && hphi.region()._type==Type.XCONTROL ) return false;
+
+ if( lo instanceof PhiNode && lo.allCons(dep) ) return false;
+ if( hi instanceof PhiNode && hi.allCons(dep) ) return true ;
+
+ if( lo instanceof PhiNode && !(hi instanceof PhiNode) ) return true;
+ if( hi instanceof PhiNode && !(lo instanceof PhiNode) ) return false;
+
+ // Same category of "others"
+ return lo._nid > hi._nid;
+ }
+
+ @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs,_mode); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/AndNode.java
new file mode 100644
index 00000000..a1c6bba1
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/AndNode.java
@@ -0,0 +1,50 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Lexer;
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+public class AndNode extends ArithNode {
+ public AndNode(Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); }
+ @Override public Tag serialTag() { return Tag.And; }
+
+ @Override public String op() { return "&"; }
+ @Override long doOp( long x, long y ) { return x & y; }
+ @Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
+ // Sharpen allowed bits if either value is narrowed
+ long mask = x.mask() & y.mask();
+ return mask < 0 ? TypeInteger.BOT : TypeInteger.make(0,mask);
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+
+ Type t1 = lhs._type;
+ Type t2 = rhs._type;
+ if( !(t1 instanceof TypeInteger && t2 instanceof TypeInteger t2i) )
+ return null; // Malformed, e.g. (17 & 3.14)
+
+ // And of -1. We do not check for (-1&x) because this will already
+ // canonicalize to (x&-1). We do not check for zero, because
+ // the compute() call will return a zero already.
+ if( t2i.isConstant() && t2i.value()==-1 && forceInt(lhs) )
+ return lhs;
+
+ // Move constants to RHS: con*arg becomes arg*con
+ if( t1.isConstant() && !t2.isConstant() )
+ return swap12();
+
+ // Do we have ((x & (phi cons)) & con) ?
+ // Do we have ((x & (phi cons)) & (phi cons)) ?
+ // Push constant up through the phi: x & (phi con0&con0 con1&con1...)
+ Node phicon = AddNode.phiCon(this,true);
+ if( phicon!=null )
+ return phicon;
+
+ return super.idealize();
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new AndNode(_loc,lhs,rhs); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ArithNode.java
new file mode 100644
index 00000000..16e9ec8d
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ArithNode.java
@@ -0,0 +1,214 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Lexer;
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+public abstract class ArithNode extends Node implements ModeNode {
+ // Source location for late reported errors
+ Lexer _loc;
+ // Mode is 0 for unknown, 1 for int, 2 for flt
+ byte _mode;
+
+ public ArithNode( Lexer loc, Node lhs, Node rhs) { super(null, lhs, rhs); _loc = loc; }
+ public ArithNode( Lexer loc, Node lhs, Node rhs, byte mode) { this(loc,lhs,rhs); _mode=mode; }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) {
+ baos.packed1(_mode);
+ }
+ abstract String op();
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ return p.open().n(in(1)).p(op()).n(in(2)).close();
+ }
+
+ abstract long doOp(long x, long y);
+ abstract TypeInteger doOp(TypeInteger x, TypeInteger y);
+ double doOp(double x, double y) { throw Utils.TODO(); }
+
+ @Override public byte mode() { return _mode; }
+ @Override public Node setMode(byte mode) {
+ assert _mode==0 && (mode==1 || mode==2);
+ unlock(); // _mode participates in GVN hash and equality
+ _mode = mode;
+ return this;
+ }
+ static byte mode( Type t1, Type t2 ) {
+ if( t1 instanceof TypeFloat || t2 instanceof TypeFloat ) return 2;
+ if( t1 instanceof TypeInteger && t2 instanceof TypeInteger ) return 1;
+ if( t1 instanceof TypeMemPtr && t2 instanceof TypeMemPtr ) return 1;
+ if( t1 instanceof TypeFunPtr && t2 instanceof TypeFunPtr ) return 1;
+ return 0;
+ }
+
+ // Int-only ops, such as &, | and shifts, can collapse to one input. If
+ // that input, or the live arms of a Phi input, are still mode-polymorphic
+ // ops, settle them before returning the value so the replacement remains
+ // at least as narrow as the int-only op.
+ static boolean forceInt(Node n) {
+ if( n instanceof ModeNode mode && mode.mode()==0 ) {
+ mode.setMode((byte)1);
+ n.setType(n.compute());
+ // Mode changes remove n from GVN even if the caller's remaining
+ // conditions prevent replacing the int-only operation by n.
+ CodeGen.CODE.add(n);
+ }
+ return true;
+ }
+
+ // Check that a settled mode agrees with all data inputs. Takes the Node
+ // instead of a varargs list both to avoid allocation and to keep this ready
+ // to become shared mode-aware Node behavior later.
+ static Parser.ParseException nodeErr(Node n, Lexer loc, String op, byte mode, boolean allowFloat) {
+ Type bad = null;
+ for( int i=1; i<n.nIns(); i++ ) {
+ Type t = n.in(i)._type;
+ if( t==null )
+ return null;
+ if( t.isHigh() )
+ return null;
+ // A weak input is not evidence of a bad mode while the graph is
+ // still being built. Loop Phis commonly await their backedge.
+ if( CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() && t == Type.BOTTOM )
+ return null;
+ bad = switch( t ) {
+ case TypeInteger ti -> mode==1 ? null : t;
+ case TypeMemPtr ti -> mode==1 ? null : t;
+ case TypeFunPtr ti -> mode==1 ? null : t;
+ case TypeFloat tf -> mode==2 ? null : t;
+ default -> t;
+ };
+ if( bad != null ) break;
+ }
+ boolean unsupportedFloat = false;
+ if( !allowFloat )
+ for( int i=1; i<n.nIns(); i++ )
+ if( n.in(i)._type instanceof TypeFloat ) {
+ bad = n.in(i)._type;
+ unsupportedFloat = true;
+ break;
+ }
+ if( bad == null )
+ return null;
+ if( unsupportedFloat || n instanceof BoolNode )
+ return Parser.error("Cannot '"+op+"' "+bad,loc);
+ if( n.nIns()==3 )
+ return Parser.error("Cannot "+n.in(1)._type+" "+op+" "+n.in(2)._type,loc);
+ return Parser.error("Cannot '"+op+"' "+bad,loc);
+ }
+
+ // Arithmetic subclasses which have a floating-point interpretation override.
+ boolean allowFloat() { return false; }
+
+ // Generic arithmetic op math: high returns high; low returns low; 2
+ // constants fold; only 2 non-constants call specialized math.
+ @Override
+ public final Type compute() {
+ Type t1 = in(1)._type, t2 = in(2)._type;
+ // Constant array payloads lie below their element ranges in the lattice.
+ if( t1 instanceof TypeConAry ary ) t1 = ary.elem();
+ if( t2 instanceof TypeConAry ary ) t2 = ary.elem();
+ if( t1==null || t2==null ) return Type.TOP;
+ if( t1.isHigh() || t2.isHigh() ) return Type.TOP;
+ // Without numeric inputs, stay below both possible result families.
+ // Pointer modes belong to comparisons, not arithmetic.
+ if( !(t1 instanceof TypeInteger || t1 instanceof TypeFloat) ||
+ !(t2 instanceof TypeInteger || t2 instanceof TypeFloat) ) return Type.BOTTOM;
+ byte mode = _mode==0 ? mode(t1,t2) : _mode;
+ if( mode==1 ) {
+ if( t1 instanceof TypeInteger x &&
+ t2 instanceof TypeInteger y )
+ return x.isConstant() && y.isConstant()
+ ? con(x,y)
+ : doOp(x,y).makeWide( (byte)Math.max(x._widen,y._widen) );
+ return TypeInteger.BOT;
+ }
+ // FP handling
+ if( t1 instanceof TypeFloat f1 && t2 instanceof TypeFloat f2 &&
+ f1.isConstant() && f2.isConstant() )
+ return TypeFloat.constant(doOp(f1.value(),f2.value()));
+ // Two f32 inputs can still produce a result needing f64 precision.
+ return TypeFloat.F64;
+ }
+
+ private TypeInteger con( Type t1, Type t2 ) {
+ return TypeInteger.constant( doOp( ((TypeInteger)t1).value(), ((TypeInteger)t2).value()) );
+ }
+
+ // Check for "op(Phi(con,x),Phi(con,y))" and push-up through the Phi.
+ // Note that this is the exact reverse of Phi pulling a common op down
+ // to reduce total op-count. We don't get in an endless push-up
+ // push-down peephole cycle because the constants all fold first.
+ // Returns "Phi(op(con,con),op(x,y))".
+
+ // Expected to be called after other ideal checks are done.
+ @Override public Node idealize() {
+ Type t1 = in(1)._type, t2 = in(2)._type;
+
+ // Can we decide int vs flt?
+ if( _mode==0 ) {
+ byte mode = mode(t1,t2);
+ if( mode!=0 ) return setMode(mode).init();
+ }
+ if( _mode==2 ) {
+ if( t1 instanceof TypeInteger ) return copy(new ToFloatNode(in(1)).peephole(),in(2));
+ if( t2 instanceof TypeInteger ) return copy(in(1),new ToFloatNode(in(2)).peephole());
+ }
+
+ if( in(1) instanceof PhiNode lhs &&
+ in(2) instanceof PhiNode rhs &&
+ lhs.nIns() >= 2 && !lhs.inProgress() &&
+ // Disallow with self-looping phi; these will collapse
+ (lhs.nIns() > 2 && lhs.in(2)!=lhs && rhs.nIns() > 2 && rhs.in(2)!=rhs) ) {
+ Node r = lhs.region();
+ if( r==rhs.region() && !(r instanceof FunNode) &&
+ // For loops, the backedge *must* fold away, lest we unroll forever
+ (!(r instanceof LoopNode) || isCon(lhs,rhs,2)) ) {
+ // Profit check: only 1 instance of `this` will remain, all the
+ // others will fold to constants. For loops, *all* must fold away
+ int cnt=0;
+ for( int i=1; i<lhs.nIns(); i++ )
+ if( isCon(lhs,rhs,i) )
+ cnt++;
+ if( lhs.nIns()-1 - cnt <= 1 ) {
+ // Profit! Push up-phi and fold
+ Node[] ns = new Node[lhs.nIns()];
+ ns[0] = lhs.in(0);
+ for( int i=1; i<lhs.nIns(); i++ ) {
+ ns[i] = isCon(lhs,rhs,i)
+ ? ConstantNode.make(con(lhs.in(i)._type, rhs.in(i)._type)).peephole()
+ : copy(lhs.in(i), rhs.in(i)).peephole();
+ }
+ String label = lhs._label==rhs._label ? lhs._label : lhs._label + rhs._label;
+ return new PhiNode(label, ns).peephole();
+ }
+ }
+ }
+
+ return null;
+ }
+
+ static boolean isCon( PhiNode lhs, PhiNode rhs, int i ) {
+ return lhs.in(i)._type.isConstant() && rhs.in(i)._type.isConstant();
+ }
+
+
+ static boolean overflow( long x, long y ) {
+ if( (x ^ y ) < 0 ) return false; // unequal signs, never overflow
+ return (x ^ (x + y)) < 0; // sum has unequal signs, so overflow
+ }
+
+ @Override public Parser.ParseException err() {
+ return nodeErr(this,_loc,op(),_mode,allowFloat());
+ }
+
+ @Override public boolean eq( Node n ) { return _mode==((ArithNode)n)._mode; }
+ @Override public int hash() { return _mode; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/BoolNode.java
new file mode 100644
index 00000000..02d3e22e
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/BoolNode.java
@@ -0,0 +1,191 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import static com.seaofnodes.simple.type.TypeInteger.*;
+
+abstract public class BoolNode extends Node implements ModeNode {
+
+ // Mode is 0 for unknown, 1 for int, 2 for flt
+ byte _mode;
+
+ public BoolNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+ public BoolNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs); _mode = mode; }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) {
+ baos.packed1(_mode);
+ }
+
+ abstract public String op(); // String opcode name
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ return p.open().n(in(1)).p(op()).n(in(2)).close();
+ }
+
+ @Override
+ public TypeInteger compute() {
+ Type t1 = in(1)._type;
+ Type t2 = in(2)._type;
+ if( t1==null || t2==null )
+ return BOOL;
+ // Exactly equals?
+ if( t1.isHigh() || t2.isHigh() )
+ return (TypeInteger)BOOL.dual();
+ if( in(1)==in(2) )
+ // LT fails, both EQ and LE succeed
+ return this instanceof LT ? FALSE : TRUE;
+ byte mode = _mode==0 ? ArithNode.mode(t1,t2) : _mode;
+ if( mode==0 )
+ return BOOL;
+ // Handle integer compares
+ if( mode==1 ) {
+ if( t1 instanceof TypeInteger i1 &&
+ t2 instanceof TypeInteger i2 )
+ return doOp(i1,i2);
+ return BOOL;
+ }
+ // Floating compare. Mixed inputs are converted in idealize().
+ if( t1 instanceof TypeFloat f1 &&
+ t2 instanceof TypeFloat f2 &&
+ f1.isConstant() && f2.isConstant() )
+ return doOp(f1.value(), f2.value()) ? TRUE : FALSE;
+ return BOOL;
+ }
+
+ TypeInteger doOp(TypeInteger t1, TypeInteger t2) { throw Utils.TODO(); }
+ boolean doOp(double lhs, double rhs) { throw Utils.TODO(); }
+ public boolean isFloat() { assert _mode!=0; return _mode==2; }
+ @Override public byte mode() { return _mode; }
+ @Override public Node setMode(byte mode) {
+ assert _mode==0 && (mode==1 || mode==2);
+ unlock(); // _mode participates in GVN hash and equality
+ _mode = mode;
+ return this;
+ }
+
+ @Override
+ public Node idealize() {
+ // Compare of same
+ if( in(1)==in(2) )
+ return this instanceof LT ? CodeGen.CODE.ZERO : ConstantNode.make(TRUE);
+ // Can we decide int vs flt?
+ Type t1 = in(1)._type, t2 = in(2)._type;
+ if( t1==null || t2==null )
+ return null;
+ if( _mode==0 ) {
+ byte mode = ArithNode.mode(t1,t2);
+ if( mode!=0 ) return setMode(mode).init();
+ }
+ if( _mode==2 ) {
+ if( t1 instanceof TypeInteger ) return copy(new ToFloatNode(in(1)).peephole(),in(2));
+ if( t2 instanceof TypeInteger ) return copy(in(1),new ToFloatNode(in(2)).peephole());
+ }
+
+
+ // Equals pushes constant to the right; 5==X becomes X==5.
+ if( this instanceof EQ ) {
+ if( !(in(2) instanceof ConstantNode) ) {
+ // con==noncon becomes noncon==con
+ if( in(1) instanceof ConstantNode || in(1)._nid > in(2)._nid )
+ // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high
+ return new EQ(in(2),in(1));
+ }
+ // Equals X==0 becomes a !X
+ if( (in(2)._type == ZERO || in(2)._type == Type.NIL) )
+ return new NotNode(in(1));
+ }
+
+ // Do we have ((phi cons) cmp con) ?
+ // Do we have ((phi cons) cmp (phi cons)) ?
+ // Push the compare up through the phi: (phi con0 cmp con, con1 cmp con...)
+ Node phicon = AddNode.phiCon(this,this instanceof EQ);
+ if( phicon!=null ) return phicon;
+
+ return null;
+ }
+
+ @Override public boolean eq( Node n ) { return _mode==((BoolNode)n)._mode; }
+ @Override public int hash() { return _mode; }
+ @Override public Parser.ParseException err() {
+ return ArithNode.nodeErr(this,null,op(),_mode,!(this instanceof ULT));
+ }
+
+ public static class EQ extends BoolNode {
+ public EQ(Node lhs, Node rhs) { super(lhs,rhs); }
+ public EQ(Node lhs, Node rhs, byte mode) { super(lhs,rhs, mode); }
+ @Override public Tag serialTag() { return Tag.EQ; }
+ public String op() { return "=="; }
+ TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
+ if( i1==i2 && i1.isConstant() ) return TRUE;
+ if( i1._max < i2._min || i1._min > i2._max ) return FALSE;
+ return BOOL;
+ }
+ @Override boolean doOp(double lhs, double rhs) { return lhs==rhs; }
+ Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs,_mode); }
+ }
+
+ public static class NE extends BoolNode {
+ public NE(Node lhs, Node rhs) { super(lhs,rhs); }
+ public NE(Node lhs, Node rhs, byte mode) { super(lhs,rhs,mode); }
+ @Override public Tag serialTag() { return Tag.NE; }
+ public String op() { return "!="; }
+ TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
+ if( i1==i2 && i1.isConstant() ) return TRUE;
+ if( i1._max < i2._min || i1._min > i2._max ) return FALSE;
+ return BOOL;
+ }
+ @Override boolean doOp(double lhs, double rhs) { return lhs!=rhs; }
+ Node copy(Node lhs, Node rhs) { return new NE(lhs,rhs,_mode); }
+ }
+
+ public static class LT extends BoolNode {
+ public LT(Node lhs, Node rhs) { super(lhs,rhs); }
+ public LT(Node lhs, Node rhs, byte mode) { super(lhs,rhs, mode); }
+ @Override public Tag serialTag() { return Tag.LT; }
+ public String op() { return "<" ; }
+ TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
+ if( i1._max < i2._min ) return TRUE;
+ if( i1._min >= i2._max ) return FALSE;
+ return BOOL;
+ }
+ @Override boolean doOp(double lhs, double rhs) { return lhs<rhs; }
+ Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs,_mode); }
+ }
+
+ public static class LE extends BoolNode {
+ public LE(Node lhs, Node rhs) { super(lhs,rhs); }
+ public LE(Node lhs, Node rhs, byte mode) { super(lhs,rhs,mode); }
+ @Override public Tag serialTag() { return Tag.LE; }
+ public String op() { return "<="; }
+ TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
+ if( i1._max <= i2._min ) return TRUE;
+ if( i1._min > i2._max ) return FALSE;
+ return BOOL;
+ }
+ @Override boolean doOp(double lhs, double rhs) { return lhs<=rhs; }
+ Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs,_mode); }
+ }
+
+ // Unsigned less that, for range checks. Not directly user writable.
+ public static class ULT extends BoolNode {
+ public ULT(Node lhs, Node rhs) { super(lhs,rhs,(byte)1); }
+ public ULT(Node lhs, Node rhs, byte mode) {
+ super(lhs,rhs,(byte)1);
+ assert mode==1;
+ }
+ @Override public Tag serialTag() { return Tag.ULT; }
+ public String op() { return "u<" ; }
+ TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
+ if( Long.compareUnsigned(i1._max,i2._min) < 0 ) return TRUE;
+ if( Long.compareUnsigned(i1._min,i2._max) >= 0 ) return FALSE;
+ return BOOL;
+ }
+ Node copy(Node lhs, Node rhs) { return new ULT(lhs,rhs); }
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
new file mode 100644
index 00000000..6a902f3c
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
@@ -0,0 +1,303 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.GlobalBits;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.type.XInt;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
+
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+/**
+ * A Phi for bulk memory. It covers all aliases except {@link #_aliases};
+ * those aliases must be represented by parallel precise MemPhis at the same
+ * program slice.
+ */
+public class BulkMemPhiNode extends PhiNode {
+
+ private static final BitSet EMPTY = new BitSet();
+
+ // Excluded Aliases; empty means NO exclusions.
+ // Directly visible by design. Empty sets share EMPTY; splitAlias performs
+ // copy-on-first-write and removes the node from GVN before mutation.
+ public BitSet _aliases;
+
+ public BulkMemPhiNode(String label, Node... inputs) {
+ this(label,EMPTY,inputs);
+ }
+
+ private BulkMemPhiNode(String label, BitSet aliases, Node... inputs) {
+ super(label, inputs);
+ _aliases = aliases;
+ }
+
+ public BulkMemPhiNode(BulkMemPhiNode phi) {
+ super(phi,phi._label);
+ _aliases = phi._aliases.isEmpty() ? EMPTY : (BitSet)phi._aliases.clone();
+ }
+
+ public BulkMemPhiNode(RegionNode r, Node sample) {
+ super(r,sample);
+ _aliases = EMPTY;
+ }
+
+ public boolean isSplit(int alias) { return _aliases.get(alias); }
+
+ @Override public Tag serialTag() { return Tag.BulkMemPhi; }
+
+ @Override
+ public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types,
+ IdentityHashMap<Node,Integer> anodes) {
+ super.packed(baos,strs,types,anodes);
+ baos.packed2(_aliases.cardinality());
+ for( int alias = _aliases.nextSetBit(0); alias >= 0; alias = _aliases.nextSetBit(alias+1) )
+ baos.packed2(alias);
+ }
+
+ static Node make(BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases) {
+ Node[] ins = new Node[bais.packed1()];
+ String label = strs[bais.packed2()];
+ BitSet bits = new BitSet();
+ for( int i=bais.packed2(); i>0; i-- ) {
+ int alias = bais.packed2();
+ if( alias >= GlobalBits.RESERVED ) alias = aliases.map(fileAliases,alias);
+ bits.set(alias);
+ }
+ return new BulkMemPhiNode(label,bits.isEmpty() ? EMPTY : bits,ins);
+ }
+
+ @Override
+ public Type compute() {
+ Type t = super.compute();
+ return t instanceof TypeMem mem ? mem :
+ t==Type.BOTTOM ? TypeMem.BOT :
+ TypeMem.TOP;
+ }
+
+ @Override
+ public Node idealize() {
+ Node progress = super.idealize();
+ if( progress != null ) return progress;
+ if( ((RegionNode)region()).inProgress() || region().nIns()<=1 )
+ return null; // Input is in-progress
+
+ // "Peek through" a MemMerge that covers this alias set on its default
+ for( int i=1; i<nIns(); i++ )
+ if( in(i) instanceof MemMergeNode mmm && canPeek(mmm) ) {
+ setDef(i,mmm.in(1));
+ return this;
+ }
+
+ // If any input or output uses or defines a specific alias we cover,
+ // make a private MemPhi for it, and add it to the excluded list.
+ for( int i=1; i<nIns(); i++ ) {
+ int alias = inputAlias(in(i));
+ if( alias!=0 )
+ return slice(alias);
+ }
+
+ for( int i=0; i<nOuts(); i++ ) {
+ int alias = outputAlias(out(i));
+ if( alias!=0 )
+ return slice(alias);
+ }
+
+ return null;
+ }
+
+ static public boolean checkMem(Node n, int alias) {
+ for( Node m : n._inputs )
+ // Bulk memory supplies this alias
+ if( m instanceof BulkMemPhiNode bulk ) {
+ if( bulk._aliases.get(alias) )
+ return false;
+ } else if( m instanceof MemMergeNode mmm &&
+ mmm.in(1) instanceof BulkMemPhiNode bulk &&
+ bulk._aliases.get(alias) &&
+ (alias >= mmm.nIns() || mmm.in(alias)==null) )
+ return false;
+ return true;
+ }
+
+
+ // True if the MemMerge has no precise slice still covered by this Phi.
+ private boolean canPeek(MemMergeNode mmm) {
+ return missingAlias(mmm)==0;
+ }
+
+ // Return an alias required by an input but still covered by this Phi, or 0.
+ private int inputAlias(Node n) {
+ return switch(n) {
+ case ParmNode p -> { assert p._idx==1; yield 0; }
+ case ProjNode proj -> 0;
+ case ConstantNode con -> {
+ assert !(con._con instanceof TypeMem tmem && tmem._alias != 1);
+ yield 0;
+ }
+ case MemMergeNode mmm -> missingAlias(mmm);
+ case MemOpNode mem -> unsplit(mem._alias);
+ case BulkMemPhiNode bulk -> missingAlias(bulk);
+ default -> throw Utils.TODO("Should not reach here");
+ };
+ }
+
+ // Return an alias required by a user but still covered by this Phi, or 0.
+ private int outputAlias(Node use) {
+ // This decision depends backwards on an immediate user's alias/set.
+ // Normal graph-neighbor enqueueing flows from defs to uses, so retain
+ // an explicit forward dependency to revisit this Phi when the user
+ // sharpens.
+ use = addDepForwards(use);
+ return switch(use) {
+ case ScopeNode scope -> 0;
+ case ParmNode parm -> 0;
+ case MemMergeNode mmm -> missingAlias(mmm);
+ case BulkMemPhiNode bulk -> missingAlias(bulk);
+ case MemOpNode mem -> unsplit(mem._alias);
+ case MemPhiNode phi -> unsplit(phi._alias);
+ case EscapeNode esc -> esc.pub()==this ? unsplit(esc.fld()._alias) : 0;
+ default -> {
+ assert use instanceof ReturnNode || use instanceof CallNode || use instanceof CLInitNode
+ : "Unexpected bulk-memory user "+use.getClass().getSimpleName()+"#"+use._nid+": "+use;
+ yield 0;
+ }
+ };
+ }
+
+ // Alias zero/one is bulk, and an already excluded precise alias cannot
+ // trigger another split of this Phi.
+ private int unsplit(int alias) {
+ if( alias <= 1 ) return 0;
+ assert !_aliases.get(alias);
+ return alias;
+ }
+
+ // First alias excluded by the neighboring bulk Phi but not by this one.
+ private int missingAlias(BulkMemPhiNode bulk) {
+ for( int alias = bulk._aliases.nextSetBit(0); alias >= 0; alias = bulk._aliases.nextSetBit(alias+1) )
+ if( !_aliases.get(alias) )
+ return alias;
+ return 0;
+ }
+
+ // First explicit MemMerge slice still covered by this Phi. A user's slice
+ // may point to this Phi after a precise Phi folds; splitting is still valid.
+ private int missingAlias( MemMergeNode mmm ) {
+ for( int alias=2; alias<mmm.nIns(); alias++ )
+ if( mmm.in(alias)!=null &&
+ mmm.alias(alias)!=mmm.in(1) &&
+ !_aliases.get(alias) )
+ return alias;
+ return 0;
+ }
+
+ // Slice out given alias
+ private MemMergeNode slice( int alias ) {
+ assert !_aliases.get(alias);
+ // Inputs are the same; MemPhi will sharpen his own inputs. If a
+ // parallel precise Phi already exists, reuse it instead of building a
+ // duplicate for the same Region/alias pair.
+ Node mem = precisePhi(alias);
+ assert ((TypeMem)mem._type)._alias==alias;
+ // A parallel Phi may still be waiting for its inputs' type changes.
+ // Do not replace this memory with slices carrying weaker escape facts.
+ if( !readySlice(mem) ) return null;
+ for( int old = _aliases.nextSetBit(0); old>=0; old = _aliases.nextSetBit(old+1) ) {
+ Node prior = precisePhi(old);
+ if( !readySlice(prior) ) return null;
+ }
+
+ BitSet aliases = ((BitSet)_aliases.clone());
+ aliases.set(alias);
+ BulkMemPhiNode bphi = new BulkMemPhiNode(_label,aliases);
+ for( int i=0; i<nIns(); i++ )
+ bphi.addDef(in(i));
+ // Installing bphi creates a new bulk user with a different exclusion
+ // set. Queue the original memory inputs before bphi itself can peep
+ // away and hide those neighbor relationships.
+ for( int i=1; i<bphi.nIns(); i++ )
+ CodeGen.CODE.add(bphi.in(i));
+ // Do not peephole bphi here, as BulkMemPhi can recursively start a
+ // second bulk rewrite before this one has finished. Let the worklist
+ // discover any further splits
+ Node bulk = bphi.init();
+ CodeGen.CODE.add(bulk);
+ CodeGen.CODE.add(mem);
+ return aggregate(bulk,alias,mem);
+ }
+
+ private boolean readySlice(Node precise) {
+ TypeMem mem = (TypeMem)_type;
+ TypeMem part = (TypeMem)precise._type;
+ if( !XInt.bit(mem._escAs,part._alias) || mem.meet(part).isa(mem) ) return true;
+ precise.setType(precise.compute());
+ if( mem.meet(precise._type).isa(mem) ) return true;
+ addDep(precise);
+ return false;
+ }
+
+ // Build aggregate memory over a bulk Phi, preserving every precise slice
+ // which the bulk has already excluded. The named alias is newly supplied
+ // (or replaces its old slice).
+ MemMergeNode aggregate(Node bulk, int alias, Node precise) {
+ MemMergeNode mmm = new MemMergeNode(null,bulk);
+ for( int old = _aliases.nextSetBit(0); old >= 0; old = _aliases.nextSetBit(old+1) )
+ { assert old != alias; mmm.alias(old,precisePhi(old));}
+ mmm.alias(alias,precise);
+ assert checkMerge(mmm);
+ mmm.init();
+ return mmm;
+ }
+
+ // Find or make the precise Phi parallel to this bulk Phi at the same merge.
+ private Node precisePhi(int alias) {
+ MemPhiNode mphi = _findPhi(alias);
+ if( mphi!=null ) return mphi;
+
+ mphi = new MemPhiNode("$"+alias,alias);
+ mphi.addDef(region());
+ // Due to cycles, must set before calling peephole
+ // Splitting preserves the bulk Phi's established escape bound. Starting
+ // at BOT would temporarily invent escapes when the new slice is merged.
+ TypeMem old = (TypeMem)_type;
+ mphi.setType(TypeMem.make(alias,old._t,old._one,old._clz,false,old._escFs,old._escAs));
+ for( int i=1; i<nIns(); i++ )
+ mphi.addDef(CodeGen.CODE.add(preciseInput(in(i),alias)));
+ return CodeGen.CODE.add(mphi);
+ }
+
+ MemPhiNode _findPhi(int alias) {
+ for( Node use : region().outs() )
+ if( use instanceof MemPhiNode mphi && mphi._alias==alias )
+ return mphi;
+ return null;
+ }
+
+ // Select an alias already split out of a predecessor's bulk memory.
+ private Node preciseInput(Node mem, int alias) {
+ if( mem instanceof MemMergeNode mmm )
+ return mmm.alias(alias);
+ if( mem instanceof BulkMemPhiNode bulk && bulk.isSplit(alias) )
+ return bulk.precisePhi(alias);
+ return mem;
+ }
+
+ // Every alias excluded by a bulk default must be supplied explicitly.
+ static boolean checkMerge(MemMergeNode mmm) {
+ if( !(mmm.in(1) instanceof BulkMemPhiNode bulk) ) return true;
+ for( int alias = bulk._aliases.nextSetBit(0); alias >= 0; alias = bulk._aliases.nextSetBit(alias+1) )
+ if( alias >= mmm.nIns() || mmm.in(alias)==null )
+ return false;
+ return true;
+ }
+
+ @Override public boolean eq(Node n) {
+ return _aliases.equals(((BulkMemPhiNode)n)._aliases) && super.eq(n);
+ }
+
+ @Override int hash() { return _aliases.hashCode(); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/CFGNode.java
new file mode 100644
index 00000000..ba744dc3
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -0,0 +1,310 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Ary;
+
+import java.util.BitSet;
+import java.util.IdentityHashMap;
+
+/** Control Flow Graph Nodes
+ * <p>
+ * CFG nodes have a immediate dominator depth (idepth) and a loop nesting
+ * depth(loop_depth).
+ * <p>
+ * idepth is computed lazily upon first request, and is valid even in the
+ * Parser, and is used by peepholes during parsing and afterward.
+ * <p>
+ * loop_depth is computed after optimization as part of scheduling.
+ *
+ * Start - Block head; has constants and trailing Funs
+ * Region - Block head; has Phis. Includes Fun and Parms.
+ * CallEnd - Block head; followed by CProj and Proj; also linked RPC Parms
+ * CProj - Block head
+ *
+ * If - Block tail; followed by CProj same block
+ * Call - Block tail; followed by CallEnd and linked Funs
+ * New/intrinsic - If followed by CProj: block tail else mid-block; followed by Proj
+ *
+ */
+public abstract class CFGNode extends Node {
+
+ public CFGNode(Node... nodes) { super(nodes); }
+ public CFGNode(CFGNode cfg) {
+ super(cfg);
+ if( cfg != null ) {
+ _idepth = cfg._idepth;
+ _idepthVersion = cfg._idepthVersion;
+ _ltree = cfg._ltree;
+ _pre = cfg._pre;
+ }
+ }
+
+ public CFGNode cfg(int idx) { return (CFGNode)in(idx); }
+
+ // Block head is Start, Region, CProj, but not e.g. If, Return, Stop
+ public boolean blockHead() { return false; }
+
+ // Get the one control following; error to call with more than one e.g. an
+ // IfNode or other multi-way branch.
+ public CFGNode uctrl() {
+ CFGNode c = null;
+ for( Node n : _outputs )
+ if( n instanceof CFGNode cfg )
+ { assert c==null; c = cfg; }
+ return c;
+ }
+
+ // Used by the encoding / final BB layout
+ public CFGNode uctrlSkipEmpty() {
+ CFGNode x = this, y;
+ while( x.nOuts() == 1 && (y=x.uctrl())!=null && !(y instanceof CallNode) ) // Skip empty blocks
+ x = y;
+ return x;
+ }
+
+ // ------------------------------------------------------------------------
+ /**
+ * Immediate dominator tree depth, used to approximate a real IDOM during
+ * parsing where we do not have the whole program, and also peepholes
+ * change the CFG incrementally.
+ * <p>
+ * See {@link <a href="https://en.wikipedia.org/wiki/Dominator_(graph_theory)">...</a>}
+ */
+ public char _idepth;
+ public char _idepthVersion;
+ public int idepth() { return validIDepth() ? _idepth : cacheIDepth(idom().idepth()+1); }
+ // Zero depth means uncached. Check before narrowing so overflow cannot wrap.
+ final int cacheIDepth(int depth) {
+ assert 0 <= depth && depth <= Character.MAX_VALUE : "Dominator depth exceeds 65535";
+ _idepthVersion = CodeGen.CODE.iDepthVersion();
+ return _idepth = (char)depth;
+ }
+
+ final boolean validIDepth() { return _idepth!=0 && _idepthVersion==CodeGen.CODE.iDepthVersion(); }
+
+
+ // Return the immediate dominator of this Node and compute dom tree depth.
+ public CFGNode idom(Node dep) { return cfg(0); }
+ public final CFGNode idom() { return idom(null); }
+
+ // Return the LCA of two idoms
+ public CFGNode domLCA(CFGNode rhs, Node dep) {
+ if( rhs==null ) return this;
+ CFGNode lhs = this;
+ while( lhs != rhs ) {
+ if( lhs==null || rhs==null ) return null;
+ int comp = lhs.idepth() - rhs.idepth();
+ if( comp >= 0 ) lhs = (dep==null ? lhs : dep.addDep(lhs)).idom();
+ if( comp <= 0 ) rhs = (dep==null ? rhs : dep.addDep(rhs)).idom();
+ }
+ return lhs;
+ }
+
+ // Safe read of the control input of a data node.
+ public static CFGNode safeCFG(Node n) {
+ if( n==null ) return null;
+ if( !(n.in(0) instanceof CFGNode cfg) ) return null;
+ return cfg._safeCFG();
+ }
+ private CFGNode _safeCFG() {
+ return switch(this) {
+ case CLInitNode init when init.bypass() -> init.cfg0()._safeCFG();
+ case CallNode call when call.cend()._folding -> call.cfg0()._safeCFG();
+ case CallEndNode cend when cend._folding -> cend.cfg(1)._safeCFG();
+ case FunNode fun when fun._folding -> fun.cfg(1)._safeCFG();
+ case ReturnNode ret when ret.fun()._folding -> ret.cfg0()._safeCFG();
+ default -> this;
+ };
+ }
+
+ // True when this node dominates `sub` in the current idom tree.
+ public boolean dominates(CFGNode sub, Node dep) {
+ for( CFGNode cfg=sub; cfg!=null; cfg=cfg.idom() )
+ if( cfg == this )
+ return true;
+ else if( dep != null ) dep.addDep(cfg);
+ return false;
+ }
+
+ public static CFGNode deepest(CFGNode a, CFGNode b) {
+ if( a == null ) return b;
+ if( b == null ) return a;
+ return b.idepth() > a.idepth() ? b : a;
+ }
+
+ public static CFGNode earlyCFG(Node n) {
+ return earlyCFG(n,new IdentityHashMap<>(),new BitSet());
+ }
+
+ public static CFGNode earlyCFG(Node n, IdentityHashMap<Node,CFGNode> cache, BitSet active) {
+ if( n == null ) return null;
+ CFGNode cached = cache.get(n);
+ if( cached != null ) return cached;
+ if( active.get(n._nid) )
+ return safeCFG(n);
+ active.set(n._nid);
+ CFGNode early = null;
+ for( Node x : n._inputs )
+ early = deepest(early,defCFG(x,cache,active));
+ active.clear(n._nid);
+ cache.put(n,early);
+ return early;
+ }
+
+ private static CFGNode defCFG(Node n, IdentityHashMap<Node,CFGNode> cache, BitSet active) {
+ if( n == null || n.isConst() ) return null; // no impact on early cfg
+ return switch( n ) {
+ case PhiNode phi -> phi.region();
+ case CFGNode cfg -> cfg;
+ case ProjNode projNode -> safeCFG( n );
+ default -> earlyCFG( n, cache, active );
+ };
+ }
+
+ public boolean sameFun(CFGNode that) {
+ FunNode fun = fun();
+ return fun != null && fun == that.fun();
+ }
+
+ // Find nearest enclosing FunNode
+ public FunNode fun() {
+ CFGNode cfg = this;
+ while( !(cfg instanceof FunNode fun) ) {
+ if( cfg==null ) return null;
+ cfg = cfg.idom();
+ }
+ return fun;
+ }
+
+ // ------------------------------------------------------------------------
+ // Loop nesting
+ public LoopNode loop() { return _ltree._head; }
+ public int loopDepth() { return _ltree==null ? 0 : _ltree.depth(); }
+
+ public LoopTree _ltree;
+ public int _pre; // Pre-order numbers for loop tree finding
+ public static class LoopTree {
+ public LoopTree _par;
+ public final LoopNode _head;
+ public int _depth;
+ LoopTree(LoopNode head) { _head = head; }
+ @Override public String toString() { return label(_head); }
+ public int depth() {
+ return _depth==0 ? (_par==null ? 0 : (_depth = _par.depth()+1)) : _depth;
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Tag all CFG Nodes with their containing LoopNode; LoopNodes themselves
+ // also refer to *their* containing LoopNode, as well as have their depth.
+ // Start contains the function bodies at depth 0; real loops add depth.
+ public void buildLoopTree( Ary<FunNode> funs, StopNode stop) {
+ _ltree = stop._ltree = CodeGen.CODE.XCTRL._ltree = new LoopTree((StartNode)this);
+ Ary<FunNode> work = new Ary<>(FunNode.class);
+
+ BitSet post = CodeGen.CODE.visit();
+ int pre = 1;
+ _pre = pre++;
+ for( FunNode fun : funs ) {
+ // Summaries have no executable CFG. Their loop trees are built
+ // only if Eval2 later loads the corresponding bodies.
+ if( fun!=null && !fun.isDead() && !fun._type.isHigh() && fun.summary()==null ) {
+ work.push(fun);
+ if(fun.in(1) instanceof StartCUNode start && start._pre==0 ) {
+ start._pre = pre++;
+ start._ltree = start.cfg(1)._ltree = _ltree;
+ post.set(start._nid);
+ }
+ };
+ }
+
+ for( int i=0; i<work._len; i++ )
+ pre = work.at(i)._bltWalk(pre,work.at(i),stop, post, work);
+ post.clear();
+ }
+
+ int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post, Ary<FunNode> work ) {
+ // Pre-walked?
+ if( _pre!=0 ) return pre;
+ _pre = pre++;
+ // Seed the function's outer tree before walking its body. Synthetic
+ // exits can be added before the post-order walk reaches the entry.
+ if( this instanceof FunNode ) _ltree = stop._ltree;
+ if( this instanceof ReturnNode ) {
+ _ltree = fun._ltree;
+ post.set(_nid);
+ return pre; // Stop/Start links describe the outside world, not a loop.
+ }
+ // Pre-walk
+ for( Node use : _outputs )
+ if( use instanceof CFGNode usecfg ) {
+ if( skip( usecfg ) ) {
+ if( usecfg._pre==0 && usecfg instanceof FunNode fun2 && fun2.summary()==null ) work.add( fun2 );
+ } else
+ pre = usecfg._bltWalk( pre, use instanceof FunNode fuse ? fuse : fun, stop, post, work );
+ }
+
+ // Post-order work: find innermost loop
+ LoopTree inner = null, ltree;
+ for( Node use : _outputs ) {
+ if( !(use instanceof CFGNode usecfg) ) continue;
+ if( skip(usecfg) ) continue;
+ if( usecfg._type == Type.XCONTROL || // Do not walk dead control
+ usecfg._type == TypeTuple.IF_NEITHER ) // Nor dead IFs
+ continue;
+ // Child visited but not post-visited?
+ if( !post.get(usecfg._nid) ) {
+ // Must be a backedge to a LoopNode then
+ ltree = usecfg._ltree = new LoopTree((LoopNode)usecfg);
+ } else {
+ // Take child's loop choice, which must exist
+ ltree = usecfg._ltree;
+ // If falling into a loop, use the target loop's parent instead
+ if( ltree._head == usecfg ) {
+ if( ltree._par == null )
+ // This loop never had an If test choose to take its
+ // exit, i.e. it is a no-exit infinite loop.
+ ltree._par = ltree._head.forceExit(fun,stop)._ltree;
+ ltree = ltree._par;
+ }
+ }
+ // Sort inner loops. The decision point is some branch far removed
+ // from either loop head OR either backedge so requires pre-order
+ // numbers to figure out innermost.
+ if( inner == null ) { inner = ltree; continue; }
+ if( inner == ltree ) continue; // No change
+ LoopTree outer = ltree._head._pre > inner._head._pre ? inner : ltree;
+ inner = ltree._head._pre > inner._head._pre ? ltree : inner;
+ inner._par = outer;
+ }
+ // Set selected loop
+ if( inner!=null )
+ _ltree = inner;
+ // Tag as post-walked
+ post.set(_nid);
+ return pre;
+ }
+
+ public boolean skip(CFGNode usecfg) {
+ // Only walk control users that are alive.
+ // Do not follow interprocedural edges. CLInit's call projection is
+ // only an analysis edge, not a branch in the caller's loop tree.
+ return usecfg instanceof XCtrlNode ||
+ (this instanceof CallNode && usecfg instanceof FunNode) ||
+ (this instanceof CLInitNode && usecfg instanceof CProjNode proj && proj._idx==3) ||
+ (this instanceof CProjNode proj && proj._idx==3 && proj.in(0) instanceof CLInitNode && usecfg instanceof FunNode) ||
+ (this instanceof ReturnNode && usecfg instanceof CallEndNode);
+ }
+
+ public static String label( CFGNode target ) {
+ return switch(target) {
+ case StartNode start -> start.label();
+ case FunNode fun -> fun.label();
+ case LoopNode loop -> "LOOP"+target._nid;
+ default -> "L"+target._nid;
+ };
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/CLInitNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/CLInitNode.java
new file mode 100644
index 00000000..f381e57a
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/CLInitNode.java
@@ -0,0 +1,210 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Lexer;
+import com.seaofnodes.print.ExprPrinter;
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.CompUnit;
+import com.seaofnodes.simple.codegen.Iter;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+import java.util.*;
+
+// Track class initializers - the code just after this point expects the named
+// class to be initialized. If this code path dies, then the dependency dies
+// also - dead code does not force class init.
+//
+public class CLInitNode extends CFGNode implements MultiNode {
+ public Lexer _loc; // For error reporting
+
+ // Inputs: control, whole memory, class pointer, loaded $init flag
+ // Results: control, whole memory, known-true flag alias memory, call control.
+ // For field references, the ptr might be the dst clz ptr or not.
+ public CLInitNode(Lexer loc, Node ctrl, Node mem, Node ptr, Node ready) {
+ super(ctrl,mem,ptr,ready);
+ _loc = loc;
+ }
+
+ CLInitNode(BAOS bais, String[] strs) { super(null,null,null,null); _loc=Lexer.unpack(bais,strs); }
+ @Override public Tag serialTag() { return Tag.CLInit; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ Lexer.packed(_loc,baos,strs);
+ }
+ @Override public void gather(HashMap<String,Integer> strs) { Lexer.gather(_loc,strs); }
+
+ public CLInitNode(CLInitNode init) {
+ super(init);
+ _loc = init._loc;
+ }
+
+ public Node mem() { return in(1); }
+ public Node ptr() { return in(2); }
+ public Node ready() { return in(3); }
+
+ // The field/alias can be unresolved until forward references are loaded.
+ public Field flag() {
+ return ptr()._type instanceof TypeMemPtr tmp && Parser.startsClzPrefix(tmp._obj._name)
+ ? tmp._obj.field("$init") : null;
+ }
+ @Override public String label() { return "CLInit"; }
+
+ // Best effort dst compunit, based on ptr - or null
+ public CompUnit cu() {
+ if( !(ptr()._type instanceof TypeMemPtr ptr && Parser.startsClzPrefix(ptr._obj._name)) )
+ return null;
+ // Convert "class:sys.ary" into "sys/ary" and lookup CompUnit
+ String fname = ptr._obj._name.substring(6).replace(".","/");
+ return CodeGen.CODE._compunits.get(fname);
+ }
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ FunNode fun = fun();
+ CompUnit cu = cu();
+ return p.p("CLInit ").p(fun._name).p(" -> ").p(cu==null ? "---" : cu._cname);
+ }
+
+ // Block head is Start, Region, CProj, but not e.g. If, Return, Stop
+ public boolean blockHead() { return true; }
+
+ private boolean instance() {
+ return ptr()._type==Type.NIL ||
+ (ptr()._type instanceof TypeMemPtr tmp && tmp._obj!=TypeStruct.BOT &&
+ !tmp._obj.isHigh() && !tmp._obj._open && !tmp._obj._fref &&
+ !Parser.startsClzPrefix(tmp._obj._name));
+ }
+
+ public boolean bypass() { return instance() || ready()._type==TypeInteger.TRUE; }
+
+ @Override public Type compute() {
+ if( in(0)._type.isHigh() || ptr()._type.isHigh() )
+ return TypeTuple.make(Type.XCONTROL,TypeMem.TOP,TypeMem.TOP,Type.XCONTROL);
+ if( instance() )
+ return TypeTuple.make(in(0)._type,mem()._type,mem()._type,Type.XCONTROL);
+ Field flag = flag();
+ Type slice = flag==null ? TypeMem.BOT : TypeMem.make(flag._alias,TypeInteger.TRUE);
+ // The initializer may write arbitrary public memory. Only its flag
+ // has a guaranteed value on the normal continuation.
+ Type bulk = ready()._type.isHigh() ? TypeMem.TOP :
+ ready()._type==TypeInteger.TRUE ? mem()._type : TypeMem.BOT;
+ Type call = ready()._type.isHigh() || ready()._type==TypeInteger.TRUE ? Type.XCONTROL : in(0)._type;
+ return TypeTuple.make(in(0)._type,bulk,slice,call);
+ }
+
+ @Override public Node pcopy(int idx, Node dep) {
+ if( idx==3 ) return null; // Analysis call edge, removed before scheduling.
+ dep.addDep(ptr());
+ dep.addDep(ready());
+ if( instance() ) return idx==0 ? in(0) : mem();
+ if( ready()._type!=TypeInteger.TRUE ) return null;
+ if( idx==0 ) return in(0);
+ if( idx==1 ) return mem();
+ // Already established on the incoming path; this is a memory fact,
+ // not a new store to the class object.
+ return CodeGen.CODE.con(((TypeTuple)_type)._types[2]);
+ }
+
+ @Override public Node idealize() {
+ if( CodeGen.CODE._phase.ordinal()<CodeGen.Phase.Opto.ordinal() ) return null;
+ if( CodeGen.CODE._phase.ordinal()<CodeGen.Phase.Lower.ordinal() ) return link() ? this : null;
+ if( CodeGen.CODE._phase!=CodeGen.Phase.Lower || bypass() || in(0)._type.isHigh() ) return null;
+ if( CodeGen.CODE._midAssert ) return this;
+ // Until entry arguments become arrays, ordinary initializers receive zero.
+ return expand(CodeGen.CODE.con(TypeInteger.ZERO));
+ }
+
+ // Before lowering, expose the conditional call to interprocedural analysis.
+ // No Return edge is needed: CLInit already conservatively models its effects.
+ public boolean link() {
+ if( in(0)._type.isHigh() || ptr()._type.isHigh() || ready()._type.isHigh() || bypass() ) return false;
+ FunNode target = CodeGen.CODE.link(cu());
+ if( target==null ) return false; // Forward class reference during parsing.
+ assert !target.isDead() : "dead initializer "+target._name;
+ CProjNode call = cproj(3);
+ if( call!=null && target._inputs.find(call)!=-1 ) return false;
+ if( CodeGen.CODE._midAssert ) return true;
+ if( call==null ) call = CodeGen.CODE.add(new CProjNode(this,3,"InitCall").init());
+ CodeGen.CODE.keepInCompUnit(target);
+ CodeGen.CODE.add(target).addDef(call);
+ for( Node use : target._outputs )
+ if( use instanceof ParmNode parm ) {
+ Node arg = switch( parm._idx ) {
+ case 0 -> CodeGen.CODE.con(TypeRPC.BOT);
+ case 1 -> mem();
+ case 2 -> ptr();
+ case 3 -> CodeGen.CODE.con(TypeInteger.ZERO);
+ default -> throw new IllegalStateException("initializer parameter");
+ };
+ CodeGen.CODE.add(parm).addDef(arg);
+ }
+ if( CodeGen.CODE._phase.ordinal()>CodeGen.Phase.Opto.ordinal() ) {
+ boolean repair = !target.compute().isa(target._type);
+ for( Node use : target._outputs )
+ if( use instanceof ParmNode && !use.compute().isa(use._type) ) repair = true;
+ if( repair ) Iter.repair(CodeGen.CODE);
+ }
+ return true;
+ }
+
+ public void unlink() {
+ CProjNode call = cproj(3);
+ if( call==null ) return;
+ keep();
+ for( Node use : call._outputs.asAry() )
+ if( use instanceof FunNode target ) {
+ CodeGen.CODE.add(target);
+ target.removeDeadPath(target._inputs.find(call));
+ }
+ call.isKill();
+ unkeep();
+ }
+
+ // Called only after cycle/type checking. The initialized arm skips the
+ // call; both arms rejoin with whole memory and a known-true flag slice.
+ private Node expand(Node arg) {
+ FunNode target = CodeGen.CODE.link(cu());
+ assert target!=null && !target.isDead();
+ keep();
+ Node ctrl = in(0);
+ Node memory = mem();
+
+ // The callee establishes its parent before setting its own flag.
+ IfNode iff = CodeGen.CODE.add(new IfNode(ctrl,ready()).init());
+ Node done = CodeGen.CODE.add(new CProjNode(iff,0,"True").init());
+ Node cold = CodeGen.CODE.add(new CProjNode(iff,1,"False").init());
+
+ Node fptr = new FunPtrNode(target.sig(),CodeGen.CODE._start,target.ret()).peephole();
+ CallNode call = CodeGen.CODE.add(new CallNode(_loc,cold,memory,ptr(),arg,fptr).init());
+ CallEndNode end = new CallEndNode(call,TypeRPC.constant(CodeGen.CODE.rpc(_loc._cu._cname)));
+ CodeGen.CODE.add(end.init());
+ // Replace the analysis edge with the ordinary call. Its arguments
+ // are the same ones already analyzed before lowering.
+ call.link(target);
+ unlink();
+ Node called = CodeGen.CODE.add(new CProjNode(end,0,ScopeNode.CTRL).init());
+ Node after = CodeGen.CODE.add(new ProjNode(end,1,ScopeNode.MEM0).init());
+ RegionNode region = CodeGen.CODE.add(new RegionNode(_loc,null,done,called).init());
+ Node merged = CodeGen.CODE.add(new BulkMemPhiNode(ScopeNode.MEM0,region,memory,after).init());
+
+ // Replace the individual outputs; no tuple-pack node or second
+ // representation of CLInit is needed while the projections collapse.
+ for( Node use : _outputs.asAry() ) {
+ if( use==null ) continue; // Our temporary keep.
+ assert use instanceof Proj;
+ int idx = ((Proj)use).idx();
+ use.subsume(idx==0 ? region : idx==1 ? merged : CodeGen.CODE.con(((TypeTuple)_type)._types[2]));
+ }
+ CodeGen.CODE.invalidateIDepthCaches();
+ return unkeep(); // Iter removes this now-unused node.
+ }
+
+ // Resolve a precise flag slice even when the parser did not yet know the
+ // class/alias and could only install the whole-memory projection.
+ static Node slice(Node mem, int alias, Node dep) {
+ if( !(mem instanceof ProjNode proj && proj._idx==1 && proj.in(0) instanceof CLInitNode init) )
+ return mem;
+ dep.addDep(init.ptr());
+ Field flag = init.flag();
+ if( flag==null || flag._alias!=alias ) return mem;
+ return new ProjNode(init,2,Parser.memName(alias)).peephole();
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/CProjNode.java
new file mode 100644
index 00000000..073e4658
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/CProjNode.java
@@ -0,0 +1,85 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Serialize;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeTuple;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+public class CProjNode extends CFGNode implements Proj {
+
+ // Which slice of the incoming multipart value
+ public int _idx;
+
+ // Debugging label
+ public String _label;
+
+ public CProjNode(Node ctrl, int idx, String label) {
+ super(ctrl);
+ _idx = idx;
+ _label = label;
+ }
+ public CProjNode(CProjNode c) { super(c); _idx = c._idx; _label = c._label; }
+ @Override public Tag serialTag() { return Tag.CProj; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed1(_idx);
+ baos.packed2(_label==null ? 0 : strs.get(_label));
+ }
+ static Node make( BAOS bais, String[] strs ) {
+ return new CProjNode(null, bais.packed1(), strs[bais.packed2()] );
+ }
+
+ @Override public String label() { return _label; }
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p(_label); }
+
+ @Override public boolean blockHead() { return true; }
+
+ public CFGNode ctrl() { return cfg(0); }
+
+ @Override
+ public Type compute() {
+ Type t = ctrl()._type;
+ return t instanceof TypeTuple tt ? tt._types[_idx] : (t.isHigh() ? Type.TOP : Type.BOTTOM);
+ }
+
+ @Override
+ public Node idealize() {
+ if( ctrl()._type instanceof TypeTuple tt ) {
+ if( tt._types[_idx]==Type.XCONTROL )
+ return CodeGen.CODE.XCTRL; // We are dead
+ if( ctrl() instanceof IfNode && tt._types[1-_idx]==Type.XCONTROL ) // Only true for IfNodes
+ return ctrl().in(0); // We become our input control
+ }
+
+ // Flip a negating if-test, to remove the not
+ if( ctrl() instanceof IfNode iff && iff.pred() != null && addDep(iff.pred()) instanceof NotNode not )
+ return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True");
+
+ // Copy of some other input
+ return ((MultiNode)ctrl()).pcopy(_idx,this);
+ }
+
+ // Only called during basic-block layout, inverts a T/F CProj
+ public void invert() {
+ _label = _idx == 0 ? "False" : "True";
+ _idx = 1-_idx;
+ }
+
+ @Override
+ public boolean eq( Node n ) { return _idx == ((CProjNode)n)._idx; }
+
+ @Override
+ int hash() { return _idx; }
+
+ @Override public int idx() { return _idx; }
+
+ @Override public void gather( HashMap<String,Integer> strs ) {
+ Serialize.gather(strs,_label);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/CallEndMach.java b/chapter29/src/main/java/com/seaofnodes/simple/node/CallEndMach.java
new file mode 100644
index 00000000..05707132
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/CallEndMach.java
@@ -0,0 +1,38 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.util.SB;
+
+public class CallEndMach extends CallEndNode implements MachNode {
+ public CallEndMach( CallEndNode n ) { super(n); }
+ // MachNode specifics, shared across all CPUs
+ public int _xslot;
+ private RegMask _retMask;
+ private RegMask _kills;
+ public void cacheRegs(CodeGen code) {
+ // Return mask depends on TFP (either GPR or FPR)
+ _retMask = code._mach.retMask(call().tfp());
+ // Kill mask is all caller-saves, and any mirror stack slots for args
+ // in registers.
+ RegMaskRW kills = (call().external() ? code._cCallerSave : code._callerSave).copy();
+ // Start of stack slots
+ int maxReg = code._mach.regs().length;
+ // Incoming function arg slots, all low numbered in the RA
+ int fslot = fun()._maxArgSlot;
+ // Killed slots for this calls outgoing args
+ int xslot = call().external()
+ ? code._mach.maxArgSlot(call().tfp(),code._cCallingConv)
+ : code._mach.maxArgSlot(call().tfp());
+ _xslot = (maxReg+fslot)+xslot;
+ for( int i=0; i<xslot; i++ )
+ kills.set((maxReg+fslot)+i);
+ _kills = kills;
+ }
+ public String op() { return "cend"; }
+ public RegMask regmap(int i) { return null; }
+ public RegMask outregmap() { return null; }
+ public RegMask outregmap(int idx) { return idx==2 ? _retMask : null; }
+ public RegMask killmap() { return _kills; }
+ public void encoding( Encoding enc ) { }
+ public void asm(CodeGen code, SB sb) { }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
new file mode 100644
index 00000000..4056e24e
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -0,0 +1,321 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+/**
+ * CallEnd
+ */
+public class CallEndNode extends CFGNode implements MultiNode {
+
+ // When set true, this Call/CallEnd/Fun/Return is being trivially inlined
+ boolean _folding;
+ // +2: must clone, so await all cleanup but OK
+ // +1: trivial now, so can inline without cloning and without waiting for cleanup
+ // 0: take a look,
+ // -1: somethings wrong, but might improve
+ // -2: never and stop asking,
+ private byte _inline;
+ public final TypeRPC _rpc;
+
+ public CallEndNode(CallNode call, TypeRPC rpc) {
+ super(new Node[]{call});
+ _rpc = rpc;
+ }
+ public CallEndNode(CallEndNode cend) { super(cend); _rpc = cend._rpc; }
+ public CallEndNode(TypeRPC rpc) {
+ super(new Node[0]);
+ _rpc = TypeRPC.constant(CodeGen.CODE._rpcs.next(rpc.rpc()));
+ }
+ private CallEndNode(int nIns, TypeRPC rpc) {
+ super(new Node[nIns]);
+ _rpc = rpc;
+ }
+ @Override public Tag serialTag() { return Tag.CallEnd; }
+ public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed1(Serialize.nIns(this,anodes));
+ // Linked CallEnds depend on Return types which depend on CallEnds;
+ // break the cycle
+ baos.packed2(types.get(_type));
+ baos.packed2(types.get(_rpc));
+ }
+ static Node make( BAOS bais, Type[] types ) {
+ int nIns = bais.packed1();
+ Type type = types[bais.packed2()];
+ Node cend = new CallEndNode(nIns,(TypeRPC)types[bais.packed2()]);
+ cend._type = type;
+ return cend;
+ }
+
+ @Override public boolean blockHead() { return true; }
+
+ public CallNode call() { return (CallNode)in(0); }
+ public boolean folding() { return _folding; }
+
+ @Override public CFGNode idom(Node dep) {
+ // Folding the idom is the one inlining Return
+ return _folding ? cfg(1) : super.idom(dep);
+ }
+
+ @Override
+ protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ p.p("cend( ");
+ p.p( in(0) instanceof CallNode ? "Call, " : "----, ");
+ for( int i=1; i<nIns()-1; i++ )
+ p.n(in(i)).p(",");
+ return p.unchar(',').unchar(' ').close();
+ }
+
+ @Override
+ public Type compute() {
+ // Something is dead or busted, early exit with high/dead result
+ if( !(in(0) instanceof CallNode call) || call._type != Type.CONTROL )
+ return TypeTuple.STATE.dual();
+ // The function pointer carries a result type even without a body.
+ Type ftype = addDep(call.fptr())._type;
+ // Something is dead or busted, early exit with high/dead result
+ if( !(ftype instanceof TypeFunPtr tfp) )
+ return ftype.isHigh() ? TypeTuple.STATE.dual() : TypeTuple.STATE;
+ // Mid-fold, just take the inlined callee's return.
+ if( _folding ) return in(1)._type;
+
+ // Unknown targets can change any memory and return the declared type.
+ if( XInt.isHigh(tfp.fidxs()) )
+ return TypeTuple.make(Type.CONTROL,TypeMem.BOT,tfp.ret());
+
+ // Check for making a "C" call
+ int externs = 0;
+ for( int fidx = XInt.next(tfp.fidxs(),0); fidx >= 0; fidx = XInt.next(tfp.fidxs(),fidx) )
+ if( CodeGen.CODE.externFunc(fidx)!=null ) externs++;
+ if( externs!=0 ) {
+ // C calls have no Return edges. Simple calls have an extra self
+ // argument, so the two cannot be targets of the same valid call.
+ assert externs==tfp.nfcns();
+ return TypeTuple.make(Type.CONTROL,call.exposed(this),tfp.ret());
+ }
+
+ // An unlinked target may have effects, so early conservative return.
+ // This also covers invalid calls (e.g. wrong arity), which never link.
+ // SCCP links valid finite targets as soon as they become known, before
+ // computing their CallEnd, so this check does not trigger during SCCP.
+ if( nIns()-1 < tfp.nfcns() )
+ return TypeTuple.make(Type.CONTROL,TypeMem.BOT,tfp.ret());
+
+ // Start at a high tuple (matching Returns' tuple)
+ TypeTuple state = TypeTuple.STATE.dual();
+ // Meet over all Returns
+ for( int i=1; i<nIns(); i++ )
+ state = (TypeTuple)state.meet(in(i)._type);
+ // Keep a live continuation even before any callee exit is reachable.
+ // Otherwise a transient no-return result can tear down the caller
+ // and lose class-init dependencies before SCCP finds those exits.
+ if( state.ctl()==Type.XCONTROL )
+ return TypeTuple.make(Type.CONTROL,TypeMem.TOP,Type.TOP);
+ // Final JOIN over the signature guarentee and meet-over-returns
+ return state.makeFrom(2,state.ret().join(tfp.ret()));
+ }
+
+ @Override
+ public Node idealize() {
+ return null;
+ }
+
+ public byte maybeInline() {
+ byte inline = inlineCandidate(false);
+ if( CodeGen.CODE._midAssert )
+ return inline;
+ _inline = inline;
+ if( _inline > 0 )
+ doInline();
+
+ return _inline;
+ }
+
+ // This CallEnd might be an inline candidate, but the actual code is in
+ // another CompUnit and needs loading to decide. Called at an empty worklist.
+ public boolean loadForInline() {
+ CodeGen code = CodeGen.CODE;
+ if( code._phase!=CodeGen.Phase.IterPostOpto || nIns()!=2 ||
+ !(in(1) instanceof ReturnNode ret) || ret.fun().summary()==null ) return false;
+ // Fetch bodies only for calls in functions compiled from source here.
+ // Calls inside imported functions become eligible when inlined into one.
+ if( !(in(0) instanceof CallNode call) || !call.fun().isFromSource() ||
+ inlineCandidate(true)<=0 ) return false;
+ code.loadForInline(ret.fun()._compunit);
+ return true;
+ }
+
+ // Check if candidate right now, sets no flags. Loading uses the same
+ // policy, but can only estimate the size of a summarized body.
+ private byte inlineCandidate(boolean loading) {
+ if( _inline < -2 ) return -2; // Never
+ if( _folding ) return -2; // No, and will collapse anyways
+ if( !(in(0) instanceof CallNode call) ) return -2; // malformed because mid-death
+
+ if( nIns()!=2 ) return -1; // Not exactly 1 link, but can change
+
+ // Have exactly 1 linked function
+ ReturnNode ret = (ReturnNode)in(1);
+ FunNode fun = ret.fun();
+
+ // Preserve initializer boundaries until post-Opto cycle validation.
+ if( fun.isClz() ) return -2;
+
+ // Heuristic forced inlining turned off via name
+ if( fun._name != null &&
+ fun._name.endsWith("_noInline") )
+ return -2; // Never, stop asking
+
+ // Disallow self-recursive inlining (loop unrolling by another name).
+ for( int i=1; i < fun.nIns(); i++ )
+ if( fun.cfg(i).fun()==fun ) // Check for linked call input inside "fun"
+ { addDep(fun); return -1; } // No, but can lose the recursive edge
+
+ // Things that might improve, but not now a candidate
+ Node fptr = call.fptr();
+ // Constant Function is being called, and its not-null
+ if( !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) )
+ { addDep(fptr); return -1; }
+
+ // Arguments are correct
+ if( call.nargs()!=tfp.nargs() || !goodArgs(call,tfp) )
+ { addDep(fptr); return -1; }
+
+ // Encouraged inlining because small size and constructor.
+ int maxSize = fun.inlineLimit();
+ // A large parse can leave only a tiny initializer. Recognize that
+ // and await cleanup to shrink the body.
+ if( fun._approxUIDs >= maxSize ) {
+ if( fun.summary()!=null || !smallBody(fun) ) return -1;
+ } else if( fun.summary()==null && fun.body().cardinality() >= maxSize ) // Expensive size check
+ return -1; // Try later (if cleanup makes the real body small enough)
+
+ if( fun.summary()!=null ) return loading ? (byte)1 : (byte)-1;
+
+ // Can be cloned but not trivial
+ if( fun.nIns() > 2 ) { addDep(fun); return 2; }
+
+ // A trivial candidate right now
+ return 1;
+ }
+
+ // Bounded, conservative recognition of straight-line initializer boilerplate.
+ // Walk uses from the entry and stop at Return, never following callers or
+ // constant inputs. This also avoids scanning sparse MemMerge input arrays.
+ // Watch only this small prefix, including an unsupported node that may fold.
+ private static final Node[] TINYBODY = new Node[15]; // Tiny worklist
+ private boolean smallBody(FunNode fun) {
+ ReturnNode ret = fun.ret();
+ for( Node in : ret._inputs )
+ if( in!=null ) addDep(in);
+ if( ret.ctrl()!=fun ) return false;
+ TINYBODY[0] = fun;
+ int len=1;
+ for( int i=0; i<len; i++ ) {
+ Node n = TINYBODY[i];
+ addDep(n);
+ if( n==ret ) continue;
+ if( n instanceof ParmNode parm && parm.region()!=fun ) return false;
+ if( n!=fun && !(n instanceof ParmNode) &&
+ !(n instanceof StoreNode) && !(n instanceof MemMergeNode) )
+ return false; // Something unexpected
+ if( n.nOuts()>TINYBODY.length ) return false; // Too big
+ for( Node use : n._outputs ) {
+ if( use==null ) continue;
+ int j=0;
+ while( j<len && TINYBODY[j]!=use ) j++;
+ if( j<len ) continue; // Already in TINYBODY
+ if( len==TINYBODY.length ) { addDep(use); return false; } // Too big
+ TINYBODY[len++] = use; // Add to worklist
+ }
+ }
+ return true;
+ }
+
+ public void doInline() {
+ assert _inline > 0; // Only called when possible to inline
+
+ ReturnNode ret = (ReturnNode)in(1);
+ FunNode fun = ret.fun();
+ CallNode call = call();
+ assert fun.sig().isa(call.fptr()._type);
+
+ // Clone first
+ if( _inline==2 ) {
+ assert !CodeGen.CODE._midAssert; // Triggered inlining
+ // Remove the existing function linkage
+ call.unlink_all();
+ // Clone the function body
+ FunNode fun2 = fun.copyBody();
+ // Call uses the unique new function
+ Node fptr2 = new FunPtrNode(fun2.sig(),CodeGen.CODE._start,fun2.ret()).peephole();
+ call.setDef(call.nIns()-1,fptr2);
+ // Link to the new function
+ call.link(fun2);
+ fun = fun2;
+ }
+
+ // Trivial inlining: call site calls a single function; single function
+ // is only called by this call site.
+ doTrivialInlining(fun);
+ }
+
+ boolean goodArgs( CallNode call, TypeFunPtr tfp ) {
+ for( int i=0; i<tfp.nargs(); i++ ) {
+ Node arg = call.arg(i+2);
+ // The graph's function-pointer type can still contain open forward
+ // references here. Compare against its recursively upgraded form,
+ // so deep mutability requirements participate before inlining.
+ Type formal = tfp.arg(i).upgradeType(Parser.TYPES);
+ if( !arg._type.isa(formal) || !arg._type.accessISA(formal) )
+ { addDep(arg); return false; }
+ }
+ return true;
+ }
+
+
+ // Do trivial inlining. Inlining does not need to clone code, merely
+ // triggers folding the Call/Fun and Return/CallEnd away.
+ private boolean doTrivialInlining( FunNode fun ) {
+ assert !CodeGen.CODE._midAssert; // Triggered inlining
+ // Trivial inline: rewrite
+ _folding = true;
+ // Rewrite Fun so the normal RegionNode ideal collapses
+ fun._folding = true;
+ fun.setDef(1,call().ctrl());// Bypass the Call;
+ fun.ret().setDef(3,null); // Return is folding also
+
+ CodeGen.CODE.addAll(fun._outputs);
+ // Repeat defs 1 layer down, for users of Parm (Phis)
+ for( Node parm : fun._outputs )
+ if( parm instanceof ParmNode )
+ CodeGen.CODE.addAll(parm._outputs);
+
+ // Inlining immediately blows all cache idepth fields past the inline point.
+ // Bump the global version number invalidating them en-masse.
+ CodeGen.CODE.invalidateIDepthCaches();
+ // More peeps
+ CodeGen.CODE.add(fun);
+ CodeGen.CODE.add(this);
+ CodeGen.CODE.addAll(_outputs);
+ CodeGen.CODE.addAll(_inputs);
+ return true; // Always return true
+ }
+
+
+ @Override public Node pcopy(int idx, Node dep) {
+ return _folding ? in(1).in(idx) : null;
+ }
+
+ @Override public Node copy() {
+ return _folding ? super.copy() : new CallEndNode(_rpc);
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/CallNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/CallNode.java
new file mode 100644
index 00000000..fcadd0e4
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/CallNode.java
@@ -0,0 +1,271 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Lexer;
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Iter;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.type.TypeRPC;
+import com.seaofnodes.simple.type.XInt;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+/**
+ * Call
+ */
+public class CallNode extends CFGNode {
+
+ // Source location for late reported errors
+ public final Lexer _loc;
+
+ public CallNode(Lexer loc, Node... nodes) {
+ super(nodes);
+ _loc = loc;
+ CodeGen.CODE._calls.add(this);
+ CodeGen.CODE.add(CodeGen.CODE._globalEscapes);
+ }
+ public CallNode(CallNode call) { super(call); _loc = call._loc; }
+ @Override public Tag serialTag() { return Tag.Call; }
+ public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed1(nIns());
+ Lexer.packed(_loc,baos,strs);
+ }
+ @Override public void gather(HashMap<String,Integer> strs) { Lexer.gather(_loc,strs); }
+ static Node make( BAOS bais, String[] strs ) {
+ int nIns = bais.packed1();
+ return new CallNode(Lexer.unpack(bais,strs),new Node[nIns]);
+ }
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ String fname = name();
+ if( fname == null ) p.n(fptr());
+ else p.p(fname);
+ p.p("( ");
+ for( int i=2; i<nIns()-1; i++ )
+ p.n(in(i)).p(",");
+ return p.unchar(',').unchar(' ').close();
+ }
+ public String name() {
+ if( fptr()._type instanceof TypeFunPtr tfp && tfp._isConstant() && tfp.notNull() ) {
+ FunNode fun = CodeGen.CODE._link(tfp);
+ if( fun !=null ) return fun._name;
+ if( fptr() instanceof ExternNode ex ) return ex._extern;
+ }
+ return null;
+ }
+
+
+
+ Node ctrl() { return in(0); }
+ Node mem () { return in(1); }
+ // Args mapped 1-to-1 on inputs, so conceptually start at 2
+ public Node arg(int idx) { return in(idx); }
+ // Same arg accounting as TFPs, although the numbering starts at 2
+ public int nargs() { return nIns()-3; } // Minus control, memory, fptr
+ // args from input 2 to last; last is function input
+ public Node fptr() { return _inputs.last(); }
+ // Error if not a TFP
+ public TypeFunPtr tfp() { return (TypeFunPtr)fptr()._type; }
+
+ // Call is to an externally supplied code
+ public boolean external() {
+ if( !(fptr()._type instanceof TypeFunPtr tfp) ) return false;
+ if( XInt.isHigh(tfp.fidxs()) ) return true;
+ for( int fidx=XInt.next(tfp.fidxs(),-1); fidx>=0; fidx=XInt.next(tfp.fidxs(),fidx) )
+ if( CodeGen.CODE.externFunc(fidx)!=null ) return true;
+ return false;
+ }
+
+ // Unknown callers contribute declared argument types to the parameters.
+ // Those broad types can hide a particular private callback supplied here.
+ // Expose actual arguments too, including calls through library summaries.
+ boolean exposesArguments(Node dep) {
+ if( external() ) return true;
+ if( !(fptr()._type instanceof TypeFunPtr tfp) ) return false;
+ for( int fidx = XInt.next(tfp.fidxs(),0); fidx>=0; fidx = XInt.next(tfp.fidxs(),fidx) ) {
+ FunNode fun = CodeGen.CODE._link(fidx);
+ if( fun!=null && !fun.isClz() ) {
+ dep.addDepForwards(fun);
+ if( fun.unknownCallers() ) return true;
+ }
+ }
+ return false;
+ }
+
+ // As in the escape chapter, native code may retain an argument even if
+ // this path never returns. StopCU reads this independently of CallEnd.
+ TypeMem exposed(Node dep) {
+ dep.addDepForwards(this);
+ dep.addDepForwards(fptr());
+ if( _type.isHigh() ) return TypeMem.START;
+ // A broad scalar target can refine to a function pointer during Iter.
+ // It cannot yet promise that the call exposes nothing.
+ if( _type!=Type.CONTROL ) return TypeMem.BOT;
+ if( !(fptr()._type instanceof TypeFunPtr) )
+ return fptr()._type.isHigh() ? TypeMem.START : TypeMem.BOT;
+ if( !external() ) return TypeMem.START;
+ Type input = dep.addDepForwards(mem())._type;
+ TypeMem escaped = input instanceof TypeMem m ? m : input.isHigh() ? TypeMem.TOP : TypeMem.BOT;
+ for( int i = 2; i<nIns()-1; i++ ) escaped = escaped.escapes(dep.addDepForwards(arg(i))._type);
+ return TypeMem.make(1,Type.BOTTOM,false,false,false,escaped._escFs,escaped._escAs);
+ }
+
+
+ // Find the Call End from the Call
+ public CallEndNode cend() {
+ // Always in use slot 0
+ if( nOuts()>0 && out(0) instanceof CallEndNode cend ) {
+ assert _cend()==cend;
+ return cend;
+ } else {
+ assert _cend()==null;
+ return null;
+ }
+ }
+ private CallEndNode _cend() {
+ CallEndNode cend=null;
+ for( Node n : _outputs )
+ if( n instanceof CallEndNode cend0 )
+ { assert cend == null; cend = cend0; }
+ return cend;
+ }
+
+ // Get the one control following; error to call with more than one e.g. an
+ // IfNode or other multi-way branch.
+ @Override public CFGNode uctrl() { return cend(); }
+
+
+ @Override
+ public Type compute() {
+ return ctrl()._type;
+ }
+
+ @Override
+ public Node idealize() {
+ // Lowering may introduce calls after the call graph has been analyzed.
+ // These use the callee interface without reopening its parameter flow.
+ if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Lower.ordinal() ) return null;
+ CallEndNode cend = cend();
+ if( cend==null ) return null; // Still building
+
+ // Link: call calls target function. Linking makes the target FunNode
+ // point to this Call, and all his Parms point to the call arguments;
+ // also the CallEnd points to the Return.
+ Node progress = null;
+ if( fptr()._type instanceof TypeFunPtr tfp && tfp.nargs() == nargs() ) {
+ // If fidxs is negative, then infinite unknown functions
+ int[] fidxs = tfp.fidxs();
+ if( !XInt.isHigh(fidxs) ) {
+ // Walk the bits and link
+ for( int fidx = XInt.next(fidxs,0); fidx > 0; fidx = XInt.next(fidxs,fidx) ) {
+ FunNode fun = CodeGen.CODE.link(fidx);
+ if( fun!=null && !fun._folding && !linked(fun) )
+ progress = link(fun);
+ }
+ }
+ }
+
+ return progress;
+ }
+
+ // True if Fun is linked to this Call
+ public boolean linked( FunNode fun ) {
+ for( Node n : fun._inputs )
+ if( n == this )
+ return true;
+ return false;
+ }
+
+ // Link so this calls fun
+ public Node link( FunNode fun ) {
+ assert !linked(fun);
+ CodeGen.CODE.add(fun).addDef(this);
+ for( Node use : fun._outputs )
+ if( use instanceof ParmNode parm ) {
+ Node def = parm._idx==0 ? ConstantNode.seed(cend()._rpc).peephole() : arg(parm._idx);
+ CodeGen.CODE.add(parm).addDef(def);
+ // Linking can happen during post-SCCP inlining. Keep an RPC
+ // Parm's cached finite set synchronized with its newly added
+ // input instead of waiting for the next worklist visit.
+ if( parm._idx==0 && parm._type instanceof TypeRPC )
+ parm._type = parm._type.meet(def._type);
+ }
+ // Call end points to function return
+ CodeGen.CODE.add(cend()).addDef(fun.ret());
+ // Loading/inlining can reveal a target after SCCP. Its new caller
+ // may bring escapes absent from the cached formal parameter types.
+ // Repair affected bounds before the next type-lifting peephole.
+ CodeGen code = CodeGen.CODE;
+ if( code._phase.ordinal()>CodeGen.Phase.Opto.ordinal() &&
+ code._phase.ordinal()<CodeGen.Phase.Lower.ordinal() ) {
+ boolean repair = !fun.compute().isa(fun._type) || !cend().compute().isa(cend()._type);
+ for( Node use : fun._outputs )
+ if( use instanceof ParmNode && !use.compute().isa(use._type) ) repair = true;
+ if( repair ) Iter.repair(code);
+ }
+ return this;
+ }
+
+ // Unlink a single function
+ public void unlink( FunNode fun, int path ) {
+ assert linked(fun);
+ for( Node use : fun._outputs )
+ if( use instanceof ParmNode )
+ use.delDef(path);
+ fun.delDef(path);
+ CodeGen.CODE.add(fun);
+ CallEndNode cend = cend();
+ ReturnNode ret = fun.ret();
+ if( cend != null ) {
+ ret.keep();
+ cend.delDef(cend._inputs.find(ret));
+ CodeGen.CODE.add(cend);
+ ret.unkeep();
+ }
+ assert !linked(fun);
+ }
+
+
+ // Unlink all linked functions
+ public void unlink_all() {
+ for( int i=0; i<_outputs._len; i++ )
+ if( out(i) instanceof FunNode fun ) {
+ int path = fun._inputs.find(this);
+ unlink(fun,path);
+ i--;
+ }
+ }
+
+ @Override
+ public Parser.ParseException err() {
+ if( fptr()._type == Type.BOTTOM )
+ return null; // Wrong, but failing earlier
+ if( !(fptr()._type instanceof TypeFunPtr tfp) )
+ return Parser.error("Expected a function, but found " + fptr()._type, _loc);
+ if( !tfp.notNull() )
+ return Parser.error( "Might be null calling "+tfp, _loc);
+ if( nargs() != tfp.nargs() )
+ return Parser.error( "Expecting "+tfp.nargs()+" arguments, but found "+nargs(), _loc);
+
+ // Check for args
+ for( int i=0; i<tfp.nargs(); i++ )
+ if( !arg(i+2)._type.isa(tfp.arg(i)) || !arg(i+2)._type.accessISA(tfp.arg(i)) ) {
+ // Constructors return private memory and carry hidden self and
+ // selfMem arguments ahead of the source-level arguments.
+ int argno = i - (tfp.ret() instanceof TypeMem ? 2 : 0);
+ return Parser.error( "Argument #" + argno + " isa " + arg(i+2)._type + ", but must be a " + tfp.arg(i), _loc );
+ }
+
+ if( tfp.isHigh() )
+ throw Utils.TODO(); // Infinite unknown TFPs? Should be fairly precise CG
+
+ return null;
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java
new file mode 100644
index 00000000..0afc5827
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.type.Type;
+
+public class CalleeSaveNode extends ProjNode implements MachNode {
+ final RegMask _mask;
+ public CalleeSaveNode(FunNode fun, int reg, String label) {
+ super(fun,reg,label);
+ fun._outputs.pop();
+ int i=0;
+ while( fun.out(i) instanceof PhiNode ) i++;
+ fun._outputs.insert(this,i);
+ _mask = new RegMask(reg);
+ }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return _mask; }
+ @Override public void encoding( Encoding enc ) { }
+
+ @Override public Type compute() { return Type.BOTTOM; }
+ @Override public Node idealize() { return null; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
new file mode 100644
index 00000000..cb99b4dd
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
@@ -0,0 +1,78 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.TypeStruct;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+import java.util.HashMap;
+
+/**
+ * A field offset. This becomes a constant *after* other optimizations which
+ * determine field sizes - some fields might be dead, or hold only zeros or
+ * small values.
+ */
+
+public class ConFldOffNode extends ConstantNode {
+ public TypeStruct _ts; // Struct holding field
+ public final String _fname; // Field name
+ public ConFldOffNode( TypeStruct ts, String fname ) { super(TypeInteger.BOT); _ts = ts; _fname = fname; }
+ @Override public Tag serialTag() { throw Utils.TODO("should not reach here"); }
+
+ @Override public String label() {
+ return _fname == " len" ? "sizeof("+_ts._name+")" : "#"+_fname;
+ }
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ return p.p(label());
+ }
+
+ @Override public Node idealize() {
+ // Some array offsets are known early
+ if( _ts.isAry() ) {
+ // Length always at offset zero
+ if( _fname==" len" )
+ return CodeGen.CODE.ZERO;
+ // The shift depends on the element
+ if( _fname=="<<" )
+ return Node.con(_ts.aryScale());
+ // The base depends on the layout (alignment for large elements may
+ // involve padding) and MAY depend on the length if I go for
+ // smaller array lengths.
+ }
+ return null;
+ }
+
+ // Convert field offset to an integer
+ public Node asOffset() {
+ int fldx = _fname==" len" ? _ts._fields.length : _ts.find(_fname);
+ if( fldx == -1 ) return null;
+ // " len" selects the terminal size slot, not a Field.
+ if( fldx < _ts._fields.length && _ts._fields[fldx]._extern )
+ return new ExternOffsetNode(_fname).peephole();
+ return ConstantNode.make(TypeInteger.constant(_ts.offset(fldx))).peephole();
+ }
+
+ // Upgrade the internal type
+ @Override boolean _upgradeType( HashMap<String,Type> TYPES) {
+ boolean progress = super._upgradeType(TYPES);
+ TypeStruct ts = (TypeStruct)_ts.upgradeType(TYPES);
+ if( ts == _ts ) return progress;
+ _ts = ts;
+ return true;
+ }
+
+ @Override public boolean eq(Node n) {
+ ConFldOffNode off = (ConFldOffNode)n; // Invariant
+ return _ts._name==off._ts._name && _fname.equals(off._fname) && super.eq(n);
+ }
+ @Override int hash() { return super.hash() * _ts._name.hashCode() * _fname.hashCode(); }
+
+ @Override public Parser.ParseException err() {
+ return _ts._fref ? Parser.error("Unknown struct type '"+_ts._name+"'",null) : null;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
new file mode 100644
index 00000000..0569f352
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
@@ -0,0 +1,91 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+/**
+ * A Constant node represents a constant value.
+ * <p>
+ * Constants have no semantic inputs. However, we set Start as an input to
+ * Constants to enable a forward graph walk. This edge carries no semantic
+ * meaning, and it is present <em>solely</em> to allow visitation.
+ * <p>
+ * The Constant's value is the value stored in it.
+ */
+
+public class ConstantNode extends TypeNode {
+ public ConstantNode( Type type ) {
+ this(type,false);
+ }
+ protected ConstantNode( Type type, boolean raw ) {
+ super(type,new Node[]{CodeGen.CODE._start});
+ assert raw || !(type instanceof TypeFunPtr tfp && tfp.isConstant()) :
+ "Unique function identities require a Return-linked FunPtrNode";
+ _type = type;
+ }
+ public ConstantNode( ConstantNode con ) { super(con); }
+ @Override public Tag serialTag() { return Tag.Con; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed2(types.get(_con)); // NPE if fails lookup
+ }
+ static Node make( BAOS bais, Type[] types) { return raw(types[bais.packed2()]); }
+
+ // Semantic constant construction through Lower. A unique internal
+ // function identity carries a lifetime edge to the function's Return.
+ public static Node make( Type type ) {
+ if( type==Type. CONTROL ) return new CtrlNode();
+ if( type==Type.XCONTROL ) return new XCtrlNode();
+ if( CodeGen.CODE._phase != null &&
+ CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Lower.ordinal() )
+ return raw(type);
+ if( type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
+ FunNode fun = CodeGen.CODE._link(tfp);
+ if( fun != null )
+ return new FunPtrNode(tfp,CodeGen.CODE._start,fun.ret()).init();
+ // TODO: A singleton FIDX can also emerge after merging and
+ // guarding several function pointers. Preserve that identity
+ // even if its internal Return is no longer available.
+ return raw(type);
+ }
+ return new ConstantNode(type);
+ }
+
+ // Exact construction for deserialization and graph-incomplete machine
+ // selection. No semantic classification, compute, idealize, or GVN.
+ public static ConstantNode raw( Type type ) { return new ConstantNode(type,true); }
+ public static ConstantNode raw( ConstantNode con ) { return new ConstantNode(con); }
+
+ // Analysis input created from whole cloth, rather than a first-class
+ // runtime value. In particular, a constructor call descriptor does not
+ // own the constructor function; the class FunPtr does.
+ public static ConstantNode seed( Type type ) { return new ConstantNode(type,true); }
+
+ @Override protected String repeatName() {
+ return _con==null || _con.toString().length()<=32 ? null : uniqueName();
+ }
+
+ @Override public String label() { return "Con"; }
+ @Override public Node copy() { return raw(this); }
+
+
+ @Override
+ protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ if( _con instanceof TypeFunPtr tfp && tfp._isConstant() && tfp.notNull() ) {
+ FunNode fun = CodeGen.CODE._link(tfp);
+ if( fun!=null && fun._name != null )
+ return p.p("{ ").p(fun._name).p("}");
+ }
+ return p.p(_con==null ? "---" : _con.toString());
+ }
+
+ @Override public boolean isConst() { return true; }
+ @Override public Type compute() { return _con; }
+ @Override public Node idealize() { return null; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ConvertNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
new file mode 100644
index 00000000..a969fb4e
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
@@ -0,0 +1,104 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.type.*;
+
+import java.util.HashMap;
+
+// Convert the incoming value; actual conversion depends on the _dst type.
+// Convert ints to floats, ptrs to ints, nullable to not-null, etc. Once the
+// input type is known precisely enough, the idealize() call triggers and
+// replaces the Convert with a concrete conversion (e.g. toFloatNode).
+//
+// Convert has no runtime dynamic behavior; it must *always* get replaced with
+// some concrete behavior (or its an error).
+public class ConvertNode extends Node {
+ private Type _dst;
+
+ public ConvertNode(Type dst, Node val) {
+ super(null,val);
+ // Folding this conversion would replace the Return-linked FunPtrNode
+ // with an unhooked ConstantNode.
+ assert !(dst instanceof TypeFunPtr && val instanceof FunPtrNode);
+ _dst = dst;
+ }
+
+ public Type dst() { return _dst; }
+ public Node val() { return in(1); }
+
+ @Override public String label() { return "Convert_"+_dst.str(); }
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ return p.p(label()).open().n(val()).close();
+ }
+
+
+ // Not much to do here, if the input type is not sharp enough to fold into
+ // the proper conversion, its not sharp enough to do much else.
+ @Override public Type compute() {
+ Type src = val()._type;
+ if( src.isHigh() ) return Type.TOP;
+ if( !(_dst instanceof TypeMemPtr) && src.isa(_dst) ) return src;
+ return _dst;
+ }
+
+ @Override public Node idealize() {
+ Type src = val()._type;
+ if( src==Type.BOTTOM || src==Type.TOP ) return null;
+ if( (src.isa(_dst) && src.accessISA(_dst)) ) return val();
+
+ // Managed arrays are represented by an object pointer, while C wants
+ // the address of the first element. The representation cast itself is
+ // zero-code; the Add skips the array header.
+ if( _dst==TypeInteger.BOT && src instanceof TypeMemPtr tmp && tmp._obj.isAry() )
+ return new AddNode(new PtrToIntNode(val()).peephole(),Parser.off(tmp._obj,"[]"));
+
+ // Integer (and nil-as-zero) to floating point.
+ if( (src instanceof TypeInteger || src==Type.NIL) && _dst instanceof TypeFloat )
+ return new ToFloatNode(val());
+
+ // Convert to i64 first, then apply the destination's integer width.
+ if( src instanceof TypeFloat && _dst instanceof TypeInteger )
+ return new ConvertNode(_dst,new ToIntegerNode(val()).peephole());
+
+ // Narrow integers produce the declared sign/zero extension.
+ if( src instanceof TypeInteger && _dst instanceof TypeInteger dst ) {
+ if( dst._min==0 )
+ return new AndNode(null,val(),con(dst._max));
+ int shift = Long.numberOfLeadingZeros(dst._max)-1;
+ Node shf = con(shift);
+ if( shf._type==TypeInteger.ZERO ) return val();
+ return new SarNode(null,new ShlNode(null,val(),shf.keep()).peephole(),shf.unkeep());
+ }
+
+ // Narrow f64 to f32.
+ if( src instanceof TypeFloat && _dst instanceof TypeFloat )
+ return new RoundF32Node(val());
+
+ // Wait for more source information, or report an error after Opto.
+ return null;
+ }
+
+ @Override boolean _upgradeType(HashMap<String,Type> TYPES) {
+ Type dst = _dst.upgradeType(TYPES);
+ if( dst==_dst ) return false;
+ unlock();
+ _dst = dst;
+ return true;
+ }
+
+ @Override public Parser.ParseException err() {
+ if( val()._type==Type.BOTTOM ||
+ val()._type instanceof TypeNil src && src.nullable() && _dst instanceof TypeNil dst && dst.notNull() ) {
+ Node out = out(0);
+ if( out instanceof PhiNode phi ) out = phi.out(0);
+ if( out instanceof StoreNode st )
+ return Parser.error( "'" + ((TypeMemPtr)st.ptr()._type)._obj._name + "' is not fully initialized, field '" + st._name + "' is only partially set in the constructor", null );
+ }
+ return Parser.error("Type "+val()._type.str()+" is not of declared type "+_dst.str(),null);
+ }
+
+ @Override public boolean eq(Node n) { return _dst==((ConvertNode)n)._dst; }
+ @Override int hash() { return _dst.hashCode(); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/CtrlNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
new file mode 100644
index 00000000..277e03dc
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.type.Type;
+
+public class CtrlNode extends CFGNode {
+ public CtrlNode() { super(CodeGen.CODE._start); }
+ @Override public Tag serialTag() { throw com.seaofnodes.simple.util.Utils.TODO(); }
+ @Override public String label() { return "Ctrl"; }
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("Cctrl"); }
+ @Override public boolean isConst() { return true; }
+ @Override public Type compute() { return Type.CONTROL; }
+ @Override public Node idealize() { return null; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/DivNode.java
new file mode 100644
index 00000000..f173f0ef
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -0,0 +1,40 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.TypeFloat;
+import java.util.BitSet;
+
+public class DivNode extends ArithNode {
+ @Override boolean allowFloat() { return true; }
+ public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+ public DivNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs, mode); }
+ @Override public Tag serialTag() { return Tag.Div; }
+
+ @Override public String op() { return _mode==1 ? "//" : "/"; }
+
+ @Override long doOp( long x, long y ) { return y==0 ? 0 : x / y; }
+ @Override double doOp(double x, double y ) { return x / y; }
+ @Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
+ return TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ // Div of 1.
+ if( in(2)._type == TypeInteger.TRUE && in(1)._type.isa(compute()) )
+ return in(1);
+
+ // Div of constant
+ if( _mode==2 ) {
+ if( in(2)._type == TypeFloat.constant(1.) && in(1)._type.isa(compute()) )
+ return in(1);
+ if( in(2)._type instanceof TypeFloat f && f.isConstant() )
+ return new MulNode(in(1),ConstantNode.make(TypeFloat.constant(1.0/f.value())).peephole(),_mode);
+ }
+
+ return super.idealize();
+ }
+
+ @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs,_mode); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/EscapeNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
new file mode 100644
index 00000000..3e95fffb
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
@@ -0,0 +1,116 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.*;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+/**
+ * Merge public and private aliases.
+ * 0 - control
+ * 1 - private pointer
+ * 2 - private alias
+ * 3 - public alias
+ */
+public class EscapeNode extends TypeNode {
+
+ public EscapeNode(Field fld, Node self, Node priv, Node pub ) {
+ super(fld,null,self,priv,pub);
+ }
+ public EscapeNode(EscapeNode esc ) {
+ super(esc); // Copy constructor
+ // Except we do not want/need self ptr past Opto.
+ // *NOTE* the weird direct stomp; this is correct because this
+ // constructor does NOT set the backedges, because it is used to
+ // copy-construct an entire graph and ALL edges will get rewritten.
+ _inputs.set(1,null);
+ _con = esc._con;
+ }
+
+ // Pointer to some private (unescaped) memory from a NewNode
+ public Node self() { return in(1); }
+ public Node priv() { return in(2); }
+ public Node pub () { return in(3); }
+ public Field fld() { return (Field)_con; }
+
+ @Override public Tag serialTag() { return Tag.Escape; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed2(types.get(_con));
+ }
+ static Node make( BAOS bais, Type[] types) {
+ return new EscapeNode((Field)types[bais.packed2()], null,null,null);
+ }
+
+ @Override
+ protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ p.p("Esc#").p(fld()._alias).p(" {");
+ if( self()==null ) p.p("---");
+ else p.n(self()).p(",");
+ p.n(priv()).p("}, ");
+ p.n(pub ());
+ return p;
+ }
+
+ @Override
+ public Type compute() {
+ if( priv()._type.isHigh() ) return TypeMem.TOP;
+ if( pub ()._type.isHigh() ) return TypeMem.TOP;
+ TypeMem mpriv = (TypeMem)priv()._type;
+ TypeMem mpub = (TypeMem)pub ()._type;
+ // Private memory comes from an allocation or other non-aliased source.
+ // Public memory comes from anywhere, might be bulk memory with a very weak mem type.
+ Field fld = fld();
+ assert mpriv._alias==1 || mpriv._alias==fld._alias;
+ assert mpub ._alias==1 || mpub ._alias==fld._alias;
+ Type tpriv = mpriv._t instanceof TypeStruct ts && ts.findAlias(fld._alias)!=-1
+ ? ts.at(ts.findAlias(fld._alias))._t
+ : mpriv._t;
+ assert !(mpub._t instanceof TypeStruct); // Never expect this, but if it starts to happen, need to optimize
+ Type tpub = mpub._t;
+
+ // t is meet of pub & priv. Pub memory can be very weak (e.g. alias 1)
+ // and we're no worse than the declared field type
+ Type t = tpriv.meet(tpub).join(fld._t);
+ int[] fidxes = XInt.meet(mpriv._escFs, mpub._escFs);
+ int[] aliases = XInt.meet(mpriv._escAs, mpub._escAs);
+ return TypeMem.make(fld._alias, t, false, false, fld._final, fidxes, aliases).escapesFrom(t);
+ }
+
+ @Override public Node idealize() {
+ // Sharpen private memory input
+ if( priv() instanceof MemMergeNode mmm ) {
+ setDef(2,mmm.alias(fld()._alias));
+ return this;
+ }
+ // Sharpen public memory input
+ if( pub() instanceof MemMergeNode mmm ) {
+ setDef(3,mmm.alias(fld()._alias));
+ return this;
+ }
+ // New allocation is dead
+ if( self()._type.isHigh() )
+ if( pub()._type.isa(_type) ) return pub();
+ else addDep(pub());
+ return null;
+ }
+
+// Upgrade the internal type
+@Override boolean _upgradeType( HashMap<String,Type> TYPES) {
+ Type t = _con.upgradeType(TYPES);
+ if( t == _con ) return false;
+ unlock(); // Unlock before changing _con
+ _con = t;
+ return true;
+}
+
+ @Override public boolean eq(Node n) {
+ if( fld()._alias!=((EscapeNode)n).fld()._alias )
+ return false;
+ assert _con == ((EscapeNode)n)._con;
+ return true;
+ }
+
+ @Override int hash() { return _con.hashCode(); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ExternNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ExternNode.java
new file mode 100644
index 00000000..978298fc
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ExternNode.java
@@ -0,0 +1,38 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.Serialize;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.SB;
+
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+/**
+ A constant with external linkage.
+ */
+
+public class ExternNode extends ConstantNode {
+ public final String _extern;
+ // An exact external function identity has no local FunNode/Return to own.
+ // Its lifetime and eventual relocation are carried by the extern symbol
+ // and CodeGen's FIDX-to-extern mapping instead.
+ public ExternNode(Type t, String ex) { super(t,true); _extern = ex; }
+ @Override public Tag serialTag() { return Tag.Extern; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed2(types.get(_con ));
+ baos.packed2( strs.get(_extern));
+ }
+ static Node make( BAOS bais, String[] strs, Type[] types) {
+ return new ExternNode(types[bais.packed2()], strs[bais.packed2()]);
+ }
+
+ @Override public String label() { return "#"+_con+":"+_extern; }
+
+ @Override public boolean eq(Node n) { return this==n; }
+ @Override public void gather( HashMap<String,Integer> strs ) {
+ Serialize.gather(strs,_extern);
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java
new file mode 100644
index 00000000..409ce5d1
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.BAOS;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+// A field address supplied by the C linker, not a displacement from the base.
+// The class base remains available for ideal type/alias analysis. Instruction
+// selection replaces it with this symbol's address and a zero displacement.
+public class ExternOffsetNode extends ExternNode {
+ public ExternOffsetNode(String name) { super(TypeInteger.BOT,name); }
+ @Override public Tag serialTag() { return Tag.ExternOffset; }
+ @Override public Node copy() { return new ExternOffsetNode(_extern); }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> nodes) {
+ baos.packed2(strs.get(_extern));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/FRefNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/FRefNode.java
new file mode 100644
index 00000000..3906f7be
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/FRefNode.java
@@ -0,0 +1,51 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Lexer;
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.parser.Var;
+import com.seaofnodes.simple.type.Type;
+
+/**
+ * A Forward Reference. Its any final constant, including functions. When
+ * the Def finally appears its plugged into the forward reference, which then
+ * peepholes to the Def.
+
+ * FRefs can be external references, to other Simple defined values in ELF
+ * files, or external FFI/C libs. If external, a file system lookup needs to
+ * find it in another file, and the name (one time) change to match the found
+ * location.
+ *
+ * Example, a ambiguous name "foo" is looked up externally; it might resolve
+ * to "A.B.foo" or "libc.foo" or the like; the name is changed to match.
+ */
+public class FRefNode extends ConstantNode {
+ public static final Type FREF_TYPE = Type.BOTTOM;
+ public String _name;
+ public final Lexer _loc; // Source location
+
+ public FRefNode( String name, Lexer loc ) { this(name,loc,FREF_TYPE); }
+ public FRefNode( String name, Lexer loc, Type type ) {
+ super(type);
+ _name = name;
+ _loc = loc;
+ }
+
+ @Override public String label() { return "FRef"+_name; }
+
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ return p.p("FRef_").p(_name);
+ }
+
+ @Override public Node idealize() {
+ // When FRef finds its definition, idealize to it
+ return nIns()==1 ? null : in(1);
+ }
+
+ public Parser.ParseException err() { return Parser.error("Undefined name '"+_name+"'",_loc); }
+
+ @Override public boolean eq(Node n) { return this==n; }
+ @Override int hash() { return _name.hashCode(); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/FldOffNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/FldOffNode.java
new file mode 100644
index 00000000..223ca79e
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/FldOffNode.java
@@ -0,0 +1,55 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Lexer;
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+import java.util.HashMap;
+
+/**
+ * A field offset. This becomes a constant *after* other optimizations which
+ * determine field sizes - some fields might be dead, or hold only zeros or
+ * small values.
+ */
+
+public class FldOffNode extends TypeNode {
+ public final String _fname; // Field name
+ public final Lexer _loc; //
+ public FldOffNode( Node base, String fname, Lexer loc ) { super(TypeInteger.BOT,base); _fname = fname; _loc=loc; }
+ @Override public Tag serialTag() { throw Utils.TODO("should not reach here"); }
+
+ @Override public String label() {
+ return _fname == " len" ? "sizeof()" : "#"+_fname;
+ }
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ return p.p(label());
+ }
+
+ @Override public Type compute() {
+ return Type.BOTTOM;
+ }
+
+ @Override public Node idealize() {
+ // When base() gets typed, this xforms into a ConFldOffNode
+ if( in(0)._type instanceof TypeMemPtr tmp )
+ return new ConFldOffNode(tmp._obj,_fname);
+ return null;
+ }
+
+ @Override public boolean eq(Node n) {
+ FldOffNode off = (FldOffNode)n; // Invariant
+ return _fname.equals(off._fname) && super.eq(n);
+ }
+ @Override int hash() { return super.hash() * _fname.hashCode(); }
+
+ @Override public Parser.ParseException err() {
+ if( in(0)._type instanceof TypeMemPtr tmp && tmp._obj._fref )
+ return Parser.error("Unknown struct type '"+tmp._obj._name+"'",null);
+ return null;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/FunNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/FunNode.java
new file mode 100644
index 00000000..d76d80b5
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/FunNode.java
@@ -0,0 +1,499 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Lexer;
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import static com.seaofnodes.simple.codegen.CodeGen.CODE;
+
+public class FunNode extends RegionNode {
+
+ // When set true, this Call/CallEnd/Fun/Return is being trivially inlined
+ public boolean _folding;
+ public boolean folding() { return _folding; }
+
+ private TypeFunPtr _sig; // Initial signature
+ private ReturnNode _ret; // Return pointer
+
+ public String _name; // Debug name
+
+ public int _approxUIDs; // Approximate function size, used as a inlining heuristic
+ public CompUnit _compunit; // Defined in source file
+ public final boolean _extern; // External function; no code, name only
+
+ private FunNode( Lexer loc, Node[] nodes, TypeFunPtr sig, String name, CompUnit compunit, boolean ext ) {
+ super(loc,nodes);
+ _name = name;
+ _sig = sig;
+ _extern = ext;
+ _compunit = compunit;
+ }
+ public FunNode( Lexer loc, TypeFunPtr sig, String name, CompUnit compunit, Node... nodes ) { this(loc,nodes,sig,name,compunit,false); }
+ public FunNode( FunNode fun ) {
+ super( fun, fun._loc );
+ _sig = fun.sig();
+ _name = fun._name;
+ _compunit = fun._compunit;
+ _extern = fun._extern;
+ }
+ @Override public Tag serialTag() { return Tag.Fun; }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes ) {
+ assert !_folding;
+ baos.packed1(Serialize.nIns(this,anodes)); // Object-local linked calls
+ baos.packed2(types.get(_sig)); // NPE if fails lookup
+ baos.packed2(_name==null ? 0 : strs.get(_name));
+ baos.packed2(_approxUIDs);
+ // _compunit set from context on loading, do not write
+ }
+ static Node make( BAOS bais, String[] strs, Type[] types) {
+ Node[] ins = new Node[bais.packed1()];
+ TypeFunPtr sig = (TypeFunPtr)types[bais.packed2()];
+ String name = strs[bais.packed2()];
+ FunNode fun = new FunNode(null,sig,name,null,ins);
+ fun._approxUIDs = bais.packed2();
+ // _compunit set from context on loading, do not read
+ return fun;
+ }
+
+ @Override public String label() { return _name == null ? "$fun"+_sig.fidx() : _name; }
+
+ // Find the one CFG user from Fun. It's not always the Return, but always
+ // the Return *is* a CFG user of Fun.
+ @Override public CFGNode uctrl() {
+ for( Node n : _outputs )
+ if( n instanceof CFGNode cfg &&
+ (cfg instanceof RegionNode || cfg.cfg0()==this) )
+ return cfg;
+ return null;
+ }
+
+ public ParmNode parm(int idx) {
+ ParmNode pidx = null;
+ for( Node n : _outputs )
+ if( n instanceof ParmNode parm && parm._idx==idx )
+ { assert pidx==null; pidx=parm; }
+ return pidx;
+ }
+ public ParmNode rpc() { return parm(0); }
+
+ // An unloaded body's Summary is constructed before every other user.
+ public SummaryNode summary() { return nOuts()>0 && out(0) instanceof SummaryNode summary ? summary : null; }
+
+ // This invocation compiles the defining CompUnit from source. Imported
+ // functions still belong to a CompUnit, but its _src is null.
+ public boolean isFromSource() { return _compunit!=null && _compunit._src!=null; }
+
+ // Cannot create the Return and Fun at the same time; one has to be first.
+ // So setting the return requires a second step.
+ public void setRet(ReturnNode ret) { _ret=ret; }
+ public ReturnNode ret() { assert _ret!=null; return _ret; }
+
+ // Signature can improve over time
+ public TypeFunPtr sig() { return _sig; }
+ public void setSig( TypeFunPtr sig ) {
+ assert sig.isa(_sig) || resolvesForwardSelf(sig,_sig);
+ if( _sig != sig ) {
+ CODE.add(this);
+ // Return uses the signature to bound recursive-result widening.
+ CODE.add(_ret);
+ // Changing the signature can allow more inlining
+ if( _ret != null )
+ for( Node use : ret().outs() )
+ if( use instanceof CallEndNode || use instanceof FunPtrNode )
+ CODE.add(use);
+ unlock();
+ _sig = sig;
+ }
+ }
+
+ // A class initializer starts parsing with an open singleton receiver and
+ // closes that same receiver after discovering its fields. Function
+ // arguments are contravariant, so ordinary isa does not describe this
+ // parser-time forward-reference resolution even though every other part
+ // of the signature sharpens normally.
+ private static boolean resolvesForwardSelf(TypeFunPtr sig, TypeFunPtr old) {
+ if( !(sig.arg(0) instanceof TypeMemPtr now) ||
+ !(old.arg(0) instanceof TypeMemPtr prior) ||
+ now._obj._name != prior._obj._name ||
+ !prior._obj._open || now._obj._open )
+ return false;
+ return sig.makeFrom(old.arg(0),0).isa(old);
+ }
+
+ @Override boolean _upgradeType( HashMap<String,Type> TYPES) {
+ TypeFunPtr sig = (TypeFunPtr)_sig.upgradeType(TYPES);
+ if( sig == _sig ) return false;
+ unlock();
+ _sig = sig;
+ return true;
+ }
+
+ public void setName( String name ) {
+ if( _name==null )
+ _name = _compunit==null || _compunit._cname==null || _compunit._cname.indexOf('.')==-1
+ ? name
+ : (_compunit._cname+"."+name).intern();
+ }
+
+ @Override
+ public Type compute() {
+ // Only dead if no callers after SCCP
+ if( unknownCallers() )
+ return Type.CONTROL;
+ Type t = Type.XCONTROL;
+ for( int i = 1; i < nIns(); i++) {
+ // Since there are no unknown callers, the Start input is only a
+ // fake hook and will be a Tuple with XControl, so ignore it. Need
+ // to be called from someplace other than Start.
+ if( !(in(i) instanceof StartNode) )
+ t = t.meet(in(i)._type);
+ }
+ return t;
+ }
+
+ @Override
+ public Node idealize() {
+
+ // Some linked path dies, except unknown caller hooks never kill Start.
+ Node progress = deadPath(unknownCallers());
+ if( progress!=null ) {
+ if( nIns()==2 && in(1) instanceof CallNode call )
+ CODE.add(call.cend()); // If no Start and one call, check for inline
+ return progress;
+ }
+
+ // Upgrade inferred or user-written return type to actual
+ if( _ret!=null && _ret._type instanceof TypeTuple tt &&
+ tt.ret() != _sig.ret() && tt.ret().isa(_sig.ret()) ) {
+ setSig(_sig.makeFrom(tt.ret()));
+ return this;
+ }
+
+ // Attempt to get rid of the unknown caller.
+ // - Must be past pessimistic Iter, which discovers and links calls
+ // through class-field loads and other initially imprecise values.
+ // - FIDX is not in the escape set
+ // - Not called anywhere else; post Opto called-not-escaped functions need to still be hooked to start
+ if( !isRoot() && in(1) instanceof StartCUNode start ) {
+ boolean preOpto = CodeGen.CODE._phase == null ||
+ CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal();
+ if( !preOpto && !start.escapedFIDX(_sig.fidx()) && nIns()<=2 && _ret!=null ) {
+ _compunit._stop.delDef(_compunit._stop._inputs.find(ret()));
+ return removeDeadPath(1);
+ } else {
+ addDep(start);
+ }
+ }
+
+
+ // If no default/unknown caller, use the normal RegionNode ideal rules
+ // to collapse
+ if( unknownCallers() ) return null;
+
+ // If folding (already inlined) down to a single input, become that input.
+ // Cannot collapse if not-inlining, and might not inline if call site
+ // calls multiple targets.
+ if( _folding && nIns()==2 && !hasPhi() ) {
+ CODE.add( CODE._stop ); // Stop will remove dead path
+ CODE.add( _ret ); // Return will compute to TOP control
+ return in(1); // Collapse if no Phis; 1-input Phis will collapse on their own
+ }
+
+ return null;
+ }
+
+ // Bypass Region idom, always assume depth == 1, one more than Start,
+ // unless folding
+ @Override public int idepth() { return _folding ? super.idepth() : 1; }
+
+ // Bypass Region idom, always assume idom is Start
+ @Override public CFGNode idom(Node dep) {
+ return _folding ? cfg(1) : null;
+ }
+
+ // Always in-progress until we run out of unknown callers
+ public boolean unknownCallers() { return nIns()>=2 && in(1) instanceof StartNode; }
+
+ // The starting initializer and public source classes are external entry
+ // points. Privacy stops at the first private class in the name's path.
+ public boolean isRoot() {
+ return isClz() && (CODE.entryClinitName().equals(_name) || (isFromSource() && isPublic()));
+ }
+
+ public boolean isModInit( ) {
+ // The one top-level <clinit> with no internal dots:
+ // "sys.<clinit>" is a module, but "sys.io.<clinit>" is not.
+ return isClz() && _name.indexOf('.')==_name.lastIndexOf('.');
+ }
+ public boolean isClz ( ) { return isClz (_name); }
+ public boolean isInit( ) { return isInit(_name); }
+ public boolean isInstance() { return isInstance(_name); }
+ public static boolean isClz (String name ) { return name!=null && name.endsWith(".<clinit>"); }
+ public static boolean isInit(String name ) { return name!=null && name.endsWith("init>"); }
+ public static boolean isInstance(String name ) { return name!=null && name.endsWith(".<init>"); }
+
+ // Allocation factories and instance initializers carry extra memory boilerplate.
+ public int inlineLimit() {
+ return isInstance() || (_name!=null && _name.endsWith(".<new>")) ? 200 : 100;
+ }
+
+ // Function is public (callable from Start directly).
+ public boolean isPublic( ) {
+ // Never true for anonymous functions
+ if( _name == null ) return false;
+ // Only public for <clinit> and <init>, all others
+ // are found via field loads. The <init> Start hook also supplies its
+ // conservative pre-Opto construction inputs; StopCU separately keeps
+ // its private-memory return from escaping as a public result.
+ if( !isInit() ) return false;
+ // Private class, has to be found via program text, not linker
+ if( _name.charAt(0)=='_' ) return false;
+
+ // No instances of "...clz1._clz2..." where the class name path
+ // includes a private class.
+ if( _name.indexOf("._") != -1 ) return false;
+
+ // Public class <cl/init>
+ return true;
+ }
+
+ @Override public boolean inProgress() {
+ return unknownCallers() && CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal();
+ }
+
+ // Build the function body set
+ public BitSet body() {
+ // Iter drains ordinary peeps between inlines; body discovery precedes
+ // this inline's folding, so function boundaries are stable.
+ assert !_folding;
+ // Forward control includes paths that never reach Return.
+ BitSet cfgs = new BitSet();
+ walkCFG(this,cfgs);
+ cfgs.set(ret()._nid);
+
+ // Top down (start to stop) all flavors. CFG limit to bitmap.
+ // If data use bottoms out in wrong CFG, returns false - but tries all outputs.
+ // If any output hits an in-CFG use (e.g. phi), then keep node.
+ BitSet body = new BitSet();
+ // The function owns its Return even when it has no executable exit.
+ body.set(ret()._nid);
+ BitSet visit = new BitSet();
+ int old;
+ do {
+ old = body.cardinality();
+ visit.clear();
+ walkDown(this, cfgs, body, visit);
+ } while( old != body.cardinality() );
+ return body;
+ }
+
+ private static void walkCFG(CFGNode n, BitSet cfgs) {
+ if( cfgs.get(n._nid) ) return;
+ cfgs.set(n._nid);
+ if( n instanceof ReturnNode ) return;
+ for( Node use : n._outputs ) {
+ if( !(use instanceof CFGNode cfg) || cfg instanceof FunNode ) continue;
+ if( cfg instanceof RegionNode ? cfg._inputs.find(n)>0 : cfg.in(0)==n )
+ walkCFG(cfg,cfgs);
+ }
+ }
+
+ private static boolean walkDown( Node n, BitSet cfgs, BitSet body, BitSet visit ) {
+ if( n==null ) return false;
+ if( visit.get(n._nid) ) return body.get(n._nid);
+ visit.set(n._nid);
+ // Visit self as CFG outside the function
+ if( n instanceof CFGNode && !cfgs.get(n._nid) )
+ return false;
+ // Pretend the NewNode is a CFG, so its projections stay with it in loops
+ if( n instanceof NewNode nnn ) cfgs.set(n._nid);
+ // Visit n.cfg() outside of function
+ if( n.in(0)!=null && !cfgs.get(n.in(0)._nid) )
+ return false;
+ // Phis inside the function must have their body flag set BEFORE
+ // recursion walks around a loop and finds them again.
+ if( n instanceof PhiNode phi && cfgs.get(phi.in(0)._nid) )
+ body.set(phi._nid); // Find data cycles
+ boolean in = n.in(0)!=null || n instanceof CFGNode;
+ for( Node use : n._outputs ) {
+ // Will hit a backedge, so 'n' MUST be in the function, and must be
+ // set before the cyclic walk finds 'n' again.
+ if( use instanceof PhiNode puse && puse.in(0) instanceof LoopNode loop && puse.in(2)==n )
+ body.set(n._nid); // Set before walk
+ in |= walkDown(use,cfgs,body,visit);
+ }
+ if( in ) body.set(n._nid);
+ return in;
+ }
+
+ // Clone function body. Give function a new FIDX.
+ FunNode copyBody() {
+ // Build the function body BitSet
+ BitSet body = body();
+ assert body.cardinality() < inlineLimit();
+ // Walk the body, cloning
+ IdentityHashMap<Node,Node> map = new IdentityHashMap<>();
+ BitSet visit = CodeGen.CODE.visit();
+ bodyCopy( visit, body, map, this );
+ bodyCopy( visit, body, map, _ret ); // May be disconnected from executable control.
+ visit.clear();
+ bodyEdge( visit, body, map, this );
+ bodyEdge( visit, body, map, _ret );
+ visit.clear();
+ assert map.size()==body.cardinality();
+
+ // New function/return cross-link each other
+ FunNode fun2 = (FunNode )map.get(this);
+ ReturnNode ret2 = (ReturnNode)map.get(_ret);
+ fun2._ret = ret2;
+ ret2._fun = fun2;
+ BitSet body2 = new BitSet();
+ for( Node n : map.values() )
+ body2.set(n._nid);
+
+ // Remove non-body callers. Body-local call edges were cloned along
+ // with their CallEnds and must stay linked so their cloned types remain
+ // monotonic during the post-Opto Iter.
+ for( int i=1; i<fun2.nIns(); i++ )
+ if( !(fun2.in(i) instanceof CallNode call) || !body2.get(call._nid) )
+ fun2.removeDeadPath(i--);
+
+ // Linking adds graph edges and worklist entries; identity-map order
+ // would make inlining depend on the host JVM's object hashes.
+ Node[] olds = map.keySet().toArray(new Node[0]);
+ Arrays.sort(olds, (x,y) -> Integer.compare(x._nid,y._nid));
+ for( Node old : olds )
+ if( old instanceof CallNode oldCall )
+ relinkClonedCall(oldCall,(CallNode)map.get(oldCall),map);
+
+ // Flip to a new FIDX to avoid confusion with the old one.
+ // This is a non-monotonic (sideways) type move, only applicable
+ // because fun2 is new. The fidx is also a purely local fiction,
+ // since this body will inline and the fidx goes dead.
+ fun2._sig = _sig.makeFrom(CodeGen.CODE._fidxs.nextInline());
+ return fun2;
+ }
+
+ private static void relinkClonedCall( CallNode oldCall, CallNode call, IdentityHashMap<Node,Node> map ) {
+ CallEndNode oldCend = oldCall.cend();
+ if( oldCend==null || oldCend._folding )
+ return;
+ for( int i=1; i<oldCend.nIns(); i++ ) {
+ ReturnNode oldRet = (ReturnNode)oldCend.in(i);
+ FunNode oldFun = oldRet.fun();
+ FunNode fun = (FunNode)map.get(oldFun);
+ if( fun==null ) fun = oldFun;
+ if( !call.linked(fun) )
+ call.link(fun);
+ }
+ CallEndNode cend = call.cend();
+ if( cend.nIns() > 1 )
+ cend._type = cend.compute();
+ }
+
+ // Clone small function
+ private void bodyCopy( BitSet visit, BitSet body, IdentityHashMap<Node,Node> map, Node n ) {
+ if( n==null ) return;
+ if( !body.get(n._nid) ) return; // Not part of the body
+ if( visit.get(n._nid) ) return; // Been there, done that
+ visit.set(n._nid);
+ Node m = n.copy();
+ CodeGen.CODE.add(m);
+ map.put(n,m);
+ m._type = n._type;
+ for( Node x : n._outputs )
+ bodyCopy(visit,body,map,x);
+ }
+ private void bodyEdge( BitSet visit, BitSet body, IdentityHashMap<Node,Node> map, Node n ) {
+ if( n==null ) return;
+ if( !body.get(n._nid) ) return; // Not part of the body
+ if( visit.get(n._nid) ) return; // Been there, done that
+ visit.set(n._nid);
+ Node m = map.get(n);
+ for( Node e : n._inputs ) {
+ Node x = e;
+ if( e != null ) {
+ if( n instanceof CallEndNode cend && e != cend.call() &&
+ (!(e instanceof ReturnNode ret) || map.get(ret.fun()) == null) )
+ continue;
+ x = map.get(e);
+ if( x == null ) {
+ if( n instanceof CallEndNode cend && e != cend.call() )
+ continue;
+ x = e;
+ }
+ }
+ m.addDef(x);
+ }
+ for( Node x : n._outputs )
+ bodyEdge(visit,body,map,x);
+ }
+
+
+ // no 2 FunNodes are ever GVN'able
+ @Override public boolean eq( Node n ) {
+ return this==n;
+ }
+
+ @Override public int hash() {
+ return _sig.hashCode();
+ }
+
+ // ------------
+ // MachNode specifics, shared across all CPUs
+
+ // Function may call other functions? X86 requires 16b aligned SP on
+ // outbound calls, but not leaf. Set during an unrelated RA scan.
+ public boolean _hasCalls;
+
+ // Maximum incoming argument slot, based on function type
+ public int _maxArgSlot;
+
+ // Frame adjust, including padding, set during early Encoding
+ public int _frameAdjust;
+
+ public String op() { return _frameAdjust==0 ? "entry" : "subi"; }
+ public void postSelect(CodeGen code) {
+ code.link(this);
+ // Max slot seen past space reserved for incoming arguments
+ _maxArgSlot = code._mach.maxArgSlot(sig());
+ }
+ public RegMask regmap(int i) { return null; }
+ public RegMask outregmap() { return null; }
+ public void encoding( Encoding enc ) { throw Utils.TODO(); }
+ public void asm(CodeGen code, SB sb) {
+ if( _frameAdjust!=0 )
+ sb.p("rsp -= #").p(_frameAdjust);
+ }
+
+ public void computeFrameAdjust(CodeGen code, int maxReg) {
+ // Max slot seen, or 0.
+ int maxSlot = Math.max(maxReg - code._mach.regs().length,_maxArgSlot);
+ // Frame adjust before rounding, can be negative for no spills
+ int size = maxSlot - _maxArgSlot;
+ _frameAdjust = size*8;
+ }
+
+ // Given a high register number and during encoding compute the stack
+ // pointer offset. Pre-RA we lack the fun._frameAdjust. We also need a
+ // FunNode.
+ public int computeStackOffset(CodeGen code, int reg) {
+ String[] regs = code._mach.regs();
+ int stackSlot = reg - regs.length;
+ int slotRotate = stackSlot < _maxArgSlot
+ ? stackSlot + (_frameAdjust>>3)
+ : stackSlot - _maxArgSlot;
+ return slotRotate*8;
+ }
+
+ @Override public void gather( HashMap<String,Integer> strs ) {
+ Serialize.gather(strs,_name);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java
new file mode 100644
index 00000000..82053fd8
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java
@@ -0,0 +1,68 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+// Constant function pointer whose type follows the callee signature.
+public class FunPtrNode extends TypeNode {
+ public FunPtrNode(TypeFunPtr tfp, StartNode start, ReturnNode ret) {
+ super(tfp,start,ret);
+ }
+ public FunPtrNode(FunPtrNode fptr) { super(fptr); }
+ @Override public Tag serialTag() { return Tag.FunPtr; }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) {
+ baos.packed2(types.get(_con));
+ }
+ static Node make(BAOS bais, Type[] types) {
+ return new FunPtrNode((TypeFunPtr)types[bais.packed2()],null,null);
+ }
+ public ReturnNode ret() { return (ReturnNode)in(1); }
+ public FunNode fun() { return ret().fun(); }
+
+ @Override public String label() { return "#"+_con; }
+ @Override public Node copy() {
+ FunPtrNode fptr = new FunPtrNode((TypeFunPtr)_con,null,null);
+ fptr._type = _type;
+ return fptr;
+ }
+
+ @Override
+ protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ FunNode fun = CodeGen.CODE._link(((TypeFunPtr)_con).fidx());
+ return fun!=null && fun._name!=null
+ ? p.p("{ ").p(fun._name).p("}")
+ : p.p(_con);
+ }
+
+ @Override public boolean isConst() { return true; }
+ @Override public Type compute() {
+ ReturnNode ret = ret();
+ if( ret == null ) return _con;
+ if( !(ret._type instanceof TypeTuple tt) ) return ret._type.oob();
+ TypeFunPtr tfp = ret.fun().sig();
+ // Both the sig and the return value must be true, so JOIN both
+ Type tret = tfp._ret.join(tt.ret());
+ return tfp.makeFrom(tret);
+ }
+ @Override public Node idealize() {
+ ReturnNode ret = ret();
+ if( ret==null ) return null;
+ // Function died (never executed), but fcn ptr is alive.
+ // Can be checked for null, or for unequals another FunPtr.
+ FunNode fun = addDep(ret.fun());
+ TypeFunPtr sig = fun.sig();
+ if( sig != _con )
+ { liftType(sig); return this; }
+ // TODO: If the function body dies but this value remains live only as
+ // a null/equality sentinel, preserve the identity without the Return.
+ if( fun.isDead() )
+ { setDef(1,null); liftType(sig.makeFrom(Type.TOP)); return this; }
+ return null;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/GlobalEscapeNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/GlobalEscapeNode.java
new file mode 100644
index 00000000..dab48530
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/GlobalEscapeNode.java
@@ -0,0 +1,40 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+
+// Escapes from calls and unloaded bodies, shared by all compilation units.
+// These include paths which never Return. Iter creates this after parsing;
+// the outer Start holds it until Unlink. Saved objects contain the resulting
+// StopCU types instead. Dependencies wake this node when contributors change;
+// Stops need waking only when the combined result changes.
+public class GlobalEscapeNode extends Node {
+ public GlobalEscapeNode() { super(new Node[0]); _type = TypeMem.BOT; }
+ @Override public String label() { return "GlobalEscape"; }
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p(label()); }
+ @Override public Node idealize() { return null; }
+
+ @Override public TypeMem compute() {
+ TypeMem xmem = StopCUNode.priorEscapes(_type);
+ for( CallNode call : CodeGen.CODE._calls )
+ if( !call.isDead() ) {
+ xmem = StopCUNode.publicBottom(xmem.escapes(call.exposed(this)),xmem);
+ // FULL memory normalizes to public bits. Preserve precise
+ // private arguments independently of that normalization.
+ if( call._type==Type.CONTROL && call.exposesArguments(this) )
+ for( int i = 0; i<call.nargs(); i++ )
+ xmem = StopCUNode.publicBottom(xmem.escapes(addDepForwards(call.arg(i+2))._type),xmem);
+ }
+ for( FunNode fun : CodeGen.CODE._linker )
+ if( fun!=null && fun.summary()!=null && !fun.summary().isDead() ) {
+ xmem = StopCUNode.publicBottom(xmem.escapes(fun.summary().exposed(this)),xmem);
+ // A reachable unloaded body can call these targets, including
+ // private initializers. Its arguments are unavailable here.
+ if( fun._type==Type.CONTROL )
+ for( int fidx : fun.summary()._classDependencyFIDXs )
+ if( fidx!=0 ) xmem = xmem.makeFrom(XInt.meet(xmem._escFs,XInt.make(fidx)),xmem._escAs);
+ }
+ return xmem;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/GuardMach.java b/chapter29/src/main/java/com/seaofnodes/simple/node/GuardMach.java
new file mode 100644
index 00000000..202b1ab8
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/GuardMach.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.SB;
+
+public class GuardMach extends GuardNode implements MachNode {
+ public GuardMach(GuardNode guard) { super(guard); }
+ @Override public String op() { return label(); }
+ @Override public RegMask regmap(int i) { assert i==1; return RegMask.FULL; }
+ @Override public RegMask outregmap() { return RegMask.FULL; }
+ @Override public int twoAddress() { return in(1)._type==Type.BOTTOM ? 0 : 1; }
+ @Override public void encoding(Encoding enc) { }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(in(1))); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/GuardNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/GuardNode.java
new file mode 100644
index 00000000..0ce504d9
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/GuardNode.java
@@ -0,0 +1,68 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeScalar;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+// A branch-proven refinement to zero/null or non-zero/non-null. The value
+// family is deliberately taken from the input when it becomes known.
+public class GuardNode extends Node {
+ public final boolean _nonZero;
+
+ public GuardNode(boolean nonZero, Node ctrl, Node val) {
+ super(ctrl,val);
+ _nonZero = nonZero;
+ }
+
+ public GuardNode(GuardNode guard) {
+ super(guard);
+ _nonZero = guard._nonZero;
+ }
+
+ @Override public Tag serialTag() { return Tag.Guard; }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) {
+ baos.write(_nonZero ? 1 : 0);
+ }
+ static Node make(BAOS bais) { return new GuardNode(bais.read()!=0,null,null); }
+
+ @Override public String label() { return _nonZero ? "(!=0)" : "(==0)"; }
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ return p.p(label()).n(in(1));
+ }
+
+ @Override public Type compute() {
+ // Guard is a pinned, path-local fact. If its control path is not
+ // executable, the fact is not executable either, regardless of how the
+ // value input sharpens later.
+ Type c = in(0)._type;
+ if( c != Type.CONTROL && c != Type.BOTTOM )
+ return Type.TOP;
+ Type t = in(1)._type;
+ if( t==Type.BOTTOM || t==Type.TOP ) return t;
+ // The scalar envelope does not identify a value family. Preserve it
+ // until SCCP learns whether this is an int, float, pointer, or fptr;
+ // choosing generic ptr here can be weaker than a later concrete type.
+ if( t==TypeScalar.BOT || t==TypeScalar.TOP ) return t;
+ Type t2 = _nonZero ? t.nonZero() : t.makeZero();
+ return t2==null ? (_type==null ? Type.TOP : _type) : t2; // No sane answer for e.g. not-zero of a zero
+ }
+
+ @Override public Node idealize() {
+ if( in(0)._type != Type.CONTROL && in(0)._type != Type.BOTTOM )
+ return null;
+ if( in(1)._type==Type.BOTTOM || in(1)._type==Type.TOP ) return null;
+ if( _type.isHighOrConst() && in(1)._type.isa(_type) )
+ return in(1);
+ return null;
+ }
+
+ @Override public boolean eq(Node n) { return _nonZero==((GuardNode)n)._nonZero; }
+ @Override int hash() { return _nonZero ? 1 : 0; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/IfNode.java
new file mode 100644
index 00000000..c83c81d0
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/IfNode.java
@@ -0,0 +1,100 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Utils;
+
+public class IfNode extends CFGNode implements MultiNode {
+
+ public IfNode(Node ctrl, Node pred) {
+ super(ctrl, pred);
+ CodeGen.CODE.add(this); // Because idoms are complex, just add it
+ }
+ public IfNode(IfNode iff) { super(iff); }
+ @Override public Tag serialTag() { return Tag.If; }
+
+ @Override
+ protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ p.p("if( ");
+ if( in(1)==null ) p.p("never");
+ else p.n(in(1));
+ return p.p(" )");
+ }
+
+ public Node ctrl() { return in(0); }
+ public Node pred() { return in(1); }
+
+ // No one unique control follows
+ @Override public CFGNode uctrl() { return null; }
+
+ @Override
+ public Type compute() {
+ // If the If node is not reachable then neither is any following Proj
+ assert !_inputs.isEmpty();
+ if (ctrl()._type != Type.CONTROL && ctrl()._type != Type.BOTTOM )
+ return TypeTuple.IF_NEITHER;
+ Node pred = pred();
+ Type t = pred._type;
+ // High types mean NEITHER side is reachable.
+ // Wait until the type falls to decide which way to go.
+ if( t.isHigh() )
+ return TypeTuple.IF_NEITHER;
+ // If constant is 0 then false branch is reachable
+ // Else true branch is reachable
+ if( t.isConstant() )
+ return (t==Type.NIL || t==TypeInteger.ZERO || (t instanceof TypeFunPtr tfp && tfp._fidxs== XInt.EMPTY) ) ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE;
+ // If adding a zero makes a difference, the predicate must not have a zero/null
+ if( !t.makeZero().isa(t) )
+ return TypeTuple.IF_TRUE;
+
+ return TypeTuple.IF_BOTH;
+ }
+
+ @Override
+ public Node idealize() {
+ // Hunt up the immediate dominator tree. If we find an identical if
+ // test on either the true or false branch, that side wins.
+ if( !pred()._type.isHighOrConst() )
+ for( CFGNode dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() )
+ if( addDep(dom) instanceof IfNode iff &&
+ prior instanceof CProjNode prj &&
+ (addDep(iff.pred())==pred() ||
+ pred() instanceof GuardNode cast && cast._nonZero && iff.pred()==cast.in(1) ) ) {
+ setDef(1,con( prj._idx==0 ? 1 : 0 ));
+ return this;
+ }
+ return null;
+ }
+
+ // MachNode variants need to support this and negate the conditional test.
+ // The following CProjs will be inverted by the caller.
+ public void negate() { throw Utils.TODO(); }
+
+ // Negate the sense of a test
+ public static String negate( String bop ) {
+ return switch( bop ) {
+ case "<" -> ">=";
+ case "<=" -> ">" ;
+ case "==" -> "!=";
+ case "!=" -> "==";
+ case ">" -> "<=";
+ case ">=" -> "<" ;
+ default -> throw Utils.TODO();
+ };
+ }
+
+ // Swap compare operands
+ public static String swap( String bop ) {
+ return switch( bop ) {
+ case "<" -> ">" ;
+ case "<=" -> ">=";
+ case "==" -> "==";
+ case "!=" -> "!=";
+ case ">" -> "<" ;
+ case ">=" -> "<=";
+ default -> throw Utils.TODO();
+ };
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/LoadNode.java
new file mode 100644
index 00000000..9aa8b311
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -0,0 +1,338 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Lexer;
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.GlobalBits;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
+import java.util.BitSet;
+
+/**
+ * Load represents extracting a value from inside a memory object,
+ * in chapter 10 this means Struct fields.
+ */
+public class LoadNode extends MemOpNode {
+
+ /**
+ * Load a value from a ptr.field.
+ *
+ * @param name The field we are loading
+ * @param mem The memory alias node - this is updated after a Store
+ * @param ptr The ptr to the struct base from where we load a field
+ * @param off The offset inside the struct base
+ */
+ public LoadNode(Lexer loc, String name, int alias, Node ctrl, Node mem, Node ptr, Node off) {
+ super(loc, name, alias, true, Type.BOTTOM, ctrl, mem, ptr, off);
+ }
+ LoadNode( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases ) {
+ super(bais,strs,types,fileAliases,aliases,true);
+ _con = Type.BOTTOM; // Serialized slot retained while MemOps share TypeNode
+ }
+ @Override public Tag serialTag() { return Tag.Load; }
+
+ @Override boolean canDrop(MemOpNode other, Node dep) {
+ return super.canDrop(other,dep) && !clobbered(dep);
+ }
+
+ // Check only immediate memory users. Writers clobber memory; Phis and
+ // aggregates might lead to a clobber, so stop rather than search further.
+ private boolean clobbered(Node dep) {
+ Node mem = mem();
+ dep.addDepForwards(mem);
+ for( Node use : mem._outputs ) {
+ if( use==null ) continue;
+ dep.addDepForwards(use);
+ if( use instanceof StoreNode || use instanceof PhiNode ||
+ use instanceof MemMergeNode ||
+ use instanceof NewNode || use instanceof EscapeNode || use instanceof CallNode ||
+ use instanceof CLInitNode ) return true;
+ }
+ return false;
+ }
+
+ // Debugger label
+ @Override public String label() { return "ld_"+mlabel(); }
+ @Override
+ protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p(".").p(_name); }
+
+ @Override
+ public Type compute() {
+ Type mem0 = mem()._type;
+ Type ptr0 = ptr()._type;
+ // Validate argument types
+ if( ptr0.isHigh() )
+ return TypeScalar.TOP;
+ if( !(mem0 instanceof TypeMem mem) )
+ return mem0.isHigh() ? TypeScalar.TOP : TypeScalar.BOT;
+ if( mem._t == Type.TOP )
+ return TypeScalar.TOP;
+ if( ptr0 == Type.NIL )
+ return Type.BOTTOM; // Load from nil is error
+ if( ptr0 == Type.BOTTOM )
+ return Type.BOTTOM; // Error in is error out
+ if( !(ptr0 instanceof TypeMemPtr tmp) )
+ return scalar(mem._t);
+ if( tmp.nullable() )
+ return Type.BOTTOM; // Load from nil-able is error
+
+ // Load field from object
+ Field pfld = tmp._obj.field(_name);
+ // No field? Open objects might yet get the field when falling;
+ // closed objects with missing field are an error.
+ if( pfld == null )
+ return tmp.isHigh() || tmp._obj.isHigh() ? TypeScalar.TOP : TypeScalar.BOT;
+
+ Type t = pfld._t;
+ boolean readonly = t instanceof TypeMemPtr view && view._ro;
+ // Load member of constant array
+ if( t instanceof TypeConAry ary )
+ t = ary.elem(); // TODO: if offset is known, can peek the constant
+ if( mem._alias==_alias )
+ t = t.join(mem._t);
+
+ // A deeply read-only base produces a deeply read-only value. The
+ // generic declared field type must not cast this information away.
+ if( ptr0.isFinal() || readonly )
+ t = t.makeRO();
+ return scalar(t);
+ }
+
+ // Loads produce ordinary scalar values. Field/memory joins can still
+ // expose the enclosing global lattice bounds through an uninitialized
+ // Type.TOP/BOTTOM field; keep those results inside the scalar envelope.
+ private static Type scalar(Type t) {
+ return t==Type.TOP ? TypeScalar.TOP : t==Type.BOTTOM ? TypeScalar.BOT : t;
+ }
+
+ @Override
+ public Node idealize() {
+ Node mem = mem();
+ Node ptr = ptr();
+
+ // Null checks are handled by types. Array elements retain control for
+ // range checks, but reading an array's length has no index to check.
+ if( in(0)!=null && ptr._type instanceof TypeMemPtr tmp &&
+ (!tmp._obj.isAry() || _name.equals("#")) ) {
+ setDef(0,null);
+ return this;
+ }
+
+ // Forward-ref loads eventually sharpen to a declared type
+ Field fld;
+ if( ptr._type instanceof TypeMemPtr tmp &&
+ (fld=tmp._obj.field(_name)) != null &&
+ _alias != fld._alias) {
+ assert _alias==1 || _alias == fld._alias;
+ assert !tmp._obj._open && !tmp._obj._fref;
+ unlock(); // Alias participates in GVN semantics
+ _alias = fld._alias;
+ return this;
+ }
+ // Must sharpen alias first
+ if( _alias == 1 )
+ return null;
+
+ Node slice = CLInitNode.slice(mem,_alias,this);
+ if( slice!=mem ) { setDef(1,slice); return this; }
+
+ if( mem instanceof MemMergeNode merge ) {
+ setDef(1,merge.alias(_alias));
+ // In support of uplifting control to a prior dominating load, if
+ // we move our memory, recheck other loads to see if they can hoist.
+ for( Node use : in(1).outs() )
+ if( use instanceof LoadNode ld && use != this && ptr == ld.ptr() )
+ CodeGen.CODE.add(ld);
+
+ return this;
+ }
+
+ // Uplift control to a prior dominating load.
+ for( Node memuse : mem._outputs )
+ // Find a prior load, has same mem,ptr,off but higher ctrl
+ if( memuse != this && memuse instanceof LoadNode ld && ptr==ld.ptr() && off()==ld.off() &&
+ cfg0()!=null && cfg0().domLCA(ld.cfg0(),this) == ld.cfg0() && // Higher control means load is legal earlier
+ addDep(ld)._type.isa(_type) ) // and not rolling backwards
+ return ld;
+
+ Node win = find(mem,ptr,null,null);
+ if( win!=null ) return folded(win);
+ if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1);
+
+ // Push a Load up through a Phi, as long as it collapses on at least
+ // one arm. If at a Loop, the backedge MUST collapse - else we risk
+ // spinning the same transform around the loop indefinitely.
+ // BEFORE (2 Sts, 1 Ld): AFTER (1 St, 0 Ld):
+ // if( pred ) ptr.x = e0; val = pred ? e0
+ // else ptr.x = e1; : e1;
+ // val = ptr.x; ptr.x = val;
+ if( mem() instanceof MemPhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 &&
+ // Retain control on both copies only if it dominates both paths.
+ // The Region itself, or a later range check, cannot move to its inputs.
+ (in(0)==null || cfg0().domLCA(memphi.region().idom(this),this)==cfg0()) &&
+ // Offset can be hoisted
+ off() instanceof ConstantNode &&
+ // Pointer can be hoisted
+ hoistPtr(ptr,memphi) ) {
+
+ // Returning to this memory means no change only if the pointer
+ // is also loop invariant. A pointer Phi may select another object.
+ Node stop = memphi.region() instanceof LoopNode &&
+ !(ptr instanceof PhiNode p && p.region()==memphi.region()) ? memphi : null;
+ if( find(memphi.in(2),ptr,stop,new BitSet())!=null ||
+ (!(memphi.region() instanceof LoopNode) && find(memphi.in(1),ptr,null,new BitSet())!=null) ) {
+ // Integer loop Phis widen to i64. Keep the load's declared
+ // width at the result, as for ordinary typed loop variables.
+ if( _type instanceof TypeInteger && !declaredType().isa(_type) ) return null;
+ PhiNode phi = new PhiNode(_name,memphi.region()).keep();
+ phi.setType(_type instanceof TypeInteger ? TypeInteger.BOT : _type);
+ for( int i=1; i<memphi.nIns(); i++ ) {
+ Node p = ptr instanceof PhiNode pp && pp.region()==memphi.region() ? pp.in(i) : ptr;
+ phi.addDef(load(memphi.in(i),p,stop,phi));
+ }
+ CodeGen.CODE.add(phi.unkeep());
+ return _type instanceof TypeInteger ? new ConvertNode(declaredType(),phi) : phi;
+ }
+ }
+
+ return null;
+ }
+
+ // Load a flavored zero from a New
+ private Node zero(NewNode nnn) {
+ Type zero = declaredType().makeZero();
+ assert zero.isa(_type); // Catch an uninitialized non-null field
+ return castRO(new ConstantNode(zero).peephole());
+ }
+
+ // Search only; no new nodes or rewiring. Return a Store/New that folds,
+ // the unchanged loop memory, or a merge whose every arm folds. Null fails.
+ // Register dependencies so a later pointer/offset rewrite retries the query.
+ private Node find(Node mem, Node ptr, Node stop, BitSet visit) {
+ if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1);
+ Field fld;
+ boolean external = ptr._type instanceof TypeMemPtr tmp &&
+ (fld=tmp._obj.field(_name))!=null && fld._extern;
+ while( mem!=null ) {
+ addDep(mem);
+ if( mem==stop ) return mem;
+ switch( mem ) {
+ case MemMergeNode merge: mem=merge.alias(_alias); break;
+ case StoreNode st:
+ // Alias 1 is still unresolved, not a proven different field.
+ if( st._alias==1 ) return null;
+ addDep(st.ptr());
+ if( _alias==st._alias && (ptr==st.ptr() || ptr==st.nnptr()) && off()==st.off() ) {
+ // A full-width load cannot narrow the forwarded value.
+ if( declaredType() instanceof TypeInteger ti && ti.log_size()==3 &&
+ !addDep(st.val())._type.isa(_type) ) return null;
+ return st;
+ }
+ if( (external || _alias==st._alias) && (external || !neverAlias(ptr,st.ptr())) &&
+ !addDep(off())._type.join(addDep(st.off())._type).isHigh() ) return null;
+ mem=st.mem();
+ break;
+ case ProjNode proj:
+ // New's memory is private; public memory is joined at Escape.
+ if( external || !(proj.in(0) instanceof NewNode nnn) ) return null;
+ if( declaredType()==Type.BOTTOM ) return null;
+ return nnn;
+ case EscapeNode esc:
+ if( external ) return null;
+ addDep(esc.self());
+ if( esc.self()==ptr ) { mem=esc.priv(); break; }
+ if( neverAlias(ptr,esc.self()) ) { mem=esc.pub(); break; }
+ return null;
+ case MemPhiNode phi:
+ addDep(phi.region());
+ // Only the original loop closes a successful cycle. Another
+ // loop or an unfinished merge needs a separate proof.
+ if( visit==null || !(phi.region() instanceof RegionNode r) || r instanceof LoopNode ||
+ r.inProgress() || phi.inProgress() || r._type!=Type.CONTROL ) return null;
+ if( visit.get(phi._nid) ) return phi; // Already proved every arm.
+ for( int i=1; i<phi.nIns(); i++ )
+ if( find(phi.in(i),ptr,stop,visit)==null ) return null;
+ visit.set(phi._nid);
+ return phi;
+ default: return null;
+ }
+ }
+ return null;
+ }
+
+ private Node folded(Node win) {
+ return win instanceof StoreNode st ? extend(castRO(st.val())) : zero((NewNode)win);
+ }
+
+ // Materialize only after the profitability search succeeds. The value Phi
+ // stands for a load which comes back around the loop with unchanged memory.
+ private Node load(Node mem, Node ptr, Node stop, PhiNode value) {
+ Node win=find(mem,ptr,stop,new BitSet());
+ if( win!=null ) {
+ if( win==stop ) return value;
+ if( win instanceof MemPhiNode mp ) {
+ PhiNode phi=new PhiNode(_name,mp.region()).keep();
+ for( int i=1; i<mp.nIns(); i++ ) phi.addDef(load(mp.in(i),ptr,stop,value));
+ return CodeGen.CODE.add(phi.unkeep().peephole());
+ }
+ Node fold=folded(win);
+ if( fold!=null ) return fold.peephole();
+ }
+ LoadNode ld=new LoadNode(_loc,_name,_alias,in(0),mem,ptr,off());
+ return ld.peephole();
+ }
+
+ private static boolean neverAlias( Node ptr1, Node ptr2 ) {
+ return ptr1.in(0) != ptr2.in(0) &&
+ // Unrelated allocations
+ ptr1 instanceof ProjNode && ptr1.in(0) instanceof NewNode &&
+ ptr2 instanceof ProjNode && ptr2.in(0) instanceof NewNode;
+ }
+
+ private static boolean hoistPtr(Node ptr, PhiNode memphi ) {
+ // Can I hoist ptr above the Region?
+ if( !(memphi.region() instanceof RegionNode r) )
+ return false; // Dead or dying Region/Phi
+ // If ptr from same Region, then yes, just use hoisted split pointers
+ if( ptr instanceof PhiNode pphi && pphi.region() == r )
+ return true;
+
+ // No, so can we lift this ptr?
+ CFGNode cptr = ptr.cfg0();
+ if( cptr != null )
+ // Pointer is controlled high
+ // TODO: Really needs to be the LCA of all inputs is high
+ return cptr.idepth() <= r.idepth();
+
+ // Dunno without a longer walk
+ return false;
+ }
+
+ // Read-Only is a deep property, and cannot be cast-away
+ private Node castRO(Node rez) {
+ if( (ptr()._type.isFinal() || declaredType().isFinal()) && !rez._type.isFinal() )
+ return new ReadOnlyNode(rez).peephole();
+ return rez;
+ }
+
+ // When a load bypasses a store, the store might truncate bits - and the
+ // load will need to zero/sign-extend.
+ private Node extend(Node val) {
+ if( !(declaredType() instanceof TypeInteger ti) ) return val;
+ if( ti._min==0 ) // Unsigned
+ return new AndNode(null,val,con(ti._max));
+ // Signed extension
+ int shift = Long.numberOfLeadingZeros(ti._max)-1;
+ Node shf = con(shift);
+ if( shift==0 ) {
+ if( !val._type.isa(_type) )
+ { addDep(val); return null; }
+ return val;
+ }
+ Node shl = new ShlNode(null,val,shf.keep()).peephole();
+ return new SarNode(null,shl,shf.unkeep());
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/LoopNode.java
new file mode 100644
index 00000000..c4d55335
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/LoopNode.java
@@ -0,0 +1,112 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Lexer;
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+public class LoopNode extends RegionNode {
+ public LoopNode( Lexer loc, Node xctrl, Node entry ) { super(loc,xctrl,entry,null); }
+ public LoopNode( LoopNode loop ) { super(loop); }
+ @Override public Tag serialTag() { return Tag.Loop; }
+ public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { }
+
+ public CFGNode entry() { return cfg(1); }
+ public CFGNode back () { return cfg(2); }
+
+ @Override
+ public Type compute() {
+ if( inProgress() ) return Type.CONTROL;
+ return entry()._type;
+ }
+
+ // Bypass Region idom, same as the default idom() using use in(1) instead of in(0)
+ @Override public int idepth() { return validIDepth() ? _idepth : cacheIDepth(entry().idepth()+1); }
+ // Bypass Region idom, same as the default idom() using use in(1) instead of in(0)
+ @Override public CFGNode idom(Node dep) { return entry(); }
+
+ // If this is an unreachable loop, it may not have an exit. If it does not
+ // (i.e., infinite loop), force an exit to make it reachable.
+ public FunNode forceExit( FunNode fun, StopNode stop ) {
+ // Walk the backedge, then immediate dominator tree util we hit this
+ // Loop again. If we ever hit a CProj from an If (as opposed to
+ // directly on the If) we found our exit.
+ CFGNode x = back();
+ while( x != this ) {
+ if( x instanceof CProjNode exit && exit.in(0) instanceof IfNode iff ) {
+ CFGNode other = iff.cproj(1-exit._idx);
+ if( other!=null && other._ltree != _ltree && nested(_ltree,other._ltree) )
+ return fun; // Found an exit, not an infinite loop
+ }
+ x = x.idom();
+ }
+ // Found a no-exit loop. Insert an exit
+ NeverNode iff = new NeverNode(1.2f,back()).init(); // Ideal never-branch
+ CProjNode t = new CProjNode(iff,0,"True" ).init();
+ CProjNode f = new CProjNode(iff,1,"False").init();
+ setDef(2,t); // True continues loop, False (never) exits loop
+ ReturnNode ret = fun.ret();
+ iff._ltree = t._ltree = _ltree;
+ ret._ltree = f._ltree = fun._ltree;
+
+ // Now fold control into the exit. Might have 1 valid exit, or an
+ // XCtrl or a bunch of prior NeverNode exits.
+ // Scheduling can prepare a newly loaded object while other code is
+ // executing. Keep synthetic nodes local; do not GVN into that graph.
+ Node top = ConstantNode.raw(Type.TOP);
+ top.setDef(0,fun);
+ Node memout = new MemMergeNode();
+ memout.addDef(f); // placeholder for control
+ memout.addDef(top);
+ for( Node u : _outputs )
+ if( u instanceof PhiNode phi && phi._type.isa(TypeMem.BOT) )
+ memout.addDef(phi);
+ memout.init();
+
+ Node ctrl = ret.ctrl(), mem = ret.mem(), expr = ret.expr();
+ if( ctrl!=null && ctrl._type != Type.XCONTROL ) {
+ // Perfect aligned exit?
+ if( !(ctrl instanceof RegionNode r &&
+ mem instanceof PhiNode pmem && pmem.region()==r &&
+ expr instanceof PhiNode prez && prez.region()==r ) ) {
+ // Nope, insert an aligned exit layer
+ RegionNode r = new RegionNode(_loc,null,ctrl).init();
+ ctrl = r; r._ltree = fun._ltree;
+ mem = new BulkMemPhiNode(r,mem).init();
+ expr = new PhiNode(r,expr).init();
+ }
+ // Append new Never exit
+ ctrl.keep().addDef(f );
+ mem .keep().addDef(memout);
+ expr.keep().addDef(top);
+ } else {
+ ctrl = f .keep();
+ mem = memout.keep();
+ expr = top .keep();
+ }
+ ret.setDef(0,ctrl.unkeep());
+ ret.setDef(1,mem .unkeep());
+ ret.setDef(2,expr.unkeep());
+ // Force sane types to exit; might locally break monotonicity
+ ret.init();
+ ret.out(0).init();
+ stop.init();
+ CodeGen.CODE.add(CodeGen.CODE._start);
+ CodeGen.CODE.add(mem);
+
+ return fun;
+ }
+
+ private static boolean nested(LoopTree inner, LoopTree outer) {
+ for( ; inner!=null; inner = inner._par )
+ if( inner == outer )
+ return true;
+ return false;
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java
new file mode 100644
index 00000000..b25fcaaf
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java
@@ -0,0 +1,26 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.type.Type;
+import java.io.ByteArrayOutputStream;
+
+// Generic machine-specific class, has a few Node implementations that have to
+// exist (abstract) but are not useful past the optimizer.
+public abstract class MachConcreteNode extends Node implements MachNode {
+
+ public MachConcreteNode(Node node) { super(node); }
+ public MachConcreteNode(Node[]nodes) { super(nodes); }
+
+ @Override public String label() { return op(); }
+ @Override public Type compute () { throw new AssertionError("Do not call compute on "+getClass().getSimpleName()); }
+ @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); }
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ p.open().p(op()).p(",");
+ for( int i=1; i<nIns(); i++ )
+ (in(i)==null ? p.p(" ---") : p.n(in(i))).p(",");
+ return p.unchar(',').close();
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/MachNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/MachNode.java
new file mode 100644
index 00000000..025b5dc4
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/MachNode.java
@@ -0,0 +1,60 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public interface MachNode {
+
+ // Easy access to the abstract Machine
+ default Machine machine() { return CodeGen.CODE._mach; }
+
+ // Run a post-instruction-selection action
+ default void postSelect( CodeGen code ) { }
+
+ // Register mask allowed on input i. 0 for no register.
+ RegMask regmap( int i );
+ // Register mask allowed as a result. 0 for no register.
+ RegMask outregmap();
+ // Multi-reg-defining machine op; idx comes from Proj.
+ // Sometimes these Projs have no uses, and just exist to kill a register.
+ default RegMask outregmap(int idx) { throw Utils.TODO(); }
+
+ // Set of killed registers
+ default RegMask killmap() { return null; }
+
+ // If this op updates the same register as input 'idx', e.g. many X86 "R1 +=
+ // R2" style ops. This forces the output live range to equal the input live
+ // range, so they both get the same register.
+
+ // This can hold true for projections, if the instruction updates many
+ // registers that it also takes in. Note that ops also use the same
+ // registers for both inputs and outputs but these are pinned and without any
+ // choices (e.g. always RCX for both an input and output) and are better
+ // served with just declaring as a fixed register.
+
+ // Returns either 0 for not-two-adr or the updated input; for projections
+ // this is their Multi's updated input.
+ default int twoAddress( ) { return 0; }
+ // Ok to switch arguments
+ default boolean commutes() { return false; }
+
+ // Instructions cheaper to recreate than to spill, such as loading small constants
+ default boolean isClone() { return false; }
+
+ // Make a clone of a cheap instruction
+ default Node copy() { return null; }
+
+ // Encoding is appended, relocations gathered.
+ void encoding( Encoding enc );
+
+ // Human-readable form appended to the SB. Things like the encoding,
+ // indentation, leading address or block labels not printed here.
+ // Just something like "R17=[R18+12] // Load array base".
+ // General form: "dst=src+src".
+ default void asm(CodeGen code, SB sb) { throw Utils.TODO(); }
+
+ // Human-readable opcode; e.g. "ld4" or "call".
+ default String op() { throw Utils.TODO(); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
new file mode 100644
index 00000000..aa3f6d57
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
@@ -0,0 +1,158 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Var;
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Utils;
+
+import java.util.*;
+
+/**
+ * Memory Merge - a merge of many aliases into a "fat memory". All aliases
+ * are here, but most will be lazy - take the default fat memory.
+ */
+public class MemMergeNode extends Node {
+
+ public MemMergeNode( Node ...nodes) { super(nodes); _type = TypeMem.BOT; }
+ public MemMergeNode(MemMergeNode mem) { super(mem); }
+ @Override public Tag serialTag() { return Tag.MemMerge; }
+ public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); }
+ static Node make( BAOS bais ) { Node mem = new MemMergeNode(); mem.setDefX(bais.packed1()-1,null); return mem; }
+
+ @Override public String label() { return "ALLMEM"; }
+ @Override public boolean isMem() { return true; }
+
+ @Override
+ protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ p.p("MEM[ ");
+ for( int j=2; j<nIns(); j++ ) {
+ p.p(j);
+ p.p(":");
+ Node n = in(j);
+ while( n instanceof ScopeNode loop ) {
+ p.p("Lazy_");
+ n = loop.mem();
+ }
+ if( n==null ) p.p("___ ");
+ else p.n(n).p(" ");
+ }
+ return p.unchar(' ').p("]");
+ }
+
+
+ @Override public Type compute() {
+ Type tmem = in(1)._type;
+ if( !(tmem instanceof TypeMem defmem) )
+ // A MemMerge is structurally memory even while its default input
+ // is a weak, not-yet-specialized Phi.
+ return tmem.isHigh() ? TypeMem.TOP : TypeMem.BOT;
+ // Is this a single private instance memory?
+ if( defmem._one ) {
+ if( !(defmem._t instanceof TypeStruct ts) )
+ return defmem;
+ // Perfect singleton memory, so all updates are parallel and
+ // independent and stack.
+ for( int i=2; i<nIns(); i++ ) {
+ Node in = in(i);
+ if( in !=null && in._type.isHigh() )
+ return TypeMem.TOP;
+ if( in instanceof StoreNode st ) {
+ Type val = ((TypeMem)st._type)._t;
+ Field old = ts.field(st._name);
+ // Lazy add an expected field for open structs.
+ if( old==null ) { assert ts._open;
+ old = Field.make(st._name,Type.BOTTOM,st._alias,st._init);
+ ts = ts.add(old);
+ }
+ Field fld = old.makeFrom(val);
+ // TODO: val leaks into perfect singleton memory, along with its fidxs and aliases
+ ts = ts.replace(fld);
+ }
+ }
+ return TypeMem.makePrivate(ts);
+
+ }
+
+ // Normal public memory; meet across escaped inputs
+ TypeMem mem = defmem;
+ for( int i=2; i<nIns(); i++ ) {
+ // Has this alias escaped?
+ if( XInt.bit(mem._escAs,i) &&
+ in(i) != null && !in(i)._type.isHigh() ) {
+ mem = (TypeMem)mem.meet(in(i)._type);
+ }
+ }
+ return mem;
+ }
+
+ @Override public Node idealize() {
+ assert nIns() != 0 && in(1)!=null; // Always have a default. if( nIns()==0 ) return null;
+
+ // Fold defaults into the default
+ boolean progress=false, allDefault=true;
+ for( int i=2; i<nIns(); i++ ) {
+ if( in(1) == in(i) ) { setDef(i,null); progress=true; }
+ else { allDefault=false; }
+ }
+
+ // If not merging any memory (all memory is just the default)
+ if( allDefault )
+ return in(1); // Become default memory
+
+ // Collapse stacked merged-mem
+ if( in(1) instanceof MemMergeNode mem ) {
+ // A dead memory cycle can lose its last use when we bypass mem.
+ // Keep the peephole's result alive until the worklist sees it.
+ keep();
+ // Goal is to swap my default mem with mem's default mem
+ for( int i=2; i<mem.nIns(); i++ ) {
+ if( mem.in(i) != null ) {
+ // deeper default mem has a non-default
+ if( i>=nIns() || in(i)==null )
+ setDefX(i,mem.in(i));
+ }
+ }
+ CodeGen.CODE.add(mem.in(1));
+ setDef(1,mem.in(1));
+ return unkeep();
+ }
+
+ return progress ? this : null;
+ }
+
+
+ public Node in( Var v ) { return in(v._idx); }
+
+ public Node alias( int alias ) {
+ assert !(in(1) instanceof BulkMemPhiNode bulk && bulk.isSplit(alias) &&
+ (alias >= nIns() || in(alias)==null));
+ return alias < nIns() && in(alias)!=null ? in(alias) : in(1);
+ }
+
+ public Node alias( int alias, Node st ) { return setDefX(alias,st); }
+
+ // Now one-time do a useless-phi removal
+ void _useless( ) {
+ for( int i=1; i<nIns(); i++ ) {
+ if( in(i) instanceof PhiNode phi ) {
+ // Do an eager useless-phi removal
+ Node in = phi.peephole();
+ CodeGen.CODE.addAll(phi._outputs);
+ phi.moveDepsToWorklist();
+ if( in != phi ) {
+ if( !phi.iskeep() ) // Keeping phi around for parser elsewhere
+ phi.subsume(in);
+ setDef(i,in); // Set the update back into Scope
+ }
+ }
+ }
+ }
+
+ @Override public boolean eq( Node n ) {
+ return this==n;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
new file mode 100644
index 00000000..24ca6157
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
@@ -0,0 +1,160 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Lexer;
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.GlobalBits;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.*;
+import java.lang.StringBuilder;
+import java.util.*;
+
+/**
+ * Convenience common base for Load and Store.
+ *
+ * 0 - Control
+ * 1 - Memory
+ * 2 - Base of object, OOP
+ * 3 - Offset, integer types
+ * 4 - Value, for Stores only
+ */
+public abstract class MemOpNode extends TypeNode {
+
+ // The equivalence alias class
+ public int _alias;
+
+ // True if load-like, false if store-like.
+ // Used on CPU-specific combined memory+arithmetic ops.
+ //
+ // Stores produce memory (maybe as part of a tuple with other things),
+ // loads do not.
+ //
+ // Loads might pick up anti-dependencies on prior Stores, and never cause an
+ // anti-dependence themselves.
+ //
+ // Stores must maximally sink to the least dominator use. Loads can be
+ // opportunistically hoisted.
+ public final boolean _isLoad;
+
+ // Field name.
+ public final String _name;
+ // Source location for late reported errors
+ public final Lexer _loc;
+
+ public MemOpNode(Lexer loc, String name, int alias, boolean isLoad, Type decl, Node ctrl, Node mem, Node ptr, Node off) {
+ super(decl, ctrl, mem, ptr, off);
+ _name = name;
+ _alias = alias;
+ _loc = loc;
+ _isLoad = isLoad;
+ }
+ public MemOpNode(Lexer loc, String name, int alias, boolean isLoad, Type decl, Node ctrl, Node mem, Node ptr, Node off, Node value) {
+ this(loc, name, alias, isLoad, decl, ctrl, mem, ptr, off);
+ addDef(value);
+ }
+ public MemOpNode( Node ideal, MemOpNode mop ) {
+ super(ideal,mop instanceof LoadNode ld ? ld.declaredType() : mop._con);
+ _name = mop._name;
+ _alias = mop._alias;
+ _loc = mop._loc;
+ _isLoad= mop._isLoad;
+ }
+
+ MemOpNode( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases, boolean isLoad ) {
+ this(null,
+ strs[bais.packed2()],
+ mapAlias(bais,fileAliases,aliases),isLoad,
+ types[bais.packed2()],null,null,null,null);
+ }
+
+ private static int mapAlias( BAOS bais, GlobalBits fileAliases, GlobalBits aliases ) {
+ int alias = bais.packed2();
+ return alias < GlobalBits.RESERVED ? alias : aliases.map(fileAliases,alias);
+ }
+
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed2(_name==null ? 0 : strs.get(_name));
+ baos.packed2(_alias);
+ baos.packed2(types.get(_con)); // NPE if fails lookup
+ assert _isLoad == this instanceof LoadNode; // No machine ops
+ }
+
+ static String mlabel(String name) { return "[]"==name ? "ary" : ("#"==name ? "len" : name); }
+ String mlabel() { return mlabel(_name); }
+
+ public Node mem() { return in(1); }
+ public Node ptr() { return in(2); }
+ public Node off() { return in(3); }
+
+ // Current declared field type. This is deliberately a live lookup:
+ // cyclic/forward types can sharpen by replacing the pointer's struct.
+ public Type declaredType() {
+ if( !(ptr()._type instanceof TypeMemPtr tmp) )
+ return Type.BOTTOM;
+ Field fld = tmp._obj.field(_name);
+ return fld == null || fld._t == null ? Type.BOTTOM : fld._t;
+ }
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return _printMach(p); }
+ protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { throw Utils.TODO(); }
+
+
+ // Extra conditions for factoring matching memory operations through a Phi.
+ // The caller has already checked the opcode, input shape and control.
+ boolean canDrop(MemOpNode other, Node dep) {
+ dep.addDep(ptr());
+ return _alias>1 && _alias==other._alias && declaredType()==other.declaredType() && err()==null;
+ }
+
+ @Override
+ public boolean eq(Node n) {
+ MemOpNode mem = (MemOpNode)n; // Invariant
+ return _alias==mem._alias && super.eq(mem);
+ }
+
+ @Override
+ int hash() { return _alias ^ super.hash(); }
+
+ @Override
+ public Parser.ParseException err() {
+ Type ptr = ptr()._type;
+ // Already an error, but better error messages come from elsewhere
+ if( ptr == Type.BOTTOM ) return null;
+ if( ptr.isHigh() ) return null; // Assume it will fall to not-null
+ // Better be a not-nil TMP
+ if( !(ptr instanceof TypeMemPtr tmp && tmp.notNull()) )
+ return Parser.error( "Might be null accessing '" + _name + "'",_loc);
+ if( tmp._obj.field(_name)==null &&
+ CodeGen.CODE._phase.ordinal() > CodeGen.Phase.FinalizeIR.ordinal() )
+ return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc);
+ // Sane field
+ if( off() instanceof ConFldOffNode && CodeGen.CODE._phase.ordinal() > CodeGen.Phase.FinalizeIR.ordinal() )
+ return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc);
+ return null;
+ }
+
+ // Memory writers which must follow this read. A packaging node is not a
+ // write; follow this alias through aggregates to its writers.
+ public Ary<Node> antiDeps( Ary<Node> antideps ) {
+ antideps.clear();
+ return antiDeps(mem(),antideps);
+ }
+
+ private Ary<Node> antiDeps( Node mem, Ary<Node> antideps ) {
+ for( Node use : mem._outputs ) {
+ if( use instanceof MemMergeNode merge ) {
+ if( merge.alias(_alias)==mem )
+ antiDeps(merge,antideps);
+ } else if( use instanceof CallNode || use instanceof CLInitNode ||
+ (use instanceof MemOpNode st && !st._isLoad && st._alias==_alias) ||
+ (use instanceof EscapeNode esc && esc.fld()._alias==_alias) ||
+ (use instanceof MemPhiNode phi && phi._alias==_alias) ||
+ (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) {
+ if( !antideps.contains(use) ) antideps.add(use);
+ }
+ }
+ return antideps;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
new file mode 100644
index 00000000..aaaedb89
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
@@ -0,0 +1,80 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.codegen.GlobalBits;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+/** A Phi for one precise memory alias. */
+public class MemPhiNode extends PhiNode {
+
+ public final int _alias;
+
+ public MemPhiNode(String label, int alias, Node... inputs) {
+ super(label, inputs);
+ assert alias > 1;
+ _alias = alias;
+ }
+
+ public MemPhiNode(MemPhiNode phi) {
+ super(phi,phi._label);
+ _alias = phi._alias;
+ }
+
+ @Override public Tag serialTag() { return Tag.MemPhi; }
+
+ @Override
+ public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types,
+ IdentityHashMap<Node,Integer> anodes) {
+ super.packed(baos,strs,types,anodes);
+ baos.packed2(_alias);
+ }
+
+ static Node make(BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases) {
+ Node[] ins = new Node[bais.packed1()];
+ String label = strs[bais.packed2()];
+ int alias = bais.packed2();
+ if( alias >= GlobalBits.RESERVED ) alias = aliases.map(fileAliases,alias);
+ return new MemPhiNode(label,alias,ins);
+ }
+
+ @Override
+ public Type compute() {
+ Type t = super.compute();
+ assert BulkMemPhiNode.checkMem(this,_alias);
+ if( !(t instanceof TypeMem tmem) ) return t;
+ assert tmem._alias==1 || tmem._alias==_alias;
+ // This Phi structurally knows its alias, but not whether the field is
+ // final. Do not infer slice finality from evolving input types.
+ return TypeMem.make(_alias,tmem._t,tmem._one,tmem._clz,false,tmem._escFs,tmem._escAs);
+ }
+
+ @Override
+ public Node idealize() {
+ // A precise memory Phi can always bypass a MemMerge and select its
+ // matching slice.
+ for( int i=1; i<nIns(); i++ ) {
+ if( in(i) instanceof MemMergeNode mem ) {
+ setDef(i,mem.alias(_alias));
+ return this;
+ }
+ Node slice = CLInitNode.slice(in(i),_alias,this);
+ if( slice!=in(i) ) { setDef(i,slice); return this; }
+ }
+ if( nIns()==3 && region() instanceof LoopNode loop && !loop.inProgress() &&
+ in(2) instanceof StoreNode st && st.mem()==this ) {
+ Node sink = st.sink(this);
+ if( sink!=null ) return sink;
+ }
+ return super.idealize();
+ }
+
+ @Override public boolean eq(Node n) {
+ return _alias==((MemPhiNode)n)._alias && super.eq(n);
+ }
+
+ @Override int hash() { return _alias; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/MinusNode.java
new file mode 100644
index 00000000..4f4343d4
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -0,0 +1,79 @@
+package com.seaofnodes.simple.node;
+
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+public class MinusNode extends Node implements ModeNode {
+ // Mode is 0 for unknown, 1 for int, 2 for flt
+ byte _mode;
+
+ public MinusNode(Node in) { super(null, in); }
+ public MinusNode(Node in, byte mode) { super(null, in); _mode = mode; }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) {
+ baos.packed1(_mode);
+ }
+ @Override public Tag serialTag() { return Tag.Minus; }
+
+ @Override public byte mode() { return _mode; }
+ @Override public Node setMode(byte mode) {
+ assert _mode==0 && (mode==1 || mode==2);
+ unlock(); // _mode participates in GVN hash and equality
+ _mode = mode;
+ return this;
+ }
+
+ @Override protected String format() { return "(-%1)"; }
+
+ @Override
+ public Type compute() {
+ Type t = in(1)._type;
+ // Constant array payloads lie below their element ranges in the lattice.
+ if( t instanceof TypeConAry ary ) t = ary.elem();
+ if( t==null )
+ return Type.TOP;
+ if( t.isHigh() )
+ return _mode==0 ? Type.TOP :
+ _mode==1 ? TypeInteger.TOP :
+ t instanceof TypeFloat ? t : TypeFloat.F64.dual();
+ if( t instanceof TypeInteger i0 ) {
+ if( _mode==2 ) return TypeFloat.F64;
+ if( i0._min != Long.MIN_VALUE && i0._max != Long.MIN_VALUE )
+ return TypeInteger.make(-i0._max,-i0._min,i0._widen);
+ return TypeInteger.BOT;
+ }
+ if( t instanceof TypeFloat f ) {
+ if( _mode==1 ) return TypeInteger.BOT;
+ return f.isConstant() ? TypeFloat.constant(-f.value()) : f;
+ }
+ // No numeric family is known. This must be below both integer and
+ // float results, in either analysis direction and with any mode.
+ return Type.BOTTOM;
+ }
+
+ @Override
+ public Node idealize() {
+ // -(-x) is x
+ if( in(1) instanceof MinusNode minus && addDep(minus.in(1))._type.isa(compute()) )
+ return minus.in(1);
+
+ // Can we decide int vs flt?
+ if( _mode==0 ) {
+ byte mode = (byte)(
+ in(1)._type instanceof TypeInteger ? 1 :
+ in(1)._type instanceof TypeFloat ? 2 : 0);
+ if( mode!=0 ) return setMode(mode).init();
+ }
+
+ return null;
+ }
+ @Override public Parser.ParseException err() {
+ return ArithNode.nodeErr(this,null,"-",_mode,true);
+ }
+ @Override public boolean eq( Node n ) { return _mode==((MinusNode)n)._mode; }
+ @Override public int hash() { return _mode; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ModeNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ModeNode.java
new file mode 100644
index 00000000..dc635447
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ModeNode.java
@@ -0,0 +1,12 @@
+package com.seaofnodes.simple.node;
+
+/**
+ * A node whose numeric input family is selected once: unknown, integer, or
+ * floating point. Implementors remain ordinary Nodes; this interface only
+ * centralizes the structural mode contract shared by arithmetic, comparisons,
+ * and unary minus.
+ */
+public interface ModeNode {
+ byte mode();
+ Node setMode(byte mode);
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/MulNode.java
new file mode 100644
index 00000000..bb99cad5
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -0,0 +1,65 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import java.util.BitSet;
+
+public class MulNode extends ArithNode {
+ @Override boolean allowFloat() { return true; }
+ public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+ public MulNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs, mode); }
+ @Override public Tag serialTag() { return Tag.Mul; }
+
+ @Override public String op() { return "*"; }
+
+ @Override long doOp( long x, long y ) { return x * y; }
+ @Override double doOp(double x, double y ) { return x * y; }
+ @Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
+ return TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ Type t1 = lhs._type;
+ Type t2 = rhs._type;
+
+ // Move constants to RHS: con*arg becomes arg*con
+ if ( t1.isConstant() && !t2.isConstant() )
+ return swap12();
+
+ // Multiply by constant
+ if ( t2.isConstant() && t2 instanceof TypeInteger i ) {
+ // Mul of 1. We do not check for (1*x) because this will already
+ // canonicalize to (x*1)
+ long c = i.value();
+ if( c==1 ) return lhs._type.isa(compute()) ? lhs : super.idealize();
+ if( c==0 ) return CodeGen.CODE.ZERO;
+ // Mul by a power of 2, +/-1. Bit patterns more complex than this
+ // are unlikely to win on an X86 vs the normal "imul", and so
+ // become machine-specific.
+ if( (c & (c-1)) == 0 )
+ return new ShlNode(null,lhs,con(Long.numberOfTrailingZeros(c)));
+ // 2**n + 1, e.g. 9
+ long d = c-1;
+ if( (d & (d-1)) == 0 )
+ return new AddNode(new ShlNode(null,lhs,con(Long.numberOfTrailingZeros(d))).peephole(),lhs);
+ // 2**n - 1, e.g. 31
+ long e = c+1;
+ if( (e & (e-1)) == 0 )
+ return new SubNode(new ShlNode(null,lhs,con(Long.numberOfTrailingZeros(e))).peephole(),lhs);
+
+ }
+
+ // Do we have ((x * (phi cons)) * con) ?
+ // Do we have ((x * (phi cons)) * (phi cons)) ?
+ // Push constant up through the phi: x * (phi con0*con0 con1*con1...)
+ Node phicon = AddNode.phiCon(this,true);
+ if( phicon!=null ) return phicon;
+
+ return super.idealize();
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs,_mode); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/MultiNode.java
new file mode 100644
index 00000000..152d525a
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/MultiNode.java
@@ -0,0 +1,29 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.util.Ary;
+
+
+public interface MultiNode {
+
+ abstract Ary<Node> outs();
+
+ // Find a projection by index
+ default ProjNode proj( int idx ) {
+ for( Node out : outs() )
+ if( out instanceof ProjNode prj && prj._idx==idx )
+ return prj;
+ return null;
+ }
+
+ // Find a projection by index
+ default CProjNode cproj( int idx ) {
+ for( Node out : outs() )
+ if( out instanceof CProjNode prj && prj._idx==idx )
+ return prj;
+ return null;
+ }
+
+ // Return not-null if this projection index is a ideal copy.
+ // Called by ProjNode ideal and used to collapse Multis.
+ default Node pcopy(int idx, Node dep) { return null; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/NeverNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/NeverNode.java
new file mode 100644
index 00000000..f7b5a58a
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/NeverNode.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeTuple;
+
+
+// "Never true" for infinite loop exits
+public class NeverNode extends IfNode {
+ public NeverNode(NeverNode ctrl) { super(ctrl); }
+ public NeverNode(float ignore, Node ctrl) { super(ctrl,null); }
+ @Override public Tag serialTag() { return Tag.Never; }
+
+ @Override public String label() { return "Never"; }
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("Never"); }
+
+ @Override public Type compute() { return TypeTuple.IF_BOTH; }
+
+ @Override public Node idealize() { return null; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/NewNode.java
new file mode 100644
index 00000000..d4df7a0b
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/NewNode.java
@@ -0,0 +1,106 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.SB;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+/**
+ * Allocation! Allocate a chunk of memory, no init.
+ * Takes in ctrl and size, and TypeStruct.
+ * Produces a ptr and private memory.
+*/
+public class NewNode extends Node implements MultiNode {
+ public TypeStruct _ts;
+ public NewNode( TypeStruct ts, Node ctrl, Node size ) { super(ctrl,size); _ts = ts; }
+
+ public Node ctrl() { return in(0); }
+ public Node size() { return in(1); }
+
+ // --- Serialization
+ public NewNode(NewNode nnn) { super(nnn); _ts = nnn._ts; }
+ @Override public Tag serialTag() { return Tag.New; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed2(types.get(_ts));
+ }
+ static Node make( BAOS bais, Type[] types) {
+ TypeStruct ts = (TypeStruct)types[bais.packed2()];
+ return new NewNode(ts,null,null);
+ }
+
+ @Override public String label() { return "new_"+(_ts.isAry() ? "ary_"+_ts._fields[1]._t.str() : _ts.str()); }
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ return p.p("new ").p(_ts.str());
+ }
+
+ @Override public TypeTuple compute() {
+ if( ctrl()._type.isHigh() )
+ return TypeTuple.DEAD_NEW;
+ TypeStruct ts = _ts.isAry() ? _ts : _ts.makeInit();
+ return TypeTuple.make(TypeMemPtr.make(_ts),
+ // Arrays get pre-zeroed by constructor.
+ // Objects start with default-zero memory for
+ // fields that can be zero-initialized. This is
+ // still private memory, so no aliases escape yet.
+ TypeMem.make(1,ts,true,false,false,null,null));
+ }
+
+ @Override public Node idealize() { return null; }
+
+ @Override boolean _upgradeType( HashMap<String,Type> TYPES) {
+ Type old = _ts; _ts = (TypeStruct)_ts.upgradeType(TYPES); return old != _ts;
+ }
+
+ // Two NewNodes are never equal; NewNodes always make independent pointers
+ // so they are not functional - they internally bump-ptr allocate always a
+ // new pointer.
+ @Override public boolean eq(Node n) { return this==n; }
+
+ @Override int hash() { return _ts.hashCode(); }
+
+
+ @Override public Parser.ParseException err() {
+ return _ts._fref ? Parser.error("Unknown struct type '"+_ts._name+"'",null) : null;
+ }
+
+
+ // ------------
+ // MachNode specifics, shared across all CPUs
+ public int _arg2Reg, _xslot;
+ private RegMask _arg3Mask;
+ private RegMask _retMask;
+ private RegMask _kills;
+ public void cacheRegs(CodeGen code) {
+ _arg2Reg = code._mach.callArgMask(TypeFunPtr.CALLOC,2,0,code._cCallingConv).firstReg();
+ _arg3Mask = code._mach.callArgMask(TypeFunPtr.CALLOC,3,0,code._cCallingConv);
+ // Return mask depends on TFP (either GPR or FPR)
+ _retMask = code._mach.retMask(TypeFunPtr.CALLOC);
+ // Kill mask is all caller-saves, and any mirror stack slots for args
+ // in registers.
+ RegMaskRW kills = code._cCallerSave.copy();
+ // Start of stack slots
+ int maxReg = code._mach.regs().length;
+ // Incoming function arg slots, all low numbered in the RA
+ int fslot = cfg0().fun()._maxArgSlot;
+ // Killed slots for this calls outgoing args
+ int xslot = code._mach.maxArgSlot(TypeFunPtr.CALLOC,code._cCallingConv);
+ _xslot = (maxReg+fslot)+xslot;
+ for( int i=0; i<xslot; i++ )
+ kills.set((maxReg+fslot)+i);
+ _kills = kills;
+ }
+ public String op() { return "alloc"; }
+ public RegMask regmap(int i) { return i==1 ? _arg3Mask : null; }
+ public RegMask outregmap() { return null; }
+ public RegMask outregmap(int idx) { return idx==0 ? _retMask : null; }
+ public RegMask killmap() { return _kills; }
+ public void asm(CodeGen code, SB sb) {
+ sb.p("#malloc, ").p(code.reg(size()));
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter29/src/main/java/com/seaofnodes/simple/node/Node.java
new file mode 100644
index 00000000..cbd35592
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -0,0 +1,779 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.BaseNode;
+
+import com.seaofnodes.simple.codegen.IterPeeps;
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.GlobalBits;
+import com.seaofnodes.simple.print.IRPrinter;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.*;
+import java.util.*;
+import java.util.function.Function;
+import static com.seaofnodes.simple.codegen.CodeGen.CODE;
+
+/**
+ * All Nodes in the Sea of Nodes IR inherit from the Node class.
+ * The Node class provides common functionality used by all subtypes.
+ * Subtypes of Node specialize by overriding methods.
+ */
+public abstract class Node extends BaseNode<Node> implements Cloneable {
+
+ /**
+ * Inputs to the node. These are use-def references to Nodes.
+ * <p>
+ * Generally fixed length, ordered, nulls allowed, no unused trailing space.
+ * Ordering is required because e.g. "a/b" is different from "b/a".
+ * The first input (offset 0) is often a {@link CFGNode} node.
+ */
+ public Ary<Node> _inputs;
+
+ /**
+ * Outputs reference Nodes that are not null and have this Node as an
+ * input. These nodes are users of this node, thus these are def-use
+ * references to Nodes.
+ * <p>
+ * Outputs directly match inputs, making a directed graph that can be
+ * walked in either direction. These outputs are typically used for
+ * efficient optimizations but otherwise have no semantics meaning.
+ */
+ public Ary<Node> _outputs;
+
+ /**
+ * Current computed type for this Node. This value changes as the graph
+ * changes and more knowledge is gained about the program.
+ */
+ public Type _type;
+
+ Node(Node... inputs) {
+ super(CODE.getUID()); // allocate unique dense ID
+ _inputs = new Ary<>(Node.class);
+ Collections.addAll(_inputs,inputs);
+ _outputs = new Ary<>(Node.class);
+ for( Node n : _inputs )
+ if( n != null )
+ n.addUse( this );
+ }
+
+ // Make a Node using the existing arrays of nodes.
+ // Used by any pass rewriting all Node classes but not the edges.
+ Node( Node n ) {
+ super(CODE.getUID()); // allocate unique dense ID
+ assert CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Select.ordinal();
+ _inputs = new Ary<>(n==null ? new Node[0] : n._inputs.asAry());
+ _outputs = new Ary<>(Node.class);
+ _type = n==null ? Type.BOTTOM : n._type;
+ _deps = null;
+ _hash = 0;
+ }
+
+ // Disk/serialized opcode tags
+ public enum Tag {
+ Add,And,BulkMemPhi,EQ,NE,LT,LE,ULT,
+ CallEnd,Call,ConFldOff,Con,CLInit,CProj,
+ Div,Escape,Extern,ExternOffset,Fun,FunPtr,Guard,
+ If,Load,Loop,MemMerge,MemPhi,Minus,Mul,
+ New,Never,Not,Or,Parm,Phi,Proj,PtrToInt,
+ ReadOnly,Return,Region,RoundF32,
+ Sar,Shl,Shr,Start,StartCU,StopCU,Stop,Store,Sub,
+ ToInteger,ToFloat,XCtrl,Xor;
+ public static final Tag[] VALS = values();
+ public Node make( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases ) {
+ return switch(this) {
+ case Add -> new AddNode(null,null,(byte)bais.packed1());
+ case And -> { int mode=bais.packed1(); assert mode==1; yield new AndNode(null,null,null); }
+ case Div -> new DivNode(null,null,(byte)bais.packed1());
+ case Guard -> GuardNode.make(bais);
+ case If -> new IfNode(null,null);
+ case Loop -> new LoopNode(null,null,null);
+ case Minus -> new MinusNode(null,(byte)bais.packed1());
+ case Mul -> new MulNode(null,null,(byte)bais.packed1());
+ case Never -> new NeverNode(1.2f, null);
+ case Not -> new NotNode(null);
+ case Or -> { int mode=bais.packed1(); assert mode==1; yield new OrNode(null,null,null); }
+ case ReadOnly-> new ReadOnlyNode((Node)null);
+ case Return ->new ReturnNode(null,null,null,null,null);
+ case RoundF32-> new RoundF32Node(null);
+ case PtrToInt-> new PtrToIntNode((Node)null);
+ case Sar -> { int mode=bais.packed1(); assert mode==1; yield new SarNode(null,null,null); }
+ case Shl -> { int mode=bais.packed1(); assert mode==1; yield new ShlNode(null,null,null); }
+ case Shr -> { int mode=bais.packed1(); assert mode==1; yield new ShrNode(null,null,null); }
+ case Start -> new StartNode(null,null,TypeInteger.BOT);
+ case Sub -> new SubNode(null,null,(byte)bais.packed1());
+ case ToFloat-> new ToFloatNode(null);
+ case ToInteger-> new ToIntegerNode(null);
+ case XCtrl -> new XCtrlNode();
+ case Xor -> { int mode=bais.packed1(); assert mode==1; yield new XorNode(null,null,null); }
+
+ case EQ -> new BoolNode.EQ (null,null,(byte)bais.packed1());
+ case NE -> new BoolNode.NE (null,null,(byte)bais.packed1());
+ case LT -> new BoolNode.LT (null,null,(byte)bais.packed1());
+ case LE -> new BoolNode.LE (null,null,(byte)bais.packed1());
+ case ULT -> new BoolNode.ULT(null,null,(byte)bais.packed1());
+
+ case Call -> CallNode.make(bais,strs);
+ case CallEnd-> CallEndNode.make(bais ,types);
+ case CLInit -> new CLInitNode(bais,strs);
+ case CProj -> CProjNode.make(bais,strs);
+ case Con -> ConstantNode.make(bais ,types);
+ case Escape-> EscapeNode.make(bais ,types);
+ case Extern-> ExternNode.make(bais,strs,types);
+ case ExternOffset-> new ExternOffsetNode(strs[bais.packed2()]);
+ case FunPtr-> FunPtrNode.make(bais, types);
+ case Fun -> FunNode.make(bais,strs,types);
+ case Load -> new LoadNode(bais,strs,types,fileAliases,aliases);
+ case MemMerge->MemMergeNode.make(bais);
+ case MemPhi-> MemPhiNode.make(bais,strs,types,fileAliases,aliases);
+ case BulkMemPhi-> BulkMemPhiNode.make(bais,strs,types,fileAliases,aliases);
+ case New -> NewNode.make(bais ,types);
+ case Parm -> ParmNode.make(bais,strs,types);
+ case Phi -> PhiNode.make(bais,strs,types);
+ case Proj -> ProjNode.make(bais,strs);
+ case Store -> new StoreNode(bais,strs,types,fileAliases,aliases);
+ case Stop -> StopNode.make(bais);
+ case StartCU-> StartCUNode.make(bais,strs,types);
+ case StopCU -> StopCUNode.make(bais);
+ case Region-> RegionNode.make(bais);
+
+ default -> throw Utils.TODO("Should not reach here");
+ };
+ }
+ };
+ public Tag serialTag() { throw Utils.TODO(); }
+ // Serialize extra data, including input counts
+ public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {}
+
+ // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ"
+ public String label() { return serialTag().toString(); }
+
+ public String p(int depth) { return IRPrinter.prettyPrint(this,depth); }
+
+ public boolean isConst() { return false; }
+
+ // ------------------------------------------------------------------------
+ // Graph Node & Edge manipulation
+
+ @Override public String typeName() { return _type==null ? null : _type.toString(); }
+
+ /**
+ * Gets the ith input node
+ * @param i Offset of the input node
+ * @return Input node or null
+ */
+ public Node in(int i) { return _inputs.get(i); }
+ public Node out(int i) { return _outputs.get(i); }
+ public final Ary<Node> outs() { return _outputs; }
+
+ public int nIns() { return _inputs.size(); }
+
+ public int nOuts() { return _outputs.size(); }
+
+ public boolean isUnused() { return nOuts() == 0; }
+
+ public CFGNode cfg0() { return (CFGNode)in(0); }
+
+ /**
+ * Change a <em>def</em> into a Node. Keeps the edges correct, by removing
+ * the corresponding <em>use->def</em> edge. This may make the original
+ * <em>def</em> go dead. This function is co-recursive with {@link #kill}.
+ * <p>
+
+ * This method is the normal path for altering a Node, because it does the
+ * proper default edge maintenance. It also <em>immediately</em> kills
+ * Nodes that lose their last use; at times care must be taken to avoid
+ * killing Nodes that are being used without having an output Node. This
+ * definitely happens in the middle of recursive {@link #peephole} calls.
+ *
+ * @param idx which def to set
+ * @param new_def the new definition
+ * @return new_def for flow coding
+ */
+ public <N extends Node> N setDef(int idx, N new_def ) {
+ unlock();
+ Node old_def = in(idx);
+ if( old_def == new_def ) return new_def; // No change
+ // If new def is not null, add the corresponding def->use edge
+ // This needs to happen before removing the old node's def->use edge as
+ // the new_def might get killed if the old node kills it recursively.
+ if( new_def != null )
+ new_def.addUse(this);
+ // Set the new_def over the old (killed) edge
+ _inputs.set(idx,new_def);
+ if( old_def != null ) { // If the old def exists, remove a def->use edge
+ if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead
+ old_def.kill(); // Kill old def
+ else CODE.add(old_def); // Else old lost a use, so onto worklist
+ }
+ moveDepsToWorklist();
+ // Return new_def for easy flow-coding
+ return new_def;
+ }
+ public <N extends Node> N setDefX(int idx, N new_def ) {
+ while( nIns() <= idx ) addDef(null);
+ return setDef(idx,new_def);
+ }
+
+ // Remove the numbered input, compressing the inputs in-place. This
+ // shuffles the order deterministically - which is suitable for Region and
+ // Phi, but not for every Node. If the def goes dead, it is recursively
+ // killed, which may include 'this' Node.
+ public Node delDef(int idx) {
+ unlock();
+ Node old_def = in(idx);
+ _inputs.del(idx);
+ if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead
+ old_def.kill(); // Kill old def
+ old_def.moveDepsToWorklist();
+ return this;
+ }
+
+ // Insert the numbered input, sliding other inputs to the right
+ public void insertDef(int idx, Node new_def) {
+ _inputs.insert(null,idx);
+ setDef(idx,new_def);
+ }
+
+ /**
+ * Add a new def to an existing Node. Keep the edges correct by
+ * adding the corresponding <em>def->use</em> edge.
+ *
+ * @param new_def the new definition, appended to the end of existing definitions
+ * @return new_def for flow coding
+ */
+ public Node addDef(Node new_def) {
+ unlock();
+ // Add use->def edge
+ _inputs.add(new_def);
+ // If new def is not null, add the corresponding def->use edge
+ if( new_def != null )
+ new_def.addUse(this);
+ return new_def;
+ }
+
+ // Breaks the edge invariants, used temporarily
+ @SuppressWarnings("unchecked")
+ protected <N extends Node> N addUse(Node n) { _outputs.add(n); return (N)this; }
+
+ // Remove node 'use' from 'def's (i.e. our) output list, by compressing the list in-place.
+ // Return true if the output list is empty afterward.
+ // Error is 'use' does not exist; ok for 'use' to be null.
+ public boolean delUse( Node use ) {
+ _outputs.del(_outputs.find(use));
+ moveDepsToWorklist(); // User-count and anti-dependence queries can now change.
+ return _outputs.isEmpty();
+ }
+
+ // Shortcut for "popping" until n nodes. A "pop" is basically a
+ // setDef(last,null) followed by lowering the nIns() count.
+ public void popUntil(int n) {
+ unlock();
+ while( nIns() > n ) {
+ Node old_def = _inputs.pop();
+ if( old_def != null && // If it exists and
+ old_def.delUse(this) ) // If we removed the last use, the old def is now dead
+ old_def.kill(); // Kill old def
+ }
+ }
+ public Node removeLast() {
+ Node old_def = _inputs.pop();
+ if( old_def != null )
+ old_def.delUse(this);
+ return old_def;
+ }
+
+ /**
+ * Kill a Node with no <em>uses</em>, by setting all of its <em>defs</em>
+ * to null. This may recursively kill more Nodes, and is basically dead
+ * code elimination.
+ */
+ public void kill( ) {
+ assert isUnused(); // Has no uses, so it is dead
+ unlock();
+ moveDepsToWorklist();
+ _type=null; // Flag as dead
+ while( nIns()>0 ) { // Set all inputs to null, recursively killing unused Nodes
+ Node old_def = _inputs.removeLast();
+ // Revisit neighbor because removed use
+ if( old_def != null ) {
+ if( CODE.add(old_def).delUse(this) )
+ old_def.kill(); // If we removed the last use, the old def is now dead
+ old_def.moveDepsToWorklist(); // Use-count changes can enable distant rewrites.
+ }
+ }
+ assert isDead(); // Really dead now
+ }
+
+ // Preserve CFG use-ordering when killing
+ public void killOrdered() {
+ CFGNode cfg = cfg0();
+ cfg._outputs.remove(cfg._outputs.find(this));
+ _inputs.set(0,null);
+ kill();
+ }
+
+ // Mostly used for asserts and printing.
+ public boolean isDead() { return isUnused() && nIns()==0 && _type==null; }
+
+ // Shortcuts to stop DCE mid-parse
+ // Add bogus null use to keep node alive
+ public <N extends Node> N keep() { return addUse(null); }
+ // Remove bogus null.
+ @SuppressWarnings("unchecked")
+ public <N extends Node> N unkeep() {
+ delUse(null);
+ return (N)this;
+ }
+ // Test "keep" status
+ public boolean iskeep() { return _outputs.find(null) != -1; }
+ public void unkill() {
+ if( unkeep().isUnused() )
+ kill();
+ }
+ public void isKill() {
+ if( isUnused() )
+ kill();
+ }
+
+ // Replace self with nnn in the graph, making 'this' go dead
+ public void subsume( Node nnn ) {
+ assert nnn!=this;
+ while( nOuts() > 0 ) {
+ Node n = _outputs.removeLast();
+ n.unlock();
+ int idx = n._inputs.find(this);
+ n._inputs.set(idx,nnn);
+ nnn.addUse(n);
+ CODE.add(n);
+ CODE.addAll(n._outputs);
+ }
+ kill();
+ }
+
+ // insert `this` immediately after `def` in the same basic block.
+ public void insertAfter( Node def ) {
+ CFGNode cfg = def.cfg0();
+ int i = cfg._outputs.find(def)+1;
+ if( cfg instanceof CallEndNode ) {
+ cfg = cfg.uctrl(); i=0;
+ } else if( def.in(0) instanceof MultiNode ) {
+ assert i==0;
+ i = cfg._outputs.find(def.in(0))+1;
+ }
+
+ while( cfg.out(i) instanceof PhiNode || cfg.out(i) instanceof CalleeSaveNode ) i++;
+ cfg._outputs.insert(this,i);
+ _inputs.set(0,cfg);
+ }
+
+ // Insert this in front of use.in(uidx) with this, and insert this
+ // immediately before use in the basic block.
+ public void insertBefore( Node use, int uidx ) {
+ CFGNode cfg = use.cfg0();
+ int i;
+ if( use instanceof PhiNode phi ) {
+ cfg = phi.region().cfg(uidx);
+ if( cfg instanceof CProjNode && cfg.in(0) instanceof NeverNode nvr )
+ cfg = nvr.cfg0();
+ i = cfg.nOuts()-1;
+ } else {
+ i = cfg._outputs.find(use);
+ }
+ cfg._outputs.insert(this,i);
+ _inputs.set(0,cfg);
+ if( _inputs._len > 1 && this instanceof SplitNode )
+ setDefOrdered(1,use.in(uidx));
+ use.setDefOrdered(uidx,this);
+ }
+
+ public void setDefOrdered( int idx, Node def) {
+ // If old is dying, remove from CFG ordered
+ Node old = in(idx);
+ if( old!=null && old.nOuts()==1 ) {
+ CFGNode cfg = old.cfg0();
+ if( cfg!=null ) {
+ cfg._outputs.remove(cfg._outputs.find(old));
+ old._inputs.set(0,null);
+ }
+ }
+ setDef(idx,def);
+ }
+
+ public void removeSplit() {
+ CFGNode cfg = cfg0();
+ cfg._outputs.remove(cfg._outputs.find(this));
+ _inputs.set(0,null);
+ if( _inputs._len > 1 ) subsume(in(1));
+ }
+
+ // ------------------------------------------------------------------------
+ // Graph-based optimizations
+
+ /**
+ * Try to peephole at this node and return a better replacement Node.
+ * Always returns some not-null Node (often this).
+ */
+ public final Node peephole( ) {
+ var obs = CODE._midAssert ? null : CODE._obs;
+ if( obs != null ) obs.before(this);
+ Node n = peepholeOpt();
+ Node rez = n==null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n);
+ if( obs != null ) obs.after(this, n==null ? null : rez, false);
+ return rez; // Cannot return null for no-progress
+ }
+
+ /**
+ * Try to peephole at this node and return a better replacement Node if
+ * possible. We compute a {@link Type} and then check and replace:
+ * <ul>
+ * <li>if the Type {@link Type#isConstant}, we replace with a {@link ConstantNode}</li>
+ * <li>in a future chapter we will look for a
+ * <a href="https://en.wikipedia.org/wiki/Common_subexpression_elimination">Common Subexpression</a>
+ * to eliminate.</li>
+ * <li>we ask the Node for a better replacement. The "better replacement"
+ * is things like {@code (1+2)} becomes {@code 3} and {@code (1+(x+2))} becomes
+ * {@code (x+(1+2))}. By canonicalizing expressions we fold common addressing
+ * math constants, remove algebraic identities and generally simplify the
+ * code. </li>
+ *
+ * Unlike peephole above, this explicitly returns null for no-change, or not-null
+ * for a better replacement (which can be this).
+ * </ul>
+ */
+ public final Node peepholeOpt( ) {
+ CODE.iterCnt();
+ // Compute initial or improved Type
+ Type old = setType(compute());
+
+ // Replace constant computations from non-constants with a constant node.
+ if( !isConst() && !(this instanceof ParmNode parm && parm.inProgress()) &&
+ _type.isHighOrConst() )
+ return ConstantNode.make(_type).peephole();
+
+ // Global Value Numbering
+ if( _hash==0 ) {
+ Node n = CODE._gvn.get(this); // Will set _hash as a side effect
+ if( n==null )
+ CODE._gvn.put(this,this); // Put in table now
+ else {
+ // Because of random worklist ordering, the two equal nodes
+ // might have different types. Because of monotonicity, both
+ // types are valid. To preserve monotonicity, the resulting
+ // shared Node has to have the best of both types.
+ n.setType(n._type.join(_type));
+ _hash = 0; // Clear, since it never went in the table
+ return deadCodeElim(n);// Return previous; does Common Subexpression Elimination
+ }
+ }
+
+ // Ask each node for a better replacement
+ Node n = idealize();
+ if( n != null ) // Something changed
+ return n; // Report progress
+
+ if( old!=_type ) return this; // Report progress;
+ CODE.iterNop();
+ return null; // No progress
+ }
+
+ // m is the new Node, self is the old.
+ // Return 'm', which may have zero uses but is alive nonetheless.
+ // If self has zero uses (and is not 'm'), {@link #kill} self.
+ private Node deadCodeElim(Node m) {
+ // If self is going dead and not being returned here (Nodes returned
+ // from peephole commonly have no uses (yet)), then kill self.
+ if( m != this && isUnused() && !isDead() ) {
+ // Killing self - and since self recursively kills self's inputs we
+ // might end up killing 'm', which we are returning as a live Node.
+ // So we add a bogus extra null output edge to stop kill().
+ m.keep(); // Keep m alive
+ kill(); // Kill self because replacing with 'm'
+ m.unkeep(); // Okay to peephole m
+ }
+ return m;
+ }
+
+ /**
+ * This function needs to be
+ * <a href="https://en.wikipedia.org/wiki/Monotonic_function">Monotonic</a>
+ * as it is part of a Monotone Analysis Framework.
+ * <a href="https://www.cse.psu.edu/~gxt29/teaching/cse597s21/slides/08monotoneFramework.pdf">See for example this set of slides</a>.
+ * <p>
+ * For Chapter 2, all our Types are really integer constants, and so all
+ * the needed properties are trivially true, and we can ignore the high
+ * theory. Much later on, this will become important and allow us to do
+ * many fancy complex optimizations trivially... because theory.
+ * <p>
+ * {@link #compute()} needs to be stand-alone, and cannot recursively call compute
+ * on its inputs, because programs are cyclic (have loops!) and this will just
+ * infinitely recurse until stack overflow. Instead, compute typically
+ * computes a new type from the {@link #_type} field of its inputs.
+ */
+ public abstract Type compute();
+
+ // Set the type. Assert monotonic progress.
+ // If changing, add users to worklist.
+ public Type setType(Type type) {
+ Type old = _type;
+ assert old == null || type.isa(old) : "Monotonicity test failed: "+this+"#"+_nid+" old="+old+" new="+type;
+ //assert !type.hasClosedBuilder0(); // New types should not have closed builders
+ if( old == type ) return old;
+ _type = type; // Set _type late for easier assert debugging
+ CODE.addAll(_outputs);
+ moveDepsToWorklist();
+ return old;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <N extends Node> N init() { _type = compute(); return (N)this; }
+
+ /**
+ * This function rewrites the current Node into a more "idealized" form.
+ * This is the bulk of our peephole rewrite rules, and we use this to
+ * e.g. turn arbitrary collections of adds and multiplies with mixed
+ * constants into a normal form that's easy for hardware to implement.
+ * Example: An array addressing expression:
+ * <pre> ary[idx+1]</pre>
+ * might turn into Sea-of-Nodes IR:
+ * <pre> (ary+12)+((idx+1) * 4)</pre>
+ * This expression can then be idealized into:
+ * <pre> ary + ((idx*4) + (12 + (1*4)))</pre>
+ * And more folding:
+ * <pre> ary + ((idx<<2) + 16)</pre>
+ * And during code-gen:
+ * <pre> MOV4 Rary,[Ridx<<2 +16] // or some such hardware-specific notation </pre>
+ * <p>
+ * {@link #idealize} has a very specific calling convention:
+ * <ul>
+ * <li>If NO change is made, return {@code null}
+ * <li>If ANY change is made, return not-null; this can be {@code this}
+ * <li>The returned Node does NOT call {@link #peephole} on itself; the {@link #peephole} call will recursively peephole it.
+ * <li>Any NEW nodes that are not directly returned DO call {@link #peephole}.
+ * </ul>
+ * <p>
+ * Examples:
+ * <table border="3">
+ * <tr><th> before </th><th> after </th><th>return </th><th>comment </th></tr>
+ * <tr><td>{@code (x+5) }</td><td>{@code (x+5) }</td><td>{@code null }</td><td>No change</td></tr>
+ * <tr><td>{@code (5+x) }</td><td>{@code (x+5) }</td><td>{@code this }</td><td>Swapped arguments</td></tr>
+ * <tr><td>{@code ((x+1)+2)}</td><td>{@code (x+(1+2))}</td><td>{@code (x+_) }</td><td>Returns 2 new Nodes</td></tr>
+ * </table>
+ *
+ * The last entry deserves more discussion. The new Node {@code (1+2)}
+ * created in {@link #idealize} calls {@link #peephole} (which then folds
+ * into a constant). The other new Node {@code (x+3)} does not call
+ * peephole, because it is returned and peephole itself will recursively
+ * call peephole.
+ * <p>
+ * Since idealize calls peephole and peephole calls idealize, you must be
+ * careful that all idealizations are <em>monotonic</em>: all transforms
+ * remove some feature, so that the set of available transforms always
+ * shrinks. If you don't, you risk an infinite peephole loop!
+ *
+ * @return Either a new or changed node, or null for no changes.
+ */
+ public abstract Node idealize();
+
+ // Some of the peephole rules get complex, and search further afield than
+ // just the nearest neighbor. These peepholes can fail the pattern match
+ // on a node some distance away, and if that node ever changes we should
+ // retry the peephole. Track a set of Nodes dependent on `this`, and
+ // revisit them if `this` changes.
+ Ary<Node> _deps;
+ public int nDeps() { return _deps == null ? 0 : _deps.size(); }
+ public Node dep(int idx) { return _deps.at(idx); }
+
+ private boolean addDepImpl( Node dep ) {
+ // Running peepholes during the big assert cannot have side effects
+ // like adding dependencies.
+ if( CODE._midAssert ) return false;
+ if( CODE._obs != null ) CODE._obs.dep(this, dep);
+ if( dep._deps==null ) dep._deps = new Ary<>(Node.class);
+ if( dep._deps .find(this) != -1 ) return false; // Already on list
+ if( dep._outputs.find(this) != -1 ) return false;
+ return true;
+ }
+
+ /**
+ * Add a node to the list of dependencies. Only add it if it's not an input
+ * or output of this node, that is, it is at least one step away. The node
+ * being added must benefit from this node being peepholed.
+ */
+ public <N extends Node> N addDep( N dep ) {
+ if( !addDepImpl(dep) ) return dep;
+ if( dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors
+ dep._deps.add(this);
+ return dep;
+ }
+
+ public <N extends Node> N addDepForwards( N dep ) {
+ if( !addDepImpl(dep) ) return dep;
+ dep._deps.add(this);
+ return dep;
+ }
+
+ // Move the dependents onto a worklist, and clear for future dependents.
+ public void moveDepsToWorklist( ) { moveDepsToWorklist(CODE._iter); }
+ public void moveDepsToWorklist( IterPeeps iter ) {
+ if( _deps == null ) return;
+ iter.addAll(_deps);
+ _deps.clear();
+ }
+
+ // Two nodes are equal if they have the same inputs and the same "opcode"
+ // which means the same Java class, plus same internal parts.
+ @Override public final boolean equals( Object o ) {
+ if( o==this ) return true;
+ if( o.getClass() != getClass() ) return false;
+ Node n = (Node)o;
+ int len = _inputs.size();
+ if( len != n._inputs.size() ) return false;
+ for( int i=0; i<len; i++ )
+ if( in(i) != n.in(i) )
+ return false;
+ return eq(n);
+ }
+ // Subclasses add extra checks (such as ConstantNodes have same constant),
+ // and can assume "this!=n" and has the same Java class.
+ public boolean eq( Node n ) { return true; }
+
+ // Cached hash. If zero, then not computed AND this Node is NOT in the GVN
+ // table - and can have its edges hacked (which will change his hash
+ // anyway). If Non-Zero then this Node is IN the GVN table, or is being
+ // probed to see if it can be inserted. His edges are "locked", because
+ // hacking his edges will change his hash.
+ int _hash;
+ // Edge and mode edits remove the node from GVN, even if its type is unchanged.
+ public boolean isUnlocked() { return _hash==0; }
+
+ // If the _hash is set, then the Node is in the GVN table; remove it.
+ void unlock() {
+ if( _hash==0 ) return;
+ Node old = CODE._gvn.remove(this); // Pull from table
+ assert old==this;
+ _hash=0; // Out of table now
+ }
+
+ // Hash of opcode and inputs
+ @Override public final int hashCode() {
+ if( _hash != 0 ) return _hash;
+ int hash = hash();
+ for( Node n : _inputs )
+ if( n != null )
+ hash = hash ^ (hash<<17) ^ (hash>>13) ^ n._nid;
+ if( hash==0 ) hash = 0xDEADBEEF; // Bad hash, so use some junky thing
+ return (_hash=hash);
+ }
+ // Subclasses add extra hash info (such as ConstantNodes constant)
+ int hash() { return 0; }
+
+ // ------------------------------------------------------------------------
+ //
+
+ /** Is this Node Memory related */
+ public boolean isMem() { return false; }
+
+ // ------------------------------------------------------------------------
+ // Peephole utilities
+
+ // Swap inputs without letting either input go dead during the swap.
+ public Node swap12() {
+ unlock(); // Hash is order dependent
+ _inputs.swap(1,2);
+ return this;
+ }
+
+ /**
+ * Does this node contain all constants? Ignores in(0), as is usually
+ * control. In an input is not a constant, we add dep as a dependency to
+ * it because dep can make progress if the input becomes a constant later.
+ * It is sufficient for one of the non-const inputs to have the dependency,
+ * so we don't bother checking the rest.
+ */
+ boolean allCons(Node dep) {
+ for( int i=1; i<nIns(); i++ )
+ if( !(in(i)._type.isConstant()) ) {
+ dep.addDep(in(i)); // If in(i) becomes a constant later, will trigger some peephole
+ return false;
+ }
+ return true;
+ }
+
+ // Make a shallow copy (same class) of this Node, with given inputs and
+ // empty outputs and a new Node ID. The original inputs are ignored.
+ // Does not need to be implemented in isCFG() nodes.
+ Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); }
+
+ public Node copy() { return copyEmpty(); }
+
+ // Exact-class copy with fresh identity and no edges. Unlike machine
+ // rematerialization copies, this never registers or copies input edges.
+ public final Node copyEmpty() {
+ Node n;
+ try { n = (Node)clone(); }
+ catch( Exception e ) { throw new RuntimeException(e); }
+ n._nid = CODE.getUID(); // allocate unique dense ID
+ n._inputs = new Ary<>(Node.class);
+ n._outputs = new Ary<>(Node.class);
+ n._deps = null;
+ n._hash = 0;
+ if( n instanceof CallNode call ) {
+ CODE._calls.add(call);
+ CODE.add(CODE._globalEscapes);
+ }
+ return n;
+ }
+
+ // Report any post-optimize errors
+ public Parser.ParseException err() { return null; }
+
+ // Common integer constants
+ public static ConstantNode con(long x) { return (ConstantNode)ConstantNode.make(TypeInteger.constant(x)).peephole(); }
+
+ // Utility to walk the entire graph applying a function; return the first
+ // not-null result.
+ final public <E> E walk( Function<Node,E> pred ) {
+ assert CODE._visit.isEmpty();
+ E rez = _walk(pred);
+ CODE._visit.clear();
+ return rez;
+ }
+
+ private <E> E _walk( Function<Node,E> pred ) {
+ if( CODE._visit.get(_nid) ) return null; // Been there, done that
+ CODE._visit.set(_nid);
+ E x = pred.apply(this);
+ if( x != null ) return x;
+ for( Node def : _inputs ) if( def != null && (x = def._walk(pred)) != null ) return x;
+ for( Node use : _outputs ) if( use != null && (x = use._walk(pred)) != null ) return x;
+ return null;
+ }
+
+ public void gather(HashMap<String,Integer> strs ) { }
+
+ // If a Type upgrade happens, put on worklist
+ boolean _upgradeType( HashMap<String,Type> TYPES ) { return false; }
+ public final void upgradeType( HashMap<String,Type> TYPES ) {
+ Type old = _type; _type = _type.upgradeType(TYPES);
+ boolean progress = _upgradeType(TYPES);
+ if( progress || old != _type ) {
+ CODE.add(this);
+ CODE.addAll(_outputs);
+ if( _deps!=null ) CODE.addAll(_deps);
+ }
+ }
+
+ /**
+ * Debugging utility to find a Node by index
+ */
+ public Node find(int nid) { return _find(nid, new BitSet()); }
+ private Node _find(int nid, BitSet bs) {
+ if( bs.get(_nid) ) return null; // Been there, done that
+ bs.set(_nid);
+ if( _nid==nid ) return this;
+ Node x;
+ for( Node def : _inputs ) if( def != null && (x = def._find(nid,bs)) != null ) return x;
+ for( Node use : _outputs ) if( use != null && (x = use._find(nid,bs)) != null ) return x;
+ return null;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/NotNode.java
new file mode 100644
index 00000000..2328c287
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/NotNode.java
@@ -0,0 +1,25 @@
+package com.seaofnodes.simple.node;
+
+
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Utils;
+
+public class NotNode extends Node {
+ public NotNode(Node in) { super(null, in); }
+ @Override public Tag serialTag() { return Tag.Not; }
+
+ @Override protected String format() { return "(!%1)"; }
+
+ @Override
+ public TypeInteger compute() {
+ Type t0 = in(1)._type;
+ if( t0.isHigh() ) return (TypeInteger)TypeInteger.BOOL.dual();
+ if( t0 == Type.NIL || t0 == TypeInteger.ZERO ) return TypeInteger.TRUE;
+ if( t0 instanceof TypeNil tn && tn.notNull() ) return TypeInteger.FALSE;
+ if( t0 instanceof TypeInteger i && (i._min > 0 || i._max < 0) ) return TypeInteger.FALSE;
+ return TypeInteger.BOOL;
+ }
+
+ @Override
+ public Node idealize() { return null; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/OrNode.java
new file mode 100644
index 00000000..c7dcc75e
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/OrNode.java
@@ -0,0 +1,45 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Lexer;
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+public class OrNode extends ArithNode {
+ public OrNode( Lexer loc, Node lhs, Node rhs) { super(loc,lhs, rhs, (byte)1); }
+ @Override public Tag serialTag() { return Tag.Or; }
+
+ @Override public String label() { return "Or"; }
+ @Override public String op() { return "|"; }
+ @Override long doOp( long x, long y ) { return x | y; }
+ @Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
+ return TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+
+ Type t1 = lhs._type;
+ Type t2 = rhs._type;
+
+ // Or of 0. We do not check for (0|x) because this will already
+ // canonicalize to (x|0)
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && t1.isa(TypeInteger.BOT) && forceInt(lhs) )
+ return lhs;
+
+ // Move constants to RHS: con*arg becomes arg*con
+ if ( t1.isConstant() && !t2.isConstant() )
+ return swap12();
+
+ // Do we have ((x | (phi cons)) | con) ?
+ // Do we have ((x | (phi cons)) | (phi cons)) ?
+ // Push constant up through the phi: x | (phi con0|con0 con1|con1...)
+ Node phicon = AddNode.phiCon(this,true);
+ if( phicon!=null ) return phicon;
+
+ return super.idealize();
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new OrNode(_loc,lhs,rhs); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ParmNode.java
new file mode 100644
index 00000000..91c471c0
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ParmNode.java
@@ -0,0 +1,109 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.codegen.Serialize;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+public class ParmNode extends PhiNode {
+
+ // Argument indices are mapped one-to-one on CallNode inputs
+ public final int _idx; // Argument index
+ public Type _declaredType;
+
+ public ParmNode(String label, int idx, Type declaredType, Node... inputs) {
+ super(label, inputs);
+ _idx = idx;
+ _declaredType = declaredType;
+ _type = declaredType;
+ }
+ public ParmNode(ParmNode parm) { super(parm, parm._label ); _idx = parm._idx; _declaredType = parm._declaredType; }
+ @Override public Tag serialTag() { return Tag.Parm; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed1(Serialize.nIns(this,anodes)); // Object-local linked calls
+ baos.packed2(_label==null ? 0 : strs.get(_label));
+ baos.packed2(types.get(_declaredType)); // NPE if fails lookup
+ baos.packed1(_idx);
+ }
+ static Node make( BAOS bais, String[] strs, Type[] types) {
+ Node[] ins = new Node[bais.packed1()];
+ String label = strs[bais.packed2()];
+ Type minType = types[bais.packed2()];
+ return new ParmNode(label, bais.packed1(), minType, ins);
+ }
+
+ @Override public String label() { return MemOpNode.mlabel(_label); }
+
+ @Override boolean _upgradeType( HashMap<String,Type> TYPES) {
+ Type t = _declaredType.upgradeType(TYPES);
+ if( t == _declaredType ) return false;
+ unlock();
+ _declaredType = t;
+ return true;
+ }
+
+ public FunNode fun() { return (FunNode)in(0); }
+
+ @Override
+ protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ if( fun()._name!=null && fun()._name.endsWith("<clinit>") && _label.equals("arg") )
+ return p.p("arg");
+ p.p("Parm_").p(_label).open();
+ for( Node in : _inputs ) p.n(in).p(",");
+ return p.unchar(',').close();
+ }
+
+
+ // Always in-progress until we run out of unknown callers
+ @Override public boolean inProgress() { return in(0) instanceof FunNode fun && fun.inProgress(); }
+
+ @Override
+ public Type compute() {
+ if( !(region() instanceof RegionNode r) )
+ return region()._type==Type.XCONTROL || region()._type==Type.TOP ? (_type instanceof TypeMem ? TypeMem.TOP : Type.TOP) : _type;
+ // During parsing Phis have to be computed type pessimistically.
+ if( r.inProgress() || in(nIns()-1)==null )
+ return _declaredType;
+ // Set type to local top of the starting type
+ Type t = Type.TOP;
+ for( int i = 1; i < nIns(); i++ ) {
+ // If the region's control input is live, add this as a dependency
+ // to the control because we can be peeped should it become dead.
+ Type ctrl = addDep(r.in(i))._type;
+ if( ctrl != Type.XCONTROL && ctrl != Type.TOP ) {
+ if( in(i)._type==Type.BOTTOM )
+ return Type.BOTTOM;
+ t = t.meet(in(i)._type);
+ }
+ }
+ // Recursion is a loop in disguise. A Parm merges the first argument
+ // with those from recursive calls, just as a loop Phi merges its entry
+ // and backedge. Use the widening rule in PhiNode.compute here too.
+ // CallEndNode.compute handles the separate cycle through return values;
+ // widening this Parm alone cannot stop f(x-1)+1 from growing its result.
+ if( t instanceof TypeInteger ti && !ti.isHigh() && !ti.isConstant() )
+ t = ti.same_but_slightly_wider_than();
+ return t.join(_declaredType);
+ }
+
+ @Override
+ public Node idealize() {
+ if( !(region() instanceof FunNode) )
+ // A dead function can lose every caller before the Parm itself is
+ // removed; there is no replacement input in that transient form.
+ return nIns()==1 ? null : in(1); // Input has collapse to e.g. starting control.
+ // If function is folding, do all possible peeps
+ if( fun()._folding ) return super.idealize();
+
+ // Skip most phi optimizations on parms
+ return null;
+ }
+
+ @Override public boolean eq( Node n ) {
+ return ((ParmNode)n)._idx==_idx && super.eq(n);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/PhiNode.java
new file mode 100644
index 00000000..75d3ff13
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/PhiNode.java
@@ -0,0 +1,288 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.Serialize;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.*;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+public class PhiNode extends Node {
+
+ public final String _label;
+
+ public PhiNode( String label, Node... inputs) {
+ super(inputs);
+ _label = label;
+ }
+ public static PhiNode make(String label, Type minType, Node... inputs) {
+ return minType instanceof TypeMem mem
+ ? mem._alias==1
+ ? new BulkMemPhiNode(label,inputs)
+ : new MemPhiNode(label,mem._alias,inputs)
+ : new PhiNode(label, inputs);
+ }
+ // Used by ParmNode
+ public PhiNode(PhiNode phi, String label) { super(phi); _label = label; }
+ // Used by instruction Selection
+ public PhiNode(PhiNode phi) { this(phi,phi._label); }
+ // Used by the infinite-loop exit breaker
+ public PhiNode(RegionNode r, Node sample) {
+ super(new Node[]{r});
+ _label = "";
+ _type = sample._type;
+ while( nIns() < r.nIns() )
+ addDef(sample);
+ }
+ @Override public Tag serialTag() { return Tag.Phi; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed1(nIns());
+ baos.packed2(_label==null ? 0 : strs.get(_label));
+ }
+ static Node make( BAOS bais, String[] strs, Type[] types) {
+ Node[] ins = new Node[bais.packed1()];
+ return new PhiNode(strs[bais.packed2()], ins);
+ }
+
+ @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); }
+
+ @Override
+ protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ if( !(region() instanceof RegionNode r) || r.inProgress() )
+ p.p("Z");
+ p.p("Phi(");
+ for( Node in : _inputs ) p.n(in).p(",");
+ return p.unchar(',').close();
+ }
+
+ public CFGNode region() { return (CFGNode)in(0); }
+ @Override public boolean isMem() { return _type instanceof TypeMem; }
+
+ @Override
+ public Type compute() {
+ if( !(region() instanceof RegionNode r) )
+ return Type.TOP;
+ // During parsing Phis have to be computed type pessimistically.
+ if( r.inProgress() || in(nIns()-1)==null )
+ return Type.BOTTOM;
+ // Set type to local top of the starting type
+ Type t = Type.TOP;
+ for( int i = 1; i < nIns(); i++ ) {
+ // If the region's control input is live, add this as a dependency
+ // to the control because we can be peeped should it become dead.
+ Type ctrl = addDep(r.in(i))._type;
+ if( ctrl != Type.XCONTROL && ctrl != Type.TOP ) {
+ if( in(i)._type==Type.BOTTOM )
+ return Type.BOTTOM;
+ t = t.meet(in(i)._type);
+ }
+ }
+
+ // Widen loop Phis so integer ranges cannot grow one step per trip forever.
+ // Recursion is a loop in disguise: ParmNode.compute widens arguments,
+ // and CallEndNode.compute widens results. Both are needed because the
+ // recursive result can feed itself without passing through a Parm.
+ // Above-center ranges may still fall to unwidened constants.
+ // Widen only below-center ranges to preserve monotonicity.
+ if( r instanceof LoopNode && // Only around loops
+ t instanceof TypeInteger ti && // Only widen integers
+ !ti.isHigh() && !ti.isConstant() ) { // Only widen live ranges
+ // Widen, to prevent infinite falling of TypeIntegers
+ return ti.same_but_slightly_wider_than();
+ }
+
+ return t;
+ }
+
+ @Override
+ public Node idealize() {
+ RegionNode r = (RegionNode)region();
+ if( r.inProgress() || nOuts()==0 )
+ return null; // Input is in-progress
+
+ // If we have only a single unique input, become it.
+ Node live = singleUniqueInput();
+ if( live != null )
+ return live;
+
+ // No bother if region is going to fold dead paths soon
+ for( int i=1; i<nIns(); i++ )
+ if( r.in(i)._type == Type.XCONTROL )
+ return null;
+
+ // Generic "pull down op"
+ Node progress;
+ if( same_op() && (progress = drop_same_op()) != null )
+ return progress;
+
+ // If merging Phi(ZERO, guardNZ(N)) at the matching `if(N)` join, the
+ // Phi is exactly N. The guard arm proves N is non-zero, and the other
+ // arm contributes N's zero value. This is only legal if N is already
+ // available at the Phi region; otherwise a return-scope/live-on-exit
+ // Phi can lose the zero arm and export a branch-local value upward.
+ Node unguard;
+ if( (unguard=matchingGuardMerge(r,1)) != null ) return unguard;
+ if( (unguard=matchingGuardMerge(r,2)) != null ) return unguard;
+
+ return null;
+ }
+
+ private Node matchingGuardMerge(RegionNode r, int nzIdx) {
+ Node zero = in(3-nzIdx);
+ if( !(in(nzIdx) instanceof GuardNode cast && cast._nonZero) ||
+ zero._type.makeZero()!=zero._type ||
+ cast.in(1)==this )
+ return null;
+ if( !(r.in(nzIdx) instanceof CProjNode nz && nz._idx==0 && nz.ctrl() instanceof IfNode iff) )
+ return null;
+ if( !(r.in(3-nzIdx) instanceof CProjNode z && z._idx==1 && z.ctrl()==iff) )
+ return null;
+ // Predicate was guarded?
+ Node n = cast.in(1), tested = null;
+ while( true ) {
+ if( iff.pred()==n ) tested = n;
+ if( !(n instanceof GuardNode guard && guard._nonZero) ) break;
+ n = guard.in(1);
+ }
+ CFGNode early = CFGNode.earlyCFG(n);
+ return tested!=null && (early==null || early.dominates(r,this)) ? n : null;
+ }
+
+ // Same op on all Phi paths; all ops have only the Phi as a use.
+ // None have a control input.
+ private boolean same_op() {
+ Node op = in(1);
+ if( op instanceof EscapeNode || op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode ||
+ op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false;
+ // A bulk Phi must split its aliases before factoring precise Stores.
+ if( this instanceof BulkMemPhiNode ) return false;
+ // Bulk splitting currently identifies parallel slices by Region/alias.
+ // Factoring can introduce another slice at a different memory point;
+ // wait until that Region's bulk partitioning has finished.
+ if( op instanceof MemOpNode )
+ for( Node use : region()._outputs )
+ if( use instanceof BulkMemPhiNode ) {
+ addDepForwards(use);
+ return false;
+ }
+ Node busy = null;
+ for( int i=1; i<nIns(); i++ ) {
+ Node n = in(i);
+ if( addDep(region().in(i))._type==Type.XCONTROL ||
+ op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false;
+ if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false;
+ // Moving a Store must remove the old effect, not duplicate it.
+ // Allow one shared arm: the other arms disappear into the single
+ // factored operation, usually reducing the total operation count.
+ addDepForwards(n);
+ if( n.nOuts()>1 ) {
+ for( Node use : n._outputs )
+ if( use!=null && use!=this ) addDepForwards(use);
+ if( n instanceof StoreNode || busy!=null ) return false;
+ busy=n;
+ }
+ for( int j=1; j<n.nIns(); j++ )
+ if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false;
+ }
+ return true;
+ }
+
+ private Node drop_same_op() {
+ Node op = in(1), cp = op.copyEmpty();
+ cp._type = null;
+ cp.addDef(null);
+ for( int j=1; j<op.nIns(); j++ ) {
+ Node x = op.in(j);
+ boolean different = false;
+ for( int i=2; i<nIns(); i++ )
+ if( in(i).in(j)!=x ) different=true;
+ if( different ) {
+ Node[] ins = new Node[nIns()];
+ ins[0] = region();
+ Type t = Type.TOP;
+ for( int i=1; i<nIns(); i++ ) {
+ ins[i] = in(i).in(j);
+ t = t.meet(ins[i]._type);
+ }
+ // Do not introduce an operand Phi mixing incompatible type families.
+ if( t==Type.BOTTOM ) {
+ for( int i=1; i<ins.length; i++ ) addDep(ins[i]);
+ cp.kill();
+ return null;
+ }
+ PhiNode phi = j==1 && op instanceof MemOpNode mem
+ ? new MemPhiNode(_label,mem._alias,ins)
+ : PhiNode.make(_label,t,ins);
+ x = phi.peephole();
+ }
+ cp.addDef(x);
+ }
+ // Factoring must not widen the result (e.g. correlated And operands).
+ if( cp.compute().isa(compute()) ) return cp;
+ for( int i=1; i<nIns(); i++ )
+ for( int j=1; j<in(i).nIns(); j++ )
+ if( in(i).in(j)!=null ) addDep(in(i).in(j));
+ cp.kill();
+ return null;
+ }
+
+ /**
+ * If only single unique input, return it
+ */
+ private Node singleUniqueInput() {
+ if( region() instanceof LoopNode loop && loop.entry()._type == Type.XCONTROL )
+ return null; // Dead entry loops just ignore and let the loop collapse
+ Node live = null;
+ for( int i=1; i<nIns(); i++ ) {
+ // If the region's control input is live, add this as a dependency
+ // to the control because we can be peeped should it become dead.
+ if( addDep(region().in(i))._type != Type.XCONTROL && in(i) != this )
+ if( live == null || live == in(i) ) live = in(i);
+ else return null;
+ }
+ return live;
+ }
+
+ @Override
+ boolean allCons(Node dep) {
+ if( !(region() instanceof RegionNode r) ) return false;
+ // When the region completes (is no longer in progress) the Phi can
+ // become a "all constants" Phi, and the "dep" might make progress.
+ dep.addDep(this);
+ if( r.inProgress() ) return false;
+ return super.allCons(dep);
+ }
+
+ // True if last input is null
+ public boolean inProgress() {
+ return in(nIns()-1) == null;
+ }
+
+ // Never equal if inProgress.
+ @Override public boolean eq( Node n ) {
+ return !inProgress() && super.eq(n);
+ }
+
+ @Override
+ public Parser.ParseException err() {
+ if( _type != Type.BOTTOM ) return null;
+
+ // Global BOTTOM retains the same role for non-scalar families.
+ for( int i=1; i<nIns(); i++ )
+ // Already an error, but better error messages come from elsewhere
+ if( in(i)._type == Type.BOTTOM )
+ return null;
+
+ SB sb = new SB().p("No common type amongst ");
+ for( int i=1; i<nIns(); i++ )
+ sb.p(in(i)._type.toString()).p(" and ");
+ return Parser.error(sb.unchar(5).toString(),null);
+ }
+
+ @Override public void gather( HashMap<String,Integer> strs ) {
+ Serialize.gather(strs,_label);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/Proj.java b/chapter29/src/main/java/com/seaofnodes/simple/node/Proj.java
new file mode 100644
index 00000000..c8b9378f
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/Proj.java
@@ -0,0 +1,2 @@
+package com.seaofnodes.simple.node;
+public interface Proj { int idx(); }
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ProjNode.java
new file mode 100644
index 00000000..fcc113a8
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ProjNode.java
@@ -0,0 +1,71 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.Serialize;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.type.TypeTuple;
+import com.seaofnodes.simple.util.BAOS;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+public class ProjNode extends Node implements Proj {
+
+ // Which slice of the incoming multipart value
+ public final int _idx;
+
+ // Debugging label
+ public final String _label;
+
+ public ProjNode(Node ctrl, int idx, String label) {
+ super(new Node[]{ctrl});
+ _idx = idx;
+ _label = label;
+ }
+ public ProjNode(ProjNode p) { super(p); _idx = p._idx; _label = p._label; }
+ @Override public Tag serialTag() { return Tag.Proj; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed1(_idx);
+ baos.packed2(_label==null ? 0 : strs.get(_label));
+ }
+ static Node make( BAOS bais, String[] strs ) {
+ return new ProjNode(null, bais.packed1(), strs[bais.packed2()] );
+ }
+
+ @Override public String label() { return _label; }
+
+ @Override
+ protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ if( _label != null ) return p.p(_label);
+ if( in(0) instanceof CallEndNode cend && cend.call()!=null )
+ return p.n(cend.call());
+ return p.p("LONELY PROJ");
+ }
+
+ @Override public CFGNode cfg0() {
+ return in(0) instanceof CFGNode cfg ? cfg : in(0).cfg0();
+ }
+
+ @Override public boolean isMem() { return _type instanceof TypeMem; }
+
+ @Override
+ public Type compute() {
+ Type t = in(0)._type;
+ return t instanceof TypeTuple tt ? tt._types[_idx] : t.oob();
+ }
+
+ @Override public Node idealize() { return ((MultiNode)in(0)).pcopy(_idx,this); }
+
+ @Override
+ public boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; }
+
+ @Override
+ int hash() { return _idx; }
+
+ @Override public int idx() { return _idx; }
+
+ @Override public void gather( HashMap<String,Integer> strs ) {
+ Serialize.gather(strs,_label);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java b/chapter29/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java
new file mode 100644
index 00000000..9a9b94fd
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.util.SB;
+
+/** Zero-code machine form of {@link PtrToIntNode}. */
+public class PtrToIntMach extends PtrToIntNode implements MachNode {
+ public PtrToIntMach(PtrToIntNode ptr) { super(ptr); }
+ @Override public String op() { return label(); }
+ @Override public RegMask regmap(int i) { assert i==1; return RegMask.FULL; }
+ @Override public RegMask outregmap() { return RegMask.FULL; }
+ @Override public int twoAddress() { return 1; }
+ @Override public void encoding(Encoding enc) { }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(in(1))); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java
new file mode 100644
index 00000000..d528b53b
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java
@@ -0,0 +1,36 @@
+package com.seaofnodes.simple.node;
+
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.TypeMemPtr;
+
+
+/**
+ * Reinterpret a non-null managed pointer as its raw integer address.
+ *
+ * This node changes representation families but emits no code. No
+ * control-dependent proof can turn a pointer into an integer; array-to-C-pointer
+ * conversion adds the array body offset separately.
+ */
+public class PtrToIntNode extends Node {
+ public PtrToIntNode(Node ptr) { super(null,ptr); }
+ public PtrToIntNode(PtrToIntNode ptr) { super(ptr); }
+
+ @Override public Tag serialTag() { return Tag.PtrToInt; }
+ @Override protected String format() { return "(i64*)%1"; }
+
+ @Override public Type compute() {
+ return in(1)._type.isHigh() ? TypeInteger.TOP : TypeInteger.BOT;
+ }
+
+ @Override public Node idealize() { return null; }
+
+ @Override public Parser.ParseException err() {
+ Type t = in(1)._type;
+ if( t instanceof TypeMemPtr ptr )
+ return ptr.notNull() ? null : Parser.error("Cannot convert a nullable pointer to an integer",null);
+ return Parser.error("Cannot convert "+t.str()+" to an integer pointer",null);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java
new file mode 100644
index 00000000..a35f5a1d
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.util.SB;
+
+/**
+ * Cast a pointer to read-only
+ */
+public class ReadOnlyMach extends ReadOnlyNode implements MachNode {
+ public ReadOnlyMach( ReadOnlyNode n ) { super(n); }
+ @Override public String op() { return label(); }
+ @Override public RegMask regmap(int i) { assert i==1; return RegMask.FULL; }
+ @Override public RegMask outregmap() { return RegMask.FULL; }
+ @Override public int twoAddress( ) { return 1; }
+ @Override public void encoding( Encoding enc ) { }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(in(1))); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java
new file mode 100644
index 00000000..0a720d5b
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java
@@ -0,0 +1,27 @@
+package com.seaofnodes.simple.node;
+
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMemPtr;
+
+/**
+ * Cast a pointer to read-only
+ */
+public class ReadOnlyNode extends Node {
+ public ReadOnlyNode( Node n ) { super(null,n); }
+ public ReadOnlyNode( ReadOnlyNode n ) { super(n); }
+ @Override public Tag serialTag() { return Tag.ReadOnly; }
+
+ @Override protected String format() { return "(const)%1"; }
+ @Override public Type compute() {
+ Type t = in(1)._type;
+ return t instanceof TypeMemPtr tmp ? tmp.makeRO() : t;
+ }
+
+ @Override public Node idealize() {
+ if( in(1)._type instanceof TypeMemPtr tmp && tmp.isFinal() )
+ return in(1);
+ return null;
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/RegionNode.java
new file mode 100644
index 00000000..8d0d56e9
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/RegionNode.java
@@ -0,0 +1,176 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Lexer;
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.BAOS;
+import java.util.*;
+
+public class RegionNode extends CFGNode {
+ // Source location for late discovered errors
+ public Lexer _loc;
+
+ public RegionNode(Lexer loc, Node... nodes) { super(nodes); _loc = loc; }
+ public RegionNode(RegionNode r, Lexer loc) { super(r); _loc = loc; }
+ public RegionNode(RegionNode r) { super(r); if( r!=null ) _loc = r._loc; }
+ @Override public Tag serialTag() { return Tag.Region; }
+ public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); }
+ static Node make( BAOS bais ) { return new RegionNode(null,new Node[bais.packed1()]); }
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ return p.p(label());
+ }
+
+ @Override public boolean blockHead() { return true; }
+
+ @Override
+ public Type compute() {
+ if( inProgress() ) return Type.CONTROL;
+ Type t = Type.XCONTROL;
+ for (int i = 1; i < nIns(); i++)
+ t = t.meet(in(i)._type);
+ return t;
+ }
+
+ @Override
+ public Node idealize() {
+ if( inProgress() ) return null;
+
+ Node progress = deadPath(false);
+ if( progress!=null ) return progress;
+
+ // If down to a single input, become that input
+ if( nIns()==2 && !hasPhi() )
+ return in(1); // Collapse if no Phis; 1-input Phis will collapse on their own
+
+ // If a CFG diamond with no merging, delete: "if( pred ) {} else {};"
+ if( !hasPhi() && nIns()>3 && // No Phi users, just a control user
+ in(1) instanceof CProjNode p1 &&
+ in(2) instanceof CProjNode p2 &&
+ addDepForwards(p1).in(0)==addDepForwards(p2).in(0) &&
+ p1.in(0) instanceof IfNode iff ) {
+ // Replace with the iff.ctrl directly
+ if( nIns()==3 ) return iff.ctrl();
+ // Just delete the path for fat Regions
+ setDef(1,iff.ctrl());
+ return delDef(2);
+ }
+
+ // Flatten stacked regions (no loops involved)
+ if( getClass() == RegionNode.class && nIns()>2 ) {
+ for( int i=1; i<nIns(); i++ )
+ if( cfg(i) instanceof RegionNode region && region.getClass() == RegionNode.class && region.nIns()>2 && !hasMidUser(region) ) {
+ assert !region.inProgress();
+ // Fold Phis
+ for( Node use : _outputs ) {
+ if( use instanceof PhiNode phi ) {
+ PhiNode phi2 = phi.in(i) instanceof PhiNode phi2x && phi2x.region()==region ? phi2x : null;
+ for( int j=1; j<region.nIns(); j++ )
+ phi.addDef(phi2==null ? phi.in(i) : phi2.in(j));
+ CodeGen.CODE.add(phi);
+ }
+ }
+
+ // Fold Region
+ for( int j=1; j<region.nIns(); j++ )
+ addDef(region.in(j));
+ setDef(i,CodeGen.CODE.XCTRL);
+ return this;
+ }
+ }
+
+ return null;
+ }
+
+ Node deadPath(boolean skipStart) {
+ // Delete dead paths into a Region
+ int path = findDeadInput(skipStart ? 2 :1);
+ if( path==0 ) return null;
+
+ // Do not delete the entry path of a loop (ok to remove the back edge
+ // and make the loop a single-entry Region which folds away the Loop).
+ // Folding the entry path confused the loop structure, moving the
+ // backedge to the entry point.
+ if( this instanceof LoopNode loop && loop.entry()==in(path) )
+ return null;
+ return removeDeadPath(path);
+ }
+
+ public Node removeDeadPath(int path) {
+ // Cannot use the obvious output iterator here, because a Phi deleting
+ // an input might recursively delete *itself*. This shuffles the
+ // output array, and we might miss iterating an unrelated Phi. So on
+ // rare occasions we repeat the loop to get all the Phis.
+ int nouts = 0;
+ while( nouts != nOuts() ) {
+ nouts = nOuts();
+ for( int i=0; i<nOuts(); i++ )
+ if( out(i) instanceof PhiNode phi && phi.nIns()==nIns() ) {
+ CodeGen.CODE.addAll(phi.in(path)._outputs);
+ CodeGen.CODE.addAll(phi.delDef(path)._outputs);
+ }
+ }
+ if( !isDead() ) delDef(path);
+ return isDead() ? CodeGen.CODE.XCTRL : this;
+ }
+
+ private int findDeadInput(int startIdx) {
+ for( int i=startIdx; i<nIns(); i++ )
+ if( in(i)._type==Type.XCONTROL )
+ return i;
+ return 0; // All inputs alive
+ }
+
+ boolean hasPhi() {
+ for( Node phi : _outputs )
+ if( phi instanceof PhiNode )
+ return true;
+ return false;
+ }
+
+ // Is there a value in-between 2 Regions? Difficult to correctly fuse
+ // without extensive testing, so just ignore.
+ boolean hasMidUser(RegionNode r) {
+ for( Node use : r._outputs ) {
+ if( use==this ) continue;
+ if( use instanceof PhiNode ) {
+ for( Node data : use._outputs ) {
+ if( !(data instanceof PhiNode phi2) || phi2.region()!=this )
+ { addDep(use); if( data!=null ) addDep(data); return true; }
+ }
+ } else
+ { addDep(use); return true; } // Control user
+ }
+ return false;
+ }
+
+
+ // Immediate dominator of Region is a little more complicated.
+ @Override public int idepth() {
+ if( validIDepth() ) return _idepth;
+ int depth=0;
+ for( Node n : _inputs )
+ if( n!=null )
+ depth = Math.max(depth,((CFGNode)n).idepth()+1);
+ return cacheIDepth(depth);
+ }
+
+ @Override public CFGNode idom(Node dep) {
+ CFGNode lca = null;
+ // Recompute from predecessors: CFG edits can change the dominator.
+ for( int i=1; i<nIns(); i++ )
+ if( !cfg(i)._type.isHigh() )
+ lca = cfg(i).domLCA(lca,dep);
+ return lca;
+ }
+
+ // True if last input is null
+ public boolean inProgress() { return nIns()>1 && in(nIns()-1) == null; }
+
+ // Never equal if inProgress
+ @Override public boolean eq( Node n ) { return !inProgress(); }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
new file mode 100644
index 00000000..182adb19
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
@@ -0,0 +1,135 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+/**
+ * The Return node has two inputs. The first input is a control node and the
+ * second is the data node that supplies the return value.
+ * <p>
+ * In this presentation, Return functions as a Stop node, since multiple <code>return</code> statements are not possible.
+ * The Stop node will be introduced in Chapter 6 when we implement <code>if</code> statements.
+ * <p>
+ * The Return's output is the value from the data node.
+ */
+public class ReturnNode extends CFGNode {
+
+ public FunNode _fun;
+
+ public ReturnNode(Node ctrl, Node mem, Node data, Node rpc, FunNode fun ) {
+ super(ctrl, mem, data, rpc);
+ _fun = fun;
+ }
+ public ReturnNode( ReturnNode ret, FunNode fun ) { super(ret); _fun = fun; }
+ @Override public Tag serialTag() { return Tag.Return; }
+
+ public Node ctrl() { return in(0); }
+ public Node mem () { return in(1); }
+ public Node expr() { return in(2); }
+ public Node rpc () { return in(3); }
+ @Override public FunNode fun() { return _fun; }
+
+ @Override
+ protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ p.p("return ");
+ if( expr()==null ) p.p("----");
+ else p.n(expr());
+ return p.p(";");
+ }
+
+ // No one unique control follows; can be many call end sites
+ @Override public CFGNode uctrl() { return null; }
+
+ @Override
+ public Type compute() {
+ Type ret = expr()._type;
+ // Recursive results can grow around Return -> CallEnd -> Add -> Phi
+ // without crossing Parm's widening point. Widen here, so FunPtr and
+ // CallEnd agree on the result, bounded by the declared signature.
+ if( ret instanceof TypeInteger ti && !ti.isHigh() && !ti.isConstant() )
+ ret = ti.same_but_slightly_wider_than();
+ return TypeTuple.make(ctrl()._type,mem()._type,ret.join(addDep(_fun).sig().ret()));
+ }
+
+ @Override public Node idealize() {
+ if( _fun.isDead() ) return null;
+
+ // Upgrade signature based on return type
+ Type ret = expr()._type;
+ TypeFunPtr fcn = _fun.sig();
+ if( ret != fcn.ret() && ret.isa(fcn.ret()) )
+ _fun.setSig(fcn.makeFrom(ret));
+
+ return null;
+ }
+
+ @Override public Parser.ParseException err() {
+ if( ctrl()._type == Type.CONTROL &&
+ expr()._type == Type.TOP )
+ return Parser.error("No defined return type",null);
+ if( ctrl()._type == Type.CONTROL &&
+ expr()._type == TypeScalar.BOT )
+ return Parser.error("Cannot return generic scalar",null);
+ // With no user constructor, <init> doubles as the public no-arg
+ // constructor. Required fields remain TOP in its private memory: a
+ // typed parser poison which deliberately emitted no Store or code.
+ if( ctrl()._type == Type.CONTROL && _fun.isInstance() ) {
+ TypeStruct self = ((TypeMemPtr)_fun.sig().arg(0))._obj;
+ if( !CodeGen.hasUserConstructor(self) && expr()._type instanceof TypeMem mem )
+ for( Field fld : self._fields ) {
+ if( !(fld._t instanceof TypeNil tn && tn.notNull()) ) continue;
+ Type actual = mem._alias==1 && mem._t instanceof TypeStruct ts
+ ? ts.field(fld._fname)._t
+ : mem._alias==fld._alias ? mem._t : Type.TOP;
+ if( actual==Type.TOP || actual instanceof TypeNil atn && atn.nullable() )
+ return Parser.error("'"+self._name+"' is not fully initialized, field '"+
+ fld._fname+"' is only partially set in the constructor",null);
+ }
+ }
+ return null;
+ }
+
+ @Override public boolean eq( Node n ) {
+ return !_fun.isDead() && !((ReturnNode)n)._fun.isDead();
+ }
+
+ // ------------
+ // MachNode specifics, shared across all CPUs
+ public String op() {
+ return _fun._frameAdjust > 0 ? "epilog" : "ret ";
+ }
+ // Correct Nodes outside the normal edges
+ public void postSelect(CodeGen code) {
+ _fun.setRet(this);
+ }
+ public RegMask regmap(int i) {
+ return i==2
+ ? CodeGen.CODE._mach.retMask(_fun.sig())
+ : CodeGen.CODE._retMasks[i];
+ }
+ public RegMask outregmap() { return null; }
+ public void encoding( Encoding enc ) { throw Utils.TODO(); }
+ public void asm(CodeGen code, SB sb) {
+ int frameAdjust = fun()._frameAdjust;
+ if( frameAdjust>0 )
+ sb.p("\n").p("rsp += #").p(frameAdjust).p("\nret ");
+ // Post code-gen, just print the "ret"
+ if( code._phase.ordinal() < CodeGen.Phase.RegAlloc.ordinal() ||
+ (code._phase.ordinal() == CodeGen.Phase.RegAlloc.ordinal() && !code._regAlloc.done()) )
+ // Prints return reg (either RAX or XMM0), RPC and then the
+ // callee-save registers.
+ for( int i=2; i<nIns(); i++ )
+ sb.p(code.reg(in(i))).p(" ");
+ // If we did not get the expected rpc, print which one we got
+ else if( code._regAlloc.regnum(rpc()) != code._mach.rpc() )
+ sb.p("[").p(code.reg(rpc())).p("]");
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter29/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java
new file mode 100644
index 00000000..65f10a79
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node;
+
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+
+
+public class RoundF32Node extends Node {
+ public RoundF32Node(Node lhs) { super(null, lhs); }
+ @Override public Tag serialTag() { return Tag.RoundF32; }
+
+ @Override protected String format() { return "((f32)%1)"; }
+
+ @Override
+ public Type compute() {
+ if (in(1)._type instanceof TypeFloat i0 && i0.isConstant() )
+ return TypeFloat.constant((float)i0.value());
+ if( in(1)._type==TypeFloat.F64 ) return TypeFloat.F32;
+ return in(1)._type;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Type t1 = lhs._type;
+
+ // RoundF32 of float
+ if( t1 instanceof TypeFloat tf && tf._sz==32 )
+ return lhs;
+
+ return null;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/SarNode.java
new file mode 100644
index 00000000..2e70f345
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/SarNode.java
@@ -0,0 +1,38 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Lexer;
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+public class SarNode extends ArithNode {
+ public SarNode(Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); }
+ @Override public Tag serialTag() { return Tag.Sar; }
+
+ @Override public String op() { return ">>"; }
+ @Override long doOp( long x, long y ) { return x >> y; }
+ @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) {
+ if( y.isConstant() ) {
+ int log = (int)y.value();
+ return TypeInteger.make(-1L<<(63-log),(1L<<(63-log))-1);
+ }
+ return TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+
+ Type t2 = rhs._type;
+
+ // Sar of 0.
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && forceInt(lhs) && lhs._type instanceof TypeInteger )
+ return lhs;
+
+ // TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5)
+
+ return super.idealize();
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new SarNode(_loc,lhs,rhs); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
new file mode 100644
index 00000000..ff3d8b4d
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -0,0 +1,720 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Lexer;
+import com.seaofnodes.print.ExprPrinter;
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.parser.Var;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.CompUnit;
+import com.seaofnodes.simple.codegen.ParseAll;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.Utils;
+import java.util.*;
+
+/**
+ * The Scope node is purely a parser helper - it tracks names to nodes with a
+ * stack of variables.
+ */
+public class ScopeNode extends MemMergeNode {
+
+ /**
+ * The control is a name that binds to the currently active control
+ * node in the graph
+ */
+ public static final String CTRL = "$ctrl";
+ public static final String ARG0 = "arg";
+ public static final String MEM0 = "$mem";
+
+ // All active/live variables in all nested scopes, all run together.
+ // Maps 1-to-1 to the _inputs array.
+ public final Ary<Var> _vars;
+
+ // Lexical scope is typed:
+ public static class Kind {
+ // Basic block scoping
+ public static class Block extends Kind { }
+ // Constructor/initializer block for an allocated struct.
+ public static class Constructor extends Block {
+ public Constructor(TypeStruct self) { _self = self; }
+ public final TypeStruct _self;
+ }
+ // Function scope
+ public static class Func extends Kind {
+ public final String _name;
+ public final boolean _constructor;
+ public Func(String name) { this(name,false); }
+ public Func(String name, boolean constructor) { _name=name; _constructor=constructor; }
+ }
+
+ public int _lexSize; // Number of Vars in this scope
+ }
+ public final Ary<Kind> _kinds;
+ // Lexical scope nesting depth
+ public int depth() { return _kinds._len; }
+ public Kind klast() { return _kinds.last(); }
+
+ // Extra guards; tested predicates and cast results
+ private final Ary<Node> _guards;
+
+ // A new ScopeNode
+ public ScopeNode() {
+ super();
+ _vars = new Ary<>(Var .class);
+ _kinds = new Ary<>(Kind.class);
+ _guards = new Ary<>(Node.class);
+ }
+
+ @Override public String label() { return "Scope"; }
+
+ @Override
+ protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ p.p("Scope[ ");
+ int j=1;
+ for( int i=0; i<nIns(); i++ ) {
+ if( j < depth() && i == _kinds.at(j)._lexSize ) { p.p("| "); j++; }
+ Var v = var(i);
+ p.p(v._type());
+ p.p(" ");
+ if( v._final ) p.p("!");
+ p.p(v._name);
+ p.p("=");
+ Node n = in(i);
+ while( n instanceof ScopeNode loop ) {
+ p.p("Lazy_");
+ n = loop.in(i);
+ }
+ p.n(n);
+ p.p(", ");
+ }
+ return p.unchar(", ").p("]");
+ }
+
+
+ public Node ctrl() { return in(0); }
+ public Node mem() { return in(1); }
+ public Var var(int i) { return _vars.at(i); }
+
+ /**
+ * The ctrl of a ScopeNode is always bound to the currently active
+ * control node in the graph, via a special name '$ctrl' that is not
+ * a valid identifier in the language grammar and hence cannot be
+ * referenced in Simple code.
+ *
+ * @param n The node to be bound to '$ctrl'
+ *
+ * @return Node that was bound
+ */
+ public <N extends Node> N ctrl(N n) { return setDef(0,n); }
+ public Node mem(Node n) { return setDef(1,n); }
+
+ public void push( Kind kind ) {
+ kind._lexSize = _vars.size();
+ _kinds.push(kind);
+ }
+
+ // Pop a lexical scope
+ public void pop() {
+ promote(null); // Promote forward references to the next outer scope
+ _pop();
+ }
+ // Pop a scope, no promote FRefs
+ public void _pop() {
+ int n = _kinds.pop()._lexSize;
+ popUntil(n); // Pop off inputs going out of scope
+ _vars.setLen(n); // Pop off variables going out of scope
+ }
+
+
+ // Look for forward references in the last lexical scope and promote to the
+ // next outer lexical scope. At the last scope declare them an error.
+ public void promote(CodeGen code) {
+ Kind kind = klast();
+ int n = kind._lexSize;
+ for( int i=n; i<nIns(); i++ ) {
+ Var v = var(i);
+ Node vn = in(i);
+ if( !v.isFRef() ) continue;
+ // Shuffle vars and inputs to move the forward reference to
+ // *before* the start of the using scope ("as if" it was not
+ // actually a forward reference).
+ for( int j=n; j<=i; j++ ) {
+ Var vtmp = var(j);
+ Node ntmp = in (j);
+ v._idx = j;
+ _vars .set(j,v );
+ _inputs.set(j,vn);
+ v = vtmp;
+ vn = ntmp;
+ }
+ kind._lexSize = ++n;
+ }
+ }
+
+
+ public boolean inFunction () { return klast() instanceof Kind.Func; }
+ // Is any enclosing scope a constructor?
+ public boolean inConstructor() { return constructorSelf() != null; }
+ // Nearest constructor self type, if any.
+ public TypeStruct constructorSelf() {
+ for( int i=depth()-1; i>=0; i-- )
+ if( _kinds.at(i) instanceof Kind.Constructor ctor )
+ return ctor._self;
+ else if( _kinds.at(i) instanceof Kind.Func func && FunNode.isInstance(func._name) )
+ return ((TypeMemPtr)var(_kinds.at(i)._lexSize).type())._obj;
+ return null;
+ }
+
+ // Find nearest enclosing function scope
+ public int enclosingFunction() {
+ for( int i=depth()-1; i>=0; i-- )
+ if( _kinds.at(i) instanceof Kind.Func func && !FunNode.isInstance(func._name) )
+ return i;
+ throw Utils.TODO("Should not reach here");
+ }
+
+ // Find nearest enclosing function or declaration scope
+ public int enclosingFuncOrDecl() {
+ for( int i=depth()-1; i>=0; i-- )
+ if( _kinds.at(i) instanceof Kind.Func func )
+ return i;
+ throw Utils.TODO("Should not reach here");
+ }
+
+ // Which lexical scope contains this Var?
+ public int kindx( Var v ) {
+ for( int i=depth()-1; i>=0; i-- )
+ if( v._idx >= _kinds.at(i)._lexSize )
+ return i;
+ return -1;
+ }
+
+ // Which lexical scope contains this Var?
+
+
+ public int kindFcnx( Var v ) {
+ for( int i=depth()-1; i>=0; i-- )
+ if( v._idx >= _kinds.at(i)._lexSize && _kinds.at(i) instanceof Kind.Func )
+ return i;
+ throw Utils.TODO("Should not reach here");
+ }
+
+ // Which function scope contains this Var?
+ public Kind kindFcn( Var v ) {
+ int kx = kindFcnx(v);
+ return kx == -1 ? null : _kinds.at(kx);
+ }
+
+ // Final fields are writable local values inside their own initializer or
+ // constructor. Final parameters, local variables, and captured bindings
+ // remain final. Use the same rule when deciding which loop Phis to build.
+ public boolean canInit(Var v) {
+ int fx=enclosingFuncOrDecl();
+ Kind.Func fun=(Kind.Func)_kinds.at(fx);
+ if( !fun._constructor || v._extern!=0 ) return false;
+ int dx=FunNode.isInstance(fun._name) ? fx : fx-1;
+ return dx>=0 && kindx(v)==dx && v._idx>=_kinds.at(dx)._lexSize+2;
+ }
+
+ private boolean loopVar(Var v) {
+ return !v._final || (canInit(v) && !v.type().isHighOrConst());
+ }
+
+ // Find name in reverse, return an index into _vars or -1. Linear scan
+ // instead of hashtable, but probably doesn't matter until the scan
+ // typically hits many dozens of variables.
+ public int find( String name ) {
+ for( int i=_vars.size()-1; i>=0; i-- )
+ if( var(i)._name.equals(name) )
+ return i;
+ return -1;
+ }
+
+ /**
+ * Create a new variable name in the current scope
+ */
+ public boolean define( String name, Type declaredType, boolean xfinal, Node init, Lexer loc ) {
+ assert _kinds.isEmpty() || name!=MEM0 ; // Later scopes do not define memory
+ if( depth() > 0 )
+ for( int i=_vars.size()-1; i>=klast()._lexSize; i-- ) {
+ Var n = var(i);
+ if( n._name.equals(name) ) {
+ if( !n.isFRef() ) return false; // Double define
+ FRefNode fref = (FRefNode)in(n._idx); // Get forward ref
+ if( !xfinal || !declaredType.isConstant() ) throw fref.err(); // Must be a final constant
+ n.defFRef(declaredType,xfinal,loc); // Declare full correct type, final, source location
+ setDef(n._idx,fref.addDef(init)); // Set FRef to defined; tell parser also
+ return true;
+ }
+ }
+ Var v = new Var(nIns(),name,declaredType,xfinal,loc,init==CodeGen.CODE.XCTRL);
+ _vars.add(v);
+ // Creating a forward reference
+ if( init==CodeGen.CODE.XCTRL )
+ //init = new FRefNode(v).init();
+ throw Utils.TODO();
+ addDef(init);
+ return true;
+ }
+
+ // Normal lookup was tried and failed. Append a forward ref in the current
+ // lexical scope. Scope pops promote it outwards "as if" the final constant
+ // had been declared before the using scope.
+
+ public Var defineFRef( String id, Type t, boolean xfinal, Lexer loc ) {
+ Var var = new Var(nIns(),id,t,xfinal,loc,true);
+ FRefNode fref = new FRefNode(id,loc).init();
+ fref._type = fref._con = t;
+ _vars.add(var);
+ addDef(fref);
+ return var;
+ }
+
+
+ // Parser state carries one complete, conservative memory value. Precise
+ // alias partitions belong to the graph and are introduced by peepholes.
+ public Node mem( int alias ) {
+ assert alias == 1;
+ return in(update(var(1),null));
+ }
+ public void mem( int alias, Node st ) {
+ assert alias == 1;
+ update(var(1),st);
+ }
+
+
+ /**
+ * Lookup a name in all scopes starting from most deeply nested.
+ *
+ * @param name Name to be looked up
+ * @return null if not found, or the implementing Node
+ */
+ public Var lookup( String name ) {
+ int idx = find(name);
+ // -1 is missed in all scopes, not found
+ return idx == -1 ? null : update(var(idx),null);
+ }
+
+ /**
+ * Redefine an existing name
+ *
+ * @param name Name being redefined
+ * @param n The node to bind to the name
+ */
+ public void update( String name, Node n ) {
+ int idx = find(name);
+ assert idx>=0;
+ update(var(idx),n);
+ }
+
+ public Var update( Var v, Node st ) {
+ Node old = in(v._idx);
+ if( old instanceof ScopeNode loop ) {
+ // Lazy Phi!
+ Node def = loop.in(v._idx);
+ old = def instanceof PhiNode phi && loop.ctrl()==phi.region()
+ // Loop already has a real Phi, use it
+ ? def
+ // Set real Phi in the loop head
+ // The phi takes its one input (no backedge yet) from a recursive
+ // lookup, which might have insert a Phi in every loop nest.
+ : loop.setDef(v._idx,PhiNode.make(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).init());
+ setDef(v._idx,old);
+ }
+ //assert !v._final || st==null;
+ if( st!=null ) setDef(v._idx,st); // Set new value
+ return v;
+ }
+
+ /**
+ * Duplicate a ScopeNode; including all levels, up to Nodes. So this is
+ * neither shallow (would dup the Scope but not the internal HashMap
+ * tables), nor deep (would dup the Scope, the HashMap tables, but then
+ * also the program Nodes).
+ * <p>
+ * If the {@code loop} flag is set, the edges are filled in as the original
+ * Scope, as an indication of Lazy Phis at loop heads. The goal here is to
+ * not make Phis at loop heads for variables which are never touched in the
+ * loop body.
+ * <p>
+ * The new Scope is a full-fledged Node with proper use<->def edges.
+ */
+ public ScopeNode dup() { return dup(false); }
+ public ScopeNode dup(boolean loop) {
+ ScopeNode dup = new ScopeNode();
+ // Our goals are:
+ // 1) duplicate the name bindings of the ScopeNode across all stack levels
+ // 2) Make the new ScopeNode a user of all the nodes bound
+ // 3) Ensure that the order of defs is the same to allow easy merging
+ dup._vars .addAll(_vars );
+ dup._kinds .addAll(_kinds );
+ dup._guards .addAll(_guards );
+ // The dup'd guards all need dup'd keepers, to keep proper accounting
+ // when later removing all guards
+ for( Node n : _guards )
+ if( !(n instanceof CFGNode) )
+ n.keep();
+ dup.addDef(ctrl()); // Control input is just copied
+
+ // Memory is one ordinary parser-state value. At loop heads the
+ // Scope sentinel triggers the same lazy-Phi machinery as variables.
+ dup.addDef(loop ? this : mem());
+
+ // Copy of other inputs
+ for( int i=2; i<nIns(); i++ )
+ // For lazy phis on loops we use a sentinel
+ // that will trigger phi creation on update
+ dup.addDef(loop && loopVar(var(i)) ? this : in(i));
+ return dup;
+ }
+
+ /**
+ * Merges the names whose node bindings differ, by creating Phi node for such names
+ * The names could occur at all stack levels, but a given name can only differ in the
+ * innermost stack level where the name is bound.
+ *
+ * @param that The ScopeNode to be merged into this
+ * @return A new node representing the merge point
+ */
+ public RegionNode mergeScopes(ScopeNode that, Lexer loc) {
+ RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep());
+ this._merge(that,r);
+ that.kill(); // Kill merged scope
+ CodeGen.CODE.add(r);
+ return r.unkeep();
+ }
+
+ public void _merge(ScopeNode that, RegionNode r) {
+ _merge(that,r,nIns());
+ }
+ public void _merge(ScopeNode that, RegionNode r, int max ) {
+ for( int i = 1; i < max; i++)
+ if( in(i) != that.in(i) ) { // No need for redundant Phis
+ // If we are in lazy phi mode we need to a lookup
+ // by name as it will trigger a phi creation
+ Var v = var(i);
+ Node lhs = this.in(this.update(v,null));
+ Node rhs = that.in(that.update(v,null));
+ setDef(i, PhiNode.make(v._name, v.type(), r, lhs, rhs).peephole());
+ }
+ }
+
+ // Balance arms of an IF. Extra lonely defs are thrown: "if(pred) int x;".
+ // Forward refs are copied to the other side, "as if" they were there all along.
+ // 'this' scope is the default, 'scope' might have frefs.
+ public void balanceIf( ScopeNode scope ) {
+ if( nIns()==scope.nIns() )
+ return; // Fast-path cutout
+ int kidx = enclosingFunction();
+ assert _kinds.at(kidx) == scope._kinds.at(kidx);
+ if( kidx==0 ) kidx = 1;
+ for( int i = _kinds.at(kidx-1)._lexSize; i < scope.nIns(); i++ ) {
+ Var n = scope.var(i);
+ if( i<nIns() && var(i)==n ) continue;
+ if( n.isFRef() ) { // RHS has forward refs
+ _vars.insert(n,i);
+ insertDef(i,scope.in(i));
+ } else
+ throw Parser.error("Cannot define a '"+n._name+"' on one arm of an if",n._loc);
+ }
+ }
+
+ public void balanceLoopFRefs( ScopeNode scope ) {
+ for( int i = nIns(); i < scope.nIns(); i++ ) {
+ Var n = scope.var(i);
+ if( n.isFRef() ) { // Loop body has forward refs
+ Var v = new Var(nIns(),n._name,n.type(),n._final,n._loc,true);
+ v._uninit = n._uninit;
+ _vars.add(v);
+ addDef(scope.in(i));
+ }
+ }
+ }
+
+
+ // ------------------------------------------------
+ // peephole the backedge scope into this loop head scope
+ // We set the second input to the phi from the back edge (i.e. loop body)
+ public void endLoop(ScopeNode back, ScopeNode exit ) {
+ Node ctrl = ctrl();
+ assert ctrl instanceof LoopNode loop && loop.inProgress();
+ ctrl.setDef(2,back.ctrl());
+
+ this._endLoop( this, back, exit);
+ back.kill(); // Loop backedge is dead
+ // Now one-time do a useless-phi removal
+ this._useless();
+ }
+
+ // Fill in the backedge of any inserted Phis
+ void _endLoop( ScopeNode scope, Node back, Node exit ) {
+ for( int i=1; i<nIns(); i++ ) {
+ if( !loopVar(var(i)) ) continue;
+ Node n = in(i);
+ if( var(i).type().isHighOrConst() && !(n instanceof PhiNode phi && phi.inProgress()) ) { // Cannot lift higher than a constant, so no Phi
+ Node con = ConstantNode.make(var(i).type()).init();
+ n.subsume(con);
+ n = con;
+ } else if( back.in(i) != scope ) {
+ PhiNode phi = (PhiNode)n;
+ assert phi.region()==scope.ctrl() && phi.in(2)==null;
+ phi.setDef(2,back.in(i)); // Fill backedge
+ }
+ if( exit.in(i) == scope ) // Replace a lazy-phi on the exit path also
+ exit.setDef(i,n);
+ }
+ }
+
+ // ------------------------------------------------
+ // Up-casting: using the results of an If to improve a value.
+ // E.g. "if( ptr ) ptr.field;" is legal because ptr is known not-null.
+ //
+ // This operation is lexical, not a full semantic theorem prover, and thus
+ // its behavior changes the language spec. Optimizations that hide the
+ // lexical structure (e.g. constant folding control flow) can hide what is
+ // guarded or how the guard is applied, making it somewhat fragile. Several
+ // good use cases are covered here, but not everything you might like.
+ //
+ // Simple lexical facts work well:
+ //
+ // ```
+ // if( ptr )
+ // return ptr.field; // `ptr` is guarded non-null here.
+ //
+ // if( a && a.ptr )
+ // return a.ptr.x; // `a` is guarded while parsing the RHS load,
+ // // and remains guarded on the true arm.
+ // ```
+ //
+ // Short-circuit expressions are lowered as nested Phis. We recursively
+ // decompose the recognized Phi shape, so chains such as:
+ //
+ // ```
+ // if( x && y && z )
+ // use(x,y,z);
+ // ```
+ //
+ // discover the nested `x && y` and then `x`, `y`, and `z`; this is not
+ // merely a one-level lookup. Likewise, nested tests carry lexical guards
+ // inwards:
+ //
+ // ```
+ // if( x && y )
+ // if( z && a )
+ // if( x.ptr && z.ptr )
+ // ...
+ // ```
+ //
+ // Here `x` is already guarded while parsing the outer true arm, `z` while
+ // parsing the inner true arm, and both are available before the matching
+ // loads. The later post-test decomposition is just a convenience for
+ // values still schedulable at the post-test control.
+ //
+ // The hard limit is that a semantic implication is not always a
+ // schedulable Node. For `(x && y)`, the true side implies that `y` was
+ // true, but `y` might be a value computed only on the RHS control arm:
+ //
+ // ```
+ // val ptr = ...;
+ // if( ptr && (ptr=fcn() ? 0 : arg ) {}
+ // ```
+ //
+ // A Guard pinned after the Phi merge and using the branch-local `ptr`
+ // value would give that value a use above its defining arm. GCM then has
+ // no legal block between the value's early control and the LCA of its
+ // uses. Thus `_addGuards` first checks that the predicate's effective
+ // early CFG dominates the control where the Guard would be pinned. If it
+ // does not, we skip that guard even if the fact is semantically true.
+ //
+ // A more complete answer would need a richer logical/proof node, or would
+ // need to rematerialize predicates on the guarded path instead of using
+ // branch-local nodes directly. Plain GuardNodes are intentionally kept to
+ // facts represented by values already available at the guard control.
+ //
+ // TODO: Make this more robust for shapes like:
+ // ```
+ // if( !ary || !ary# ) exit(); // facts about ary are lost, despite the early exit
+ // for( ...; ary# ) ...;
+ // ```
+ // Here the guard ends up guarding the whole expression (both ary and ary#
+ // are not-null), but loses that ary is not-null. A better answer might be
+ // a dedicated logical expression node, or some sort of gather-of-checked
+ // values to Guard post-test.
+
+ public void addGuards( CFGNode ctrl, Node pred, boolean invert ) {
+ _guards.add(ctrl); // Marker between guard sets
+ // add pred & its cast to the normal input list, with special Vars
+ if( pred==null || pred.isDead() )
+ return; // Dead, do not add any guards
+ _addGuards(ctrl,pred,invert);
+ }
+
+ private void _addGuards( CFGNode ctrl, Node pred, boolean invert ) {
+ if( pred==null || pred.isDead() )
+ return;
+ CFGNode early = CFGNode.earlyCFG(pred);
+ if( early != null && !early.dominates(ctrl,null) )
+ return;
+ // A single negation is handled below. For !!p (including p != null)
+ // or a negated short-circuit Phi, also discover the underlying facts.
+ if( pred instanceof NotNode not &&
+ (not.in(1) instanceof NotNode || not.in(1) instanceof PhiNode) ) {
+ pred.keep(); // Recursive peepholes may rediscover this Not.
+ _addGuards(ctrl,not.in(1),!invert);
+ pred.unkeep();
+ }
+ // Short-circuit logic is represented by a Phi. For `a || b` being
+ // false, or `a && b` being true, both individual operands have the
+ // same proven value as the whole expression. The "skipped RHS" path
+ // is the Region input whose Phi value is the controlling If predicate.
+ if( pred instanceof PhiNode phi && phi.nIns()==3 && phi.region() instanceof RegionNode r && r.nIns()==3 )
+ for( int i=1; i<3; i++ )
+ if( r.in(i) instanceof CProjNode prj && prj.ctrl() instanceof IfNode iff ) {
+ Node skip = shortCircuitSkippedPred(phi.in(i),iff.pred(),prj,invert);
+ if( skip != null ) {
+ _addGuards(ctrl,skip,invert);
+ _addGuards(ctrl,phi.in(3-i),invert);
+ break;
+ }
+ }
+ // Invert the If conditional
+ if( invert )
+ pred = pred instanceof NotNode not ? not.in(1) : CodeGen.CODE.add(new NotNode(pred).peephole());
+ Node zeroPred = pred instanceof NotNode not ? not.in(1) : null;
+ // This is a zero/null test.
+ // Compute the positive test type.
+ _addGuard(true,ctrl,pred);
+
+ // Compute the negative test type.
+ if( zeroPred != null )
+ _addGuard(false,ctrl,zeroPred);
+ }
+
+ private static Node shortCircuitSkippedPred( Node val, Node pred, CProjNode prj, boolean invert ) {
+ if( val == null )
+ return null;
+ if( val == pred )
+ return (prj._idx==0)==invert ? val : null;
+ if( val instanceof NotNode not && not.in(1)==pred )
+ return (prj._idx==1)==invert ? val : null;
+ if( val._type == TypeInteger.TRUE && invert )
+ return prj._idx==0 ? pred : CodeGen.CODE.add(new NotNode(pred).peephole());
+ if( val._type == TypeInteger.FALSE && !invert )
+ return prj._idx==1 ? pred : CodeGen.CODE.add(new NotNode(pred).peephole());
+ return null;
+ }
+
+ private void _addGuard(boolean nonZero, Node ctrl, Node pred) {
+ Node guard = new GuardNode(nonZero,ctrl,pred).peephole();
+ if( guard != pred ) {
+ pred.keep();
+ guard.keep();
+ _guards.add(pred);
+ _guards.add(guard);
+ replace(pred,guard,nonZero);
+ }
+ }
+
+
+ // Remove matching pred/cast pairs from this guarded region.
+ public void removeGuards( Node ctrl ) {
+ assert ctrl instanceof CFGNode;
+ // Pop the guards up to this region's marker.
+ while( true ) {
+ Node g = _guards.pop();
+ if( g == ctrl ) break;
+ if( g instanceof CFGNode ) continue;
+ g .unkill(); // Pop/kill cast
+ _guards.pop().unkill(); // Pop/kill pred
+ }
+ }
+
+ // If we find a guarded instance of pred, replace with the upcasted version
+ public Node upcastGuard( Node pred ) {
+ // If finding an instanceof pred, replace with cast.
+ // Otherwise, just pred itself.
+ int i = _guards._len;
+ while( i > 0 ) {
+ Node cast = _guards.at(--i);
+ if( cast instanceof CFGNode ) continue; // Marker between guard sets
+ Node xpred = _guards.at(--i);
+ if( xpred == pred )
+ return cast;
+ }
+ return pred;
+ }
+
+ // Kill guards also
+ @Override public void kill() {
+ for( Node n : _guards )
+ if( !(n instanceof CFGNode) )
+ n.unkill();
+ _guards.clear();
+ // Can have lazy uses remaining
+ if( isUnused() )
+ super.kill();
+ }
+
+
+ private void replace( Node old, Node cast, boolean nonZero ) {
+ assert old!=null && old!=cast;
+ for( int i=0; i<nIns(); i++ )
+ if( in(i)==old || nonZero && nullCheckedPhi(in(i),old) )
+ setDef(i,cast);
+ }
+
+ private static boolean nullCheckedPhi( Node n, Node old ) {
+ if( !(n instanceof PhiNode phi) || phi.nIns()!=3 )
+ return false;
+ if( phi.in(1)==null || phi.in(2)==null )
+ return false;
+ int zero = phi.in(1)._type == phi.in(1)._type.makeZero() ? 1 :
+ phi.in(2)._type == phi.in(2)._type.makeZero() ? 2 : -1;
+ if( zero == -1 )
+ return false;
+ Node val = phi.in(3-zero);
+ return val==old ||
+ val instanceof GuardNode guard && guard._nonZero && guard.in(1)==old;
+ }
+
+ // ------------------------------------------------------------------------
+ // Insert a top-level public external symbol, "as-if" some #include/import
+ // was executed at the top of tile.
+ public Var insertExtern( ExternNode ext ) {
+ // Size of top-level scope
+ int lexSize = _kinds.at(0)._lexSize;
+ Var var = new Var(lexSize,ext._extern,ext._con,true,null);
+ insert(var,ext,0);
+ return var;
+ }
+
+ // ------------------------------------------------------------------------
+ // Insert a Var with Node n in the lex scope just prior to kidx
+ private void insert( Var var, Node n, int kidx ) {
+ // Insert just prior to scope kidx
+ int nidx = _kinds.at(kidx)._lexSize;
+ _vars.insert(var,nidx);
+ insertDef(nidx,n);
+ // All outer Kinds lexical offsets bump by one
+ for( int i = kidx; i < depth(); i++ )
+ _kinds.at(i)._lexSize++;
+ // All Vars outside the innermost lexical scope bump out one
+ for( int i=nidx+1; i<_vars._len; i++ )
+ var(i)._idx++;
+ }
+
+ // Save the existing Node state to an array
+ public Node[] save() {
+ Node[] olds = new Node[nIns()];
+ for( int i=0; i<nIns(); i++ )
+ olds[i] = in(i)==null ? null : in(i).keep();
+ return olds;
+ }
+
+ // Restore saved Node state back into scope
+ public void restore( Node[] olds ) {
+ assert olds.length<=nIns();
+ for( int i=0; i<olds.length; i++ )
+ setDef(i,olds[i]==null ? null : olds[i].unkeep());
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ShlNode.java
new file mode 100644
index 00000000..fffc71f0
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ShlNode.java
@@ -0,0 +1,50 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Lexer;
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+public class ShlNode extends ArithNode {
+ public ShlNode(Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); }
+ @Override public Tag serialTag() { return Tag.Shl; }
+
+ @Override public String op() { return "<<"; }
+ @Override long doOp( long x, long y ) { return x << y; }
+ @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) {
+ if( x == TypeInteger.ZERO )
+ return TypeInteger.ZERO;
+ if( y.isConstant() ) {
+ int shf = (int)y.value();
+ // If no overflow, shift endpoints
+ if( !(((x._min<<shf)>>shf) != x._min ||
+ ((x._max<<shf)>>shf) != x._max ) )
+ return TypeInteger.make(x._min<<shf,x._max<<shf);
+ }
+ return TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+
+ if( rhs._type instanceof TypeInteger shl && shl.isConstant() ) {
+ // Shl of 0.
+ if( (shl.value()&63)==0 && forceInt(lhs) && lhs._type instanceof TypeInteger )
+ return lhs;
+ // (x + c) << i => (x << i) + (c << i)
+ if( lhs instanceof AddNode add && addDep(add).in(2)._type instanceof TypeInteger c && c.isConstant() ) {
+ long sum = c.value() << shl.value();
+ if( Integer.MIN_VALUE <= sum && sum <= Integer.MAX_VALUE )
+ return new AddNode(new ShlNode(_loc,add.in(1),rhs).peephole(), CodeGen.CODE.con(sum) );
+ }
+ }
+
+ // TODO: x << 3 << (y ? 1 : 2) ==> x << (y ? 4 : 5)
+
+ return super.idealize();
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new ShlNode(_loc,lhs,rhs); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ShrNode.java
new file mode 100644
index 00000000..1bf86f90
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ShrNode.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Lexer;
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+public class ShrNode extends ArithNode {
+ public ShrNode(Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); }
+ @Override public Tag serialTag() { return Tag.Shr; }
+
+ @Override public String op() { return ">>>"; }
+ @Override long doOp( long x, long y ) { return x >>> y; }
+ @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) {
+ return x == TypeInteger.ZERO ? x : TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ Type t2 = rhs._type;
+
+ // Shr of 0.
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && forceInt(lhs) && lhs._type instanceof TypeInteger )
+ return lhs;
+
+ // TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5)
+
+ return super.idealize();
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new ShrNode(_loc,lhs,rhs); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/SplitNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/SplitNode.java
new file mode 100644
index 00000000..3042f857
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/SplitNode.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.LRG;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.SB;
+
+public abstract class SplitNode extends MachConcreteNode {
+ public final LRG _lrg; // Live range for split
+ public final String _kind; // Kind of split
+ public final byte _round; // Debugging info for which RegAlloc round
+ public SplitNode(LRG lrg,String kind, byte round, Node[] nodes) { super(nodes); _lrg=lrg; _kind = kind; _round = round; }
+ // A register-only transfer may need another split to reach stack storage.
+ public boolean isNonStackSplit() { return false; }
+ @Override public String op() { return "mov"; }
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ p.p("mov(");
+ if( in(1) == null ) p.p("---");
+ else p.n(in(1));
+ return p.close();
+ }
+ public FunNode fun(Encoding enc) { return enc._code._regAlloc.lrg(this)._fun; }
+ @Override public Type compute() { return in(0)._type; }
+ @Override public Node idealize() { return null; }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
+ }
+ @Override public String comment() { return _kind + " #"+ _round; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/StartCUNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/StartCUNode.java
new file mode 100644
index 00000000..cd51f603
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/StartCUNode.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Serialize;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+// Start of a single compilation unit, distinct from the outer whole-program Start.
+public class StartCUNode extends StartNode {
+ public final String _fname;
+ public StartCUNode(StartNode start, StopCUNode stop, Type arg, String fname) { super(start,stop,arg); _fname = fname; }
+ public StartCUNode(StartCUNode start) { super(start); _fname = start._fname; }
+ @Override public Tag serialTag() { return Tag.StartCU; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes ) {
+ baos.packed2(types.get(_arg));
+ baos.packed2(strs.get(_fname));
+ }
+ static Node make( BAOS bais, String[] strs, Type[] types ) {
+ return new StartCUNode(null,null,types[bais.packed2()],strs[bais.packed2()]);
+ }
+
+ @Override public CFGNode cfg0() { return (CFGNode)in(0); }
+
+ // IDom is cfg0() and depth is always Start+1 which is 0+1 or 1
+ @Override public int idepth() { return 1; }
+ @Override public CFGNode idom(Node dep) { return cfg0(); }
+ @Override public void gather( HashMap<String,Integer> strs ) {
+ Serialize.gather(strs,_fname);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/StartNode.java
new file mode 100644
index 00000000..78bd8219
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/StartNode.java
@@ -0,0 +1,81 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Utils;
+import java.util.Arrays;
+import java.util.HashSet;
+
+/**
+ * The Start node represents the external world calling, be it calling the
+ * entry <clinit> or just some linked caller calling.
+ * <p>
+ * Start initially has 1 input (arg) from outside and the initial control.
+ * In ch10 we also add mem aliases as structs get defined; each field in struct
+ * adds a distinct alias to Start's tuple.
+ * <p>
+ * By ch25, Start is the external world (or linker) calling with everything
+ * that has escaped. Start is NOT the entry <clinit> itself, which can assume
+ * no memory nor fidxs exist nor escaped. Start gets escaped things from Stop;
+ * together these close the Grand Cycle between the code we know and the code
+ * we don't.
+ */
+public class StartNode extends LoopNode implements MultiNode {
+
+ final Type _arg;
+
+ public StartNode(StartNode start, StopNode stop, Type arg) { super(null,start,stop); _arg = arg; }
+ public StartNode(StartNode start) { super(start); _arg = start==null ? null : start._arg; }
+ @Override public Tag serialTag() { return Tag.Start; }
+ public Type arg() { return _arg; }
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ return p.p(label());
+ }
+
+ @Override public CFGNode cfg0() { return null; }
+
+ // Get the one control following; error to call with more than one e.g. an
+ // IfNode or other multi-way branch. For Start, it's the class init for
+ // this compilation unit.
+ @Override public CFGNode uctrl() {
+ // Find module.<clinit>, it's the start.
+ CFGNode C = null;
+ for( Node use : _outputs )
+ if( use instanceof FunNode fun && CodeGen.CODE.entryClinitName().equals(fun._name) )
+ { assert C==null; C = fun; }
+ if( C==null )
+ for( FunNode fun : CodeGen.CODE._linker )
+ if( fun!=null && !fun.isDead() && CodeGen.CODE.entryClinitName().equals(fun._name) )
+ { assert C==null; C = fun; }
+ return C;
+ }
+
+ boolean escapedFIDX(int fidx) {
+ if( !(_type instanceof TypeTuple tt) )
+ return !_type.isHigh(); // TOP never escapes, BOTTOM always escapes
+ TypeMem tmem = (TypeMem)tt._types[1];
+ if( tmem == TypeMem.TOP ) return false;
+ return XInt.bit(tmem._escFs,fidx);
+ }
+
+ @Override public TypeTuple compute() {
+ StopNode stop = (StopNode)in(1);
+ TypeMem tmem = stop._type instanceof TypeTuple tt
+ ? tt.mem()
+ : stop._type.isHigh() ? TypeMem.START : TypeMem.BOT;
+ assert tmem._alias==1 && !tmem._one;
+ return TypeTuple.make(Type.CONTROL,tmem,_arg);
+ }
+
+ @Override public Node idealize() { return null; }
+
+ // No immediate dominator, and idepth==0
+ @Override public int idepth() { return 0; }
+ @Override public CFGNode idom(Node dep) { return null; }
+
+ @Override public boolean eq( Node n ) { return true; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/StopCUNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
new file mode 100644
index 00000000..a6116e93
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
@@ -0,0 +1,134 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+// Stop of a single compilation unit, distinct from the outer whole-program Stop.
+public class StopCUNode extends StopNode {
+ public StopCUNode() { super(); }
+ public StopCUNode(StopNode stop) { super(stop); }
+ @Override public Tag serialTag() { return Tag.StopCU; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); }
+ static Node make( BAOS bais ) {
+ StopCUNode stop = new StopCUNode();
+ stop.setDefX(bais.packed1()-1,null);
+ return stop;
+ }
+
+ public StartCUNode start() {
+ for( Node use : _outputs )
+ if( use instanceof StartCUNode start )
+ return start;
+ return null;
+ }
+
+ @Override
+ public TypeTuple compute() {
+ // During Parsing, new Stops and Returns are being added. This Stop
+ // cannot know if more are coming, so must assume the worst.
+ if( CodeGen.CODE._phase ==null || CodeGen.CODE._phase == CodeGen.Phase.Parse )
+ return TypeTuple.STATE;
+
+ StartCUNode start = start();
+ // Memory start from all returns
+ TypeMem xmem = priorEscapes(_type instanceof TypeTuple old ? old.mem() : _type);
+
+ for( Node def : _inputs ) {
+ ReturnNode ret = (ReturnNode)def;
+ if( ret._type == Type.TOP ) continue;
+ if( !(ret._type instanceof TypeTuple tret) )
+ return TypeTuple.STATE; // Must be Type.BOTTOM
+
+ FunNode fun = ret.fun();
+ boolean noUserCtor = fun.isInstance() &&
+ !CodeGen.hasUserConstructor(((TypeMemPtr)fun.sig().arg(0))._obj);
+ // Only entry initializers seed escape discovery. Constructors
+ // and methods are discovered through escaping class/instance fields.
+ boolean pub = fun.isRoot();
+ TypeFunPtr sig = fun.sig();
+ // If public init, the function escapes and the related class escapes
+ if( pub ) {
+ xmem = xmem.escapes(sig);
+ // Classes also escape their public clazz pointer, which then
+ // escapes all their public fields. Private fields do not
+ // escape, and are only available from the local class code.
+ if( fun.isClz() )
+ xmem = xmem.escapesAliases(sig._sig[0]);
+ }
+
+ // The instance <init> Start edge supplies conservative pre-Opto
+ // inputs, but its private-memory result is consumed only by the
+ // allocation sequence and is never an externally visible return.
+ if( fun.isInstance() && !noUserCtor ) continue;
+
+ // Does this function escape? If not, then no need to MEET
+ // here as its effects are not visible to the outside world.
+ addDepForwards(start); // Check again if function escapes
+ if( pub || start.escapedFIDX(sig.fidx()) ) {
+ // External callers already hold their object arguments, so
+ // writes through those arguments expose addresses too.
+ for( Node use : fun._outputs )
+ if( use instanceof ParmNode p && p._idx>=2 )
+ xmem = publicBottom(xmem.escapes(addDepForwards(p)._type),xmem);
+ if( tret.ctl().isHigh() ) continue; // Return not reachable
+ // Capture (precisely?) all escaping pointer aliases and fidxs.
+ // Escaped fidxs means the linked world can call that function;
+ // escaped aliases means the linked world can R/W those aliases.
+ xmem = publicBottom(xmem.escapes(tret.ret()),xmem);
+ xmem = (TypeMem)xmem.meet(publicBottom(tret.mem(),xmem));
+ }
+ }
+ // All units share escapes from calls and unloaded bodies, including
+ // paths with no Return. Revisit only when that shared result changes.
+ GlobalEscapeNode global = CodeGen.CODE._globalEscapes;
+ if( global!=null && !global.isDead() )
+ xmem = publicBottom(xmem.escapes(addDepForwards(global)._type),xmem);
+ assert xmem._alias==1 && !xmem._one;
+ return TypeTuple.make(Type.CONTROL,xmem,Type.BOTTOM);
+ }
+
+ // FULL escape sets arise from global BOTTOM. Across a compilation-unit
+ // boundary global BOTTOM exposes every public field, while retaining any
+ // private escapes already discovered precisely.
+ static TypeMem publicBottom(TypeMem mem, TypeMem prior) {
+ // The pessimistic Iter pass naturally lifts transient FULL inputs to
+ // precise finite sets. Replacing FULL early there would let a later
+ // input refinement add bits, reversing Iter's monotonic direction.
+ // This normalization is needed only for Opto's optimistic grand cycle.
+ if( CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() ||
+ CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Lower.ordinal() ) return mem;
+ int[] escFs = XInt.isHigh(mem._escFs)
+ ? XInt.meet(prior._escFs,CodeGen.CODE.publicFIDXs())
+ : mem._escFs;
+ int[] escAs = XInt.isHigh(mem._escAs)
+ ? XInt.meet(prior._escAs,CodeGen.CODE.publicAliases())
+ : mem._escAs;
+ return escFs==mem._escFs && escAs==mem._escAs ? mem : mem.makeFrom(escFs,escAs);
+ }
+
+ // In Opto, a precise escape set can widen to FULL. Public-only
+ // normalization must retain private bits discovered on an earlier step.
+ static TypeMem priorEscapes(Type old) {
+ TypeMem mem = TypeMem.START;
+ if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() &&
+ CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.Lower.ordinal() &&
+ old instanceof TypeMem prior )
+ mem = mem.makeFrom(XInt.meet(mem._escFs,prior._escFs),
+ XInt.meet(mem._escAs,prior._escAs));
+ return mem;
+ }
+
+ @Override
+ public Node idealize() {
+ int len = nIns();
+ for( int i=0; i<nIns(); i++ )
+ if( in(i) instanceof ReturnNode ret && addDep(ret.fun()).isDead() )
+ delDef(i--);
+ if( len != nIns() ) return this;
+ return null;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/StopNode.java
new file mode 100644
index 00000000..6d453324
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/StopNode.java
@@ -0,0 +1,103 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Serialize;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+// A collection of function ReturnNodes for a compilation unit.
+public class StopNode extends CFGNode {
+
+ public StopNode() {
+ super();
+ // Edges may still be missing, including when loading a StopCU.
+ _type = TypeTuple.STATE;
+ }
+ public StopNode(StopNode stop) { super(stop); }
+ @Override public Tag serialTag() { return Tag.Stop; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(Serialize.nIns(this,anodes)); }
+ static Node make( BAOS bais ) {
+ StopNode stop = new StopNode();
+ stop.setDefX(bais.packed1()-1,null);
+ return stop;
+ }
+
+ @Override
+ protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ // For the sake of many old tests, and single value prints as "return val"
+ ReturnNode ret1 = null;
+ for( Node n : _inputs ) {
+ if( n instanceof ReturnNode ret ) {
+ if( ret1 != null ) { ret1=null; break; }
+ ret1 = ret;
+ }
+ }
+ if( ret1!=null ) return p.n(ret1);
+
+ p.p("Stop[ ");
+ for( Node n : _inputs )
+ if( n instanceof ReturnNode ret ) {
+ String name = ret.fun()._name;
+ if( name== null || !name.startsWith("sys.") )
+ p.n(ret).p(" ");
+ }
+ return p.p("]");
+ }
+
+ @Override public boolean blockHead() { return true; }
+
+
+ // If a single Return, return it.
+ // Otherwise, null because ambiguous.
+ public ReturnNode ret() {
+ ReturnNode ret1 = null;
+ for( Node n : _inputs ) {
+ // TODO: remove isPublic
+ if( n instanceof ReturnNode ret && ret.fun().isPublic() ) {
+ if( ret1 != null ) return null; // Ambiguous
+ ret1 = ret;
+ }
+ }
+ return ret1;
+ }
+
+ @Override
+ public TypeTuple compute() {
+ // During Parsing, new Stops and Returns are being added. This Stop
+ // cannot know if more are coming, so must assume the worst.
+ if( CodeGen.CODE._phase ==null || CodeGen.CODE._phase == CodeGen.Phase.Parse )
+ return TypeTuple.STATE;
+
+ // Just meet-over-inputs.
+ TypeTuple tt = TypeTuple.STOP_HIGH;
+ for( Node def : _inputs )
+ tt = (TypeTuple)tt.meet(def._type);
+ assert tt.mem()._alias==1 && !tt.mem()._one;
+ return tt;
+ }
+
+ @Override public Node idealize() {
+ int len = nIns();
+ for( int i=0; i<nIns(); i++ )
+ if( in(i).nIns()==0 )
+ delDef(i--);
+ if( len != nIns() ) return this;
+ return null;
+ }
+
+ @Override public int idepth() {
+ if( validIDepth() ) return _idepth;
+ int d=0;
+ for( Node ret : _inputs )
+ // All public results are hooked on the Stop, including data
+ // values, so must check CFG
+ if( ret instanceof CFGNode cfg )
+ d = Math.max(d,cfg.idepth()+1);
+ return cacheIDepth(d);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/StoreNode.java
new file mode 100644
index 00000000..96e15a58
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -0,0 +1,341 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.parser.Lexer;
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.GlobalBits;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+/**
+ * Store represents setting a value to a memory based object, in chapter 10
+ * this means a field inside a struct.
+ */
+public class StoreNode extends MemOpNode {
+
+ final boolean _init; // Initializing writes are allowed to write null
+ // Semantic store width in bytes. Zero is the sole undecided state;
+ // otherwise hardware and ideal Stores are the 1/2/4/8-byte variants.
+ private byte _size;
+
+ /**
+ * @param name The struct field we are assigning to
+ * @param mem The memory alias node - this is updated after a Store
+ * @param ptr The ptr to the struct base where we will store a value
+ * @param off The offset inside the struct base
+ * @param value Value to be stored
+ */
+ public StoreNode(Lexer loc, String name, int alias, Type decl, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init) {
+ this(loc,name,alias,decl,ctrl,mem,ptr,off,value,init,(byte)0);
+ }
+ private StoreNode(Lexer loc, String name, int alias, Type decl, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init, byte size) {
+ super(loc, name, alias, false, decl, ctrl, mem, ptr, off, value);
+ _init = init;
+ _size = size;
+ }
+ StoreNode( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases ) {
+ super(bais,strs,types,fileAliases,aliases,false);
+ addDef(null);
+ _init = bais.read() != 0;
+ _size = (byte)bais.read();
+ }
+ @Override public Tag serialTag() { return Tag.Store; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ super.packed(baos,strs,types, anodes );
+ baos.write(_init ? 1 : 0);
+ baos.write(_size);
+ }
+
+ @Override boolean canDrop(MemOpNode other, Node dep) {
+ return super.canDrop(other,dep) && _init==((StoreNode)other)._init;
+ }
+
+ // Debugger label
+ @Override public String label() { return "st_"+mlabel(); }
+ @Override public boolean isMem() { return true; }
+
+ public Node nnptr() {
+ if( !(ptr() instanceof GuardNode cast) ) return ptr();
+ assert cast._nonZero;
+ return cast.in(1);
+ }
+ public Node val() { return in(4); }
+ public int storeSize() { assert _size==1 || _size==2 || _size==4 || _size==8; return _size; }
+ public int log_size() { return Integer.numberOfTrailingZeros(storeSize()); };
+
+ @Override
+ protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ return p.p(".").p(_name).p("=").p( val()).p(";");
+ }
+
+ @Override
+ public Type compute() {
+ Type val = val()._type;
+ Type mem0 = mem()._type;
+ Type ptr0 = ptr()._type;
+ // Validate argument types
+ if( ptr0.isHigh() )
+ return TypeMem.TOP;
+ if( !(mem0 instanceof TypeMem mem) ||
+ !(ptr0 instanceof TypeMemPtr ptr) )
+ return mem0.isHigh() || ptr0==Type.NIL ? TypeMem.TOP : TypeMem.BOT;
+
+ // Sharpen memory value; required for narrowing stores where the parser
+ // inserts zero/sign masking and somebody reads the TypeMem type.
+ Type decl = declaredType();
+ if( decl != Type.BOTTOM )
+ { assert decl != Type.TOP; val = val.join(decl); }
+ // A singleton class's final constant field lives in the static image.
+ // Its precise memory slice is a constant too; ordinary peepholes can
+ // replace this Store before scheduling or instruction selection.
+ // A constant runtime value alone is not enough: mutable fields (notably
+ // $init) still need their writes, and instance fields need allocation.
+ Field fld = ptr._obj.field(_name);
+ if( _alias!=1 && ptr._one && Parser.startsClzPrefix(ptr._obj._name) &&
+ fld!=null && fld._final && decl.isConstant() )
+ return TypeMem.make(_alias,val,true,true,true,mem._escFs,mem._escAs).escapesFrom(val);
+
+ // Allocation uses a private TypeMem and nothing else does. This
+ // memory is truly private; a temporary singleton until it escapes -
+ // which is never does in a constructor.
+ if( !_name.equals("[]") && mem._one )
+ // Just track the stored value
+ return TypeMem.make(_alias,val,true,false,_init,null,null).escapesFrom(val);
+
+ // Normal aliasing Store.
+ assert mem._alias==1 || mem._alias==_alias; // Perfect aliasing
+
+ // Single alias store. If memory is fat, attempt to get a narrow slice.
+ Type tfld = mem._alias==1 && mem._t instanceof TypeStruct ts
+ ? ts._fields[ts.findAlias(_alias)]._t
+ : mem._t;
+
+ // Same alias, meet into other fields.
+ Type t = val.meet(tfld);
+ // Bulk memory's final bit describes its conservative remainder, not
+ // this newly precise slice. Slice finality comes from the field.
+ boolean xfinal = fld != null && fld._final;
+ return TypeMem.make(_alias,t,mem._one,mem._clz,xfinal,mem._escFs,mem._escAs).escapesFrom(val);
+ }
+
+ @Override
+ public Node idealize() {
+ // Freeze the semantic Store variant as soon as the pointer exposes a
+ // canonical field declaration. Waiting until after structural memory
+ // peeps can lose the declaration to a flow-sensitive constant field
+ // state. Imported Stores already carry a serialized non-zero width.
+ if( _size==0 && (_size= storageType()) != 0 ) {
+ unlock(); // Width participates in the cached hash.
+ return init();
+ }
+
+ assert !(mem()._type instanceof TypeMem mem) || _alias == 1 || mem._alias == 1 || _alias == mem._alias;
+
+ // Simple store-after-MemMerge to a known alias can bypass. Happens when inlining.
+ Node mem = mem();
+ if( _alias != 1 && mem instanceof MemMergeNode merge ) {
+ setDef(1,merge.alias(_alias));
+ return this;
+ }
+
+ Field fld;
+ if( _alias==1 && ptr()._type instanceof TypeMemPtr tmp && (fld=tmp._obj.field(_name)) != null && _size != 0 ) {
+ // Expand Store(bulkMem,alias#1) into MemMerge(bulkMem,#N:Store(bulkMem,alias#N)).
+
+ // Normally I'd like to just drop in this xform and move on, but
+ // users of the bulk Store memory might nontheless depend on the
+ // sharp memory type (e.g. I64) from the store's memory. Using the
+ // MemMerge means using the bulk memory which e.g. might be BOT.
+ // Check for store users with the same alias, and use the new store
+ // directly, preserving the sharper graph.
+ Node st = new StoreNode(_loc,_name,fld._alias,fld._t,in(0),mem,ptr(),off(),val(),_init,_size).peephole();
+ for( int i=0; i<nOuts(); i++ ) {
+ Node use = out(i);
+ if( useAlias(use)==fld._alias ) {
+ int idx = use._inputs.find(this);
+ assert idx != -1;
+ CodeGen.CODE.add(use).setDef(idx,st);
+ i--; // setDef removed use from this Store's outputs
+ }
+ }
+ MemMergeNode mmm = mem instanceof BulkMemPhiNode bulk
+ ? bulk.aggregate(mem,fld._alias,st)
+ : new MemMergeNode(null,mem);
+ if( !(mem instanceof BulkMemPhiNode) )
+ mmm.alias(fld._alias,st);
+ return mmm.init();
+ }
+
+ // Simple store-after-store on same address. Should pick up the
+ // required init-store being stomped by a first user store.
+ if( mem instanceof StoreNode st &&
+ ptr()==st.ptr() && // Must check same object
+ off()==st.off() && // And same offset (could be "same alias" but this handles arrays to same index)
+ ptr()._type instanceof TypeMemPtr && // No bother if weird dead pointers
+ // Must have exactly one use of "this" or you get weird
+ // non-serializable memory effects in the worse case.
+ checkOnlyUse(st) ) {
+ assert _name==st._name; // Equiv class aliasing is perfect
+ setDef(1,st.mem());
+ return this;
+ }
+
+ // Private constructor memory describes the value that the next helper
+ // reads. Keep its conversion, even if a machine store could truncate it.
+ // This is the same rule as in the construction and escape chapters.
+ boolean priv = mem()._type instanceof TypeMem tm && tm._one;
+ // Value is automatically truncated by narrow store
+ if( !priv && _size!=0 && val() instanceof AndNode and && and.in(2)._type.isConstant() ) {
+ int log = log_size();
+ if( log<3 ) { // And-mask vs narrow store
+ long mask = ((TypeInteger)and.in(2)._type).value();
+ long bits = (1L<<(8<<log))-1;
+ // Mask does not mask any of the stored bits
+ if( (bits&mask)==bits )
+ // So and-mask is already covered by the store
+ { setDef(4,and.in(1)); return this; }
+ }
+ }
+
+ // Store will chop high order bits off; math to change those bits can be dropped.
+ if( !priv && _size!=0 && val() instanceof SarNode shr &&
+ shr.in(1) instanceof ShlNode shl &&
+ shr.in(2)._type.isConstant() &&
+ shl.in(2)._type.isConstant() ) {
+ TypeInteger shrC = (TypeInteger) shr.in(2)._type;
+
+ // size of the thing that sign-extends
+ int base_size = (1 << shr.in(1)._type.log_size()) << 3;
+ int not_affected_bits = base_size - (int) shrC.value();
+ int store_size = (1 << log_size()) << 3;
+ // if the store is unrelated to the shift amount, then get rid of the shift
+ if( shl.in(2)._type == shr.in(2)._type && shrC.value() >= store_size && not_affected_bits >= store_size) {
+ setDef(4, shl.in(1));
+ return this;
+ }
+ }
+
+ return null;
+ }
+
+ // Flow-sensitive pointer types can carry a current field value (e.g. the
+ // constant zero in a u32 field). Storage width comes from the canonical
+ // struct declaration. If the pointer/definition is not available yet,
+ // leave the Store undecided instead of consulting the value or `_con`.
+ private byte storageType() {
+ if( !(ptr()._type instanceof TypeMemPtr tmp) )
+ return 0;
+ TypeStruct obj = (TypeStruct)Parser.TYPES.get(tmp._obj._name);
+ Field fld = obj.field(_name);
+ if( fld == null || fld._t == null )
+ return 0;
+ if( fld._t == Type.BOTTOM )
+ return 0; // Store into a final field
+ assert fld._t != Type.TOP;
+ // Freshly parsed field stores retain the declaring structure on their
+ // symbolic offset even after the pointer's flow type sharpens.
+ if( fld._t.isConstant() ) {
+ if( !(off() instanceof ConFldOffNode coff) )
+ return 0;
+ fld = coff._ts.field(_name);
+ if( fld == null || fld._t == null )
+ return 0;
+ }
+ // Compute storage size from field type
+ byte size = (byte)(1 << fld._t.log_size());
+ assert size==1 || size==2 || size==4 || size==8;
+ return size;
+ }
+
+ // Alias required by a direct consumer of this Store's memory result.
+ // Keep in sync with BulkMemPhiNode.outputAlias.
+ private int useAlias(Node use) {
+ return switch( use ) {
+ case MemOpNode mem -> mem._alias;
+ case MemPhiNode phi -> phi._alias;
+ case EscapeNode esc -> esc.pub()==this ? esc.fld()._alias : 0;
+ default -> 0;
+ };
+ }
+
+ // Phi(mem, Store(phi,ptr,off,val)) -> Store(mem,ptr,off,Phi(load(mem),val)).
+ // With no observers of the backedge store, only the last value matters.
+ // The entry load preserves the field even when the loop takes zero trips.
+ Node sink(MemPhiNode phi) {
+ phi.addDepForwards(this);
+ if( nOuts()!=1 || _alias==1 || _size==0 || _init ) return null;
+ phi.addDep(ptr());
+ // Start with a fixed field of an allocation dominating the loop.
+ // In particular, do not sink stores through a loop-varying address.
+ if( !(ptr() instanceof ProjNode p) || !(p.in(0) instanceof NewNode obj) ||
+ !(ptr()._type instanceof TypeMemPtr tmp) || tmp._obj.isAry() ||
+ !(off() instanceof ConstantNode) || err()!=null ) return null;
+ LoopNode loop = (LoopNode)phi.region();
+ // Bulk splitting identifies precise slices by Region/alias. Finish it
+ // before replacing this memory point with a Store.
+ for( Node use : loop._outputs )
+ if( use instanceof BulkMemPhiNode ) {
+ phi.addDepForwards(use);
+ return null;
+ }
+ phi.addDep(obj);
+ CFGNode ctrl = loop.entry();
+ while( ctrl!=null && ctrl!=obj.cfg0() ) {
+ phi.addDep(ctrl);
+ ctrl=ctrl.idom(phi);
+ }
+ if( ctrl==null ) return null;
+
+ Node mem = phi.in(1);
+ Node init = new LoadNode(_loc,_name,_alias,null,mem,ptr(),off()).peephole();
+ Node value = CodeGen.CODE.add(new PhiNode(_name,loop,init,val()).peephole());
+ Node sink = new StoreNode(_loc,_name,_alias,_con,null,mem,ptr(),off(),value,_init,_size);
+ // Break the memory cycle, killing the old store. Keep the Phi alive
+ // even if that store was its last use, until our caller replaces it.
+ phi.keep();
+ phi.setDef(2,mem);
+ phi.unkeep();
+ return sink;
+ }
+
+ // Check that "mem" has no uses except "this"
+ private boolean checkOnlyUse(Node mem) {
+ if( mem.nOuts()==1 ) return true;
+ // Add deps on the other uses (can be e.g. ScopeNode mid-parse) so that
+ // when the other uses go away we can retry.
+ for( Node use : mem._outputs )
+ if( use != this )
+ addDep(use);
+ return false;
+ }
+
+ @Override
+ public Parser.ParseException err() {
+ Parser.ParseException err = super.err();
+ if( err != null ) return err;
+ assert _size>0 || CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.FinalizeIR.ordinal();
+ if( ptr()._type == Type.TOP ) // Dead store?
+ return null;
+ TypeMemPtr tmp = (TypeMemPtr)ptr()._type;
+ Field f = tmp._obj.field(_name);
+ if( f!=null && (tmp._ro || f._final) && !_init )
+ return Parser.error("Cannot modify final field '"+_name+"'",_loc);
+ return null;
+ }
+
+ // Do not GVN Stores. Two equal Stores on two of three paths are the easy
+ // failure: sharing them makes GCM place the Store on all three paths.
+ // Sharing is safe only if every exit path goes through an original Store,
+ // or GCM splits the shared Store to keep it on those paths. Stores must
+ // sink, not run on extra paths. GVN does not check this; GCM does not split.
+ // Identity also disables generic Phi factoring of Stores.
+ // The closed-loop Store sink is a separate peep and still applies.
+ @Override public boolean eq(Node n) { return this == n; }
+ @Override int hash() { return super.hash() ^ _size; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/SubNode.java
new file mode 100644
index 00000000..c2e285c9
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -0,0 +1,56 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import java.util.BitSet;
+
+public class SubNode extends ArithNode {
+ @Override boolean allowFloat() { return true; }
+ public SubNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+ public SubNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs,mode); }
+ @Override public Tag serialTag() { return Tag.Sub; }
+
+ @Override public String op() { return "-"; }
+
+ @Override long doOp( long x, long y ) { return x - y; }
+ @Override double doOp(double x, double y ) { return x - y; }
+ @Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
+ // Sub of same is 0
+ if( in(1)==in(2) )
+ return TypeInteger.ZERO;
+ // Fold ranges like {2-3} - {0-1} into {1-3}.
+ if( !AddNode.overflow(x._min,-y._max) &&
+ !AddNode.overflow(x._max,-y._min) &&
+ y._min != Long.MIN_VALUE )
+ return TypeInteger.make(x._min-y._max,x._max-y._min);
+
+ return TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ Type t1 = lhs._type;
+ Type t2 = rhs._type;
+
+ // Keep invalid operands and unresolved numeric modes for type checking.
+ if( _mode==1 && t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ if( y.isConstant() && y.value()==0 ) return lhs;
+ if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
+ }
+
+ // x - (-y) is x+y
+ if( rhs instanceof MinusNode minus )
+ return new AddNode(lhs,minus.in(1));
+
+ // (-x) - y is -(x+y)
+ if( lhs instanceof MinusNode minus )
+ return new MinusNode(new AddNode(minus.in(1),rhs).peephole());
+
+ return super.idealize();
+ }
+
+ @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs,_mode); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/SummaryNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/SummaryNode.java
new file mode 100644
index 00000000..c22b4bdd
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/SummaryNode.java
@@ -0,0 +1,67 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.codegen.*;
+import java.util.HashMap;
+
+// Analysis of an imported function whose implementation has not been loaded.
+// This is never an inlineable or executable body.
+public class SummaryNode extends CFGNode implements MultiNode {
+
+ public TypeTuple _state;
+ // Callees and touched class initializers, by FIDX, for class-init cycle checks.
+ public final int[] _classDependencyFIDXs;
+
+ public SummaryNode(TypeTuple state, int[] classDependencyFIDXs, Node... inputs) {
+ super(inputs);
+ _state = state;
+ _classDependencyFIDXs = classDependencyFIDXs;
+ CodeGen.CODE.add(CodeGen.CODE._globalEscapes);
+ }
+
+ @Override public String label() { return "Summary"; }
+ @Override public boolean blockHead() { return true; }
+ @Override public Type compute() {
+ if( in(0)._type.isHigh() ) return TypeTuple.STATE.dual();
+ // A library summary does not forget escapes already present in the caller.
+ TypeMem mem = in(1)._type instanceof TypeMem m ? m : in(1)._type.isHigh() ? TypeMem.TOP : TypeMem.BOT;
+ mem = (TypeMem)mem.meet(_state.mem());
+ for( int i = 2; i<nIns(); i++ ) mem=mem.escapes(in(i)._type);
+ return TypeTuple.make(_state.ctl(),mem,_state.ret());
+ }
+ @Override public Node idealize() { return null; }
+ public TypeMem exposed(Node dep) {
+ dep.addDepForwards(this);
+ if( dep.addDepForwards(in(0))._type.isHigh() ) return TypeMem.START;
+ TypeMem mem = (TypeMem)_state.mem();
+ for( int i = 2; i<nIns(); i++ ) mem=mem.escapes(dep.addDepForwards(in(i))._type);
+ return TypeMem.make(1,Type.BOTTOM,false,false,false,mem._escFs,mem._escAs);
+ }
+ @Override boolean _upgradeType(HashMap<String,Type> types) {
+ TypeTuple state = (TypeTuple)_state.upgradeType(types);
+ if( state==_state ) return false;
+ unlock();
+ _state=state;
+ return true;
+ }
+ // Materialize an object and publish real FIDX targets without rewriting
+ // callers or reoptimizing their active graphs. The evaluator schedules
+ // this object's checked ideal IR before entering any of its functions.
+ public static ElfReader load(CodeGen code, FunNode fun) {
+ assert fun.summary()!=null;
+ ElfReader elf = code.getElf(fun._compunit._obj);
+ assert elf._nodes==null; // Not loaded yet, or we would already have code for `fun`
+ elf.loadSimple(code); // Load all the IR, including code for `fun`
+ StopNode stop = (StopNode)elf._nodes.last();
+ for( Node n : stop._inputs ) {
+ StopCUNode stopcu = (StopCUNode)n;
+ for( Node r : stopcu._inputs ) {
+ FunNode target = ((ReturnNode)r).fun();
+ target._compunit._start = stopcu.start();
+ target._compunit._stop = stopcu;
+ code.link(target);
+ }
+ }
+ return elf;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java
new file mode 100644
index 00000000..151a326e
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java
@@ -0,0 +1,26 @@
+package com.seaofnodes.simple.node;
+
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+import com.seaofnodes.simple.type.TypeInteger;
+
+
+public class ToFloatNode extends Node {
+ public ToFloatNode(Node lhs) { super(null, lhs); }
+ @Override public Tag serialTag() { return Tag.ToFloat; }
+
+ @Override protected String format() { return "(flt)%1"; }
+
+ @Override
+ public Type compute() {
+ if( in(1)._type.isHigh() ) return TypeFloat.F64.dual();
+ if( in(1)._type == Type.NIL ) return TypeFloat.FZERO;
+ if( in(1)._type instanceof TypeInteger i0 && i0.isConstant() )
+ return TypeFloat.constant(i0.value());
+ return TypeFloat.F64;
+ }
+
+ @Override public Node idealize() { return null; }
+ @Override Node copy(Node lhs, Node rhs) { return new ToFloatNode(lhs); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java
new file mode 100644
index 00000000..33806300
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+import com.seaofnodes.simple.type.TypeInteger;
+
+// Truncate toward zero, saturating overflow; NaN converts to zero.
+public class ToIntegerNode extends Node {
+ public ToIntegerNode(Node lhs) { super(null,lhs); }
+ @Override public Tag serialTag() { return Tag.ToInteger; }
+ @Override public String label() { return "ToInteger"; }
+ @Override protected String format() { return "(int)%1"; }
+
+ @Override public Type compute() {
+ Type t = in(1)._type;
+ if( t.isHigh() ) return TypeInteger.TOP;
+ if( t instanceof TypeFloat tf && tf.isConstant() )
+ return TypeInteger.constant((long)tf.value());
+ return TypeInteger.BOT;
+ }
+
+ @Override public Node idealize() { return null; }
+ @Override Node copy(Node lhs, Node rhs) { return new ToIntegerNode(lhs); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/TypeNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/TypeNode.java
new file mode 100644
index 00000000..38b0b288
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/TypeNode.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
+import java.util.BitSet;
+import java.util.HashMap;
+
+// Node carries an extra internal type
+public abstract class TypeNode extends Node {
+ public Type _con;
+ public TypeNode(Type t, Node... nodes) { super(nodes); assert t!=null; _con = t; }
+ public TypeNode(TypeNode tn) { super(tn); _con = tn._con; }
+ // Copy edges from ideal, but 'this' is a Type-Mach-Node (e.g. MemOpX86) and needs a _con
+ public TypeNode(Node ideal, Type con) { super(ideal); _con = con; }
+
+ // Upgrade the internal type
+ @Override boolean _upgradeType( HashMap<String,Type> TYPES) {
+ return liftType(_con.upgradeType(TYPES));
+ }
+
+ // Lift type
+ public boolean liftType( Type t ) {
+ if( t == _con ) return false;
+ unlock(); // Unlock before changing _con
+ _con = t;
+ return true;
+ }
+
+ @Override public boolean eq(Node n) { return _con==((TypeNode)n)._con; }
+ @Override int hash() { return _con.hashCode(); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
new file mode 100644
index 00000000..d727ce2b
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
@@ -0,0 +1,16 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.type.Type;
+
+public class XCtrlNode extends CFGNode {
+ public XCtrlNode() { super(new Node[]{CodeGen.CODE._start}); }
+ @Override public Tag serialTag() { return Tag.XCtrl; }
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("Xctrl"); }
+ @Override public boolean isConst() { return true; }
+ @Override public Type compute() { return Type.XCONTROL; }
+ @Override public Node idealize() { return null; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter29/src/main/java/com/seaofnodes/simple/node/XorNode.java
new file mode 100644
index 00000000..85fb3259
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/XorNode.java
@@ -0,0 +1,44 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.parser.Lexer;
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+public class XorNode extends ArithNode {
+ public XorNode(Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); }
+ @Override public Tag serialTag() { return Tag.Xor; }
+
+ @Override public String op() { return "^"; }
+ @Override long doOp( long x, long y ) { return x ^ y; }
+ @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) {
+ return TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+
+ Type t1 = lhs._type;
+ Type t2 = rhs._type;
+
+ // Xor of 0. We do not check for (0^x) because this will already
+ // canonicalize to (x^0)
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && forceInt(lhs) && lhs._type instanceof TypeInteger )
+ return lhs;
+
+ // Move constants to RHS: con*arg becomes arg*con
+ if ( t1.isConstant() && !t2.isConstant() )
+ return swap12();
+
+ // Do we have ((x ^ (phi cons)) ^ con) ?
+ // Do we have ((x ^ (phi cons)) ^ (phi cons)) ?
+ // Push constant up through the phi: x ^ (phi con0^con0 con1^con1...)
+ Node phicon = AddNode.phiCon(this,true);
+ if( phicon!=null ) return phicon;
+
+ return super.idealize();
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new XorNode(_loc,lhs,rhs); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java
new file mode 100644
index 00000000..76e47ca2
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class AddARM extends MachConcreteNode implements MachNode {
+ AddARM( Node add) { super(add); }
+ @Override public String op() { return "add"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ // ADD (shifted register)
+ @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_ADD); }
+ // General form: "rd = rs1 + rs2"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java
new file mode 100644
index 00000000..67a10b48
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class AddFARM extends MachConcreteNode implements MachNode {
+ AddFARM(Node addf) { super(addf); }
+ @Override public String op() { return "addf"; }
+ @Override public RegMask regmap(int i) { return arm.DMASK; }
+ @Override public RegMask outregmap() { return arm.DMASK; }
+
+ //FADD (scalar)
+ @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_OP_ADD);}
+
+ // Default on double precision for now(64 bits)
+ // General form: "addf rd = src1 + src2
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java
new file mode 100644
index 00000000..4c635d07
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java
@@ -0,0 +1,28 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class AddIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ AddIARM(Node add, int imm) {
+ super(add);
+ _inputs.pop();
+ _imm = imm;
+ }
+ @Override public String op() {
+ return _imm == 1 ? "inc" : (_imm == -1 ? "dec" : "addi");
+ }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ //ADD (immediate)
+ @Override public void encoding( Encoding enc ) {
+ arm.imm_inst(enc,this, in(1), Arm64.OPI_ADD,_imm);
+ }
+ // General form: "addi rd = rs1 + imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + #").p(_imm);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java
new file mode 100644
index 00000000..1a0365ad
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.SB;
+
+public class AndARM extends MachConcreteNode implements MachNode {
+ AndARM(Node and) { super(and); }
+ @Override public String op() { return "and"; }
+ @Override public String glabel() { return "&"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ // AND (shifted register)
+ @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_AND); }
+ // General form: #rd = rs1 & rs2
+ @Override public void asm(CodeGen code, SB sb){
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java
new file mode 100644
index 00000000..6292e98e
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java
@@ -0,0 +1,25 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class AndIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ AndIARM(Node and, int imm) {
+ super(and);
+ _inputs.pop();
+ _imm = imm;
+ }
+ @Override public String op() { return "andi"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ arm.imm_inst_n(enc,this, in(1), Arm64.OPI_AND,_imm);
+ }
+ // General form: "andi rd = rs1 & imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & #").p(Arm64.decodeImm12(_imm));
+ }
+} \ No newline at end of file
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java
new file mode 100644
index 00000000..77f14d4c
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// Arithmetic Shift Right (register)
+public class AsrARM extends MachConcreteNode implements MachNode {
+ AsrARM(Node asr) {super(asr);}
+ @Override public String op() { return "sar"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.DMASK; }
+ @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,Arm64.OP_ASR); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java
new file mode 100644
index 00000000..7cb42eec
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.SB;
+
+public class AsrIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ AsrIARM(Node asr, int imm) {
+ super(asr);
+ _inputs.pop();
+ _imm = imm;
+ }
+ @Override public String op() { return "asri"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short rd = enc.reg(this);
+ short rn = enc.reg(in(1));
+ assert _imm > 0;
+ enc.add4(Arm64.imm_shift(Arm64.OPI_ASR,_imm, 0b111111, rn, rd));
+ }
+ // General form: "asri rd = rs1 >> imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> #").p(_imm);
+ }
+} \ No newline at end of file
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java
new file mode 100644
index 00000000..31bbde86
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java
@@ -0,0 +1,80 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// Jump on flags, uses flags
+public class BranchARM extends IfNode implements MachNode, RIPRelSize {
+ String _bop;
+ final boolean _fp;
+ BranchARM(IfNode iff, String bop, boolean fp ) {
+ super(iff);
+ _bop = bop;
+ _fp = fp;
+ }
+ @Override public String op() { return "j"+_bop; }
+ @Override public String label() { return op(); }
+
+ @Override public void postSelect(CodeGen code) {
+ Node set = in(1);
+ if( set==null ) return; // Never-node cutout
+ Node cmp = set.in(1);
+ // Bypass an expected Set and just reference the cmp directly
+ if( set instanceof SetARM)
+ _inputs.set(1,cmp);
+ else
+ throw Utils.TODO();
+ }
+
+ @Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public void negate() { _bop = negate(_bop); }
+
+ @Override public void encoding( Encoding enc ) {
+ // Assuming that condition flags are already set. These flags are set
+ // by comparison (or sub). No need for regs because it uses flags
+ if( in(1)!=null ) {
+ // B.cond
+ enc.add4( Arm64.b_cond(Arm64.OP_BRANCH, 0, Arm64.make_condition(_bop, _fp)) );
+ } else {
+ if( _bop=="!=" ) return; // Inverted, no code
+ enc.add4(Arm64.b(Arm64.OP_UJMP, 0));
+ }
+ enc.jump(this,cproj(0));
+ }
+
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) {
+ if( in(1)==null && _bop=="!=" ) return 0; // Inverted never-node, no code
+ if( -(1<<19) <= delta && delta < (1<<19) ) return 4;
+ // 2 word encoding needs a tmp register, must teach RA
+ throw Utils.TODO();
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ assert !( in(1)==null && _bop=="!=" ); // Inverted never-node, no code no patch
+ if( opLen==4 ) {
+ enc.patch4(opStart,Arm64.b_cond(Arm64.OP_BRANCH, delta, Arm64.make_condition(_bop, _fp)));
+ } else {
+ throw Utils.TODO();
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ if( in(1)!=null ) { // Never-node
+ String src = code.reg(in(1));
+ if( src!="flags" ) sb.p(src).p(" ");
+ } else if( _bop=="!=" ) {
+ sb.p("never");
+ return;
+ }
+ CFGNode prj = cproj(0).uctrlSkipEmpty();
+ if( !prj.blockHead() ) prj = prj.cfg0();
+ sb.p(label(prj));
+ }
+ @Override public String comment() { return "L"+cproj(1)._nid; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java
new file mode 100644
index 00000000..4de28f3a
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java
@@ -0,0 +1,50 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.util.SB;
+
+public class CallARM extends CallNode implements MachNode, RIPRelSize {
+ final TypeFunPtr _tfp;
+ final String _name;
+ CallARM(CallNode call, TypeFunPtr tfp) {
+ super(call);
+ _inputs.pop(); // Pop constant target
+ assert tfp.isConstant();
+ _tfp = tfp;
+ _name = CodeGen.CODE.funcName(tfp.fidx());
+ }
+
+ @Override public String op() { return "call"; }
+ @Override public String label() { return op(); }
+ @Override public String name() { return _name; }
+ @Override public TypeFunPtr tfp() { return _tfp; }
+ @Override public RegMask regmap(int i) { return arm.callInMask(_tfp,i,fun()._maxArgSlot); }
+ @Override public RegMask outregmap() { return null; }
+ @Override public int nargs() { return nIns()-2; } // Minus control, memory, fptr
+
+ @Override public void encoding( Encoding enc ) {
+ FunNode fun = CodeGen.CODE.link(_tfp);
+ if( fun==null ) enc.external(this,_name);
+ else enc.relo(this);
+ // BL
+ enc.add4(Arm64.b(Arm64.OP_CALL,0)); // Target patched at link time
+ }
+
+ // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding
+ @Override public byte encSize(int delta) { return 4; }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ enc.patch4(opStart,Arm64.b(Arm64.OP_CALL,delta));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(_name).p(" ");
+ for( int i=0; i<nargs(); i++ )
+ sb.p(code.reg(arg(i+2))).p(", ");
+ sb.unchar(2);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java
new file mode 100644
index 00000000..96a64cd0
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java
@@ -0,0 +1,36 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.util.SB;
+
+public class CallRRARM extends CallNode implements MachNode {
+ final TypeFunPtr _tfp;
+ CallRRARM(CallNode call) { super(call); _tfp = call.tfp(); }
+ @Override public TypeFunPtr tfp() { return _tfp; }
+ @Override public String op() { return "callr"; }
+ @Override public String label() { return op(); }
+ @Override public RegMask regmap(int i) {
+ return i==_inputs._len-1
+ ? arm.RMASK // Function call target
+ : arm.callInMask(tfp(),i,fun()._maxArgSlot); // Normal argument
+ }
+ @Override public RegMask outregmap() { return null; }
+
+ @Override public void encoding( Encoding enc ) {
+ // Needs a register, typically a jump-and-link-register opcode
+ // blr
+ short self = enc.reg(fptr());
+ enc.add4(Arm64.blr(Arm64.OP_CALLRARM, self));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(fptr())).p(" ");
+ for( int i=0; i<nargs(); i++ )
+ sb.p(code.reg(arg(i))).p(", ");
+ sb.unchar(2);
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java
new file mode 100644
index 00000000..1bd9c372
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class CmpARM extends MachConcreteNode implements MachNode {
+ CmpARM( Node cmp ) { super(cmp); }
+ @Override public String op() { return "cmp"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.FLAGS_MASK; }
+
+ // SUBS (shifted register)
+ @Override public void encoding( Encoding enc ) { arm.r_reg_subs(enc,this,Arm64.OP_CMP); }
+
+ // General form: "cmp rs1, rs2"
+ @Override public void asm(CodeGen code, SB sb) {
+ String dst = code.reg(this);
+ if( dst!="flags" ) sb.p(dst).p(" = ");
+ sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java
new file mode 100644
index 00000000..67ffc27b
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// compare instruction on float regs input(D-registers)
+public class CmpFARM extends MachConcreteNode implements MachNode{
+ CmpFARM(Node cmp) {super(cmp);}
+ @Override public String op() { return "cmpf"; }
+ @Override public RegMask regmap(int i) { return arm.DMASK; }
+ @Override public RegMask outregmap() { return arm.FLAGS_MASK; }
+
+ // FCMP
+ @Override public void encoding( Encoding enc ) { arm.f_cmp(enc,this); }
+ // General form: "cmp d0, d1"
+ @Override public void asm(CodeGen code, SB sb) {
+ String dst = code.reg(this);
+ if( dst!="flags" ) sb.p(dst).p(" = ");
+ sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java
new file mode 100644
index 00000000..cd4dd9cf
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java
@@ -0,0 +1,41 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// Compare with immediate.
+// CMP (immediate) - SUBS
+public class CmpIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ final String _bop;
+ CmpIARM(BoolNode bool, int imm) {
+ super(bool);
+ _inputs.pop();
+ _bop = bool.op();
+ _imm = imm;
+ }
+ CmpIARM( CmpIARM cmp ) {
+ super((Node)null);
+ addDef(null);
+ addDef(cmp.in(1));
+ _bop = cmp._bop;
+ _imm = cmp._imm;
+ }
+ @Override public String op() { return _imm==0 ? "test" : "cmp"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.FLAGS_MASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public Node copy() { return new CmpIARM(this); }
+
+ @Override public void encoding( Encoding enc ) { arm.imm_inst_subs(enc,in(1),in(1), Arm64.OPI_CMP,_imm); }
+
+ // General form: "cmp rs1, 1"
+ @Override public void asm(CodeGen code, SB sb) {
+ String dst = code.reg(this);
+ if( dst!="flags" ) sb.p(dst).p(" = ");
+ sb.p(code.reg(in(1)));
+ if( _imm != 0 ) sb.p(", #").p(_imm);
+ }
+ }
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java
new file mode 100644
index 00000000..e94b6491
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class DivARM extends MachConcreteNode implements MachNode {
+ DivARM( Node div ) { super(div); }
+ @Override public String op() { return "div"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ // SDIV
+ @Override public void encoding( Encoding enc ) { arm.madd(enc,this,Arm64.OP_DIV,3); }
+ // General form: "div dst /= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java
new file mode 100644
index 00000000..68ad6047
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class DivFARM extends MachConcreteNode implements MachNode {
+ DivFARM( Node divf) { super(divf); }
+ @Override public String op() { return "divf"; }
+ @Override public RegMask regmap(int i) { return arm.DMASK; }
+ @Override public RegMask outregmap() { return arm.DMASK; }
+
+ // FDIV (scalar)
+ @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_DIV); }
+ // General form: "VDIF = dst /= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java
new file mode 100644
index 00000000..814a82c2
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+// Convert a narrow C return register to Simple's full-width integer value.
+public class ExtendARM extends MachConcreteNode {
+ final int _bits;
+ final boolean _signed;
+ ExtendARM(ProjNode prj, TypeInteger ret) {
+ super(prj);
+ _inputs.set(0,null);
+ _inputs.add(new ProjARM(prj));
+ _bits = 8 << ret.log_size();
+ _signed = ret._min < 0;
+ }
+ @Override public String op() { return (_signed ? "sext" : "zext")+_bits; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding(Encoding enc) {
+ short dst = enc.reg(this), src = enc.reg(in(1));
+ // SBFM/UBFM aliases: sign/zero extend the low byte, short or int.
+ enc.add4(Arm64.imm_shift(_signed ? Arm64.OPI_ASR : Arm64.OPI_LSR,0,_bits-1,src,dst));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java
new file mode 100644
index 00000000..31dcfc36
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class F8ToIARM extends MachConcreteNode implements MachNode {
+ F8ToIARM(Node f8toi) { super(f8toi); }
+ @Override public String op() { return "cvti"; }
+ @Override public RegMask regmap(int i) { return arm.DMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ enc.add4(Arm64.floatToInteger(enc.reg(in(1))-arm.D_OFFSET,enc.reg(this)));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1)));
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java
new file mode 100644
index 00000000..db2540f7
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java
@@ -0,0 +1,45 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.ConstantNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+//FMOV (scalar, immediate)
+//Floating-point move immediate.
+public class FltARM extends ConstantNode implements MachNode, RIPRelSize {
+ final String _ext;
+ FltARM( ConstantNode con, String ext ) { super(con); _ext = ext; }
+ @Override public String op() { return "ld8"; }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return arm.DMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public FltARM copy() { return new FltARM(this,_ext); }
+
+ @Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) throw Utils.TODO();
+ enc.largeConstant(this,_con,0,-1/*TODO: ARM-style ELF patching*/);
+ short dst = (short)(enc.reg(this) - arm.D_OFFSET);
+ enc.add4(Arm64.load_pc(Arm64.OPF_ARM, 0, dst));
+ }
+
+ // Delta is from opcode start.
+ @Override public byte encSize(int delta) { return 4; }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ short dst = (short)(enc.reg(this) - arm.D_OFFSET);
+ enc.patch4(opStart, Arm64.load_pc(Arm64.OPF_ARM, delta, dst));
+ }
+
+ // Human-readable form appended to the SB. Things like the encoding,
+ // indentation, leading address or block labels not printed here.
+ // Just something like "ld4\tR17=[R18+12] // Load array base".
+ // General form: "op\tdst=src+src"
+ @Override public void asm(CodeGen code, SB sb) {
+ _con.print(sb.p(code.reg(this)).p(" = #"));
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java
new file mode 100644
index 00000000..17cafcde
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.FunNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class FunARM extends FunNode implements MachNode {
+ FunARM(FunNode fun) { super(fun); }
+ @Override public void computeFrameAdjust(CodeGen code, int maxReg) {
+ super.computeFrameAdjust(code,maxReg);
+ _frameAdjust = (_frameAdjust+15) & -16; // Keep SP aligned, including leaf spills.
+ }
+ @Override public void encoding( Encoding enc ) {
+ int sz = _frameAdjust;
+ if( sz == 0 ) return; // Skip if no frame adjust
+ if( sz >= 1L<<12 ) throw Utils.TODO();
+ enc.add4(Arm64.imm_inst(Arm64.OPI_SUB, sz, arm.RSP, arm.RSP));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java
new file mode 100644
index 00000000..b1c30afc
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java
@@ -0,0 +1,26 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class I2F8ARM extends MachConcreteNode implements MachNode {
+ I2F8ARM(Node i2f8) { super(i2f8); }
+ @Override public String op() { return "cvt"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.DMASK; }
+ @Override public void encoding( Encoding enc ) {
+ // SCVTF
+ short self = (short)(enc.reg(this )-arm.D_OFFSET);
+ short reg1 = enc.reg(in(1));
+ int body = Arm64.float_cast(Arm64.OP_FLOAT_C, 1, reg1, self);
+ enc.add4(body);
+ }
+
+ // General form: "i2f8 (flt)int_value"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1)));
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java
new file mode 100644
index 00000000..72b76edd
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java
@@ -0,0 +1,39 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.ConstantNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// Integer constants
+public class IntARM extends ConstantNode implements MachNode {
+ final String _ext;
+ IntARM( ConstantNode con, String ext ) { super(con); _ext = ext; }
+
+ @Override public String op() { return "ldi"; }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ @Override public boolean isClone() { return true; }
+ @Override public IntARM copy() { return new IntARM(this,_ext); }
+
+ @Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) throw Utils.TODO();
+ short self = enc.reg(this);
+ long x = _con==Type.NIL ? 0 : ((TypeInteger)_con).value();
+ Arm64.constant(enc,self,x);
+ }
+
+ // Human-readable form appended to the SB. Things like the encoding,
+ // indentation, leading address or block labels not printed here.
+ // Just something like "ld4\tR17=[R18+12] // Load array base".
+ // General form: "op\tdst=src+src"
+ @Override public void asm(CodeGen code, SB sb) {
+ String reg = code.reg(this);
+ _con.print(sb.p(reg).p(" = #"));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
new file mode 100644
index 00000000..53494092
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
@@ -0,0 +1,34 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.LoadNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.SB;
+
+// Load memory addressing on ARM
+// Support imm, reg(direct), or reg+off(indirect) addressing
+// Base = base - base pointer, offset is added to base
+// idx = null
+// off = off - offset added to base
+
+public class LoadARM extends MemOpARM {
+ LoadARM(LoadNode ld, Node base, Node idx, int off) {
+ super(ld, base, idx, off, 0);
+ }
+ @Override public String op() { return "ld"+_sz; }
+ @Override public RegMask outregmap() { return size() >= 4 ? arm.MEM_MASK : arm.WMASK; }
+
+ private static final int[] OP_LOADS = new int[]{ Arm64.OP_LOAD_IMM_8, Arm64.OP_LOAD_IMM_16, Arm64.OP_LOAD_IMM_32, Arm64.OP_LOAD_IMM_64, };
+
+ private static final int[] OP_LOAD_RS = new int[]{ Arm64.OP_LOAD_R_8 , Arm64.OP_LOAD_R_16 , Arm64.OP_LOAD_R_32 , Arm64.OP_LOAD_R_64, };
+
+ // ldr(immediate - unsigned offset) | ldr(register)
+ @Override public void encoding( Encoding enc ) {
+ ldst_encode(enc, OP_LOADS[Integer.numberOfTrailingZeros(size())], OP_LOAD_RS[_con.log_size()], this, size());
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(",");
+ asm_address(code,sb);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java
new file mode 100644
index 00000000..954cfac2
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// Logical Shift Left (register)
+public class LslARM extends MachConcreteNode implements MachNode {
+ LslARM(Node asr) {super(asr);}
+ @Override public String op() { return "shr"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,Arm64.OP_LSL); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java
new file mode 100644
index 00000000..b32153ab
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java
@@ -0,0 +1,34 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.SB;
+
+public class LslIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ LslIARM(Node lsl, int imm) {
+ super(lsl);
+ _inputs.pop();
+ _imm = imm;
+ }
+ @Override public String op() { return "lsli"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short rd = enc.reg(this);
+ short rn = enc.reg(in(1));
+ assert _imm > 0;
+ // UBFM <Xd>, <Xn>, #(-<shift> MOD 64), #(63-<shift>)
+ // immr must be (-<shift> MOD 64) = 64 - shift
+ enc.add4(Arm64.imm_shift(Arm64.OPI_LSL, 64 - _imm, (64 - _imm) - 1, rn, rd));
+ }
+ // General form: "lsli rd = rs1 << imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << #").p(_imm);
+ }
+} \ No newline at end of file
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java
new file mode 100644
index 00000000..91be34e3
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// Logical Shift Right (register)
+public class LsrARM extends MachConcreteNode implements MachNode {
+ LsrARM(Node asr) {super(asr);}
+ @Override public String op() { return "shr"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,Arm64.OP_LSR); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java
new file mode 100644
index 00000000..243ce968
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java
@@ -0,0 +1,29 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// logical right shift immediate
+public class LsrIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ LsrIARM(Node lsr, int imm) {
+ super(lsr);
+ _inputs.pop();
+ _imm = imm;
+ }
+ @Override public String op() { return "lsri"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short rd = enc.reg(this);
+ short rn = enc.reg(in(1));
+ assert _imm > 0;
+ enc.add4(Arm64.imm_shift(Arm64.OPI_LSR,_imm, 0b111111, rn,rd));
+ }
+ // General form: "lsri rd = rs1 >>> imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> #").p(_imm);
+ }
+} \ No newline at end of file
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java
new file mode 100644
index 00000000..14eaf510
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java
@@ -0,0 +1,81 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.SB;
+import java.lang.StringBuilder;
+
+public abstract class MemOpARM extends MemOpNode implements MachNode {
+ final int _off; // Limit 9 bits sized, or (13 bits<<logsize) unsigned
+ final int _imm; // Limit ? bits
+ final int _bytes;
+ final char _sz;
+ MemOpARM(MemOpNode mop, Node ptr, Node idx, int off, int imm) {
+ super(mop,mop);
+ assert ptr._type instanceof TypeMemPtr && !(ptr instanceof AddNode);
+ assert ptr() == ptr;
+ _inputs.setX(2, ptr);
+ _inputs.setX(3, idx);
+ _off = off;
+ _imm = imm;
+ _bytes = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size();
+ _sz = (char)('0'+_bytes);
+ }
+
+ // Store-based flavors have a value edge
+ MemOpARM( MemOpNode mop, Node ptr, Node idx, int off, int imm, Node val ) {
+ this(mop,ptr,idx,off,imm);
+ _inputs.setX(4,val);
+ }
+
+ @Override public String label() { return op();}
+ Node val() { return in(4); } // Only for stores
+
+ @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { return p.p(".").p(_name); }
+
+ @Override public Type compute() { throw new AssertionError("Do not call compute on "+getClass().getSimpleName()); }
+ @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); }
+
+ int size() { return _bytes; }
+
+ // Wider mask to store both GPRs and FPRs
+ @Override public RegMask regmap(int i) {
+ // 0 - ctrl
+ if( i==1 ) return null; // memory
+ if( i==2 ) return arm.RMASK; // ptr/base
+ if( i==3 ) return arm.RMASK; // off/index
+ if( i==4 ) return size() >= 4 ? arm.MEM_MASK : arm.RMASK; // value
+ return null; // Anti-dependence
+ }
+
+ // Shared encoding for loads and stores(int and float)
+ public void ldst_encode( Encoding enc, int opcode_imm, int opcode_reg, Node xval, int size) {
+ short ptr = enc.reg(ptr());
+ short off = enc.reg(off());
+ short val = enc.reg(xval);
+ // The allocated register selects integer vs SIMD/FP encoding, even for
+ // bitwise copies. ARM numbers each register bank from zero in the opcode.
+ boolean fp = val >= arm.D0;
+ assert !fp || size >= 4;
+ if( fp ) {
+ opcode_imm |= 1<<4;
+ opcode_reg |= 1<<5;
+ val -= arm.D0;
+ }
+ int body = off() == null
+ ? Arm64.load_str_imm(opcode_imm, _off, ptr, val, size)
+ : Arm64.indr_adr(opcode_reg, off, Arm64.STORE_LOAD_OPTION.SXTX, 0, ptr, val);
+ enc.add4(body);
+ }
+
+ SB asm_address(CodeGen code, SB sb) {
+ sb.p("[").p(code.reg(ptr())).p("+");
+ if( off() != null ) sb.p(code.reg(off()));
+ else sb.p(_off);
+ return sb.p("]");
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java
new file mode 100644
index 00000000..f598cefb
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// mulh signed multiply instruction(no-imm form)
+public class MulARM extends MachConcreteNode implements MachNode {
+ MulARM(Node mul) {super(mul);}
+ @Override public String op() { return "mul"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ @Override public void encoding( Encoding enc ) { arm.madd(enc,this,Arm64.OP_MUL,31); }
+ // General form: "rd = rs1 * rs2"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java
new file mode 100644
index 00000000..a4f81fd4
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class MulFARM extends MachConcreteNode implements MachNode{
+ MulFARM(Node mulf) {super(mulf);}
+ @Override public String op() { return "mulf"; }
+ @Override public RegMask regmap(int i) { return arm.DMASK; }
+ @Override public RegMask outregmap() { return arm.DMASK; }
+ @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_MUL); }
+ // Default on double precision for now(64 bits)
+ // General form: "VMUL.f32 rd = src1 * src2
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java
new file mode 100644
index 00000000..881b41f9
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java
@@ -0,0 +1,25 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+public class NegARM extends MachConcreteNode implements MachNode {
+ NegARM(Node sub) {
+ super(sub);
+ //_inputs.pop();
+ }
+ @Override public String op() { return "neg"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ // reverse subtract with immediate 0
+ arm.imm_inst(enc,this, in(1), 0b00100110, 0);
+ }
+ // General form: "subi rd = rs1 - imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = -").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java
new file mode 100644
index 00000000..8bb8983e
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java
@@ -0,0 +1,39 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class NewARM extends NewNode implements MachNode, RIPRelSize {
+ // A pre-zeroed chunk of memory.
+ NewARM(NewNode nnn) { super(nnn); }
+ @Override public void encoding( Encoding enc ) {
+ // BL(branch with link)
+ enc.external(this,"calloc").
+ add4(Arm64.mov(Arm64.OP_MOVZ, 0, 1, 0)). // movz x0,#1
+ add4(Arm64.b(Arm64.OP_CALL, 0));
+ }
+
+ // Patch is for running "new" in a JIT.
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) { return 4*2; }
+
+ // Patch is for running "new" in a JIT.
+ // Delta is from opcode start
+ @Override public void patch(Encoding enc, int opStart, int opLen, int delta ) {
+ // Negative patches are JIT emulator targets.
+ if( opStart+delta < 0 ) {
+ enc.patch4(opStart+4, Arm64.b_calloc(Arm64.OP_CALL, delta-4));
+ } else {
+ throw Utils.TODO();
+ }
+ }
+ // General form: "alloc #bytes PC"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p("\n"); // Multi-op trigger
+ sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n");
+ sb.p("call #calloc");
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
new file mode 100644
index 00000000..f8e450f6
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
@@ -0,0 +1,28 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.util.SB;
+
+public class NotARM extends MachConcreteNode implements MachNode{
+ NotARM(NotNode not) {super(not);}
+ @Override public String op() { return "not"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public RegMask killmap() { return arm.FLAGS_MASK; }
+ @Override public void encoding( Encoding enc ) {
+ // subs xzr, rs, #0
+ // cset rd, eq // Set rd to 1 if rs == 0 (equal), else 0
+ // subtracting zero from rs will just yield rs, it sets the zero flag and then it's used in cset
+ short self = enc.reg(this );
+ short reg1 = enc.reg(in(1));
+ int subs = Arm64.imm_inst(Arm64.OP_SUBS, 0, reg1, 31);
+ enc.add4(subs);
+ int cset = Arm64.cset(Arm64.OP_CSET, 31, Arm64.COND.EQ, 31, self);
+ enc.add4(cset);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java
new file mode 100644
index 00000000..5b7264ec
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// Not using the ORRS variant.
+public class OrARM extends MachConcreteNode implements MachNode {
+ OrARM(Node and) { super(and); }
+ @Override public String op() { return "or"; }
+ @Override public String glabel() { return "|"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_OR); }
+ // General form: #rd = rs1 & rs2
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java
new file mode 100644
index 00000000..86d07bee
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java
@@ -0,0 +1,25 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class OrIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ OrIARM(Node or, int imm) {
+ super(or);
+ _inputs.pop();
+ _imm = imm;
+ }
+ @Override public String op() { return "ori"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ arm.imm_inst_n(enc, this, in(1), Arm64.OPI_OR, _imm);
+ }
+ // General form: "ori rd = rs1 | imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | #").p(_imm);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java
new file mode 100644
index 00000000..b419fe0a
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.ParmNode;
+
+public class ParmARM extends ParmNode implements MachNode {
+ final RegMask _rmask;
+ ParmARM(ParmNode parm) {
+ super(parm);
+ _rmask = arm.callInMask(fun().sig(),_idx,0);
+ }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return _rmask; }
+ @Override public void encoding( Encoding enc ) { }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java
new file mode 100644
index 00000000..4b7d6f13
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java
@@ -0,0 +1,15 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.ProjNode;
+
+public class ProjARM extends ProjNode implements MachNode {
+ ProjARM( ProjNode p ) { super(p); }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() {
+ return ((MachNode)in(0)).outregmap(_idx);
+ }
+ @Override public void encoding( Encoding enc ) { }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java
new file mode 100644
index 00000000..a7ed5073
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java
@@ -0,0 +1,16 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class RetARM extends ReturnNode implements MachNode {
+ RetARM(ReturnNode ret, FunNode fun) { super(ret, fun); fun.setRet(this); }
+ @Override public void encoding( Encoding enc ) {
+ int frameAdjust = ((FunARM)fun())._frameAdjust;
+ if( frameAdjust > 0 )
+ enc.add4(Arm64.imm_inst(Arm64.OPI_ADD, frameAdjust, arm.RSP, arm.RSP));
+ enc.add4(Arm64.ret(Arm64.OP_RET));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java
new file mode 100644
index 00000000..e5b06c39
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java
@@ -0,0 +1,36 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// conditional set?
+// e.g CSET, stores flag info into GPRS after compare
+public class SetARM extends MachConcreteNode implements MachNode {
+ final String _bop; // One of <,<=,==
+ final boolean _fp; // FP compare (unsigned encoding)
+ SetARM(Node cmp, String bop, boolean fp) {
+ super(cmp);
+ _inputs.setLen(1); // Pop the cmp inputs
+ // Replace with the matched cmp
+ _inputs.push(cmp);
+ _bop = bop;
+ _fp = fp;
+ }
+ @Override public String op() { return "set"+_bop; }
+ @Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ // Using RM==RN==31 implies a *zero* value, not R31.
+ int body = Arm64.cset(Arm64.OP_CSET,0b11111, Arm64.make_condition(_bop, _fp), 0b011111, enc.reg(this));
+ enc.add4(body);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this));
+ String src = code.reg(in(1));
+ if( src!="flags" ) sb.p(" = ").p(src);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java
new file mode 100644
index 00000000..dcfeb5d8
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java
@@ -0,0 +1,60 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.FunNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.node.SplitNode;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class SplitARM extends SplitNode {
+ SplitARM(LRG lrg, String kind, byte round) { super(lrg,kind,round,new Node[2]);}
+ @Override public RegMask regmap(int i) { return arm.SPLIT_MASK; }
+ @Override public RegMask outregmap() { return arm.SPLIT_MASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this );
+ short src = enc.reg(in(1));
+
+ boolean dstX = dst >= arm.D_OFFSET;
+ boolean srcX = src >= arm.D_OFFSET;
+
+ if (dst >= arm.MAX_REG) {
+ // store to sp
+ if(src >= arm.MAX_REG) {
+ throw Utils.TODO();
+ }
+ int off = fun(enc).computeStackOffset(enc._code,dst);
+ if( srcX ) src -= arm.D_OFFSET;
+ enc.add4(Arm64.load_str_imm(Arm64.OP_STORE_IMM_64, off, arm.RSP, src, 8));
+ return;
+ }
+
+ if(src >= arm.MAX_REG) {
+ // Load from SP
+ int off = fun(enc).computeStackOffset(enc._code,src);
+ if( dstX ) dst -= arm.D_OFFSET;
+ enc.add4(Arm64.load_str_imm(Arm64.OP_LOAD_IMM_64, off, arm.RSP, dst, 8));
+ return;
+ }
+
+ // pick opcode based on regs
+ if(!dstX && !srcX) {
+ // GPR->GPR
+ enc.add4(Arm64.mov_reg(Arm64.OP_MOV, src, dst));
+ } else if(dstX && srcX) {
+ // FPR->FPR
+ // fmov reg
+ enc.add4(Arm64.f_mov_reg(Arm64.OP_FMOV_REG, src - arm.D_OFFSET,dst - arm.D_OFFSET));
+ } else if(!srcX && dstX) {
+ // GPR->FPR
+ // FMOV(general) 64 bits to DOUBLE-PRECISION
+ enc.add4(Arm64.f_mov_general(Arm64.OP_FMOV, 0b01, 0, 0b111, src, dst - arm.D_OFFSET));
+ } else if( srcX && !dstX) {
+ //FPF->GPR
+ // FMOV(general) DOUBLE-PRECISION to 64 bits
+ enc.add4(Arm64.f_mov_general(Arm64.OP_FMOV, 0b01, 0, 0b110, src - arm.D_OFFSET, dst));
+ }
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java
new file mode 100644
index 00000000..25e5d501
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+
+// NZCV transfers require a GPR. Further splits can spill that GPR to the stack.
+public class SplitFlagsARM extends SplitARM {
+ private static final RegMask MASK = new RegMask(arm.WR_BITS,1L);
+ SplitFlagsARM(LRG lrg,String kind,byte round) { super(lrg,kind,round); }
+ @Override public boolean isNonStackSplit() { return true; }
+ @Override public RegMask regmap(int i) { return MASK; }
+ @Override public RegMask outregmap() { return MASK; }
+
+ @Override public void encoding(Encoding enc) {
+ short dst=enc.reg(this), src=enc.reg(in(1));
+ if( dst==src ) return;
+ if( src==arm.FLAGS ) enc.add4(Arm64.flags(dst,false));
+ else if( dst==arm.FLAGS ) enc.add4(Arm64.flags(src,true));
+ else super.encoding(enc);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java
new file mode 100644
index 00000000..c9b2127a
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.codegen.*;
+
+// Keep a saved NZCV value in GPRs and stack slots through subsequent spills.
+public class SplitGPRARM extends SplitARM {
+ private static final RegMask MASK = new RegMask(arm.WR_BITS,-2L);
+ SplitGPRARM(LRG lrg,String kind,byte round) { super(lrg,kind,round); }
+ @Override public RegMask regmap(int i) { return MASK; }
+ @Override public RegMask outregmap() { return MASK; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java
new file mode 100644
index 00000000..0b90ac8b
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java
@@ -0,0 +1,38 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.node.StoreNode;
+import com.seaofnodes.simple.util.SB;
+
+// Store memory addressing on ARM
+// Support imm, reg(direct), or reg+off(indirect) addressing
+// base - base pointer, offset is added to base
+// null - index never allowed (no [reg+reg] mode)
+// off - offset added to base
+// imm - immediate value to store, only if val is null
+// val - value to store or null
+
+//e.g s.cs[0] = 67; // C
+// base = s.cs, off = 4, imm = 67, val = null
+public class StoreARM extends MemOpARM {
+ StoreARM(StoreNode st, Node base, Node idx, int off, Node val) {
+ super(st, base, idx, off, 0, val);
+ }
+ @Override public String op() { return "st"+_sz; }
+ @Override public RegMask outregmap() { return null; }
+
+ private static final int[] OP_STORES = new int[]{ Arm64.OP_STORE_IMM_8, Arm64.OP_STORE_IMM_16, Arm64.OP_STORE_IMM_32, Arm64.OP_STORE_IMM_64, };
+
+ private static final int[] OP_STORE_RS = new int[]{ Arm64.OP_STORE_R_8, Arm64.OP_STORE_R_16, Arm64.OP_STORE_R_32, Arm64.OP_STORE_R_64, };
+
+ @Override public void encoding( Encoding enc ) {
+ ldst_encode(enc, OP_STORES[Integer.numberOfTrailingZeros(size())], OP_STORE_RS[Integer.numberOfTrailingZeros(_bytes)], val(), size());
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ asm_address(code,sb).p(",");
+ if( val()==null ) sb.p("#").p(_imm);
+ else sb.p(code.reg(val()));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java
new file mode 100644
index 00000000..fd5cd4de
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class SubARM extends MachConcreteNode implements MachNode {
+ SubARM(Node sub) { super(sub); }
+ @Override public String op() { return "sub"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_SUB); }
+
+ // General form: "sub # rd = rs1 - rs2"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java
new file mode 100644
index 00000000..1a751434
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class SubFARM extends MachConcreteNode implements MachNode {
+ SubFARM(Node subf) {super(subf);}
+ @Override public String op() { return "subf"; }
+ @Override public RegMask regmap(int i) { return arm.DMASK; }
+ @Override public RegMask outregmap() { return arm.DMASK; }
+ @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_SUB); }
+ // Default on double precision for now(64 bits)
+ // General form: "vsub.f32 rd = src1 + src2
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java
new file mode 100644
index 00000000..b2c0b2ed
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java
@@ -0,0 +1,29 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+public class SubIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ SubIARM(Node sub, int imm) {
+ super(sub);
+ _inputs.pop();
+ _imm = imm;
+ }
+ @Override public String op() { return _imm == -1 ? "dec" : "subi"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ arm.imm_inst(enc,this, in(1), Arm64.OPI_SUB,_imm);
+ }
+
+ // General form: "subi rd = rs1 - imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - #").p(_imm);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java
new file mode 100644
index 00000000..a053ba46
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java
@@ -0,0 +1,51 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class TFPARM extends FunPtrNode implements MachNode, RIPRelSize {
+ final String _ext;
+ // Pointer to a Simple function
+ TFPARM( FunPtrNode fptr ) { super(fptr); _ext = null; }
+ // Pointer to an Extern "C" function
+ TFPARM( TypeFunPtr fptr, String ext ) { super(fptr,CodeGen.CODE._start,null); _type = fptr; _ext = ext; }
+ @Override public String op() { return "ldx"; }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public TFPARM copy() { return new TFPARM((TypeFunPtr)_con,_ext); }
+ @Override public void encoding( Encoding enc ) {
+ if( _ext==null ) enc.relo(this); // Internal patch
+ else enc.external(this,_ext); // ELF-file external patch
+ short dst = enc.reg(this);
+ // adrp x0, 0
+ enc.add4(Arm64.adrp(1,0, Arm64.OP_ADRP, 0,dst));
+ // add x0, x0, 0
+ arm.imm_inst(enc,Arm64.OPI_ADD,0, dst);
+ }
+
+ @Override public byte encSize(int delta) {
+ return 8;
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ short rpc = enc.reg(this);
+ if(opLen == 8 ) {
+ arm.patch_adrp_add(enc, opStart, delta, rpc);
+ } else {
+ // should not happen as one instruction is 4 byte, and TFP arm encodes 2.
+ throw Utils.TODO();
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ String reg = code.reg(this);
+ _type.print(sb.p(reg).p(" #"));
+ }
+ @Override public boolean eq(Node n) { return this==n; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java
new file mode 100644
index 00000000..b2531f97
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java
@@ -0,0 +1,49 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.ConstantNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.type.TypeMemPtr;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class TMPARM extends ConstantNode implements MachNode, RIPRelSize {
+ final String _ext;
+ TMPARM( ConstantNode con, String ext ) { super(con); _ext = ext; }
+ @Override public String op() { return "ldp"; }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public TMPARM copy() { return new TMPARM(this,_ext); }
+ @Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) enc.externalData(this,_ext);
+ else enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1);
+ short dst = enc.reg(this);
+ // adrp x0, 0
+ enc.add4(Arm64.adrp(1,0, Arm64.OP_ADRP, 0,dst));
+ // add x0, x0, 0
+ arm.imm_inst(enc,Arm64.OPI_ADD,0, dst);
+ }
+
+ @Override public byte encSize(int delta) {
+ return 8;
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ short dst = enc.reg(this);
+ if(opLen == 8 ) {
+ arm.patch_adrp_add(enc, opStart, delta, dst);
+ } else {
+ throw Utils.TODO();
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ String reg = code.reg(this);
+ _con.print(sb.p(reg).p(" #"));
+ }
+ @Override public boolean eq(Node n) { return this==n; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java
new file mode 100644
index 00000000..aecd02bb
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java
@@ -0,0 +1,48 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// unconditional jump
+public class UJmpARM extends CFGNode implements MachNode, RIPRelSize {
+ UJmpARM() { }
+ @Override public String op() { return "jmp"; }
+ @Override public String label() { return op(); }
+ @Override protected String format() { return "jmp "; }
+ @Override public RegMask regmap(int i) {return null; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public Type compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+ @Override public void encoding( Encoding enc ) {
+ enc.jump(this,uctrl());
+ int body = Arm64.b(Arm64.OP_UJMP, 0);
+ enc.add4(body);
+ }
+
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) {
+ if( -(1<<26) <= delta && delta < (1<<26) ) return 4;
+ // 2 word encoding needs a tmp register, must teach RA
+ throw Utils.TODO();
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ if( opLen==4 ) {
+ enc.patch4(opStart,Arm64.b(Arm64.OP_UJMP, delta));
+ } else {
+ throw Utils.TODO();
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ CFGNode target = uctrl();
+ //assert target.nOuts() > 1; // Should optimize jmp to empty targets
+ sb.p(label(target));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java
new file mode 100644
index 00000000..d56397a3
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class XorARM extends MachConcreteNode implements MachNode{
+ XorARM(Node xor) {super(xor);}
+ @Override public String op() { return "xor"; }
+ @Override public String glabel() { return "^"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_XOR); }
+ // General form: "rd = x1 ^ x2"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java
new file mode 100644
index 00000000..12143ccd
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java
@@ -0,0 +1,27 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.util.SB;
+
+public class XorIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ XorIARM(Node xor, int imm) {
+ super(xor);
+ _inputs.pop();
+ _imm = imm;
+ }
+
+ @Override public String op() { return "xori"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ // General form: "xori rd = rs1 ^ imm"
+ @Override public void encoding( Encoding enc ) { arm.imm_inst_n(enc,this, in(1), Arm64.OPI_XOR,_imm); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ #").p(_imm);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
new file mode 100644
index 00000000..3c965da5
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -0,0 +1,503 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import static com.seaofnodes.isa.Arm64.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Utils;
+
+public class arm extends Machine {
+ public arm( CodeGen code ) {
+ if( !"SystemV".equals(code._callingConv) )
+ throw new IllegalArgumentException("Unknown calling convention "+code._callingConv);
+ }
+
+ // ARM64
+ @Override public String name() { return "arm"; }
+
+ // GPR(S)
+ public static final int X0 = 0, X1 = 1, X2 = 2, X3 = 3, X4 = 4, X5 = 5, X6 = 6, X7 = 7;
+ public static final int X8 = 8, X9 = 9, X10 = 10, X11 = 11, X12 = 12, X13 = 13, X14 = 14, X15 = 15;
+ public static final int X16 = 16, X17 = 17, X18 = 18, X19 = 19, X20 = 20, X21 = 21, X22 = 22, X23 = 23;
+ public static final int X24 = 24, X25 = 25, X26 = 26, X27 = 27, X28 = 28, X29 = 29, X30 = 30, RSP = 31;
+
+ // Floating point registers
+ public static final int D0 = 32, D1 = 33, D2 = 34, D3 = 35, D4 = 36, D5 = 37, D6 = 38, D7 = 39;
+ public static final int D8 = 40, D9 = 41, D10 = 42, D11 = 43, D12 = 44, D13 = 45, D14 = 46, D15 = 47;
+ public static final int D16 = 48, D17 = 49, D18 = 50, D19 = 51, D20 = 52, D21 = 53, D22 = 54, D23 = 55;
+ public static final int D24 = 56, D25 = 57, D26 = 58, D27 = 59, D28 = 60, D29 = 61, D30 = 62, D31 = 63;
+
+ static final int FLAGS = 64;
+ static final int MAX_REG = 65;
+ public static final int D_OFFSET = 32;
+
+ static final String[] REGS = new String[] {
+ "X0", "X1", "X2", "X3", "X4", "X5", "X6", "X7",
+ "X8", "X9", "X10", "X11", "X12", "X13", "X14", "X15",
+ "X16", "X17", "X18", "X19", "X20", "X21", "X22", "X23",
+ "X24", "X25", "X26", "X27", "X28", "X29", "RPC", "RSP",
+ "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7",
+ "D8", "D9", "D10", "D11", "D12", "D13", "D14", "D15",
+ "D16", "D17", "D18", "D19", "D20", "D21", "D22", "D23",
+ "D24", "D25", "D26", "D27", "D28", "D29", "D30", "D31",
+ "flags"
+ };
+ @Override public String[] regs() { return REGS; }
+
+ // from (x0-x30)
+ // General purpose register mask: pointers and ints, not floats
+ static final long RD_BITS = 0xFFFFFFFFL;
+ static final RegMask RMASK = new RegMask(RD_BITS);
+ static final long WR_BITS = 0x7FFFFFFFL; // All the GPRs, not RSP
+ static final RegMask WMASK = new RegMask(WR_BITS);
+
+ // Float mask from(d0–d31)
+ static final long FP_BITS = 0xFFFFFFFFL<<D0;
+ static final RegMask DMASK = new RegMask(FP_BITS);
+
+ // Load/store mask; both GPR and FPR
+ static final RegMask MEM_MASK = new RegMask(WR_BITS | FP_BITS);
+
+ static final RegMask SPLIT_MASK = new RegMask(WR_BITS | FP_BITS, -2L/*skip flags*/);
+
+ static final RegMask FLAGS_MASK = new RegMask(FLAGS);
+
+ // Arguments masks
+ static final RegMask X0_MASK = new RegMask(X0);
+ static final RegMask X1_MASK = new RegMask(X1);
+ static final RegMask X2_MASK = new RegMask(X2);
+ static final RegMask X3_MASK = new RegMask(X3);
+ static final RegMask X4_MASK = new RegMask(X4);
+ static final RegMask X5_MASK = new RegMask(X5);
+ static final RegMask X6_MASK = new RegMask(X6);
+ static final RegMask X7_MASK = new RegMask(X7);
+
+ // Arguments(float) masks
+ static final RegMask D0_MASK = new RegMask(D0);
+ static final RegMask D1_MASK = new RegMask(D1);
+ static final RegMask D2_MASK = new RegMask(D2);
+ static final RegMask D3_MASK = new RegMask(D3);
+ static final RegMask D4_MASK = new RegMask(D4);
+ static final RegMask D5_MASK = new RegMask(D5);
+ static final RegMask D6_MASK = new RegMask(D6);
+ static final RegMask D7_MASK = new RegMask(D7);
+
+ // Calling convention; returns a machine-specific register
+ // for incoming argument idx.
+ // index 0 for control, 1 for memory, real args start at index 2
+ static final RegMask[] CALLINMASK = new RegMask[] {
+ X0_MASK,
+ X1_MASK,
+ X2_MASK,
+ X3_MASK,
+ X4_MASK,
+ X5_MASK,
+ X6_MASK,
+ X7_MASK,
+ };
+ static final RegMask[] XMMS = new RegMask[] {
+ D0_MASK,
+ D1_MASK,
+ D2_MASK,
+ D3_MASK,
+ D4_MASK,
+ D5_MASK,
+ D6_MASK,
+ D7_MASK,
+ };
+
+ // ARM ENCODING
+
+ // True if signed 9-bit immediate
+ private static boolean imm9(TypeInteger ti) {
+ // 55 = 64-9
+ return ti.isConstant() && ((ti.value()<<55)>>55) == ti.value();
+ }
+ // True if signed 12-bit immediate
+ private static boolean imm12(TypeInteger ti) {
+ // 52 = 64-12
+ return ti.isConstant() && ((ti.value()<<52)>>52) == ti.value();
+ }
+
+ // Can we encode this in ARM's 12-bit LOGICAL immediate form?
+ // Some combination of shifted bit-masks.
+ private static int imm12Logical(TypeInteger ti) {
+ return ti.isConstant() ? Arm64.logicalImmediate(ti.value()) : -1;
+ }
+
+ public static void imm_inst(Encoding enc, Node n,Node n2, int opcode, int imm12) {
+ short self = enc.reg(n);
+ short reg1 = enc.reg(n2);
+
+ // ADD/SUB encode an unsigned magnitude; switch operation for a negative addend.
+ if( imm12<0 && (opcode==OPI_ADD || opcode==OPI_SUB) ) {
+ opcode ^= OPI_ADD ^ OPI_SUB;
+ imm12 = -imm12;
+ }
+ int body = Arm64.imm_inst(opcode, imm12&0xFFF, reg1, self);
+ enc.add4(body);
+ }
+
+ public static void imm_inst_subs(Encoding enc, Node n,Node n2, int opcode, int imm12) {
+ short reg1 = enc.reg(n2);
+ // 31 = 11111
+ int body = Arm64.imm_inst(opcode, imm12&0xFFF, reg1, 31);
+ enc.add4(body);
+ }
+
+ // for cases where rs1 and dst are the same, eg add x0, x0, 1
+ public static void imm_inst(Encoding enc, int opcode, int imm12, int self) {
+ int body = Arm64.imm_inst(opcode, imm12&0xFFF, self, self);
+ enc.add4(body);
+ }
+
+ public static void imm_inst_n(Encoding enc, Node n, Node n2, int opcode, int imm13) {
+ short self = enc.reg(n);
+ short reg1 = enc.reg(n2);
+ int body = Arm64.imm_inst_n(opcode, imm13, reg1, self);
+ enc.add4(body);
+ }
+
+ // for cases where rs1 and dst are the same, eg add x0, x0, 1
+ public static int imm_inst_l(Encoding enc, Node n, int opcode, int imm12) {
+ short self = enc.reg(n);
+ short reg1 = enc.reg(n.in(1));
+ int body = Arm64.imm_inst(opcode, imm12&0xFFF, reg1, self);
+ return body;
+ }
+
+ // for normal add, reg1, reg2 cases (reg-to-reg)
+ // using shifted-reg form
+
+ public static void r_reg(Encoding enc, Node n, int opcode) {
+ short self = enc.reg(n);
+ short reg1 = enc.reg(n.in(1));
+ short reg2 = enc.reg(n.in(2));
+ int body = Arm64.r_reg(opcode, 0, reg2, 0, reg1, self >= 32 ? reg1: self);
+ enc.add4(body);
+ }
+
+ public static void r_reg_subs(Encoding enc, Node n, int opcode) {
+ short reg1 = enc.reg(n.in(1));
+ short reg2 = enc.reg(n.in(2));
+ // 31 = 11111
+ int body = Arm64.r_reg(opcode, 0, reg2, 0, reg1, 31);
+ enc.add4(body);
+ }
+
+ public static void shift_reg(Encoding enc, Node n, int op2) {
+ short self = enc.reg(n);
+ short reg1 = enc.reg(n.in(1));
+ short reg2 = enc.reg(n.in(2));
+ int body = Arm64.shift_reg(0b10011010110, reg2, op2, reg1, self);
+ enc.add4(body);
+ }
+
+ // MUL can be considered an alias for MADD with the third operand Ra being set to 0
+
+ public static void madd(Encoding enc, Node n, int opcode, int ra) {
+ short self = enc.reg(n);
+ short reg1 = enc.reg(n.in(1));
+ short reg2 = enc.reg(n.in(2));
+ int body = Arm64.madd(opcode, reg2, ra, reg1, self);
+ enc.add4(body);
+ }
+
+ public static void f_scalar(Encoding enc, Node n, int op ) {
+ short self = (short)(enc.reg(n) -D_OFFSET);
+ short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET);
+ short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET);
+ int body = Arm64.f_scalar(0b00011110, 1, reg2, op, reg1, self);
+ enc.add4(body);
+ }
+
+ public static void patch_adrp_add(Encoding enc, int opStart, int delta, int rd) {
+ int target = opStart + delta;
+ int pageDelta = ((target & ~0xFFF) - (opStart & ~0xFFF)) >> 12;
+ int imm21 = pageDelta & 0x1FFFFF;
+ enc.patch4(opStart , Arm64.adrp(1, imm21 & 3, OP_ADRP, imm21 >>> 2, rd));
+ enc.patch4(opStart+4, Arm64.imm_inst_l(OPI_ADD, target & 0xFFF, rd));
+ }
+
+ public static void f_cmp(Encoding enc, Node n) {
+ short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET);
+ short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET);
+ int body = Arm64.f_cmp(0b00011110, 3, reg2, reg1);
+ enc.add4(body);
+ }
+
+ @Override public RegMask callArgMask(TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); }
+ static RegMask callInMask(TypeFunPtr tfp, int idx, int maxArgSlot ) {
+ if( idx==0 ) return CodeGen.CODE._rpcMask;
+ if( idx==1 ) return null;
+ // Count floats in signature up to index
+ if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence
+ boolean hidden = hiddenSelf(tfp);
+ int sigidx = idx-2;
+ if( hidden ) {
+ if( sigidx==0 ) return null;
+ sigidx--;
+ }
+ // Count floats in signature up to index
+ int fcnt=0;
+ for( int i=hidden ? 1 : 0; i<idx-2; i++ )
+ if( tfp.arg(i) instanceof TypeFloat)
+ fcnt++;
+ // Floats up to XMMS in XMM registers
+ if( tfp.arg(idx-2) instanceof TypeFloat ) {
+ if( fcnt < XMMS.length )
+ return XMMS[fcnt];
+ } else {
+ RegMask[] cargs = CALLINMASK;
+ if( sigidx-fcnt < cargs.length )
+ return cargs[sigidx-fcnt];
+ }
+ // Pass on stack slot (8 and higher)
+ if( maxArgSlot>0 ) throw Utils.TODO();
+ return new RegMask(MAX_REG + 1 + sigidx);
+ }
+
+ // Return the max stack slot used by this signature, or 0
+ @Override public short maxArgSlot( TypeFunPtr tfp ) {
+ int icnt=0, fcnt=0; // Count of ints, floats
+ for( int i=hiddenSelf(tfp) ? 1 : 0; i<tfp.nargs(); i++ ) {
+ if( tfp.arg(i) instanceof TypeFloat ) fcnt++;
+ else icnt++;
+ }
+ int nstk = Math.max(icnt-8,0)+Math.max(fcnt-8,0);
+ return (short)nstk;
+ }
+ private static boolean hiddenSelf(TypeFunPtr tfp) {
+ return tfp.nargs() > 0 && tfp.arg(0) == TypePtr.PTR;
+ }
+
+ private static final long CALLEE_SAVE =
+ 1L<<X19 |
+ 1L<<X20 | 1L<<X21 | 1L<<X22 | 1L<<X23 |
+ 1L<<X24 | 1L<<X25 | 1L<<X26 | 1L<<X27 |
+ 1L<<X28 |
+ 1L<<D9 | 1L<<D10 | 1L<<D11 |
+ 1L<<D12 | 1L<<D13 | 1L<<D14 | 1L<<D15;
+ static final long CALLER_SAVE = ~CALLEE_SAVE & ~(1L<<RSP);
+ @Override public long callerSave() { return CALLER_SAVE; }
+ @Override public long neverSave() { return 1L<<RSP; }
+ @Override public RegMask retMask( TypeFunPtr tfp ) {
+ return tfp.ret() instanceof TypeFloat ? D0_MASK : X0_MASK;
+ }
+ @Override public int rpc() { return X30; }
+
+ // Create a split op; any register to any register, including stack slots
+ @Override public SplitNode split(LRG lrg, String kind, byte round) {
+ RegMask def = lrg._machDef==null ? null : lrg._machDef.outregmap(), use = lrg.useMask();
+ if( (def!=null && def.size1() && def.test(FLAGS)) ||
+ (use!=null && use.size1() && use.test(FLAGS)) )
+ return new SplitFlagsARM(lrg,kind,round);
+ // Spilling the saved flags requires a GPR between NZCV and the stack.
+ if( lrg._machDef instanceof SplitFlagsARM || lrg._machDef instanceof SplitGPRARM )
+ return new SplitGPRARM(lrg,kind,round);
+ return new SplitARM(lrg,kind,round);
+ }
+
+ // Return a MachNode unconditional branch
+ @Override public CFGNode jump() {
+ return new UJmpARM();
+ }
+
+ // Instruction selection
+ @Override public Node instSelect(Node n ) {
+ return switch( n ) {
+ case AddNode add -> add(add);
+ case AndNode and -> and(and);
+ case BoolNode bool -> cmp(bool);
+ case CallNode call -> call(call);
+ case PtrToIntNode ptr -> new PtrToIntMach(ptr);
+ case GuardNode guard -> new GuardMach(guard);
+ case CallEndNode cend-> new CallEndMach(cend);
+ case CLInitNode init -> new CLInitNode(init);
+ case CProjNode c -> new CProjNode(c);
+ case ConstantNode con-> con(con);
+ case DivNode div -> div.mode()==2 ? new DivFARM(div) : new DivARM(div);
+ case EscapeNode esc -> new EscapeNode(esc);
+ case FunNode fun -> new FunARM(fun);
+ case FunPtrNode fptr -> fptr(fptr);
+ case IfNode iff -> jmp(iff);
+ case LoadNode ld -> ld(ld);
+ case MemMergeNode mem-> new MemMergeNode(mem);
+ case MinusNode neg -> {
+ if( neg.mode()==2 ) throw Utils.TODO();
+ yield new NegARM(neg);
+ }
+ case MulNode mul -> mul.mode()==2 ? new MulFARM(mul) : new MulARM(mul);
+ case NewNode nnn -> new NewARM(nnn);
+ case NotNode not -> new NotARM(not);
+ case OrNode or -> or(or);
+ case ParmNode parm -> new ParmARM(parm);
+ case BulkMemPhiNode phi -> new BulkMemPhiNode(phi);
+ case MemPhiNode phi -> new MemPhiNode(phi);
+ case PhiNode phi -> new PhiNode(phi);
+ case ProjNode prj -> prj(prj);
+ case ReadOnlyNode read -> new ReadOnlyMach(read);
+ case ReturnNode ret -> new RetARM(ret,ret.fun());
+ case SarNode sar -> asr(sar);
+ case ShlNode shl -> lsl(shl);
+ case ShrNode shr -> lsr(shr);
+ case StartCUNode start -> new StartCUNode(start);
+ case StopCUNode stop -> new StopCUNode(stop);
+ case StartNode start -> new StartNode(start);
+ case StopNode stop -> new StopNode(stop);
+ case StoreNode st -> st(st);
+ case SubNode sub -> sub.mode()==2 ? new SubFARM(sub) : sub(sub);
+ case ToFloatNode tfn -> new I2F8ARM(tfn);
+ case ToIntegerNode cvt -> new F8ToIARM(cvt);
+ case XorNode xor -> xor(xor);
+
+ case LoopNode loop -> new LoopNode(loop);
+ case RegionNode region-> new RegionNode(region);
+ default -> throw Utils.TODO();
+ };
+ }
+
+ private Node cmp(BoolNode bool){
+ Node cmp = _cmp(bool);
+ return new SetARM(cmp, bool.op(), cmp instanceof CmpFARM);
+ }
+ private Node _cmp(BoolNode bool) {
+ if( bool.isFloat() )
+ return new CmpFARM(bool);
+ return bool.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti
+ ? new CmpIARM(bool, (int)ti.value())
+ : new CmpARM(bool);
+ }
+
+ private Node ld(LoadNode ld) {
+ return new LoadARM(address(ld), ld.ptr(), idx, off);
+ }
+
+ private Node jmp(IfNode iff) {
+ // If/Bool combos will match to a Cmp/Set which sets flags.
+ // Most general arith ops will also set flags, which the Jmp needs directly.
+ // Loads do not set the flags, and will need an explicit TEST
+ String op = "!=";
+ boolean fp = false;
+ if( iff.in(1) instanceof BoolNode bool ) { op = bool.op(); fp = bool.isFloat(); }
+ else if( iff.in(1)==null ) op = "=="; // Never-node cutout
+ else iff.setDef(1, new BoolNode.NE(iff.in(1),
+ ConstantNode.raw(TypeInteger.ZERO),
+ (byte)(iff.in(1)._type instanceof TypeFloat ? 2 : 1)));
+ return new BranchARM(iff, op, fp);
+ }
+
+ private Node add(AddNode add) {
+ if( add.mode()==2 )
+ return new AddFARM(add);
+ return add.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && imm12(ti)
+ ? new AddIARM(add, (int)ti.value())
+ : new AddARM(add);
+ }
+
+ private Node sub(SubNode sub) {
+ return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)
+ ? new SubIARM(sub, (int)(ti.value()))
+ : new SubARM(sub);
+ }
+
+ private Node con( ConstantNode con ) {
+ if( !con._con.isConstant() )
+ return ConstantNode.raw(con); // Default unknown caller inputs
+ String ext = con instanceof ExternNode ext0 ? ext0._extern : null;
+ return switch( con._con ) {
+ case TypeInteger ti -> new IntARM(con,ext);
+ case TypeFloat tf -> new FltARM(con,ext);
+ case TypeMemPtr tmp -> new TMPARM(con,ext);
+ case TypeFunPtr tfp -> new TFPARM(tfp,ext);
+ case TypeNil tn -> throw Utils.TODO();
+ // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
+ case Type t -> t==Type.NIL ? new IntARM(con,ext) : ConstantNode.raw(con);
+ };
+ }
+
+ private Node fptr( FunPtrNode con ) {
+ return new TFPARM(con);
+ }
+
+ private Node call(CallNode call){
+ return call.fptr() instanceof FunPtrNode fptr
+ ? new CallARM(call, (TypeFunPtr)fptr._type)
+ : call.fptr() instanceof ConstantNode con
+ ? new CallARM(call, (TypeFunPtr)con._type)
+ : new CallRRARM(call);
+ }
+
+ private Node or(OrNode or) {
+ int imm12;
+ return or.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1
+ ? new OrIARM(or, imm12)
+ : new OrARM(or);
+ }
+
+ private Node xor(XorNode xor) {
+ int imm12;
+ return xor.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1
+ ? new XorIARM(xor, imm12)
+ : new XorARM(xor);
+ }
+
+ private Node and(AndNode and) {
+ int imm12;
+ return and.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1
+ ? new AndIARM(and, imm12)
+ : new AndARM(and);
+ }
+
+ private Node asr(SarNode asr) {
+ return asr.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && ti.value() >= 0 && ti.value() < 63
+ ? new AsrIARM(asr, (int)ti.value())
+ : new AsrARM(asr);
+ }
+
+ private Node lsl(ShlNode lsl) {
+ return lsl.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && ti.value() >= 0 && ti.value() < 63
+ ? new LslIARM(lsl, (int)ti.value())
+ : new LslARM(lsl);
+ }
+
+ private Node lsr(ShrNode lsr) {
+ return lsr.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && ti.value() >= 0 && ti.value() < 63
+ ? new LsrIARM(lsr, (int)ti.value())
+ : new LsrARM(lsr);
+ }
+
+ private Node prj(ProjNode prj) {
+ if( prj.in(0) instanceof CLInitNode ) return new ProjNode(prj);
+ TypeInteger ret = cReturn(prj);
+ if( ret!=null ) return new ExtendARM(prj,ret);
+ return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjARM(prj);
+ }
+
+ private static int off;
+ private static Node idx;
+ private Node st(StoreNode st) {
+ return new StoreARM(address(st),st.ptr(),idx,off,st.val());
+ }
+
+ // Gather addressing mode bits prior to constructing. This is a builder
+ // pattern, but saving the bits in a *local* *global* here to keep mess
+ // contained.
+ private <N extends MemOpNode> N address(N mop ) {
+ off = 0; // Reset
+ idx = null;
+ Node base = mop.ptr();
+ // Skip/throw-away a ReadOnly, only used to typecheck
+ if( base instanceof ReadOnlyNode read ) base = read.in(1);
+ assert !(base instanceof AddNode) && base._type instanceof TypeMemPtr; // Base ptr always, not some derived
+ if( mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm9(ti) ) {
+ off = (int)ti.value();
+ assert off == ti.value(); // In 32-bit range
+ } else {
+ idx = mop.off();
+ }
+ return mop;
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java
new file mode 100644
index 00000000..59bad64e
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java
@@ -0,0 +1,42 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.FunPtrNode;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// Add upper 20bits to PC. Immediate comes from the relocation info.
+public class AUIPC extends FunPtrNode implements MachNode, RIPRelSize {
+ AUIPC( FunPtrNode fptr ) { super(fptr); }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public AUIPC copy() { return new AUIPC(this); }
+ @Override public String op() { return "auipc"; }
+ @Override public void encoding( Encoding enc ) {
+ enc.relo(this);
+ short dst = enc.reg(this);
+ enc.add4(RiscV.u_type(RiscV.OP_AUIPC, dst, 0));
+ }
+
+ // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding
+ @Override public byte encSize(int delta) { return 4; }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ //short rpc = enc.reg(this);
+ //// High half is where the TFP constant used to be, the last input
+ //short auipc = enc.reg(in(_inputs._len-1));
+ //enc.patch4(opStart,RiscV.i_type(0x67, rpc, 0, auipc, delta));
+ throw Utils.TODO();
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ String reg = code.reg(this);
+ sb.p(reg).p(" = PC+#");
+ _type.print(sb);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java
new file mode 100644
index 00000000..d87e49b9
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java
@@ -0,0 +1,18 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// fadd.d
+public class AddFRISC extends MachConcreteNode implements MachNode {
+ AddFRISC(Node addf) {super(addf);}
+ @Override public String op() { return "addf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; }
+ @Override public RegMask outregmap() { return riscv.FMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,RiscV.RM.RNE,1); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java
new file mode 100644
index 00000000..8012b61e
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.Utils;
+
+public class AddIRISC extends ImmRISC {
+ // Used to inst-selection as a direct match against an ideal Add/Sub
+ public AddIRISC( Node add, int imm12, boolean pop ) { super(add,imm12,pop); }
+ @Override public String op() { return "addi"; }
+ @Override public String glabel() { return "+"; }
+ @Override int opcode() { return RiscV.OP_IMM; }
+ @Override int func3() {return 0;}
+ @Override public AddIRISC copy() {
+ // Clone the AddI, using the same inputs-only code used during inst select.
+ // Output edges are missing.
+ AddIRISC add = new AddIRISC(in(1),_imm12,false);
+ // Copys happen when output edges should be valid, so correct missing output edges.
+ in(1)._outputs.add(add);
+ return add;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java
new file mode 100644
index 00000000..7010916f
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class AddRISC extends MachConcreteNode implements MachNode {
+ public AddRISC( Node add ) { super(add); }
+ AddRISC( Node base, Node off ) {
+ super(new Node[3]);
+ _inputs.set(1,base);
+ _inputs.set(2,off );
+ }
+ @Override public String op() { return "add"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0,0); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java
new file mode 100644
index 00000000..9fd65e7d
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java
@@ -0,0 +1,13 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.Utils;
+
+public class AndIRISC extends ImmRISC {
+ public AndIRISC( Node and, int imm) { super(and,imm); }
+ @Override public String op() { return "andi"; }
+ @Override public String glabel() { return "&"; }
+ @Override int opcode() { return RiscV.OP_IMM; }
+ @Override int func3() {return 7;}
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java
new file mode 100644
index 00000000..1845985e
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java
@@ -0,0 +1,16 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class AndRISC extends MachConcreteNode implements MachNode {
+ public AndRISC(Node and) { super(and); }
+ @Override public String op() { return "and"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0b111,0); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java
new file mode 100644
index 00000000..a9d03da5
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java
@@ -0,0 +1,95 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// Conditional branch such as: BEQ
+public class BranchRISC extends IfNode implements MachNode, RIPRelSize {
+ String _bop;
+ // label is obtained implicitly
+ public BranchRISC( IfNode iff, String bop, Node n1, Node n2 ) {
+ super(iff);
+ _bop = bop;
+ _inputs.setX(1,n1);
+ _inputs.setX(2,n2);
+ }
+
+ @Override public String op() { return "b"+_bop; }
+ @Override public String label() { return op(); }
+ @Override public String comment() { return "L"+cproj(1)._nid; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public void negate() {
+ _bop = negate(_bop);
+ // Cannot encode ">" or "<=", so flip these
+ if( _bop.equals(">") || _bop.equals("<=") ) {
+ swap12(); // Cannot negate the test, so swap the operands
+ _bop = swap(_bop); // Swap test to match
+ }
+ }
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ p.p("if( ");
+ if( in(1)==null ) p.p("0");
+ else p.n(in(1));
+ p.p(_bop);
+ if( in(2)==null ) p.p("0");
+ else p.n(in(2));
+ return p.p(" )");
+ }
+
+ @Override public void encoding( Encoding enc ) {
+ if( in(1)==null && _bop=="!=" ) return; // Inverted never-node, no code
+ enc.jump(this,cproj(0));
+ if( in(1)==null ) // Never node
+ enc.add4(RiscV.j_type(RiscV.OP_JAL, 0, 0));
+ else {
+ // Todo: relocs (for offset - immf)
+ short src1 = enc.reg(in(1));
+ short src2 = in(2)==null ? (short)riscv.ZERO : enc.reg(in(2));
+ enc.add4(RiscV.b_type(RiscV.OP_BRANCH, RiscV.jumpop(_bop), src1, src2, 0));
+ }
+ }
+
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) {
+ if( in(1)==null && _bop=="!=" ) return 0; // Inverted never-node, no code
+ if( -4*1024 <= delta && delta < 4*1024 ) return 4;
+ // 2 word encoding needs a tmp register, must teach RA
+ throw Utils.TODO();
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ assert !( in(1)==null && _bop=="!=" ); // Inverted never-node, no code no patch
+ if( in(1)==null ) { // Never node
+ enc.patch4(opStart,RiscV.j_type(RiscV.OP_JAL, 0, delta));
+ } else {
+ short src1 = enc.reg(in(1));
+ short src2 = in(2)==null ? (short)riscv.ZERO : enc.reg(in(2));
+ if( opLen==4 ) {
+ enc.patch4(opStart,RiscV.b_type(RiscV.OP_BRANCH, RiscV.jumpop(_bop), src1, src2, delta));
+ } else {
+ throw Utils.TODO();
+ }
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ if( in(1)==null && _bop=="!=" ) {
+ sb.p("never");
+ return;
+ }
+ String src1 = in(1)==null ? "#0" : code.reg(in(1));
+ String src2 = in(2)==null ? "#0" : code.reg(in(2));
+ sb.p(src1).p(" ").p(_bop).p(" ").p(src2).p(" ");
+ CFGNode prj = cproj(0).uctrlSkipEmpty();
+ if( !prj.blockHead() ) prj = prj.cfg0();
+ sb.p(label(prj));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java
new file mode 100644
index 00000000..b1426a3f
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java
@@ -0,0 +1,62 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class CallRISC extends CallNode implements MachNode, RIPRelSize {
+ final TypeFunPtr _tfp;
+ final String _name;
+ CallRISC( CallNode call, TypeFunPtr tfp ) {
+ super(call);
+ assert tfp.isConstant();
+ _inputs.pop(); // Pop constant target
+ _tfp = tfp;
+ _name = CodeGen.CODE.funcName(tfp.fidx());
+ }
+
+ @Override public String op() { return "call"; }
+ @Override public String label() { return op(); }
+ @Override public String name() { return _name; }
+ @Override public TypeFunPtr tfp() { return _tfp; }
+ @Override public RegMask regmap(int i) { return riscv.callInMask(_tfp,i,fun()._maxArgSlot); }
+ @Override public RegMask outregmap() { return riscv.RPC_MASK; }
+ @Override public int nargs() { return nIns()-2; } // Minus control, memory, fptr
+
+ @Override public void encoding( Encoding enc ) {
+ // Short form +/-4K: beq r0,r0,imm12
+ // Long form: auipc rX,imm20/32; jal r0,[rX+imm12/32]
+ FunNode fun = CodeGen.CODE.link(_tfp);
+ if( fun==null ) enc.external(this,_name);
+ else enc.relo(this);
+ short rpc = enc.reg(this);
+ enc.add4(RiscV.j_type(RiscV.OP_JAL, rpc, 0));
+ }
+
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) {
+ if( -(1L<<20) <= delta && delta < (1L<<20) ) return 4;
+ // 2 word encoding needs a tmp register, must teach RA
+ throw Utils.TODO();
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ short rpc = enc.reg(this);
+ if( opLen==4 ) {
+ enc.patch4(opStart,RiscV.j_type(RiscV.OP_JAL, rpc, delta));
+ } else {
+ throw Utils.TODO();
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(_name).p(" ");
+ for( int i=0; i<nargs(); i++ )
+ sb.p(code.reg(arg(i+2))).p(", ");
+ sb.unchar(2);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java
new file mode 100644
index 00000000..68f1016a
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java
@@ -0,0 +1,35 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.util.SB;
+
+public class CallRRISC extends CallNode implements MachNode {
+ final TypeFunPtr _tfp;
+ CallRRISC( CallNode call ) { super(call); _tfp = call.tfp(); }
+ @Override public TypeFunPtr tfp() { return _tfp; }
+ @Override public String op() { return "callr"; }
+ @Override public String label() { return op(); }
+ @Override public RegMask regmap(int i) {
+ return i==_inputs._len-1
+ ? riscv.RMASK // Function call target
+ : riscv.callInMask(tfp(),i,fun()._maxArgSlot); // Normal argument
+ }
+ @Override public RegMask outregmap() { return riscv.RPC_MASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ short rpc = enc.reg(this);
+ short src = enc.reg(in(_inputs._len-1));
+ int body = RiscV.i_type(RiscV.OP_JALR, rpc, 0, src, 0);
+ enc.add4(body);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(fptr())).p(" ");
+ for( int i=0; i<nargs(); i++ )
+ sb.p(code.reg(arg(i))).p(", ");
+ sb.unchar(2);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java
new file mode 100644
index 00000000..8eb4fb3b
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// fdiv.d
+public class DivFRISC extends MachConcreteNode implements MachNode{
+ DivFRISC(Node divf) {super(divf);}
+ @Override public String op() { return "divf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; }
+ @Override public RegMask outregmap() { return riscv.FMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,RiscV.RM.RUP,13); }
+ // Default on double precision for now(64 bits)
+ // General form: "fdiv.d rd = src1 / src2
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java
new file mode 100644
index 00000000..515ffa47
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class DivRISC extends MachConcreteNode implements MachNode{
+ public DivRISC(Node div) {super(div);}
+ @Override public String op() { return "div"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,4,1); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java
new file mode 100644
index 00000000..5bd3ec1c
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+// Convert a narrow C return register to Simple's full-width integer value.
+public class ExtendRISC extends MachConcreteNode {
+ final int _bits;
+ final boolean _signed;
+ ExtendRISC(ProjNode prj, TypeInteger ret) {
+ super(prj);
+ _inputs.set(0,null);
+ _inputs.add(new ProjRISC(prj));
+ _bits = 8 << ret.log_size();
+ _signed = ret._min < 0;
+ }
+ @Override public String op() { return (_signed ? "sext" : "zext")+_bits; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding(Encoding enc) {
+ short dst = enc.reg(this), src = enc.reg(in(1));
+ int shift = 64-_bits;
+ enc.add4(RiscV.i_type(RiscV.OP_IMM,dst,1,src,shift));
+ enc.add4(RiscV.i_type(RiscV.OP_IMM,dst,5,dst,shift | (_signed ? 0x400 : 0)));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java
new file mode 100644
index 00000000..a56ad216
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class F8ToIRISC extends MachConcreteNode implements MachNode {
+ F8ToIRISC(Node f8toi) {super(f8toi);}
+ @Override public String op() { return "cvti"; }
+ @Override public RegMask regmap(int i) { assert i==1; return riscv.FMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ RiscV.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-riscv.F_OFFSET);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java
new file mode 100644
index 00000000..a110d156
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java
@@ -0,0 +1,48 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.ConstantNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.type.TypeFloat;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class FltRISC extends ConstantNode implements MachNode, RIPRelSize {
+ final String _ext;
+ FltRISC( ConstantNode con, String ext ) { super(con); _ext = ext; }
+ @Override public String op() { return "ld8"; }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return riscv.FMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public FltRISC copy() { return new FltRISC(this,_ext); }
+
+ @Override public void encoding( Encoding enc ) {
+ enc.largeConstant(this,_con, 0, -1);
+ short dst = (short)(enc.reg(this) - riscv.F_OFFSET);
+ short tmp = (short)riscv.T6;
+ // AUIPC dst,#hi20_constant_pool
+ enc.add4(RiscV.u_type(RiscV.OP_AUIPC, tmp, 0));
+ // Load dst,[dst+#low12_constant_pool]
+ enc.add4(RiscV.i_type(RiscV.OP_LOADFP, dst, 0b11, tmp, 0));
+ }
+ @Override public RegMask killmap() { return new RegMask(riscv.T6); }
+
+ // Delta is from opcode start.
+ @Override public byte encSize(int delta) { return 8; }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ short dst = (short)(enc.reg(this) - riscv.F_OFFSET);
+ short tmp = (short)riscv.T6;
+ // AUIPC dst,#hi20_constant_pool
+ enc.patch4(opStart , RiscV.u_type(RiscV.OP_AUIPC, tmp, delta>>12));
+ // Load dst,[dst+#low12_constant_pool]
+ enc.patch4(opStart+4, RiscV.i_type(RiscV.OP_LOADFP, dst, 0b11, tmp, delta & 0xFFF));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ _con.print(sb.p(code.reg(this)).p(" = #"));
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java
new file mode 100644
index 00000000..d3d64b06
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.FunNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class FunRISC extends FunNode implements MachNode {
+ FunRISC(FunNode fun) {super(fun);}
+ @Override public void computeFrameAdjust(CodeGen code, int maxReg) {
+ super.computeFrameAdjust(code,maxReg);
+ if( _hasCalls ) // If non-leaf, pad to 16b
+ _frameAdjust = ((_frameAdjust+8) & -16);
+ }
+ @Override public void encoding( Encoding enc ) {
+ int sz = _frameAdjust;
+ if( sz == 0 ) return; // Skip if no frame adjust
+ if( sz >= 1L<<12 ) throw Utils.TODO();
+ enc.add4(RiscV.i_type(RiscV.OP_IMM, riscv.RSP, 0, riscv.RSP, -sz & 0xFFF));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java
new file mode 100644
index 00000000..1b53ab0f
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// fcvt.d.w
+// Converts a 32-bit signed integer, in integer register rs1 into a double-precision floating-point number in floating-point register rd.
+public class I2F8RISC extends MachConcreteNode implements MachNode {
+ I2F8RISC(Node i2f8) {super(i2f8);}
+ @Override public String op() { return "cvtf"; }
+ @Override public RegMask regmap(int i) { assert i==1; return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.FMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short dst = (short)(enc.reg(this )-riscv.F_OFFSET);
+ short src1 = enc.reg(in(1));
+ int body = RiscV.r_type(RiscV.OP_FP,dst,RiscV.RM.RNE.ordinal(),src1,0,0x69);
+ enc.add4(body);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java
new file mode 100644
index 00000000..ee247c3e
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java
@@ -0,0 +1,55 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+abstract public class ImmRISC extends MachConcreteNode implements MachNode {
+ public final int _imm12;
+ ImmRISC( Node add, int imm12, boolean pop ) {
+ super(add);
+ _imm12 = imm12;
+ // If pop, we are moving from an ideal Node to MachNode; the ideal Node
+ // inputs got copied here, but we are absorbing the immediate into the
+ // MachNode and so do not want the constant input.
+ if( pop ) _inputs.pop();
+ // if NOT pop, we are the 2nd-half of a 2-part expansion, reset the
+ // input edges to no-control
+ else { _inputs.set(0,null); _inputs.setX(1,add); }
+ }
+ ImmRISC( Node add, int imm12 ) { this(add,imm12,true); }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public boolean isClone() {
+ // Allow cloning of the 2nd half of a 2-part constant; this might
+ // trigger cloning the first part also.
+ return in(1) instanceof LUI || in(1) instanceof AUIPC;
+ }
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ int imm12 = (_imm12<<20)>>20; // Sign extend 12 bits
+ p.p("( ").n(in(1));
+ return p.p(String.format(" %s #%d )",glabel(),imm12));
+ }
+
+ abstract int opcode();
+ abstract int func3();
+
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this );
+ short src = enc.reg(in(1));
+ enc.add4(RiscV.i_type(opcode(), dst, func3(), src, _imm12 & 0xFFF));
+ }
+
+ // General form: "addi rd = rs1 + imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ int imm12 = (_imm12<<20)>>20; // Sign extend 12 bits
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ").p(glabel()).p(" #").p(imm12);
+ if( in(1) instanceof LUI lui )
+ sb.p(" // #").hex4((int)(((TypeInteger)lui._con).value()) + imm12);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java
new file mode 100644
index 00000000..dd4a02a3
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java
@@ -0,0 +1,49 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.ConstantNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// Special instruction for loading 8 byte constants from the constant pool.
+
+public class Int8RISC extends ConstantNode implements MachNode, RIPRelSize {
+ final String _ext;
+ Int8RISC( ConstantNode con, String ext ) { super(con); _ext = ext; }
+ @Override public String op() { return "ld8"; }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public Int8RISC copy() { return new Int8RISC(this,_ext); }
+
+ @Override public byte encSize(int delta) { return 8; }
+
+ @Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) throw Utils.TODO();
+ short dst = enc.reg(this);
+ short tmp = (short)riscv.T6;
+ enc.largeConstant(this,_con, 0, -1);
+ // AUIPC dst,#hi20_constant_pool
+ enc.add4(RiscV.u_type(RiscV.OP_AUIPC, tmp, 0));
+ // Load dst,[dst+#low12_constant_pool]
+ enc.add4(RiscV.i_type(RiscV.OP_LOAD, dst, 0b11, tmp, 0));
+ }
+ @Override public RegMask killmap() { return new RegMask(riscv.T6); }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ short dst = enc.reg(this);
+ short tmp = (short)riscv.T6;
+ // AUIPC dst,#hi20_constant_pool
+ enc.patch4(opStart , RiscV.u_type(RiscV.OP_AUIPC, tmp, delta>>12));
+ // Load dst,[dst+#low12_constant_pool]
+ enc.patch4(opStart+4, RiscV.i_type(RiscV.OP_LOAD, dst, 0b11, tmp, delta & 0xFFF));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ _con.print(sb.p(code.reg(this)).p(" = #"));
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java
new file mode 100644
index 00000000..35d2bc65
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.ConstantNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+// 12-bit integer constant. Larger constants are made up in the instruction
+// selection by adding with a LUI.
+public class IntRISC extends ConstantNode implements MachNode {
+ IntRISC(ConstantNode con) { super(con); }
+ @Override public String op() { return "ldi"; }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public IntRISC copy() { return new IntRISC(this); }
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this);
+ int val = _con==Type.NIL ? 0 : (int)(((TypeInteger)_con).value() & 0xFFF);
+ // Explicit truncation of larger immediates; this will sign-extend on
+ // load and this is handled during instruction selection.
+ enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 0, riscv.ZERO, val));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ String reg = code.reg(this);
+ _con.print(sb.p(reg).p(" = #"));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java
new file mode 100644
index 00000000..aedd8b47
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.ConstantNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+// Load upper 20bits.
+public class LUI extends ConstantNode implements MachNode {
+ public LUI( int imm20 ) {
+ super(TypeInteger.constant(imm20));
+ assert (imm20 & 0xFFF)==0;
+ }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public LUI copy() { return new LUI((int)((TypeInteger)_con).value()); }
+ @Override public String op() { return "lui"; }
+ @Override public void encoding( Encoding enc ) {
+ long x = ((TypeInteger)_con).value();
+ int imm20 = (int)(x>>12) & 0xFFFFF;
+ short dst = enc.reg(this);
+ int lui = RiscV.u_type(RiscV.OP_LUI, dst, imm20);
+ enc.add4(lui);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ String reg = code.reg(this);
+ sb.p(reg).p(" = #").hex4((int)(((TypeInteger)_con).value()));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java
new file mode 100644
index 00000000..e9f3ec02
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java
@@ -0,0 +1,65 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// Load memory addressing on RISC
+// Support imm, reg(direct), or reg+off(indirect) addressing
+// Base = base - base pointer, offset is added to base
+// idx = null
+// off = off - imm12 added to base
+public class LoadRISC extends MemOpRISC {
+ public LoadRISC(LoadNode ld, Node base, int off) { super(ld, base, off, null); }
+ @Override public String op() { return "ld" +_sz; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.MEM_MASK; }
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this );
+ short ptr = enc.reg(ptr());
+ int op = dst >= riscv.F_OFFSET ? RiscV.OP_LOADFP : RiscV.OP_LOAD;
+ if( dst >= riscv.F_OFFSET ) dst -= riscv.F_OFFSET;
+ enc.add4(RiscV.i_type(op, dst, func3(), ptr, _off));
+ }
+ @Override int func3() {
+ int func3 = -1;
+ // no unsigned flavour for store, so both signed and unsigned trigger the same
+ Type declType = _con;
+ if( declType == TypeInteger. I8 ) func3=0; // LB
+ if( declType == TypeInteger.I16 ) func3=1; // LH
+ if( declType == TypeInteger.I32 ) func3=2; // LW
+ if( declType == TypeInteger.BOT ) func3=3; // LD
+ if( declType == TypeInteger. U8 ) func3=4; // LBU
+ if( declType == TypeInteger.BOOL) func3=4; // LBU
+ if( declType == TypeInteger.U16 ) func3=5; // LHU
+ if( declType == TypeInteger.U32 ) func3=6; // LWU
+ if( declType instanceof TypeInteger ti ) {
+ if( -128 <= ti._min && ti._max < 128 ) func3 = 0; // LB
+ }
+
+ // float
+ if( declType == TypeFloat.F32) func3 = 2; // fLW fSW
+ if( declType == TypeFloat.F64) func3 = 3; // fLD fSD
+
+ if( declType instanceof TypeMemPtr) func3 = 3; // 8 byte pointers (pick ld)
+ if( declType instanceof TypeFunPtr) func3 = 3; // 8 byte function pointers
+ if( declType instanceof TypeConAry ary )
+ func3 = switch( ary.log_size() ) {
+ case 0 -> 4; // LBU
+ case 1 -> 5; // LHU
+ case 2 -> 2; // LW
+ case 3 -> 3; // LD
+ default -> throw Utils.TODO("RISC-V constant-array load for "+declType);
+ };
+ if( func3 == -1 ) throw Utils.TODO("RISC-V load for "+declType);
+ return func3;
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(",");
+ asm_address(code,sb);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java
new file mode 100644
index 00000000..e0bbc219
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java
@@ -0,0 +1,51 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+
+public abstract class MemOpRISC extends MemOpNode implements MachNode {
+ final int _off; // Limit 12 bits
+ final char _sz;
+ final int _bytes;
+ MemOpRISC(MemOpNode mop, Node base, int off, Node val) {
+ super(mop,mop);
+ assert base._type instanceof TypeMemPtr;
+ _inputs.setX(2, base ); // Base can be an Add, is no longer raw object base
+ _inputs.setX(3, null); // Never an index
+ _inputs.setX(4, val );
+ _off = off;
+ _bytes = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size();
+ _sz = (char)('0'+_bytes);
+ }
+
+ @Override public String label() { return op(); }
+ Node val() { return in(4); } // Only for stores
+
+ @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { return p.p(".").p(_name); }
+
+ @Override public Type compute() { throw new AssertionError("Do not call compute on "+getClass().getSimpleName()); }
+ @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); }
+
+ // func3 is based on load/store size and extend
+ abstract int func3();
+
+ // Register mask allowed on input i.
+ @Override public RegMask regmap(int i) {
+ // 0 - ctrl
+ // 1 - memory
+ if( i==2 ) return riscv.RMASK; // base
+ // 2 - index
+ if( i==4 ) return riscv.MEM_MASK; // value
+ throw Utils.TODO();
+ }
+
+ SB asm_address(CodeGen code, SB sb) {
+ sb.p("[").p(code.reg(ptr())).p("+");
+ return sb.p(_off).p("]");
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java
new file mode 100644
index 00000000..dacec2b5
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java
@@ -0,0 +1,18 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// fmul.d
+public class MulFRISC extends MachConcreteNode implements MachNode {
+ MulFRISC(Node mulf) {super(mulf);}
+ @Override public String op() { return "mulf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; }
+ @Override public RegMask outregmap() { return riscv.FMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,RiscV.RM.RNE,9); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java
new file mode 100644
index 00000000..9cb9ace7
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// mulh signed multiply instruction(no-imm form)
+public class MulRISC extends MachConcreteNode implements MachNode{
+ public MulRISC(Node mul) {super(mul);}
+ @Override public String op() { return "mul"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0,1); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java
new file mode 100644
index 00000000..3070c56c
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class NegRISC extends MachConcreteNode implements MachNode {
+ public NegRISC( Node neg ) { super(neg); }
+ @Override public String op() { return "neg"; }
+ @Override public RegMask regmap(int i) { assert i==1; return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this );
+ short src = enc.reg(in(1));
+ RiscV.r_type(RiscV.OP,dst,0,0,src,0x20);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = -").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java
new file mode 100644
index 00000000..40d0359c
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java
@@ -0,0 +1,49 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.print.ASMPrinter;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class NewRISC extends NewNode implements MachNode, RIPRelSize {
+ // A pre-zeroed chunk of memory.
+ NewRISC( NewNode nnn ) { super(nnn); }
+ @Override public void encoding( Encoding enc ) {
+ // Generic external encoding; 2 ops.
+ enc.external(this,"calloc");
+ // A1 is a caller-save, allowed to crush building external address
+ // auipc
+ enc.add4(RiscV.i_type(RiscV.OP_IMM , _arg2Reg , 0, riscv.ZERO, 1));
+ enc.add4(RiscV.u_type(RiscV.OP_AUIPC, riscv.A2 , 0));
+ enc.add4(RiscV.i_type(RiscV.OP_JALR , riscv.RPC, 0, riscv.A2, 0));
+ }
+
+ // Patch is for running "new" in a JIT.
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) { return 4*3; }
+
+ // Patch is for running "new" in a JIT.
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ // Negative patches are JIT emulator targets.
+ if( opStart+delta < 0 ) {
+ delta -= 4; // Offset from the AUIPC start
+ int imm20 = delta>>12;
+ if( ((delta>>11)&1)==1 ) imm20++; // Correct accidental sign extension
+ int imm12 = delta&0xFFF;
+ enc.patch4(opStart+4,RiscV.u_type(RiscV.OP_AUIPC, riscv.A2 , imm20));
+ enc.patch4(opStart+8,RiscV.i_type(RiscV.OP_JALR , riscv.RPC, 0, riscv.A2, imm12));
+ } else
+ throw Utils.TODO();
+ }
+
+ // General form: "alloc #bytes PC"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p("\n"); // Multi-op trigger
+ sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n");
+ sb.p("auipc a2=#calloc\n");
+ sb.p("call a2+#calloc");
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
new file mode 100644
index 00000000..6f499db1
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.NotNode;
+import com.seaofnodes.simple.util.SB;
+
+public class NotRISC extends MachConcreteNode implements MachNode {
+ public NotRISC(NotNode not) { super(not); }
+ @Override public String op() { return "not"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this );
+ short src = enc.reg(in(1));
+ // SEQZ is unsigned: negative nonzero inputs must also produce false.
+ enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 3, src, 1));
+ }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java
new file mode 100644
index 00000000..937595a3
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java
@@ -0,0 +1,13 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.Utils;
+
+public class OrIRISC extends ImmRISC {
+ public OrIRISC( Node and, int imm) { super(and,imm); }
+ @Override int opcode() { return RiscV.OP_IMM; }
+ @Override int func3() { return 6; }
+ @Override public String glabel() { return "|"; }
+ @Override public String op() { return "ori"; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java
new file mode 100644
index 00000000..a310a1f4
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java
@@ -0,0 +1,16 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class OrRISC extends MachConcreteNode implements MachNode {
+ public OrRISC(Node or) { super(or); }
+ @Override public String op() { return "or"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,6,0); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java
new file mode 100644
index 00000000..0f8db4e5
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.ParmNode;
+
+public class ParmRISC extends ParmNode implements MachNode {
+ final RegMask _rmask;
+ ParmRISC(ParmNode parm) {
+ super(parm);
+ _rmask = riscv.callInMask(fun().sig(),_idx,0);
+ }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return _rmask; }
+ @Override public void encoding( Encoding enc ) { }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java
new file mode 100644
index 00000000..2f9ac469
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java
@@ -0,0 +1,15 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.ProjNode;
+
+public class ProjRISC extends ProjNode implements MachNode {
+ ProjRISC(ProjNode p) { super(p); }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() {
+ return ((MachNode)in(0)).outregmap(_idx);
+ }
+ @Override public void encoding( Encoding enc ) { }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java
new file mode 100644
index 00000000..33143c6b
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.FunNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.ReturnNode;
+import com.seaofnodes.simple.util.Utils;
+
+public class RetRISC extends ReturnNode implements MachNode {
+ public RetRISC(ReturnNode ret, FunNode fun) { super(ret, fun); fun.setRet(this); }
+ @Override public void encoding( Encoding enc ) {
+ int sz = fun()._frameAdjust;
+ if( sz >= 1L<<12 ) throw Utils.TODO();
+ if( sz != 0 )
+ enc.add4(RiscV.i_type(RiscV.OP_IMM, riscv.RSP, 0, riscv.RSP, sz & 0xFFF));
+ short rpc = enc.reg(rpc());
+ enc.add4(RiscV.i_type(RiscV.OP_JALR, riscv.ZERO, 0, rpc, 0));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java
new file mode 100644
index 00000000..d037dd5c
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java
@@ -0,0 +1,34 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.BoolNode;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.util.SB;
+
+// corresponds to feq.d = equal
+// fle.d = less than or equal
+// flt.d = less than
+public class SetFRISC extends MachConcreteNode implements MachNode {
+ final String _bop; // One of <,<=,==
+ SetFRISC( BoolNode bool ) {
+ super(bool);
+ assert bool.isFloat();
+ _bop = bool.op();
+ }
+ @Override public String op() { return "set"+_bop; }
+ @Override public RegMask regmap(int i) { return riscv.FMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this );
+ short src1 = (short)(enc.reg(in(1))-riscv.F_OFFSET);
+ short src2 = (short)(enc.reg(in(2))-riscv.F_OFFSET);
+ int body = RiscV.r_type(RiscV.OP_FP,dst,RiscV.fsetop(_bop),src1,src2,0b1010001/*FCMP*/);
+ enc.add4(body);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ").p(_bop).p(" ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
new file mode 100644
index 00000000..9bc86565
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.node.Node;
+
+// corresponds to slti,sltiu
+public class SetIRISC extends ImmRISC {
+ final boolean _unsigned; // slti vs sltiu
+ public SetIRISC( Node src, int imm12, boolean unsigned ) {
+ this(src,imm12,unsigned,true);
+ }
+ public SetIRISC( Node src, int imm12, boolean unsigned, boolean pop ) {
+ super(src,imm12,pop);
+ if( !pop ) _inputs.setLen(2);
+ _unsigned = unsigned;
+ }
+ @Override public String op() { return "slt" + (_unsigned ? "u":"") + "i"; }
+ @Override public String glabel() { return "<"+ (_unsigned ? "u":""); }
+ @Override int opcode() { return 0b0010011; }
+ @Override int func3() { return _unsigned ? 0b011 : 0b010; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java
new file mode 100644
index 00000000..848cc59f
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// corresponds to slt sltu
+public class SetRISC extends MachConcreteNode implements MachNode {
+ final boolean _unsigned;
+ public SetRISC( Node cmp, boolean unsigned ) { super(cmp); _unsigned=unsigned; }
+ @Override public String op() { return "slt" + (_unsigned ? "u":""); }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ riscv.r_type(enc,this,_unsigned ? 0b011 : 0b010,0);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" < ").p(code.reg(in(2)));
+ }
+} \ No newline at end of file
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java
new file mode 100644
index 00000000..1425caa2
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java
@@ -0,0 +1,13 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.Utils;
+
+public class SllIRISC extends ImmRISC {
+ public SllIRISC( Node and, int imm) { super(and,imm); }
+ @Override int opcode() { return RiscV.OP_IMM; }
+ @Override int func3() { return 1; }
+ @Override public String glabel() { return "<<"; }
+ @Override public String op() { return "slli"; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java
new file mode 100644
index 00000000..5a4d4cc6
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// Shift left logical
+public class SllRISC extends MachConcreteNode implements MachNode {
+ public SllRISC(Node sll) { super(sll); }
+ @Override public String op() { return "shl"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,1,0); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java
new file mode 100644
index 00000000..d455337b
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java
@@ -0,0 +1,61 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.FunNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.node.SplitNode;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class SplitRISC extends SplitNode {
+ SplitRISC( LRG lrg, String kind, byte round ) { super(lrg, kind,round, new Node[2]); }
+ @Override public RegMask regmap(int i) { return riscv.SPLIT_MASK; }
+ @Override public RegMask outregmap() { return riscv.SPLIT_MASK; }
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this );
+ short src = enc.reg(in(1));
+
+ // Flag for being either XMM or stack
+ boolean dstX = dst >= riscv.F_OFFSET;
+ boolean srcX = src >= riscv.F_OFFSET;
+
+ if( dst >= riscv.MAX_REG ) {
+ // Store to SP
+ if( src >= riscv.MAX_REG ) {
+ throw Utils.TODO(); // Very rare stack-stack move
+ }
+ int off = fun(enc).computeStackOffset(enc._code,dst);
+ int op = srcX ? RiscV.OP_STOREFP : RiscV.OP_STORE;
+ if( srcX ) src -= riscv.F_OFFSET;
+ enc.add4(RiscV.s_type(op, 0b011, riscv.RSP, src, off));
+ return;
+ }
+ if( src >= riscv.MAX_REG ) {
+ // Load from SP
+ int off = fun(enc).computeStackOffset(enc._code,src);
+ int op = dstX ? RiscV.OP_LOADFP : RiscV.OP_LOAD;
+ if( dstX ) dst -= riscv.F_OFFSET;
+ enc.add4(RiscV.i_type(op, dst, 0b011, riscv.RSP, off));
+ return;
+ }
+ // pick opcode based on regs
+ if( !dstX && !srcX ) {
+ // GPR->GPR
+ enc.add4(RiscV.r_type(RiscV.OP,dst,0,src,riscv.ZERO,0));
+ } else if( dstX && srcX ) {
+ // FPR->FPR
+ src -= riscv.F_OFFSET;
+ dst -= riscv.F_OFFSET;
+ enc.add4(RiscV.r_type(RiscV.OP_FP, dst, 0b000, src, src, 0b0010101)); // dst = FMIN(src,src)
+ } else if(!srcX && dstX) {
+ //GPR->FPR
+ // fmv.d.x
+ enc.add4(RiscV.r_type(RiscV.OP_FP, dst - riscv.F_OFFSET, 0, src, 0, 0b1111001));
+ } else if(srcX && !dstX) {
+ //FPR->GPR
+ //fmv.x.d
+ enc.add4(RiscV.r_type(RiscV.OP_FP, dst, 0, src - riscv.F_OFFSET, 0, 0b1110001));
+ }
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java
new file mode 100644
index 00000000..b316b303
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java
@@ -0,0 +1,15 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.Utils;
+
+public class SraIRISC extends ImmRISC {
+ public SraIRISC( Node and, int imm) {
+ super(and,imm | (0x20 << 5));
+ }
+ @Override int opcode() { return RiscV.OP_IMM; }
+ @Override int func3() { return 5;}
+ @Override public String glabel() { return ">>"; }
+ @Override public String op() { return "srai"; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java
new file mode 100644
index 00000000..e2e34415
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// Right Shift Arithmetic
+public class SraRISC extends MachConcreteNode implements MachNode {
+ public SraRISC(Node sra) { super(sra); }
+ @Override public String op() { return "sar"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0b101,0b00100000); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java
new file mode 100644
index 00000000..d0ea6b18
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java
@@ -0,0 +1,13 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.Utils;
+
+public class SrlIRISC extends ImmRISC {
+ public SrlIRISC( Node and, int imm, boolean pop) { super(and,imm,pop); }
+ @Override int opcode() { return RiscV.OP_IMM; }
+ @Override int func3() { return 5; }
+ @Override public String glabel() { return ">>>"; }
+ @Override public String op() { return "srli"; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java
new file mode 100644
index 00000000..1eccc1c8
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// Right Shift Logical
+public class SrlRISC extends MachConcreteNode implements MachNode {
+ public SrlRISC(Node srli) { super(srli); }
+ @Override public String op() { return "shr"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,7,0); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
new file mode 100644
index 00000000..c3fcf4d3
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
@@ -0,0 +1,56 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// Store memory addressing on ARM
+// Support imm, reg(direct), or reg+off(indirect) addressing
+// Base = base - base pointer, offset is added to base
+// idx = null
+// off = off - offset added to base)
+// imm = imm - immediate value to store
+// val = Node of immediate value to store(null if its a constant immediate)
+
+//e.g s.cs[0] = 67; // C
+// base = s.cs, off = 4, imm = 67, val = null
+
+// sw rs2,offset(rs1)
+public class StoreRISC extends MemOpRISC {
+ StoreRISC( StoreNode st, Node base, int off, Node val ) { super(st, base, off, val); }
+ @Override public String op() { return "st"+_sz; }
+ @Override public RegMask regmap(int i) {
+ // 0 - ctrl
+ if( i==1 ) return null; // mem
+ if( i==2 ) return riscv.RMASK; // ptr
+ // 2 - index
+ if( i==4 ) return _bytes >= 4
+ ? riscv.MEM_MASK // Word/dword value in GPR or FPR
+ : riscv.RMASK; // Byte/short stores require a GPR
+ return null; // Anti-dependence
+ }
+ @Override public RegMask outregmap() { return null; }
+
+ @Override public void encoding( Encoding enc ) {
+ short val = enc.reg(val());
+ short ptr = enc.reg(ptr());
+ int op = val >= riscv.F_OFFSET ? RiscV.OP_STOREFP : RiscV.OP_STORE;
+ if( val >= riscv.F_OFFSET ) val -= riscv.F_OFFSET;
+ enc.add4(RiscV.s_type(op, func3()&7, ptr, val == -1 ? riscv.ZERO : val, _off));
+ }
+
+ // func3 is based on load/store size and extend
+ @Override int func3() {
+ return Integer.numberOfTrailingZeros(_bytes);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ asm_address(code,sb).p(",");
+ if( val()==null ) sb.p("#").p("0");
+ else sb.p(code.reg(val()));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java
new file mode 100644
index 00000000..044820cf
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java
@@ -0,0 +1,18 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// fsub.d
+public class SubFRISC extends MachConcreteNode implements MachNode {
+ SubFRISC(Node subf) {super(subf);}
+ @Override public String op() { return "subf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; }
+ @Override public RegMask outregmap() { return riscv.FMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,RiscV.RM.RNE,5); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java
new file mode 100644
index 00000000..2adfce23
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java
@@ -0,0 +1,16 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class SubRISC extends MachConcreteNode implements MachNode {
+ public SubRISC( Node sub ) { super(sub); }
+ @Override public String op() { return "sub"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0,0x20); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java
new file mode 100644
index 00000000..44ef1a95
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java
@@ -0,0 +1,56 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class TFPRISC extends FunPtrNode implements MachNode, RIPRelSize {
+ final String _ext;
+ // Pointer to a Simple function
+ TFPRISC( FunPtrNode fptr ) { super(fptr); _ext = null; }
+ // Pointer to an Extern "C" function
+ TFPRISC( TypeFunPtr fptr, String ext ) { super(fptr,CodeGen.CODE._start,null); _type = fptr; _ext = ext; }
+ @Override public String op() { return "ldx"; }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public TFPRISC copy() { return new TFPRISC((TypeFunPtr)_con,_ext); }
+ @Override public void encoding( Encoding enc ) {
+ if( _ext==null ) enc.relo(this); // Internal patch
+ else enc.external(this,_ext); // ELF-file external patch
+ // TODO: 1 op encoding, plus a TODO if it does not fit
+ short dst = enc.reg(this);
+ // auipc t0,0
+ enc.add4(RiscV.u_type(RiscV.OP_AUIPC, dst, 0));
+ // addi t1,t0 + #0
+ enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 0, dst, 0));
+ }
+
+ @Override public byte encSize(int delta) { return 8; }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ short rpc = enc.reg(this);
+ if( opLen==8 ) {
+ int hi20 = (delta + 0x800) >> 12;
+ int lo12 = delta - (hi20 << 12);
+ // AUIPC (upper 20 bits)
+ // opstart of add
+ enc.patch4(opStart ,RiscV.u_type(RiscV.OP_AUIPC, rpc, hi20));
+ // addi(low 12 bits)
+ enc.patch4(opStart+4,RiscV.i_type(RiscV.OP_IMM, rpc, 0, rpc, lo12 & 0xFFF));
+ // addi
+ } else {
+ // should not happen as one instruction is 4 byte, and TFP arm encodes 2.
+ throw Utils.TODO();
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ _type.print(sb.p(code.reg(this)).p(" #"));
+ }
+ @Override public boolean eq(Node n) { return this==n; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java
new file mode 100644
index 00000000..a5ff3753
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java
@@ -0,0 +1,44 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.ConstantNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.type.TypeMemPtr;
+import com.seaofnodes.simple.util.SB;
+
+public class TMPRISC extends ConstantNode implements MachNode, RIPRelSize {
+ final String _ext;
+ TMPRISC( ConstantNode con, String ext ) { super(con); _ext = ext; }
+ @Override public String op() { return "ldp"; }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public TMPRISC copy() { return new TMPRISC(this,_ext); }
+ @Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) enc.externalData(this,_ext);
+ else enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1);
+ short dst = enc.reg(this);
+ // AUIPC dst,#hi20_constant_pool
+ enc.add4(RiscV.u_type(RiscV.OP_AUIPC, dst, 0));
+ // addi dst,[dst+#low12_constant_pool]
+ enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 0, dst, 0));
+ }
+
+ @Override public byte encSize(int delta) { return 8; }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ short dst = enc.reg(this);
+ // AUIPC dst,#hi20_constant_pool
+ enc.patch4(opStart , RiscV.u_type(RiscV.OP_AUIPC, dst, (delta+0x800)>>12));
+ // ADDI's low 12 bits are signed; round the AUIPC page accordingly.
+ enc.patch4(opStart+4, RiscV.i_type(RiscV.OP_IMM, dst, 0, dst, delta & 0xFFF));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ _con.print(sb.p(code.reg(this)).p(" #"));
+ }
+ @Override public boolean eq(Node n) { return this==n; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java
new file mode 100644
index 00000000..448d9ec3
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java
@@ -0,0 +1,49 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// unconditional jump
+public class UJmpRISC extends CFGNode implements MachNode, RIPRelSize {
+ @Override public String op() { return "jmp"; }
+ @Override public String label() { return op(); }
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("jmp "); }
+ @Override public RegMask regmap(int i) {return null; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public Type compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+ @Override public void encoding( Encoding enc ) {
+ // Short form +/-4K: beq r0,r0,imm12
+ // Long form: auipc rX,imm20/32; jal r0,[rX+imm12/32]
+ enc.jump(this,uctrl());
+ enc.add4(RiscV.j_type(RiscV.OP_JAL, 0, 0));
+ }
+
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) {
+ if( -(1L<<20) <= delta && delta < (1L<<20) ) return 4;
+ // 2 word encoding needs a tmp register, must teach RA
+ throw Utils.TODO();
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ if( opLen==4 ) {
+ enc.patch4(opStart,RiscV.j_type(RiscV.OP_JAL, 0, delta));
+ } else {
+ throw Utils.TODO();
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ CFGNode target = uctrl();
+ //assert target.nOuts() > 1; // Should optimize jmp to empty targets
+ sb.p(label(target));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java
new file mode 100644
index 00000000..f4323cae
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java
@@ -0,0 +1,14 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.Utils;
+
+public class XorIRISC extends ImmRISC {
+ public XorIRISC( Node and, int imm) { super(and,imm); }
+ public XorIRISC( Node and, int imm, boolean pop) { super(and,imm,pop); }
+ @Override int opcode() { return RiscV.OP_IMM; }
+ @Override int func3() { return 4; }
+ @Override public String glabel() { return "^"; }
+ @Override public String op() { return "xori"; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java
new file mode 100644
index 00000000..413a7c3b
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java
@@ -0,0 +1,16 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class XorRISC extends MachConcreteNode implements MachNode {
+ public XorRISC(Node or) { super(or); }
+ @Override public String op() { return "xor"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,4,0); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
new file mode 100644
index 00000000..0195542c
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -0,0 +1,450 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import static com.seaofnodes.isa.RiscV.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Utils;
+
+public class riscv extends Machine {
+
+ public riscv(CodeGen code) {
+ if( !"SystemV".equals(code._callingConv) )
+ throw new IllegalArgumentException("Unknown calling convention "+code._callingConv);
+ }
+
+ @Override public String name() {return "riscv";}
+
+ // Using ABI names instead of register names
+ public static int ZERO = 0, RPC= 1, RSP= 2, S12= 3, S13= 4, T0 = 5, T1 = 6, T2 = 7;
+ public static int S0 = 8, S1 = 9, A0 = 10, A1 = 11, A2 = 12, A3 = 13, A4 = 14, A5 = 15;
+ public static int A6 = 16, A7 = 17, S2 = 18, S3 = 19, S4 = 20, S5 = 21, S6 = 22, S7 = 23;
+ public static int S8 = 24, S9 = 25, S10= 26, S11= 27, T3 = 28, T4 = 29, T5 = 30, T6 = 31;
+
+ // FP registers
+ public static int F0 = 32, F1 = 33, F2 = 34, F3 = 35, F4 = 36, F5 = 37, F6 = 38, F7 = 39;
+ public static int FS0 = 40, FS1 = 41, FA0 = 42, FA1 = 43, FA2 = 44, FA3 = 45, FA4 = 46, FA5 = 47;
+ public static int FA6 = 48, FA7 = 49, FS2 = 50, FS3 = 51, FS4 = 52, FS5 = 53, FS6 = 54, FS7 = 55;
+ public static int FS8 = 56, FS9 = 57, FS10 = 58, FS11 = 59, FT8 = 60, FT9 = 61, FT10 = 62, FT11 = 63;
+
+ static final int MAX_REG = 64;
+
+ public static final int F_OFFSET = 32;
+
+ static final String[] REGS = new String[] {
+ //"zero","rpc" , "rsp" , "s12" , "s13" , "t0" , "t1" , "t2" ,
+ //"s0" , "s1" , "a0" , "a1" , "a2" , "a3" , "a4" , "a5" ,
+ //"a6" , "a7" , "s2" , "s3" , "s4" , "s5" , "s6" , "s7" ,
+ //"s8" , "s9" , "s10" , "s11" , "t3" , "t4" , "t5" , "t6" ,
+ //"f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" ,
+ //"fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" ,
+ //"fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" ,
+ //"fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11"
+ // Register-based naming hella easier to debug than "official" register
+ // names when single-stepping in debugger.
+ "zero", "r1" , "rsp" , "r3" , "r4" , "r5" , "r6" , "r7" ,
+ "r8" , "r9" , "r10" , "r11", "r12", "r13", "r14", "r15",
+ "r16" , "r17" , "r18" , "r19", "r20", "r21", "r22", "r23",
+ "r24" , "r25" , "r26" , "r27", "r28", "r29", "r30", "r31",
+ "f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" ,
+ "fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" ,
+ "fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" ,
+ "fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11"
+ };
+ @Override public String[] regs() { return REGS; }
+
+ // General purpose register mask: pointers and ints, not floats
+ static final long RD_BITS = 0b11111111111111111111111111111111L; // All the GPRs
+ static final RegMask RMASK = new RegMask(RD_BITS);
+
+ static final long WR_BITS = 0b11111111111111111111111111111010L; // All the GPRs, minus ZERO and RSP
+ static final RegMask WMASK = new RegMask(WR_BITS);
+ // Float mask from(f0-ft10). TODO: ft10,ft11 needs a larger RegMask
+ static final long FP_BITS = 0b11111111111111111111111111111111L<<F0;
+ static final RegMask FMASK = new RegMask(FP_BITS);
+
+ // Load/store mask; both GPR and FPR
+ static final RegMask MEM_MASK = new RegMask(WR_BITS | FP_BITS);
+
+ static final RegMask SPLIT_MASK = new RegMask( WR_BITS | FP_BITS, -1L );
+
+ // Arguments masks
+ public static RegMask A0_MASK = new RegMask(A0);
+ public static RegMask A1_MASK = new RegMask(A1);
+ public static RegMask A2_MASK = new RegMask(A2);
+ public static RegMask A3_MASK = new RegMask(A3);
+ public static RegMask A4_MASK = new RegMask(A4);
+ public static RegMask A5_MASK = new RegMask(A5);
+ public static RegMask A6_MASK = new RegMask(A6);
+ public static RegMask A7_MASK = new RegMask(A7);
+
+ public static final RegMask RPC_MASK = new RegMask(RPC);
+
+ // Float arguments masks
+ public static RegMask FA0_MASK = new RegMask(FA0);
+ public static RegMask FA1_MASK = new RegMask(FA1);
+ public static RegMask FA2_MASK = new RegMask(FA2);
+ public static RegMask FA3_MASK = new RegMask(FA3);
+ public static RegMask FA4_MASK = new RegMask(FA4);
+ public static RegMask FA5_MASK = new RegMask(FA5);
+ public static RegMask FA6_MASK = new RegMask(FA6);
+ public static RegMask FA7_MASK = new RegMask(FA7);
+
+ // Int/ptr arguments calling convention
+ static RegMask[] CALLINMASK = new RegMask[] {
+ A0_MASK,
+ A1_MASK,
+ A2_MASK,
+ A3_MASK,
+ A4_MASK,
+ A5_MASK,
+ A6_MASK,
+ A7_MASK
+ };
+
+ static RegMask[] XMMS = new RegMask[] {
+ FA0_MASK,
+ FA1_MASK,
+ FA2_MASK,
+ FA3_MASK,
+ FA4_MASK,
+ FA5_MASK,
+ FA6_MASK,
+ FA7_MASK
+ };
+
+ // Since riscv instructions are fixed we can just or them together
+
+ public static void r_type(Encoding enc, Node n, int func3, int func7) {
+ short dst = enc.reg(n);
+ short src1 = enc.reg(n.in(1));
+ short src2 = enc.reg(n.in(2));
+ int body = RiscV.r_type(OP,dst,func3,src1,src2,func7);
+ enc.add4(body);
+ }
+
+ public static void rf_type(Encoding enc, Node n, RM func3, int func7) {
+ short dst = (short)(enc.reg(n )-F_OFFSET);
+ short src1 = (short)(enc.reg(n.in(1))-F_OFFSET);
+ short src2 = (short)(enc.reg(n.in(2))-F_OFFSET);
+ int body = RiscV.r_type(OP_FP,dst,func3.ordinal(),src1,src2,func7);
+ enc.add4(body);
+ }
+
+ @Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); }
+ static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot ) {
+ if( idx==0 ) return RPC_MASK;
+ if( idx==1 ) return null;
+ if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence
+ boolean hidden = hiddenSelf(tfp);
+ int sigidx = idx-2;
+ if( hidden ) {
+ if( sigidx==0 ) return null;
+ sigidx--;
+ }
+ // Count floats in signature up to index
+ int fcnt=0;
+ for( int i=hidden ? 1 : 0; i<idx-2; i++ )
+ if( tfp.arg(i) instanceof TypeFloat )
+ fcnt++;
+ // Floats up to XMMS in XMM registers
+ if( tfp.arg(idx-2) instanceof TypeFloat ) {
+ if( fcnt < XMMS.length )
+ return XMMS[fcnt];
+ } else {
+ RegMask[] cargs = CALLINMASK;
+ if( sigidx-fcnt < cargs.length )
+ return cargs[sigidx-fcnt];
+ }
+ // Pass on stack slot(8 and higher)
+ if( maxArgSlot>0 ) throw Utils.TODO();
+ return new RegMask(MAX_REG + 1 + sigidx);
+ }
+
+ @Override public short maxArgSlot( TypeFunPtr tfp ) {
+ int icnt=0, fcnt=0; // Count of ints, floats
+ for( int i=hiddenSelf(tfp) ? 1 : 0; i<tfp.nargs(); i++ ) {
+ if( tfp.arg(i) instanceof TypeFloat ) fcnt++;
+ else icnt++;
+ }
+ int nstk = Math.max(icnt-8,0)+Math.max(fcnt-8,0);
+ return (short)nstk;
+ }
+ private static boolean hiddenSelf(TypeFunPtr tfp) {
+ return tfp.nargs() > 0 && tfp.arg(0) == TypePtr.PTR;
+ }
+
+ static final long CALLER_SAVE =
+ (1L<<RPC) |
+ (1L<<A0 ) | (1L<<A1 ) | (1L<<A2 ) | (1L<<A3 ) | (1L<<A4 ) | (1L<<A5 ) | (1L<<A6 ) | (1L<<A7 ) |
+ (1L<<T0 ) | (1L<<T1 ) | (1L<<T2 ) | (1L<<T3 ) | (1L<<T4 ) | (1L<<T5 ) | (1L<<T6 ) |
+ (1L<<FA0) | (1L<<FA1) | (1L<<FA2) | (1L<<FA3) | (1L<<FA4) | (1L<<FA5) | (1L<<FA6) | (1L<<FA7) |
+ (1L<<F0 ) | (1L<<F1 ) | (1L<<F2 ) | (1L<<F3 ) | (1L<<F4 ) | (1L<<F5 ) | (1L<<F6 ) | (1L<<F7 ) |
+ (1L<<FT8) | (1L<<FT9) | (1L<<FT10)| (1L<<FT11);
+
+ @Override public long callerSave() { return CALLER_SAVE; }
+ @Override public long neverSave() { return (1L<<RSP) | (1L<<ZERO); }
+ @Override public RegMask retMask( TypeFunPtr tfp ) {
+ return tfp.ret() instanceof TypeFloat ? FA0_MASK : A0_MASK;
+ }
+ @Override public int rpc() { return RPC; }
+
+ // Return a MachNode unconditional branch
+ @Override public CFGNode jump() { return new UJmpRISC(); }
+
+ // Create a split op; any register to any register, including stack slots
+ @Override public SplitNode split(LRG lrg, String kind, byte round ) { return new SplitRISC(lrg, kind,round); }
+
+ // True if signed 12-bit immediate
+ public static boolean imm12(TypeInteger ti) {
+ // 52 = 64-12
+ return ti.isConstant() && ((ti.value()<<52)>>52) == ti.value();
+ }
+ // True if HIGH 20-bit signed immediate, with all zeros low.
+ public static boolean imm20Exact(TypeInteger ti) {
+ // shift left 32 to clear out the upper 32 bits.
+ // shift right SIGNED to sign-extend upper 32 bits; then shift 12 more to clear out lower 12 bits.
+ // shift left 12 to re-center the bits.
+ return ti.isConstant() && (((ti.value()<<32)>>44)<<12) == ti.value();
+ }
+
+ @Override public Node instSelect( Node n ) {
+ return switch (n) {
+ case AddNode add -> add(add);
+ case AndNode and -> and(and);
+ case BoolNode bool -> cmp(bool);
+ case CallNode call -> call(call);
+ case PtrToIntNode ptr -> new PtrToIntMach(ptr);
+ case GuardNode guard -> new GuardMach(guard);
+ case CallEndNode cend -> new CallEndMach(cend);
+ case CLInitNode init -> new CLInitNode(init);
+ case CProjNode c -> new CProjNode(c);
+ case ConstantNode con -> con(con);
+ case DivNode div -> div.mode()==2 ? new DivFRISC(div) : new DivRISC(div);
+ case EscapeNode esc -> new EscapeNode(esc);
+ case FunNode fun -> new FunRISC(fun);
+ case FunPtrNode fptr -> fptr(fptr);
+ case IfNode iff -> jmp(iff);
+ case LoadNode ld -> ld(ld);
+ case MemMergeNode mem -> new MemMergeNode(mem);
+ case MinusNode neg -> {
+ if( neg.mode()==2 ) throw Utils.TODO();
+ yield new NegRISC(neg);
+ }
+ case MulNode mul -> mul.mode()==2 ? new MulFRISC(mul) : new MulRISC(mul);
+ case NewNode nnn -> nnn(nnn);
+ case NotNode not -> new NotRISC(not);
+ case OrNode or -> or(or);
+ case ParmNode parm -> new ParmRISC(parm);
+ case BulkMemPhiNode phi-> new BulkMemPhiNode(phi);
+ case MemPhiNode phi -> new MemPhiNode(phi);
+ case PhiNode phi -> new PhiNode(phi);
+ case ProjNode prj -> prj(prj);
+ case ReadOnlyNode read-> new ReadOnlyMach(read);
+ case ReturnNode ret -> new RetRISC(ret, ret.fun());
+ case SarNode sar -> sra(sar);
+ case ShlNode shl -> sll(shl);
+ case ShrNode shr -> srl(shr);
+ case StartCUNode start -> new StartCUNode(start);
+ case StopCUNode stop -> new StopCUNode(stop);
+ case StartNode start -> new StartNode(start);
+ case StopNode stop -> new StopNode(stop);
+ case StoreNode st -> st(st);
+ case SubNode sub -> sub.mode()==2 ? new SubFRISC(sub) : sub(sub);
+ case ToFloatNode tfn -> i2f8(tfn);
+ case ToIntegerNode cvt -> new F8ToIRISC(cvt);
+ case XorNode xor -> xor(xor);
+
+ case LoopNode loop -> new LoopNode(loop);
+ case RegionNode region -> new RegionNode(region);
+ default -> throw Utils.TODO();
+ };
+ }
+
+ private Node add(AddNode add) {
+ if( add.mode()==2 )
+ return new AddFRISC(add);
+ if( add.in(2) instanceof ConstantNode off2 && off2._con instanceof TypeInteger ti && imm12(ti) )
+ return new AddIRISC(add, (int)ti.value(),true);
+ return new AddRISC(add);
+ }
+
+ private Node and(AndNode and) {
+ if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) {
+ if( imm12(ti) )
+ return new AndIRISC(and, (int)ti.value());
+ // Could be any size low bit mask
+ if( ti.value() == 0xFFFFFFFFL )
+ return new SrlIRISC(new SllIRISC(and,32),32,false);
+ }
+ return new AndRISC(and);
+ }
+
+ private Node call(CallNode call) {
+ return call.fptr() instanceof FunPtrNode fptr
+ ? new CallRISC(call, (TypeFunPtr)fptr._type)
+ : call.fptr() instanceof ConstantNode con
+ ? new CallRISC(call, (TypeFunPtr)con._type)
+ : new CallRRISC(call);
+ }
+ private Node nnn(NewNode nnn) {
+ // TODO: pass in the TFP for alloc
+ return new NewRISC(nnn);
+ }
+
+ private Node cmp(BoolNode bool) {
+ // Float variant directly implemented in hardware
+ if( bool.isFloat() )
+ return new SetFRISC(bool);
+
+ // Only < and <u are implemented in hardware.
+ // x < y - as-is
+ // x <= y - flip and negate; !(y < x); `slt tmp=y,x; xori dst=tmp,#1`
+ // x == y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1`
+
+ // x > y - swap; y < x
+ // x >= y - swap and negate; !(x < y); `slt tmp=y,x;` then NOT.
+ // x != y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1` then NOT.
+
+ // The ">", ">=" and "!=" in Simple include a NotNode, which can be
+ // implemented with a XOR. If one of the above is followed by a NOT
+ // we can remove the double XOR in the encodings.
+
+ return switch( bool.op() ) {
+ case "<" -> bool.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)
+ ? new SetIRISC(bool, (int)ti.value(),false)
+ : new SetRISC(bool, false);
+ // x <= y - flip and negate; !(y < x); `slt tmp=y,x; xori dst=tmp,#1`
+ case "<=" -> new XorIRISC(new SetRISC(bool.swap12(), false),1,false);
+ // x == y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1`
+ case "==" -> new SetIRISC(new SubRISC(bool),1,true,false);
+ default -> throw Utils.TODO();
+ };
+ }
+
+ private Node con( ConstantNode con ) {
+ if( !con._con.isConstant() )
+ return ConstantNode.raw(con); // Default unknown caller inputs
+ String ext = con instanceof ExternNode ext0 ? ext0._extern : null;
+ return switch( con._con ) {
+ case TypeInteger ti -> {
+ // External; without knowing the size assume we have to load something
+ if( ext!=null ) yield new Int8RISC(con,ext);
+ if( imm12(ti) ) yield new IntRISC(con);
+ long x = ti.value();
+ if( imm20Exact(ti) ) yield new LUI((int)x);
+ if( (x<<32)>>32 == x ) { // Signed lower 32-bit immediate
+ // Here, the low 12 bits get sign-extended, which means if
+ // bit11 is set, the value is negative and lowers the LUI
+ // value. Add a bit 12 to compensate
+ if( ((x>>11)&1)==1 ) x += 0x1000;
+ yield new AddIRISC(new LUI((int)(x & ~0xFFF)), (int)(x & 0xFFF),false);
+ }
+ // Need more complex sequence for larger constants... or a load
+ // from a constant pool, which does not need an extra register
+ yield new Int8RISC(con,null);
+ }
+ // Load from constant pool
+ case TypeFloat tf -> new FltRISC(con,ext);
+ case TypeMemPtr tmp -> new TMPRISC(con,ext);
+ case TypeFunPtr tfp -> new TFPRISC(tfp,ext);
+ case TypeNil tn -> throw Utils.TODO();
+ // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
+ case Type t -> t==Type.NIL ? new IntRISC(con) : ConstantNode.raw(con);
+ };
+ }
+
+ private Node fptr( FunPtrNode con ) {
+ return new TFPRISC(con);
+ }
+
+ private Node jmp( IfNode iff ) {
+ if( iff.in(1) instanceof BoolNode bool && !bool.isFloat() ) {
+ // if less than or equal switch inputs
+ String bop = bool.op();
+ if( bop.equals("<=") || bop.equals(">") )
+ return new BranchRISC(iff, IfNode.swap(bop), bool.in(2), bool.in(1));
+ return new BranchRISC(iff, bop, bool.in(1), bool.in(2));
+ }
+ // Vs zero
+ return new BranchRISC(iff, iff.in(1)==null ? "==" : "!=", iff.in(1), null);
+ }
+
+ private Node or(OrNode or) {
+ if( or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti))
+ return new OrIRISC(or, (int)ti.value());
+ return new OrRISC(or);
+ }
+
+ private Node xor(XorNode xor) {
+ if( xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti))
+ return new XorIRISC(xor, (int)ti.value());
+ return new XorRISC(xor);
+ }
+
+ private Node sra(SarNode sar) {
+ if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti))
+ return new SraIRISC(sar, (int)ti.value());
+ return new SraRISC(sar);
+ }
+
+ private Node srl(ShrNode shr) {
+ if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti))
+ return new SrlIRISC(shr, (int)ti.value(),true);
+ return new SrlRISC(shr);
+ }
+
+ private Node sll(ShlNode sll) {
+ if( sll.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti))
+ return new SllIRISC(sll, (int)ti.value());
+ return new SllRISC(sll);
+ }
+
+ private Node sub(SubNode sub) {
+ return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)
+ ? new AddIRISC(sub, (int)(-ti.value()),true)
+ : new SubRISC(sub);
+ }
+
+ private Node i2f8(ToFloatNode tfn) {
+ assert tfn.in(1)._type instanceof TypeInteger ti;
+ return new I2F8RISC(tfn);
+ }
+
+ private Node prj(ProjNode prj) {
+ if( prj.in(0) instanceof CLInitNode ) return new ProjNode(prj);
+ TypeInteger ret = cReturn(prj);
+ if( ret!=null ) return new ExtendRISC(prj,ret);
+ return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjRISC(prj);
+ }
+
+ private Node ld(LoadNode ld) {
+ return new LoadRISC(ld,address(ld),off);
+ }
+
+ private Node st(StoreNode st) {
+ Node xval = st.val() instanceof ConstantNode con && con._con == TypeInteger.ZERO ? null : st.val();
+ return new StoreRISC(st,address(st), off, xval);
+ }
+
+ // Gather addressing mode bits prior to constructing. This is a builder
+ // pattern, but saving the bits in a *local* *global* here to keep mess
+ // contained.
+ private static int off;
+ private Node address( MemOpNode mop ) {
+ off = 0; // Reset
+ Node base = mop.ptr();
+ // Skip/throw-away a ReadOnly, only used to typecheck
+ if( base instanceof ReadOnlyNode read ) base = read.in(1);
+ assert !(base instanceof AddNode) && base._type instanceof TypeMemPtr; // Base ptr always, not some derived
+ if( mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) {
+ off = (int)ti.value();
+ } else {
+ base = new AddRISC(base,mop.off());
+ base._type = mop.ptr()._type;
+ }
+ return base;
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java
new file mode 100644
index 00000000..74af9266
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class AddFMemX86 extends MemOpX86 {
+ AddFMemX86( AddNode add, LoadNode ld , Node base, Node idx, int off, int scale, Node val ) {
+ super(add,ld, base, idx, off, scale, 0, val );
+ }
+ @Override public String op() { return "addf"+_sz; }
+ @Override public RegMask regmap(int i) {
+ if( i==1 ) return null; // Memory
+ if( i==2 ) return x86_64_v2.RMASK; // base
+ if( i==3 ) return x86_64_v2.RMASK; // index
+ if( i==4 ) return x86_64_v2.XMASK; // value
+ throw Utils.TODO();
+ }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+ @Override public int twoAddress() { return 4; }
+ @Override public void encoding( Encoding enc ) {
+ X86.memory(enc,0xF2,0x0F58,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(ptr()),enc.reg(idx()),_off,_scale);
+ }
+
+ // General form: "add dst = src + [base + idx<<2 + 12]"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ");
+ sb.p(val()==null ? "#"+_imm : code.reg(val())).p(" + ");
+ asm_address(code,sb);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java
new file mode 100644
index 00000000..c7d0cf9c
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class AddFX86 extends MachConcreteNode implements MachNode {
+ AddFX86( Node addf ) { super(addf); }
+ @Override public String op() { return "addf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+ @Override public int twoAddress() { return 1; }
+ @Override public boolean commutes() { return true; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.sse(enc,0xF2,0x0F58,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET);
+ }
+
+ // General form: "addf dst += src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" += ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java
new file mode 100644
index 00000000..0e652a93
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class AddIX86 extends ImmX86 {
+ AddIX86( Node add, int imm ) { super(add,imm); }
+ @Override public String op() {
+ return _imm == 1 ? "inc" : (_imm == -1 ? "dec" : "addi");
+ }
+ @Override public String glabel() { return "+"; }
+ @Override int opcode() { return 0x81; }
+ @Override int mod() { return 0; }
+ @Override public final void encoding( Encoding enc ) {
+ if( _imm== 1 || _imm== -1 ) {
+ short dst = enc.reg(this); // Also src1
+ X86.unary(enc,0xFF,_imm==1 ? 0 : 1,dst);
+ } else super.encoding(enc);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java
new file mode 100644
index 00000000..ed65b0a8
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class AddMemX86 extends MemOpX86 {
+ AddMemX86( AddNode add, LoadNode ld , Node base, Node idx, int off, int scale, int imm, Node val ) {
+ super(add,ld, base, idx, off, scale, imm, val );
+ }
+ @Override public String op() { return "add"+_sz; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public int twoAddress() { return 4; }
+ @Override public void encoding( Encoding enc ) {
+ X86.memory(enc,0,0x03,true,enc.reg(this),enc.reg(ptr()),enc.reg(idx()),_off,_scale);
+ }
+
+ // General form: "add dst += [base + idx<<2 + 12]"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" += ");
+ asm_address(code,sb);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java
new file mode 100644
index 00000000..84543e8f
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class AddX86 extends RegX86 {
+ AddX86( Node add ) { super(add); }
+ @Override public String op() { return "add"; }
+ @Override public String glabel() { return "+"; }
+ @Override int opcode() { return 0x03; }
+ @Override public boolean commutes() { return true; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java
new file mode 100644
index 00000000..3c1b567c
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class AndIX86 extends ImmX86 {
+ AndIX86( Node add, int imm ) { super(add,imm); }
+ @Override public String op() { return "andi"; }
+ @Override public String glabel() { return "&"; }
+ @Override int opcode() { return 0x81; }
+ @Override int mod() { return 4; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java
new file mode 100644
index 00000000..dedbce45
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class AndX86 extends RegX86 {
+ AndX86( Node add ) { super(add); }
+ @Override public String op() { return "and"; }
+ @Override public String glabel() { return "&"; }
+ @Override int opcode() { return 0x23; }
+ @Override public boolean commutes() { return true; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java
new file mode 100644
index 00000000..3109df63
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.CallNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.util.SB;
+
+public class CallRX86 extends CallNode implements MachNode {
+ final TypeFunPtr _tfp;
+ CallRX86( CallNode call ) { super(call); _tfp = call.tfp(); }
+ @Override public TypeFunPtr tfp() { return _tfp; }
+ @Override public String op() { return "callr"; }
+ @Override public String label() { return op(); }
+ @Override public RegMask regmap(int i) {
+ return i==_inputs._len-1
+ ? x86_64_v2.WMASK // Function call target
+ : x86_64_v2.callInMask(tfp(),i,fun()._maxArgSlot); // Normal argument
+ }
+ @Override public RegMask outregmap() { return null; }
+ @Override public void encoding( Encoding enc ) {
+ X86.callRegister(enc,enc.reg(fptr()));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(fptr())).p(" ");
+ for( int i=0; i<nargs(); i++ )
+ sb.p(code.reg(arg(i))).p(" ");
+ sb.unchar(2);
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java
new file mode 100644
index 00000000..6bafbf37
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java
@@ -0,0 +1,55 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.util.SB;
+
+public class CallX86 extends CallNode implements MachNode, RIPRelSize {
+ final TypeFunPtr _tfp;
+ final String _name;
+ final boolean _extern;
+ CallX86( CallNode call, TypeFunPtr tfp ) {
+ super(call);
+ _inputs.pop(); // Pop constant target
+ assert tfp.isConstant();
+ _tfp = tfp;
+ _name = CodeGen.CODE.funcName(tfp.fidx());
+ _extern = CodeGen.CODE.externFunc(tfp.fidx()) != null;
+ }
+ @Override public String op() { return "call"; }
+ @Override public String label() { return op(); }
+ @Override public String name() { return _name; }
+ @Override public TypeFunPtr tfp() { return _tfp; }
+ @Override public RegMask regmap(int i) {
+ return _extern
+ ? x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot,CodeGen.CODE._cCallingConv)
+ : x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot);
+ }
+ @Override public RegMask outregmap() { return null; }
+ @Override public int nargs() { return nIns()-2; } // Minus control, memory, fptr
+ @Override public boolean external() { return _extern; }
+
+ @Override public void encoding( Encoding enc ) {
+ FunNode fun = CodeGen.CODE.link(_tfp);
+ if( fun==null ) enc.external(this,_name);
+ else enc.relo(this);
+ X86.call(enc);
+ }
+
+ // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding
+ @Override public byte encSize(int delta) { return 5; }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ enc.patch4(opStart+1, delta-5);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(_name).p(" ");
+ for( int i=0; i<nargs(); i++ )
+ sb.p(code.reg(in(i+2))).p(", ");
+ sb.unchar(2);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java
new file mode 100644
index 00000000..5ef7a476
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class CmpFX86 extends MachConcreteNode implements MachNode {
+ CmpFX86( Node cmp ) { super(cmp); }
+ @Override public String op() { return "cmpf"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.XMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.sse(enc,0x66,0x0F2E,false,enc.reg(in(1))-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET);
+ }
+
+ // General form: "cmp src1,src2"
+ @Override public void asm(CodeGen code, SB sb) {
+ String dst = code.reg(this);
+ if( dst!="flags" ) sb.p(dst).p(" = ");
+ sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java
new file mode 100644
index 00000000..151f8ca8
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java
@@ -0,0 +1,38 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// Compare immediate. Sets flags.
+public class CmpIX86 extends MachConcreteNode implements MachNode {
+ final int _imm;
+ CmpIX86( Node cmp, int imm, boolean swap ) {
+ super(cmp);
+ _inputs.del(swap ? 1 : 2);
+ _imm = imm;
+ }
+ CmpIX86( CmpIX86 cmp ) {
+ super((Node)null);
+ addDef(null);
+ addDef(cmp.in(1));
+ _imm = cmp._imm;
+ }
+
+ @Override public String op() { return _imm==0 ? "test" : "cmp"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public Node copy() { return new CmpIX86(this); }
+
+ @Override public final void encoding( Encoding enc ) {
+ X86.imm(enc,0x81,7,enc.reg(in(1)),_imm);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ String dst = code.reg(this);
+ if( dst!="flags" ) sb.p(dst).p(" = ");
+ sb.p(code.reg(in(1)));
+ if( _imm != 0 ) sb.p(", #").p(_imm);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java
new file mode 100644
index 00000000..4014045b
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java
@@ -0,0 +1,44 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+import com.seaofnodes.simple.util.SB;
+
+public class CmpMemX86 extends MemOpX86 {
+ final boolean _swap; // Op switched LHS, RHS
+ CmpMemX86( BoolNode bool, LoadNode ld, Node base, Node idx, int off, int scale, int imm, Node val, boolean swap ) {
+ super(bool,ld, base, idx, off, scale, imm, val );
+ _swap = swap;
+ assert !_swap || val() != null || bool.op()=="==" || bool.op()=="!="; // Cannot swap with immediate
+ }
+ @Override public String op() { return ((val()==null && _imm==0) ? "test" : "cmp") + _sz; }
+ @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; }
+ @Override public void encoding( Encoding enc ) {
+ short ptr=enc.reg(ptr()), idx=enc.reg(idx()), src=enc.reg(val());
+ if( src==-1 ) {
+ assert !_swap;
+ X86.cmpImm(enc,_con.log_size(),_imm,ptr,idx,_off,_scale);
+ } else encVal(enc,_con,ptr,idx,src,_off,_scale,_swap);
+ }
+
+ static void encVal(Encoding enc, Type decl, short ptr, short idx, short src, int off, int scale, boolean _swap) {
+ assert !(decl instanceof TypeFloat); // Selected separately as CmpFX86.
+ X86.cmpMem(enc,decl.log_size(),src,ptr,idx,off,scale,_swap);
+ }
+
+ // General form: "cmp dst = src, [base + idx<<2 + 12]"
+ @Override public void asm(CodeGen code, SB sb) {
+ String dst = code.reg(this);
+ if( dst!="flags" ) sb.p(dst).p(" = ");
+ if( _swap ) asm_address(code,sb).p(",");
+ if( val()==null ) {
+ if( _imm!=0 ) sb.p("#").p(_imm);
+ } else {
+ sb.p(code.reg(val()));
+ }
+ if( !_swap ) asm_address(code,sb.p(", "));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java
new file mode 100644
index 00000000..7faea17b
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java
@@ -0,0 +1,28 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class CmpX86 extends MachConcreteNode implements MachNode {
+ CmpX86( Node add ) { super(add); }
+ @Override public String op() { return "cmp"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; }
+ // This one is marginal: cloning a Cmp[reg,reg] means stretching the
+ // lifetimes of 2 normal registers in exchange for shortening a flags
+ // register lifetime. Spilling flags is (probably) relatively expensive
+ // compared to some normal registers - and those normal registers might not
+ // be spilling!
+ @Override public boolean isClone() { return true; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.reg(enc,0x3B,enc.reg(in(1)),enc.reg(in(2)));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ String dst = code.reg(this);
+ if( dst!="flags" ) sb.p(dst).p(" = ");
+ sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java
new file mode 100644
index 00000000..7bbb5102
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class DivFX86 extends MachConcreteNode implements MachNode {
+ DivFX86( Node divf) { super(divf); }
+ @Override public String op() { return "divf"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.XMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+ @Override public int twoAddress() { return 1; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.sse(enc,0xF2,0x0F5E,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET);
+ }
+
+
+ // General form: "divf dst /= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" /= ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java
new file mode 100644
index 00000000..9f375a80
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java
@@ -0,0 +1,25 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class DivX86 extends MachConcreteNode implements MachNode {
+ DivX86( Node div ) { super(div); }
+ @Override public String op() { return "div"; }
+ @Override public RegMask regmap(int i) {
+ return (i==1) ? x86_64_v2.RAX_MASK : x86_64_v2.DIV_MASK;
+ }
+ @Override public RegMask outregmap() { return x86_64_v2.RAX_MASK; }
+ @Override public RegMask killmap() { return x86_64_v2.RDX_MASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.divide(enc,enc.reg(in(2)));
+ }
+ // General form: "div dst /= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2)));
+ }
+ @Override public String comment() { return "kill rdx"; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java
new file mode 100644
index 00000000..1a6ae123
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java
@@ -0,0 +1,29 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+// Convert a narrow C return register to Simple's full-width integer value.
+public class ExtendX86 extends MachConcreteNode {
+ final int _bits;
+ final boolean _signed;
+ ExtendX86(ProjNode prj, TypeInteger ret) {
+ super(prj);
+ _inputs.set(0,null);
+ _inputs.add(new ProjX86(prj));
+ _bits = 8 << ret.log_size();
+ _signed = ret._min < 0;
+ }
+ @Override public String op() { return (_signed ? "sext" : "zext")+_bits; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public void encoding(Encoding enc) {
+ X86.extend(enc,_bits,_signed,enc.reg(this),enc.reg(in(1)));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java
new file mode 100644
index 00000000..a79a1017
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class F8ToIX86 extends MachConcreteNode implements MachNode {
+ F8ToIX86(Node f8toi ) { super(f8toi); }
+ @Override public String op() { return "cvti"; }
+ @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.XMASK; }
+ @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-x86_64_v2.XMM_OFFSET);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java
new file mode 100644
index 00000000..59f06341
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java
@@ -0,0 +1,39 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class FltX86 extends ConstantNode implements MachNode, RIPRelSize{
+ private byte _encSize;
+ final String _ext;
+ FltX86( ConstantNode con, String ext ) { super(con); _ext = ext; }
+ @Override public String op() { return "ld8"; }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public Node copy() { return new FltX86(this,_ext); }
+
+ @Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) throw Utils.TODO();
+ _encSize=X86.rip(enc,0xF2,0x0F10,false,enc.reg(this)-x86_64_v2.XMM_OFFSET);
+ enc.largeConstant(this,_con,_encSize-4,2/*ELF PC32*/);
+ }
+
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) {
+ // TODO: 1-byte RIP offset?
+ return (byte)_encSize;
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ enc.patch4(opStart+opLen-4,delta-opLen);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ _con.print(sb.p(code.reg(this)).p(" = #"));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java
new file mode 100644
index 00000000..0e44aee7
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.node.FunNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.util.SB;
+
+public class FunX86 extends FunNode implements MachNode {
+ FunX86( FunNode fun ) { super(fun); }
+ @Override public void postSelect(CodeGen code) {
+ super.postSelect(code);
+ _maxArgSlot = code._mach.maxArgSlot(sig(),code._callingConv);
+ // One more for RPC pre-pushed; X86 special
+ _maxArgSlot++;
+ }
+
+ @Override public void computeFrameAdjust(CodeGen code, int maxReg) {
+ super.computeFrameAdjust(code,maxReg);
+ // Alignment, but off by RPC
+ if( _hasCalls ) // If non-leaf, pad to 16b
+ _frameAdjust = ((_frameAdjust+8+8) & -16)-8;
+ }
+
+ @Override public void encoding( Encoding enc ) {
+ int sz = _frameAdjust;
+ if( sz == 0 ) return; // Skip if no frame adjust
+ X86.imm(enc,0x81,5,x86_64_v2.RSP,sz);
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java
new file mode 100644
index 00000000..ee357901
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class I2f8X86 extends MachConcreteNode implements MachNode {
+ I2f8X86(Node i2f8 ) { super(i2f8); }
+ @Override public String op() { return "cvtf"; }
+ @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.WMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.sse(enc,0xF2,0x0F2A,true,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(1)));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java
new file mode 100644
index 00000000..5a4ff4b2
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+public abstract class ImmX86 extends MachConcreteNode implements MachNode {
+ final int _imm;
+ ImmX86( Node add, int imm ) {
+ super(add);
+ _inputs.pop(); // Pop constant input off
+ _imm = imm;
+ }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public int twoAddress() { return 1; }
+ @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; }
+
+ abstract int opcode();
+ abstract int mod();
+
+ @Override public void encoding( Encoding enc ) {
+ X86.imm(enc,opcode(),mod(),enc.reg(this),_imm);
+ }
+
+ // General form: "addi dst += #imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java
new file mode 100644
index 00000000..2ffaa3d1
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java
@@ -0,0 +1,43 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// Integer constants
+public class IntX86 extends ConstantNode implements MachNode {
+ final String _ext;
+ IntX86( ConstantNode con, String ext ) { super(con); _ext = ext; }
+ @Override public String op() {
+ return _con == Type.NIL || _con == TypeInteger.ZERO ? "xor" : "ldi";
+ }
+ @Override public boolean isClone() { return true; }
+ @Override public Node copy() { return new IntX86(this,_ext); }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ // Zero-set uses XOR kills flags
+ @Override public RegMask killmap() {
+ return _con == Type.NIL || _con == TypeInteger.ZERO ? x86_64_v2.FLAGS_MASK : null;
+ }
+
+ @Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) throw Utils.TODO();
+ X86.constant(enc,enc.reg(this),_con==Type.NIL ? 0 : ((TypeInteger)_con).value());
+ }
+
+ // Human-readable form appended to the SB. Things like the encoding,
+ // indentation, leading address or block labels not printed here.
+ // Just something like "ld4\tR17=[R18+12] // Load array base".
+ // General form: "op\tdst=src+src"
+ @Override public void asm(CodeGen code, SB sb) {
+ String reg = code.reg(this);
+ if( _con == Type.NIL || _con == TypeInteger.ZERO )
+ sb.p(reg).p(",").p(reg);
+ else
+ _con.print(sb.p(reg).p(" = #"));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java
new file mode 100644
index 00000000..10cee059
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java
@@ -0,0 +1,65 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// Jump on flags, uses flags
+public class JmpX86 extends IfNode implements MachNode, RIPRelSize {
+ String _bop;
+ JmpX86( IfNode iff, String bop ) {
+ super(iff);
+ _bop = bop;
+ }
+ @Override public String op() { return "j"+_bop; }
+ @Override public String label() { return op(); }
+ @Override public void postSelect(CodeGen code) {
+ Node set = in(1);
+ if( set==null ) return; // Never-node cutout
+ Node cmp = set.in(1);
+ // Bypass an expected Set and just reference the cmp directly
+ if( set instanceof SetX86 setx && (cmp instanceof CmpX86 || cmp instanceof CmpIX86 || cmp instanceof CmpMemX86 || cmp instanceof CmpFX86) ) {
+ _inputs.set( 1, cmp );
+ _bop = setx._bop;
+ } else
+ throw Utils.TODO();
+ }
+ @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.FLAGS_MASK; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public void negate() { _bop = negate(_bop); }
+
+ @Override public void encoding( Encoding enc ) {
+ if( in(1)==null && _bop=="!=" ) return;
+ X86.branch(enc,in(1)==null ? -1 : x86_64_v2.jumpop(_bop)&15);
+ enc.jump(this,cproj(0));
+ }
+
+ // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding
+ @Override public byte encSize(int delta) {
+ if( in(1)==null && _bop=="!=" ) return 0;
+ return X86.branchSize(delta,in(1)!=null);
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ assert !(in(1)==null && _bop=="!=");
+ X86.patchBranch(enc.bits(),opStart,opLen,delta,in(1)==null ? -1 : x86_64_v2.jumpop(_bop)&15);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ if( in(1)!=null ) { // Never-node
+ String src = code.reg(in(1));
+ if( src!="flags" ) sb.p(src).p(" ");
+ } else if( _bop=="!=" ) {
+ sb.p("never");
+ return;
+ }
+ CFGNode prj = cproj(0).uctrlSkipEmpty(); // 0 is True is jump target
+ if( !prj.blockHead() ) prj = prj.cfg0();
+ sb.p(label(prj));
+ }
+
+ @Override public String comment() { return "L"+cproj(1)._nid; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java
new file mode 100644
index 00000000..4b00e4dd
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java
@@ -0,0 +1,38 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class LeaX86 extends MachConcreteNode implements MachNode {
+ final int _scale;
+ final int _offset;
+ LeaX86( Node add, Node base, Node idx, int scale, int offset ) {
+ super(add);
+ assert scale==0 || scale==1 || scale==2 || scale==3;
+ _inputs.pop();
+ _inputs.pop();
+ _inputs.push(base);
+ _inputs.push(idx);
+ _scale = scale;
+ _offset = offset;
+ }
+
+ @Override public String op() { return "lea"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.lea(enc,enc.reg(this),enc.reg(in(1)),enc.reg(in(2)),_offset,_scale);
+ }
+
+ // General form: "lea dst = base + 4*idx + 12"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ");
+ if( in(1) != null )
+ sb.p(code.reg(in(1))).p(" + ");
+ sb.p(code.reg(in(2))).p("<<").p(_scale);
+ if( _offset!=0 ) sb.p(" + #").p(_offset);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java
new file mode 100644
index 00000000..5c472dbe
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java
@@ -0,0 +1,44 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.LoadNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class LoadX86 extends MemOpX86 {
+ LoadX86( LoadNode ld, Node base, Node idx, int off, int scale ) {
+ super(ld,ld, base, idx, off, scale, 0);
+ }
+ @Override public String op() { return "ld"+_sz; }
+ @Override public RegMask outregmap() { return _con instanceof TypeFloat ? x86_64_v2.XMASK : x86_64_v2.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ // REX.W + 8B /r MOV r64, r/m64
+ // Zero extension for u8, u16 and u32 but sign extension i8, i16, i32
+ // Use movsx and movzx
+ short dst = enc.reg(this );
+ short ptr = enc.reg(ptr());
+ short idx = enc.reg(idx());
+ enc(enc, _con, dst, ptr, idx, _off, _scale);
+ }
+
+ static void enc( Encoding enc, Type decl, short dst, short ptr, short idx, int off, int scale ) {
+ // The chapter chooses the register bank and extension from its own type facts.
+ boolean xmm=dst>=x86_64_v2.XMM_OFFSET;
+ if( xmm && decl.log_size()==3 ) decl=TypeFloat.F64;
+ else if( !xmm && decl instanceof TypeFloat )
+ decl=decl==TypeFloat.F32 ? TypeInteger.U32 : TypeInteger.BOT;
+ boolean signed=decl.isa(TypeInteger.I8) || decl==TypeInteger.I16 || decl==TypeInteger.I32;
+ X86.load(enc,decl.log_size(),signed,xmm,
+ xmm ? dst-x86_64_v2.XMM_OFFSET : dst,ptr,idx,off,scale);
+ }
+
+
+ // General form: "ldN dst,[base + idx<<2 + 12]"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(",");
+ asm_address(code,sb);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java
new file mode 100644
index 00000000..2e18a058
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java
@@ -0,0 +1,28 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class MemAddX86 extends MemOpX86 {
+ MemAddX86( StoreNode st, Node base, Node idx, int off, int scale, int imm, Node val ) {
+ super(st, st, base, idx, off, scale, imm, val );
+ }
+ @Override public String op() {
+ return (_imm == 1 ? "inc" : (_imm == -1 ? "dec" : "add")) + _sz;
+ }
+ // Register mask allowed as a result. 0 for no register.
+ @Override public RegMask outregmap() { return null; }
+ @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; }
+ @Override public void encoding( Encoding enc ) {
+ X86.addMem(enc,enc.reg(val()),_imm,enc.reg(ptr()),enc.reg(idx()),_off,_scale);
+ }
+ // General form: "add [base + idx<<2 + 12] += src"
+ @Override public void asm(CodeGen code, SB sb) {
+ asm_address(code,sb);
+ if( val()==null ) {
+ if( _imm != 1 && _imm != -1 ) sb.p(" += #").p(_imm);
+ } else sb.p(" += ").p(code.reg(val()));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
new file mode 100644
index 00000000..fcca7f8f
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
@@ -0,0 +1,89 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.SB;
+import java.lang.StringBuilder;
+
+// Generic X86 memory operand base.
+// inputs:
+// ctrl - will appear for possibly RCE (i.e. array ops)
+// mem - memory dependence edge
+// base - Basic object pointer
+// idx/null - Scaled index offset, or null if none
+// val/null - Value to store, as part of an op-to-mem or op-from-mem. Null for loads, or if an immediate is being used
+// Constants:
+// offset - offset added to base. Can be zero.
+// scale - scale on index; only 0,1,2,4,8 allowed, 0 is only when index is null
+// imm - immediate value to store or op-to-mem, and only when val is null
+public abstract class MemOpX86 extends MemOpNode implements MachNode {
+ final int _off; // Limit 32 bits
+ final int _scale; // Limit 0,1,2,3
+ final int _imm; // Limit 32 bits
+ final char _sz; // Handy print name
+ MemOpX86( Node op, MemOpNode mop, Node base, Node idx, int off, int scale, int imm ) {
+ super(op,mop);
+ assert base._type instanceof TypeMemPtr && !(base instanceof AddNode);
+ assert (idx==null && scale==0) || (idx!=null && 0<= scale && scale<=3);
+
+ // Copy memory parts from e.g. the LoadNode over the opcode, e.g. an Add
+ if( op != mop ) {
+ _inputs.set(0,mop.in(0)); // Control from mem op
+ _inputs.set(1,mop.in(1)); // Memory from mem op
+ _inputs.set(2,base); // Base handed in
+ }
+
+ assert ptr() == base;
+ _inputs.setX(3,idx);
+ _off = off;
+ _scale = scale;
+ _imm = imm;
+ int size = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size();
+ _sz = (char)('0'+size);
+ }
+
+ // Store-based flavors have a value edge
+ MemOpX86( Node op, MemOpNode mop, Node base, Node idx, int off, int scale, int imm, Node val ) {
+ this(op,mop,base,idx,off,scale,imm);
+ _inputs.setX(4,val);
+ }
+
+ Node idx() { return in(3); }
+ Node val() { return in(4); } // Only for stores, including op-to-memory
+
+ @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) {
+ return p.p(".").p(_name);
+ }
+
+ @Override public String label() { return op(); }
+ @Override public Type compute() { throw new AssertionError("Do not call compute on "+getClass().getSimpleName()); }
+ @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); }
+
+ // Register mask allowed on input i.
+ @Override public RegMask regmap(int i) {
+ if( i==1 ) return null; // Memory
+ if( i==2 ) return x86_64_v2.RMASK; // base in GPR
+ if( i==3 ) return x86_64_v2.RMASK; // index in GPR
+ if( i==4 ) return _sz >= '4'
+ ? x86_64_v2.MEM_MASK // value in GPR or XMM
+ : x86_64_v2.RMASK; // Bytes and shorts in GPR only
+ return null; // Anti-dependence
+ }
+
+ // "[base + idx<<2 + 12]"
+ SB asm_address(CodeGen code, SB sb) {
+ sb.p("[").p(code.reg(ptr()));
+ if( idx() != null ) {
+ sb.p("+").p(code.reg(idx()));
+ if( _scale != 0 )
+ sb.p("*").p((1<<_scale));
+ }
+ if( _off != 0 )
+ sb.p("+").p(_off);
+ return sb.p("]");
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java
new file mode 100644
index 00000000..74616db7
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class MulFX86 extends MachConcreteNode implements MachNode {
+ MulFX86( Node mulf) { super(mulf); }
+ @Override public String op() { return "mulf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+ @Override public int twoAddress() { return 1; }
+ @Override public boolean commutes() { return true; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.sse(enc,0xF2,0x0F59,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET);
+ }
+
+ // General form: "mulf dst *= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" *= ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java
new file mode 100644
index 00000000..5dcb253d
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java
@@ -0,0 +1,27 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class MulIX86 extends MachConcreteNode implements MachNode {
+ final int _imm;
+ MulIX86( Node add, int imm ) {
+ super(add);
+ _inputs.pop(); // Pop the constant input
+ _imm = imm; // Record the constant
+ }
+ @Override public String op() { return "muli"; }
+ @Override public String glabel() { return "*"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ // Rare 3-address form
+ @Override public final void encoding( Encoding enc ) {
+ X86.imul(enc,enc.reg(this),enc.reg(in(1)),_imm);
+ }
+ // General form: "addi dst += #imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * #").p(_imm);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java
new file mode 100644
index 00000000..6f3fcdeb
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class MulX86 extends MachConcreteNode implements MachNode {
+ MulX86( Node mul ) { super(mul); }
+ @Override public String op() { return "mul"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public int twoAddress() { return 1; }
+ @Override public boolean commutes() { return true; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.reg(enc,0x0FAF,enc.reg(this),enc.reg(in(2)));
+ }
+
+ // General form: "mul dst *= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java
new file mode 100644
index 00000000..86610f6d
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class NegX86 extends MachConcreteNode implements MachNode {
+ NegX86(MinusNode not) { super(not); }
+ @Override public String op() { return "neg"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.RMASK; }
+ @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; }
+ @Override public int twoAddress() { return 1; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.unary(enc,0xF7,3,enc.reg(this));
+ }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java
new file mode 100644
index 00000000..d6a06c2f
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.util.SB;
+
+public class NewX86 extends NewNode implements MachNode {
+ // A pre-zeroed chunk of memory.
+ NewX86( NewNode nnn ) { super(nnn); }
+ @Override public void encoding( Encoding enc ) {
+ enc.external(this,"calloc");
+ X86.constant(enc,_arg2Reg,1);
+ X86.call(enc);
+ }
+ // General form: "alloc #bytes PC"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p("\n"); // Multi-op trigger
+ sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n");
+ sb.p("call #calloc");
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java
new file mode 100644
index 00000000..1e4e7c26
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class NotX86 extends MachConcreteNode implements MachNode {
+ NotX86(NotNode not) { super(not); }
+ @Override public String op() { return "not"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.RMASK; }
+ @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ assert !(in(1) instanceof NotNode); // Cleared out by peeps
+ assert !(in(1) instanceof BoolNode);
+ X86.not(enc,enc.reg(this),enc.reg(in(1)));
+ }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java
new file mode 100644
index 00000000..15f4824d
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class OrIX86 extends ImmX86 {
+ OrIX86( Node add, int imm ) { super(add,imm); }
+ @Override public String op() { return "ori"; }
+ @Override public String glabel() { return "|"; }
+ @Override int opcode() { return 0x81; }
+ @Override int mod() { return 1; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java
new file mode 100644
index 00000000..bc8f0ea8
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class OrX86 extends RegX86 {
+ OrX86( Node add ) { super(add); }
+ @Override public String op() { return "or"; }
+ @Override public String glabel() { return "|"; }
+ @Override int opcode() { return 0x0B; }
+ @Override public boolean commutes() { return true; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java
new file mode 100644
index 00000000..5cfdd804
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.ParmNode;
+
+public class ParmX86 extends ParmNode implements MachNode {
+ final RegMask _rmask;
+ ParmX86( ParmNode parm ) {
+ super(parm);
+ _rmask = x86_64_v2.callInMask(fun().sig(),_idx,1/*RPC*/);
+ }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return _rmask; }
+ @Override public void encoding( Encoding enc ) { }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java
new file mode 100644
index 00000000..046c7b4c
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java
@@ -0,0 +1,15 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.ProjNode;
+
+public class ProjX86 extends ProjNode implements MachNode {
+ ProjX86( ProjNode p ) { super(p); }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() {
+ return ((MachNode)in(0)).outregmap(_idx);
+ }
+ @Override public void encoding( Encoding enc ) {}
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java
new file mode 100644
index 00000000..06755143
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public abstract class RegX86 extends MachConcreteNode {
+ RegX86( Node add ) { super(add); }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; }
+ @Override public int twoAddress() { return 1; }
+ abstract int opcode();
+ @Override public void encoding( Encoding enc ) {
+ X86.reg(enc,opcode(),enc.reg(in(1)),enc.reg(in(2)));
+ }
+ // General form: "add dst += src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" ").p(glabel()).p("= ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java
new file mode 100644
index 00000000..e4467bb4
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java
@@ -0,0 +1,15 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class RetX86 extends ReturnNode implements MachNode {
+ RetX86( ReturnNode ret, FunNode fun ) { super(ret, fun); fun.setRet(this); }
+ @Override public void encoding( Encoding enc ) {
+ int sz = fun()._frameAdjust;
+ if( sz!=0 ) X86.imm(enc,0x81,0,x86_64_v2.RSP,sz);
+ X86.ret(enc);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java
new file mode 100644
index 00000000..cf6275dc
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.SB;
+
+// Arithmetic Right Shift
+public class SarIX86 extends MachConcreteNode implements MachNode {
+ final int _imm;
+ SarIX86( Node sari, int imm ) { super(sari); assert x86_64_v2.imm8(imm); _inputs.pop(); _imm = imm; }
+ @Override public String op() { return "sari"; }
+ @Override public String glabel() { return ">>"; }
+ int opcode() { return 0xC1; }
+ int mod() { return 7; }
+
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public int twoAddress() { return 1; }
+
+ @Override public void encoding(Encoding enc) {
+ X86.shift(enc,mod(),enc.reg(this),_imm);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java
new file mode 100644
index 00000000..92493712
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.Utils;
+
+public class SarX86 extends RegX86 {
+ SarX86( Node add ) { super(add); }
+ @Override public String op() { return "sar"; }
+ @Override public String glabel() { return ">>"; }
+ @Override public RegMask regmap(int i) {
+ if (i == 1) return x86_64_v2.WMASK;
+ if (i == 2) return x86_64_v2.RCX_MASK;
+ throw Utils.TODO();
+ }
+ @Override int opcode() { return 0xD3; }
+ @Override public void encoding( Encoding enc ) {
+ X86.unary(enc,opcode(),7,enc.reg(this));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java
new file mode 100644
index 00000000..864c1b55
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java
@@ -0,0 +1,44 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// Corresponds to the x86 instruction "sete && setne".
+// Use result of comparison without jump.
+public class SetX86 extends MachConcreteNode implements MachNode {
+ final String _bop; // One of <,<=,==
+ final boolean _unsigned;
+ // Constructor expects input is an X86 and not an Ideal node.
+ SetX86( Node cmp, String bop, boolean unsigned ) {
+ super(cmp);
+ _inputs.setLen(1); // Pop the cmp inputs
+ // Replace with the matched cmp
+ _inputs.push(cmp);
+ _bop = bop;
+ _unsigned = unsigned;
+ }
+ @Override public String op() { return "set"+_bop; }
+ @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.FLAGS_MASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ int op=switch (_bop) {
+ case "==" -> 0x94; // SETE
+ case "<" -> _unsigned ? 0x92 : 0x9C; // SETB /SETL
+ case "<=" -> _unsigned ? 0x96 : 0x9E; // SETBE/SETLE
+ case ">=" -> _unsigned ? 0x93 : 0x9D; // SETAE/SETGE
+ case ">" -> _unsigned ? 0x97 : 0x9F; // SETA /SETG
+ default -> throw Utils.TODO();
+ };
+ X86.set(enc,op&15,enc.reg(this));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this));
+ String src = code.reg(in(1));
+ if( src!="flags" ) sb.p(" = ").p(src);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java
new file mode 100644
index 00000000..1e62243e
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.SB;
+
+public class ShlIX86 extends MachConcreteNode implements MachNode {
+ final int _imm;
+ ShlIX86( Node shli, int imm ) {
+ super(shli);
+ assert x86_64_v2.imm8(imm);
+ _inputs.pop(); // Pop constant input off
+ _imm = imm;
+ }
+ public int opcode() { return 0xC1; }
+ public int mod() { return 4; }
+
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public int twoAddress() { return 1; }
+
+ @Override public String op() { return "shli"; }
+ @Override public String glabel() { return "<<"; }
+ @Override public void encoding(Encoding enc) {
+ X86.shift(enc,mod(),enc.reg(this),_imm);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java
new file mode 100644
index 00000000..768d97e7
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.Utils;
+
+public class ShlX86 extends RegX86 {
+ ShlX86( Node add ) { super(add); }
+ @Override public String op() { return "shl"; }
+ @Override public String glabel() { return "<<"; }
+ @Override public RegMask regmap(int i) {
+ if (i == 1) return x86_64_v2.WMASK;
+ if (i == 2) return x86_64_v2.RCX_MASK;
+ throw Utils.TODO();
+ }
+ @Override int opcode() { return 0xD3; }
+ @Override public final void encoding( Encoding enc ) {
+ X86.unary(enc,opcode(),4,enc.reg(this));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java
new file mode 100644
index 00000000..8a0d1b58
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java
@@ -0,0 +1,27 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.SB;
+
+public class ShrIX86 extends MachConcreteNode implements MachNode {
+ final int _imm;
+ ShrIX86( Node shri, int imm ) { super(shri); assert x86_64_v2.imm8(imm); _inputs.pop(); _imm = imm; }
+ @Override public String op() { return "shri"; }
+ @Override public String glabel() { return ">>>"; }
+ int opcode() { return 0xC1; }
+ int mod() { return 5; }
+
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public int twoAddress() { return 1; }
+
+ @Override public void encoding(Encoding enc) {
+ X86.shift(enc,mod(),enc.reg(this),_imm);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java
new file mode 100644
index 00000000..1ba20fee
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.Utils;
+
+public class ShrX86 extends RegX86 {
+ ShrX86( Node add ) { super(add); }
+ @Override public String op() { return "shr"; }
+ @Override public String glabel() { return ">>>"; }
+ @Override public RegMask regmap(int i) {
+ if (i == 1) return x86_64_v2.WMASK;
+ if (i == 2) return x86_64_v2.RCX_MASK;
+ throw Utils.TODO();
+ }
+ @Override int opcode() { return 0xD3; }
+ @Override public final void encoding( Encoding enc ) {
+ X86.unary(enc,opcode(),5,enc.reg(this));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java
new file mode 100644
index 00000000..cb7694f9
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java
@@ -0,0 +1,25 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+
+// Flag snapshots can use GPRs or stack slots, but never XMM registers.
+public class SplitFlagsX86 extends SplitX86 {
+ private static final RegMask MASK = new RegMask(x86_64_v2.WR_BITS | (1L<<x86_64_v2.FLAGS) | x86_64_v2.SPILLS,-1L);
+ SplitFlagsX86(LRG lrg,String kind,byte round) { super(lrg,kind,round); }
+ @Override public RegMask regmap(int i) { return MASK; }
+ @Override public RegMask outregmap() { return MASK; }
+
+ @Override public void encoding(Encoding enc) {
+ short dst=enc.reg(this), src=enc.reg(in(1));
+ if( dst==src ) return;
+ if( dst==x86_64_v2.FLAGS || src==x86_64_v2.FLAGS ) {
+ boolean toFlags = dst==x86_64_v2.FLAGS;
+ int reg = toFlags ? src : dst;
+ if( reg>=x86_64_v2.MAX_REG )
+ X86.flagsStack(enc,fun(enc).computeStackOffset(enc._code,reg),toFlags);
+ else
+ X86.flags(enc,reg,toFlags);
+ } else super.encoding(enc);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
new file mode 100644
index 00000000..46f2cb16
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
@@ -0,0 +1,45 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.FunNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.node.SplitNode;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class SplitX86 extends SplitNode {
+ SplitX86( LRG lrg, String kind, byte round ) { super(lrg,kind,round, new Node[2]); }
+ @Override public RegMask regmap(int i) { return x86_64_v2.SPLIT_MASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.SPLIT_MASK; }
+
+ // Moves among GPRs, XMM registers, and stack slots.
+ @Override public void encoding( Encoding enc ) {
+ short dst=enc.reg(this), src=enc.reg(in(1));
+ assert dst!=x86_64_v2.FLAGS && src!=x86_64_v2.FLAGS;
+ boolean dstX=dst>=x86_64_v2.XMM_OFFSET, srcX=src>=x86_64_v2.XMM_OFFSET;
+ // Stack spills
+ if( dst >= x86_64_v2.MAX_REG ) {
+ int off = fun(enc).computeStackOffset(enc._code,dst);
+ if( src >= x86_64_v2.MAX_REG ) {
+ // Rare stack-stack move. push [RSP+soff]; pop [RSP+doff]
+ int soff = fun(enc).computeStackOffset(enc._code,src);
+ X86.stackCopy(enc,soff,off);
+ return;
+ }
+ StoreX86.encVal(enc, srcX ? TypeFloat.F64 : TypeInteger.BOT, (short)x86_64_v2.RSP, (short)-1/*index*/, src, off, 0);
+ return;
+ }
+ if( src >= x86_64_v2.MAX_REG ) {
+ int off = fun(enc).computeStackOffset(enc._code,src);
+ LoadX86.enc(enc, dstX ? TypeFloat.F64 : TypeInteger.BOT, dst, (short)x86_64_v2.RSP, (short)-1, off, 0);
+ return;
+ }
+
+ X86.move(enc,dstX,srcX,dstX ? dst-x86_64_v2.XMM_OFFSET : dst,
+ srcX ? src-x86_64_v2.XMM_OFFSET : src);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java
new file mode 100644
index 00000000..9ffa25fc
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java
@@ -0,0 +1,48 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.ConstantNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.node.StoreNode;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+import com.seaofnodes.simple.util.SB;
+
+public class StoreX86 extends MemOpX86 {
+ StoreX86( StoreNode st, Node base, Node idx, int off, int scale, int imm, Node val ) {
+ super(st,st, base, idx, off, scale, imm, val);
+ }
+ @Override public String op() { return "st"+_sz; }
+ @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) {
+ Node val = val();
+ p.p(".").p(_name).p("=");
+ if( val==null ) p.p(_imm);
+ else p.n(val);
+ return p.p(";");
+ }
+ // Register mask allowed as a result. 0 for no register.
+ @Override public RegMask outregmap() { return null; }
+ @Override public void encoding( Encoding enc ) {
+ short ptr=enc.reg(ptr()), idx=enc.reg(idx()), src=enc.reg(val());
+ if( src==-1 ) X86.storeImm(enc,Integer.numberOfTrailingZeros(_sz-'0'),_imm,ptr,idx,_off,_scale);
+ else encVal(enc,_con,ptr,idx,src,_off,_scale);
+ }
+
+ // Non-immediate encoding
+ static void encVal( Encoding enc, Type decl, short ptr, short idx, short src, int off, int scale ) {
+ boolean xmm=src>=x86_64_v2.XMM_OFFSET;
+ X86.store(enc,decl.log_size(),xmm,
+ xmm ? src-x86_64_v2.XMM_OFFSET : src,ptr,idx,off,scale);
+ }
+
+
+ // General form: "stN [base + idx<<2 + 12],val"
+ @Override public void asm(CodeGen code, SB sb) {
+ asm_address(code,sb).p(",");
+ if( val()==null ) sb.p("#").p(_imm);
+ else sb.p(code.reg(val()));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java
new file mode 100644
index 00000000..29f0033a
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class SubFX86 extends MachConcreteNode implements MachNode {
+ SubFX86( Node subf) { super(subf); }
+ @Override public String op() { return "subf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+ @Override public int twoAddress() { return 1; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.sse(enc,0xF2,0x0F5C,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET);
+ }
+
+ // General form: "subf dst -= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" -= ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java
new file mode 100644
index 00000000..8da34bf5
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java
@@ -0,0 +1,10 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class SubX86 extends RegX86 {
+ SubX86( Node add ) { super(add); }
+ @Override public String op() { return "sub"; }
+ @Override public String glabel() { return "-"; }
+ @Override int opcode() { return 0x2B; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java
new file mode 100644
index 00000000..e3563713
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java
@@ -0,0 +1,51 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// Function constants
+public class TFPX86 extends FunPtrNode implements MachNode, RIPRelSize {
+ private byte _opLen;
+ final String _ext; // External name
+ // Pointer to a Simple function
+ TFPX86( FunPtrNode fptr ) { this((TypeFunPtr)fptr._type,null); }
+ // Spill clone: copy inputs without registering their reverse edges;
+ // insertBefore will place and register the clone in its use block.
+ private TFPX86( TFPX86 fptr ) { super(fptr); _ext = fptr._ext; }
+ // Pointer to an Extern "C" function
+ TFPX86( TypeFunPtr fptr, String ext ) { super(fptr,CodeGen.CODE._start,null); _type = fptr; _ext = ext; }
+ @Override public String op() { return "ldx"; }
+ @Override public String label() { return op(); }
+ @Override public boolean isClone() { return true; }
+ @Override public TFPX86 copy() { return new TFPX86(this); }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ if( _ext==null ) enc.relo(this);
+ else enc.external(this,_ext);
+ _opLen=X86.rip(enc,0,0x8D,true,enc.reg(this));
+ }
+
+ @Override public byte encSize(int delta) { return _opLen; }
+
+ // Delta is from opcode start, X86 expects it from opcode end
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ enc.patch4(opStart + opLen - 4, delta - opLen);
+ }
+
+ // Human-readable form appended to the SB. Things like the encoding,
+ // indentation, leading address or block labels not printed here.
+ // Just something like "ld4\tR17=[R18+12] // Load array base".
+ // General form: "op\tdst=src+src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this));
+ if( _ext == null ) _type.print(sb.p(" #"));
+ else sb.p(_ext);
+ }
+ @Override public boolean eq(Node n) { return this==n; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java
new file mode 100644
index 00000000..682597f4
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java
@@ -0,0 +1,39 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeMemPtr;
+import com.seaofnodes.simple.util.SB;
+
+public class TMPX86 extends ConstantNode implements MachNode, RIPRelSize{
+ private byte _opLen;
+ final String _ext;
+ TMPX86( ConstantNode con, String ext ) { super(con); _ext = ext; }
+ @Override public String op() { return "ldp"; }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public Node copy() { return new TMPX86(this,_ext); }
+
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this);
+ // 0 or 1 for REX depending on the dst.
+ _opLen = X86.rip(enc,0,0x8D,true,dst);
+ if( _ext!=null ) enc.externalData(this,_ext);
+ else enc.largeConstant(this,((TypeMemPtr)_con)._obj, _opLen-4, 2/*ELF encoding PC32*/);
+ }
+
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) { return _opLen; }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ enc.patch4(opStart + opLen - 4, delta - opLen);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ _con.print(sb.p(code.reg(this)).p(" = #"));
+ }
+ @Override public boolean eq(Node n) { return this==n; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java
new file mode 100644
index 00000000..591a51c3
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java
@@ -0,0 +1,41 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// unconditional jump
+public class UJmpX86 extends CFGNode implements MachNode, RIPRelSize {
+ UJmpX86() { }
+ @Override public String op() { return "jmp"; }
+ @Override public String label() { return op(); }
+ @Override protected String format() { return "jmp "; }
+ @Override public RegMask regmap(int i) {return null; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public Type compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+ @Override public void encoding( Encoding enc ) {
+ enc.jump(this,uctrl());
+ X86.branch(enc,-1);
+ }
+
+ // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding
+ @Override public byte encSize(int delta) {
+ return X86.branchSize(delta,false);
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ X86.patchBranch(enc.bits(),opStart,opLen,delta,-1);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ CFGNode target = uctrl();
+ //assert target.nOuts() > 1; // Should optimize jmp to empty targets
+ sb.p(label(target));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java
new file mode 100644
index 00000000..2d989a50
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class XorIX86 extends ImmX86 {
+ XorIX86( Node add, int imm ) { super(add,imm); }
+ @Override public String op() { return "xori"; }
+ @Override public String glabel() { return "^"; }
+ @Override int opcode() { return 0x81; }
+ @Override int mod() { return 6; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java
new file mode 100644
index 00000000..86703903
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class XorX86 extends RegX86 {
+ XorX86( Node add ) { super(add); }
+ @Override public String op() { return "xor"; }
+ @Override public String glabel() { return "^"; }
+ @Override int opcode() { return 0x33; }
+ @Override public boolean commutes() { return true; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
new file mode 100644
index 00000000..3dc93bf6
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
@@ -0,0 +1,596 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Utils;
+
+public class x86_64_v2 extends Machine {
+ public x86_64_v2( CodeGen code ) {}
+ // X86-64 V2. Includes e.g. SSE4.2 and POPCNT.
+ @Override public String name() { return "x86_64_v2"; }
+ @Override public int defaultOpSize() { return 5; }
+
+ public static final int RAX = 0, RCX = 1, RDX = 2, RBX = 3, RSP = 4, RBP = 5, RSI = 6, RDI = 7;
+ public static final int R08 = 8, R09 = 9, R10 = 10, R11 = 11, R12 = 12, R13 = 13, R14 = 14, R15 = 15;
+
+ public static final int XMM0 = 16, XMM1 = 17, XMM2 = 18, XMM3 = 19, XMM4 = 20, XMM5 = 21, XMM6 = 22, XMM7 = 23;
+ public static final int XMM8 = 24, XMM9 = 25, XMM10 = 26, XMM11 = 27, XMM12 = 28, XMM13 = 29, XMM14 = 30, XMM15 = 31;
+ public static final int FLAGS = 32;
+ public static final int MAX_REG = 33;
+ public static final int RPC = 33;
+
+ // Human-readable name for a register number, e.g. "RAX".
+ public static final String[] REGS = new String[]{
+ "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi",
+ "r8" , "r9" , "r10", "r11", "r12", "r13", "r14", "r15",
+ "xmm0", "xmm1", "xmm2" , "xmm3" , "xmm4" , "xmm5" , "xmm6" , "xmm7" ,
+ "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15",
+ "flags",
+ };
+ // Hard crash for bad register number, fix yer bugs!
+ @Override public String[] regs() { return REGS; }
+
+ public static int XMM_OFFSET = 16;
+ // General purpose register mask: pointers and ints, not floats
+ static final long RD_BITS = 0b1111111111111111L; // All the GPRs
+ static RegMask RMASK = new RegMask(RD_BITS);
+ // No RSP in the *write* general set.
+ static final long WR_BITS = 0b1111111111101111L; // All the GPRs minus RSP
+ static RegMask WMASK = new RegMask(WR_BITS);
+ // Xmm register mask
+ static final long FP_BITS = 0b1111111111111111L << XMM0; // All the XMMs
+ static RegMask XMASK = new RegMask(FP_BITS);
+ static RegMask FLAGS_MASK = new RegMask(FLAGS);
+ static RegMask RPC_MASK = new RegMask(RPC);
+ // Divide mask: exclude RDX:RAX
+ static RegMask DIV_MASK = new RegMask(RD_BITS & ~(1L<<RAX) & ~(1L<<RDX));
+
+ static final long SPILLS = -(1L << MAX_REG);
+ static final RegMask SPLIT_MASK = new RegMask(WR_BITS | FP_BITS | SPILLS, -1L );
+
+ // Load/store mask; both GPR and FPR
+ static RegMask MEM_MASK = new RegMask(WR_BITS | FP_BITS);
+
+ public static RegMask RAX_MASK = new RegMask(RAX);
+ public static RegMask RCX_MASK = new RegMask(RCX);
+ public static RegMask RDX_MASK = new RegMask(RDX);
+ public static RegMask RDI_MASK = new RegMask(RDI);
+ public static RegMask R08_MASK = new RegMask(R08);
+ public static RegMask R09_MASK = new RegMask(R09);
+ public static RegMask RSI_MASK = new RegMask(RSI);
+
+ public static RegMask XMM0_MASK = new RegMask(XMM0);
+
+ // Encoding
+ public static int REX = 0x40;
+ public static int REX_W = 0x48;
+ public static int REX_WR = 0x4C;
+ public static int REX_WRB= 0x4D;
+ public static int REX_WB = 0x49;
+
+ // opcode included here
+ // 0F 84 cd JE rel32
+ // 0F 85 cd JNE rel32
+ // 0F 8F cd JG rel32
+ // 0F 8C cd JL rel32
+ // 0F 8E cd JLE rel32
+ // 0F 8D cd JGE rel32
+ static public int jumpop(String op) {
+ return switch (op) {
+ case "==" -> 0x84;
+ case "!=" -> 0x85;
+ case ">" -> 0x8F;
+ case "<" -> 0x8C;
+ case "<=" -> 0x8E;
+ case ">=" -> 0x8D;
+ default -> throw new IllegalArgumentException("Too many arguments");
+ };
+ }
+
+ // Limit of float args passed in registers
+ static RegMask[] XMMS8 = new RegMask[]{
+ new RegMask(XMM0), new RegMask(XMM1), new RegMask(XMM2), new RegMask(XMM3),
+ new RegMask(XMM4), new RegMask(XMM5), new RegMask(XMM6), new RegMask(XMM7),
+ };
+
+ // Map from function signature and argument index to register.
+ // Used to set input registers to CallNodes, and ParmNode outputs.
+ @Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); }
+ @Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot, String conv ) { return callInMask(tfp,idx,maxArgSlot,conv); }
+ static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot ) {
+ return callInMask(tfp,idx,maxArgSlot,CodeGen.CODE._callingConv);
+ }
+ static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot, String conv ) {
+ if( idx==0 ) return RPC_MASK;
+ if( idx==1 ) return null;
+ if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence
+ return switch( conv ) {
+ case "SystemV" -> callSys5 (tfp,idx,maxArgSlot);
+ case "win64" -> callWin64(tfp,idx,maxArgSlot);
+ default -> throw Utils.TODO();
+ };
+ }
+
+ // Maximum stack args used by this signature
+ @Override public short maxArgSlot( TypeFunPtr tfp ) {
+ return maxArgSlot(tfp,CodeGen.CODE._callingConv);
+ }
+ @Override public short maxArgSlot( TypeFunPtr tfp, String conv ) {
+ return switch( conv ) {
+ case "SystemV" -> maxArgSlotSys5 (tfp);
+ case "win64" -> maxArgSlotWin64(tfp);
+ default -> throw Utils.TODO();
+ };
+ }
+
+ // Win64 - max *4* args in registers of all kinds; after that on stack.
+ // Stack args land in increasing memory order with empty/mirror slots for
+ // every register.
+ //
+ // foo( int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, ... )
+ //
+ // -- Prior Frame --
+ // i6
+ // f5
+ // i4
+ // empty mirror, XMM3= f3
+ // empty mirror, R08 = i2
+ // empty mirror, XMM1= f1
+ // empty mirror, RCX = i0
+ // -- Caller Frame; 16b align --
+ // RPC
+ // PAD/ALIGN
+ // -- Callee Frame; 16b align --
+ static RegMask[] WIN64_CALL = new RegMask[] {
+ RCX_MASK,
+ RDX_MASK,
+ R08_MASK,
+ R09_MASK,
+ };
+
+ static RegMask callWin64(TypeFunPtr tfp, int idx, int maxArgSlot ) {
+ // idx 2,3,4,5 passed in registers, with stack slot mirrors.
+ // idx >= 6 passed on stack, starting at slot#1 (#0 reserved for RPC).
+ int sigidx = idx-2;
+ if( hiddenSelf(tfp) ) {
+ if( sigidx==0 ) return null;
+ sigidx--;
+ }
+ if( sigidx >= 4 )
+ return new RegMask(MAX_REG+maxArgSlot+sigidx);
+ return tfp.arg(idx-2) instanceof TypeFloat
+ ? XMMS8 [sigidx]
+ : WIN64_CALL[sigidx];
+ }
+ static short maxArgSlotWin64(TypeFunPtr tfp) {
+ return (short)Math.max(4,tfp.nargs() - (hiddenSelf(tfp) ? 1 : 0));
+ }
+ static short maxIncomingArgSlotWin64(TypeFunPtr tfp) {
+ return (short)Math.max(0,tfp.nargs() - (hiddenSelf(tfp) ? 1 : 0) - 4);
+ }
+
+ // Sys5: max 6 GPRs and 8 FPRS filled first. Extra args land in increasing
+ // memory order as needed - no mirror space.
+ //
+ // foo( int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, ... )
+ // RDI =i0, RSI =i2, RDX =i4, RCX =i6, R08 =i8, R09 =i10
+ // XMM0=f1, XMM1=f3, XMM2=f5, XMM3=f7, XMM4=f9, XMM5=f11, XMM6=f13, XMM7=f15
+ //
+ // -- Prior Frame --
+ // f18
+ // i18
+ // f17
+ // i16
+ // i14
+ // i12
+ // -- Caller Frame; 16b align --
+ // RPC
+ // PAD/ALIGN
+ // -- Callee Frame; 16b align --
+ // SystemV(param passing)
+ static RegMask[] SYS5_CALL = new RegMask[] {
+ RDI_MASK,
+ RSI_MASK,
+ RDX_MASK,
+ RCX_MASK,
+ R08_MASK,
+ R09_MASK,
+ };
+
+ static RegMask callSys5(TypeFunPtr tfp, int idx, int maxArgSlot ) {
+ // First 6 integers passed in registers: rdi,rsi,rdx,rcx,r08,r09
+ // First 8 floats passed in registers: xmm0-xmm7
+ int sigidx = idx-2;
+ if( hiddenSelf(tfp) ) {
+ if( sigidx==0 ) return null;
+ sigidx--;
+ }
+ int icnt=0, fcnt=0; // Count of ints, floats
+ for( int i=hiddenSelf(tfp) ? 1 : 0; i<idx-2; i++ ) {
+ if( tfp.arg(i) instanceof TypeFloat ) fcnt++;
+ else icnt++;
+ }
+ int nstk = Math.max(icnt-6,0)+Math.max(fcnt-8,0);
+ return tfp.arg(idx-2) instanceof TypeFloat
+ ? fcnt<8 ? XMMS8 [fcnt] : new RegMask(MAX_REG+maxArgSlot+nstk)
+ : icnt<6 ? SYS5_CALL[icnt] : new RegMask(MAX_REG+maxArgSlot+nstk);
+ }
+ static short maxArgSlotSys5(TypeFunPtr tfp) {
+ int icnt=0, fcnt=0; // Count of ints, floats
+ for( int i=hiddenSelf(tfp) ? 1 : 0; i<tfp.nargs(); i++ ) {
+ if( tfp.arg(i) instanceof TypeFloat ) fcnt++;
+ else icnt++;
+ }
+ int nstk = Math.max(icnt-6,0)+Math.max(fcnt-8,0);
+ return (short)nstk;
+ }
+ private static boolean hiddenSelf(TypeFunPtr tfp) {
+ return tfp.nargs() > 0 && tfp.arg(0) == TypePtr.PTR;
+ }
+
+ static final long SYSTEM5_CALLER_SAVE =
+ (1L << RAX) | (1L << RCX) | (1L << RDX) |
+ (1L << RDI) | (1L << RSI) |
+ (1L << R08) | (1L << R09) | (1L << R10) | (1L << R11) |
+ (1L << FLAGS) | // Flags are killed
+ // All FP regs are killed
+ FP_BITS;
+
+ static final long WIN64_CALLER_SAVE =
+ (1L<< RAX) | (1L<< RCX) | (1L<< RDX) |
+ (1L<< R08) | (1L<< R09) | (1L<< R10) | (1L<< R11) |
+ (1L<<FLAGS)| // Flags are killed
+ // Only XMM0-XMM5 are killed; XMM6-XMM15 are preserved
+ (1L<<XMM0) | (1L<<XMM1) | (1L<<XMM2) | (1L<<XMM3) |
+ (1L<<XMM4) | (1L<<XMM5) ;
+
+ @Override public long callerSave() {
+ return callerSave(CodeGen.CODE._callingConv);
+ }
+ @Override public long callerSave(String conv) {
+ return switch (conv) {
+ case "SystemV" -> SYSTEM5_CALLER_SAVE;
+ case "win64" -> WIN64_CALLER_SAVE;
+ default -> throw new IllegalArgumentException("Unknown calling convention: " + conv);
+ };
+ }
+ @Override public String cCallingConv(String callingConv) {
+ return callingConv;
+ }
+ @Override public long neverSave() { return 1L<<RSP; }
+ @Override public RegMask retMask( TypeFunPtr tfp ) {
+ return tfp.ret() instanceof TypeFloat ? XMM0_MASK : RAX_MASK;
+ }
+ @Override public int rpc() { return RPC; }
+
+
+ // Create a split op; any register to any register, including stack slots
+ @Override public SplitNode split(LRG lrg, String kind, byte round ) {
+ return lrg._machDef!=null && lrg._machDef.outregmap().test(FLAGS)
+ ? new SplitFlagsX86(lrg,kind,round)
+ : new SplitX86(lrg,kind,round);
+ }
+
+ // Return a MachNode unconditional branch
+ @Override public CFGNode jump() {
+ return new UJmpX86();
+ }
+
+ // Instruction selection
+ @Override
+ public Node instSelect(Node n) {
+ return switch (n) {
+ case AddNode add -> add(add);
+ case AndNode and -> and(and);
+ case BoolNode bool -> cmp(bool);
+ case CLInitNode init -> new CLInitNode(init);
+ case CProjNode c -> new CProjNode(c);
+ case CallEndNode cend -> new CallEndMach(cend);
+ case CallNode call -> call(call);
+ case PtrToIntNode ptr -> new PtrToIntMach(ptr);
+ case GuardNode guard -> new GuardMach(guard);
+ case ConstantNode con -> con(con);
+ case DivNode div -> div.mode()==2 ? new DivFX86(div) : new DivX86(div);
+ case EscapeNode esc -> new EscapeNode(esc);
+ case FunNode fun -> new FunX86(fun);
+ case FunPtrNode fptr -> fptr(fptr);
+ case IfNode iff -> jmp(iff);
+ case LoadNode ld -> ld(ld);
+ case MemMergeNode mem -> new MemMergeNode(mem);
+ case MinusNode neg -> {
+ if( neg.mode()==2 ) throw Utils.TODO();
+ yield new NegX86(neg);
+ }
+ case MulNode mul -> mul.mode()==2 ? new MulFX86(mul) : mul(mul);
+ case NewNode nnn -> new NewX86(nnn);
+ case NotNode not -> new NotX86(not);
+ case OrNode or -> or(or);
+ case ParmNode parm -> new ParmX86(parm);
+ case BulkMemPhiNode phi-> new BulkMemPhiNode(phi);
+ case MemPhiNode phi -> new MemPhiNode(phi);
+ case PhiNode phi -> new PhiNode(phi);
+ case ProjNode prj -> prj(prj);
+ case ReadOnlyNode read-> new ReadOnlyMach(read);
+ case ReturnNode ret -> new RetX86(ret, ret.fun());
+ case SarNode sar -> sar(sar);
+ case ShlNode shl -> shl(shl);
+ case ShrNode shr -> shr(shr);
+ case StartCUNode start -> new StartCUNode(start);
+ case StopCUNode stop -> new StopCUNode(stop);
+ case StartNode start -> new StartNode(start);
+ case StopNode stop -> new StopNode(stop);
+ case StoreNode st -> st(st);
+ case SubNode sub -> sub.mode()==2 ? new SubFX86(sub) : sub(sub);
+ case ToFloatNode tfn -> i2f8(tfn);
+ case ToIntegerNode cvt -> new F8ToIX86(cvt);
+ case XCtrlNode x -> ConstantNode.raw(Type.XCONTROL);
+ case XorNode xor -> xor(xor);
+
+ case LoopNode loop -> new LoopNode(loop);
+ case RegionNode region -> new RegionNode(region);
+ default -> throw Utils.TODO();
+ };
+ }
+
+ public static boolean imm8( long imm ) { return -128 <= imm && imm <= 127; }
+
+ public static boolean imm32( long imm ) { return (int)imm==imm; }
+
+ // Attempt a full LEA-style break down.
+ private Node add(AddNode add) {
+ if( add.mode()==2 )
+ return new AddFX86(add);
+ Node lhs = add.in(1);
+ Node rhs = add.in(2);
+ if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && ld.declaredType().log_size() >= 3)
+ return new AddMemX86(add, address(ld), ld.ptr(), idx, off, scale, 0, rhs);
+ // Attempt a full LEA-style break down.
+ // Returns one of AddX86, AddIX86, LeaX86, or LHS
+ if( rhs instanceof ConstantNode off2 && off2._con instanceof TypeInteger toff ) {
+ long imm = toff.value();
+ assert imm!=0; // Folded in peeps
+ if( (int)imm != imm ) // Full 64bit immediate
+ return new AddX86(add);
+ // Now imm <= 32bits
+ if( lhs instanceof AddNode ladd )
+ // ((base + (idx << scale)) + off)
+ return _lea(add, ladd.in(1), ladd.in(2), (int)imm);
+ if( lhs instanceof ShlNode shift )
+ // (idx << scale) + off; no base
+ return _lea(add, null, shift, (int)imm);
+
+ // lhs + rhs1
+ return new AddIX86(add, (int)imm);
+ }
+ return _lea(add, lhs, rhs, 0);
+ }
+
+
+ private Node _lea(Node add, Node base, Node idx, int off) {
+ int scale = 0;
+ if( base instanceof ShlNode && !(idx instanceof ShlNode) )
+ throw Utils.TODO(); // Bug in canonicalization, should on RHS
+ if( idx instanceof ShlNode shift && shift.in(2) instanceof ConstantNode shfcon &&
+ shfcon._con instanceof TypeInteger tscale && 0 <= tscale.value() && tscale.value() <= 3 ) {
+ idx = shift.in(1);
+ scale = ((int) tscale.value());
+ }
+ // (base + idx) + off
+ return off == 0 && scale == 0
+ ? new AddX86(add)
+ : new LeaX86(add, base, idx, scale, off);
+ }
+
+
+ private Node and(AndNode and) {
+ if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) )
+ return new AndIX86(and, (int)ti.value());
+ return new AndX86(and);
+ }
+
+ private Node call(CallNode call) {
+ return call.fptr() instanceof FunPtrNode con
+ ? new CallX86(call, (TypeFunPtr)con._type)
+ : call.fptr() instanceof ConstantNode con && con._type instanceof TypeFunPtr
+ ? new CallX86(call, (TypeFunPtr)con._type)
+ : new CallRX86(call);
+ }
+
+ // Because X86 flags, a normal ideal Bool is 2 X86 ops: a "cmp" and at "setz".
+ // Ideal If reading from a setz will skip it and use the "cmp" instead.
+ private static boolean swap, unsigned;
+ private Node cmp( BoolNode bool ) {
+ swap = unsigned = false;
+ Node cmp = _cmp(bool);
+ return new SetX86(cmp, swap ? IfNode.swap(bool.op()) : bool.op(), unsigned);
+ }
+
+ private Node _cmp(BoolNode bool) {
+ // Float variant
+ if( bool.isFloat() ) {
+ unsigned = true;
+ return new CmpFX86(bool);
+ }
+ Node lhs = bool.in(1);
+ Node rhs = bool.in(2);
+
+ // Since cmp does not record a BOP, SetX/Jmp need to know if the CMP is swapped.
+
+ // Vs memory
+ if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && rhs._type.isa(ld.declaredType()) )
+ return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(rhs), val, false);
+
+ // Operands swap in the encoding directly, no need for Set/Jmp to swap `bop`
+ if( rhs instanceof LoadNode ld && ld.nOuts() == 1 && lhs._type.isa(ld.declaredType()) &&
+ (!(lhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value())) ||
+ bool.op()=="==" || bool.op()=="!=") )
+ return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(lhs), val, true );
+
+ // Vs immediate
+ if( rhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) )
+ return new CmpIX86(bool, (int)ti.value(), false);
+
+ // Operand swap compare; Set and Jmp need to swap `bop`
+ if( lhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) )
+ return new CmpIX86(bool, (int)ti.value(), swap=true);
+
+ // x vs y
+ return new CmpX86(bool);
+ }
+
+ private Node con( ConstantNode con ) {
+ if( !con._con.isConstant() )
+ return ConstantNode.raw(con); // Default unknown caller inputs
+ String ext = con instanceof ExternNode ext0 ? ext0._extern : null;
+ return switch (con._con) {
+ case TypeInteger ti -> new IntX86(con,ext);
+ case TypeFloat tf -> new FltX86(con,ext);
+ case TypeMemPtr tmp -> new TMPX86(con,ext);
+ case TypeFunPtr tfp -> new TFPX86(tfp,ext);
+ case TypeNil tn -> throw Utils.TODO();
+ // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
+ case Type t -> t == Type.NIL ? new IntX86(con,null) : ConstantNode.raw(con);
+ };
+ }
+
+ private Node fptr( FunPtrNode con ) {
+ return new TFPX86(con);
+ }
+
+ private Node i2f8(ToFloatNode tfn) {
+ assert tfn.in(1)._type instanceof TypeInteger ti;
+ return new I2f8X86(tfn);
+ }
+
+ private Node jmp(IfNode iff) {
+ // If/Bool combos will match to a Cmp/Set which sets flags.
+ // Most general arith ops will also set flags, which the Jmp needs directly.
+ // Loads do not set the flags, and will need an explicit TEST
+ String op = "!=";
+ if( iff.in(1) instanceof BoolNode bool ) op = swap ? IfNode.swap(bool.op()) : bool.op();
+ else if( iff.in(1)==null ) op = "=="; // Never-node cutout
+ else iff.setDef(1, new BoolNode.NE(iff.in(1),
+ ConstantNode.raw(TypeInteger.ZERO),
+ (byte)(iff.in(1)._type instanceof TypeFloat ? 2 : 1)));
+ return new JmpX86(iff, op);
+ }
+
+ private Node ld(LoadNode ld) {
+ return new LoadX86(address(ld), ld.ptr(), idx, off, scale);
+ }
+
+ private Node mul(MulNode mul) {
+ if( mul.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) )
+ return new MulIX86(mul, (int)ti.value());
+ return new MulX86(mul);
+ }
+
+ private Node or(OrNode or) {
+ if( or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) )
+ return new OrIX86(or, (int)ti.value());
+ return new OrX86(or);
+ }
+
+ private Node prj( ProjNode prj ) {
+ if( prj.in(0) instanceof CLInitNode ) return new ProjNode(prj);
+ TypeInteger ret = cReturn(prj);
+ if( ret!=null ) return new ExtendX86(prj,ret);
+ return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjX86(prj);
+ }
+
+ private Node sar(SarNode sar) {
+ if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value()))
+ return new SarIX86(sar, (int)(ti.value() & 0xff) );
+ return new SarX86(sar);
+ }
+
+ private Node shl(ShlNode shl) {
+ if( shl.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value()))
+ return new ShlIX86(shl, (int)(ti.value() & 0xff) );
+ return new ShlX86(shl);
+ }
+
+ private Node shr( ShrNode shr ) {
+ if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value()) )
+ return new ShrIX86(shr, (int)(ti.value() & 0xff) );
+ return new ShrX86(shr);
+ }
+
+ private Node st( StoreNode st ) {
+ // Look for "*ptr op= val"
+ Node op = st.val();
+ if( op instanceof AddNode ) {
+ if( op.in(1) instanceof LoadNode ld && stld_match(st,ld) )
+ return new MemAddX86(address(st), st.ptr(), idx, off, scale, imm(op.in(2)), val);
+ if( op.in(2) instanceof LoadNode ld && stld_match(st,ld) )
+ return new MemAddX86(address(st), st.ptr(), idx, off, scale, imm(op.in(1)), val);
+ }
+ return new StoreX86(address(st), st.ptr(), idx, off, scale, imm(st.val()), val);
+ }
+ private static boolean stld_match(StoreNode st, LoadNode ld ) {
+ return
+ ld.in(0) == st.in(0) &&
+ ld.mem() == st.mem() &&
+ ld.ptr() == st.ptr() &&
+ ld.off() == st.off();
+ }
+
+
+ private Node sub (SubNode sub ){
+ return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti
+ ? new AddIX86(sub, (int)-ti.value())
+ : new SubX86(sub);
+ }
+
+ private Node xor (XorNode xor){
+ if( xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) )
+ return new XorIX86(xor, (int)ti.value());
+ return new XorX86(xor);
+ }
+
+ // Gather X86 addressing mode bits prior to constructing. This is a
+ // builder pattern, but saving the bits in a *local* *global* here to keep
+ // mess contained.
+ private static int off, scale, imm;
+ private static Node idx, val;
+ private <N extends MemOpNode > N address(N mop) {
+ off = scale = imm = 0; // Reset
+ idx = val = null;
+ Node base = mop.ptr();
+ // Skip/throw-away a ReadOnly, only used to typecheck
+ if(base instanceof ReadOnlyNode read) base = read.in(1);
+ assert !(base instanceof AddNode) && base._type instanceof TypeMemPtr; // Base ptr always, not some derived
+ if(mop.off() instanceof AddNode add && add.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) {
+ off = (int) ti.value();
+ assert off == ti.value(); // In 32-bit range
+ idx = add.in(1);
+ if(idx instanceof ShlNode shift && shift.in(2) instanceof ConstantNode shfcon &&
+ shfcon._con instanceof TypeInteger tscale && 0 <= tscale.value() && tscale.value() <= 3) {
+ idx = shift.in(1);
+ scale = (int) tscale.value();
+ }
+ } else {
+ if(mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti) {
+ off = (int) ti.value();
+ assert off == ti.value(); // In 32-bit range
+ } else {
+ idx = mop.off();
+ }
+ }
+ return mop;
+ }
+
+ private int imm( Node xval ) {
+ assert val == null && imm == 0;
+ if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti && ti.isConstant() && imm32(ti.value()) ) {
+ val = null;
+ imm = (int) ti.value();
+ assert imm == ti.value(); // In 32-bit range
+ } else {
+ val = xval;
+ }
+ return imm;
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Allocations.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Allocations.java
new file mode 100644
index 00000000..c5b8566b
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Allocations.java
@@ -0,0 +1,199 @@
+package com.seaofnodes.simple.parser;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Ary;
+
+/** Allocation expressions and declaration-side allocation helper graphs. */
+final class Allocations {
+ private final Parser P;
+
+ Allocations(Parser P) { this.P = P; }
+
+ /**
+ Parse an allocation
+ */
+ Node parseNew() {
+ Type t = P.types.parseType();
+ if( t==null ) {
+ String tname = P._lexer.matchId();
+ if( tname==null || Parser.KEYWORDS.contains(tname) )
+ throw P.error("Expected a type");
+ t = P.types.forwardRefType(tname);
+ }
+ // Parse ary[ length_expr ]
+ if( P.match("[") ) {
+ if( !t.makeZero().isa(t) )
+ throw P.error("Cannot allocate a non-nullable, since arrays are always zero/null filled");
+ Node len = P.expressions.parseAssignment().keep();
+ P.require("]");
+ return allocArray(P.types.typeAry(t,false),len);
+ } else {
+ if( !(t instanceof TypeMemPtr tmp) )
+ throw P.error("Cannot allocate a "+t.str());
+ return allocStruct(tmp._obj);
+ }
+ }
+
+ private Node allocStruct(TypeStruct ts) {
+ Ary<Node> ctorArgs = constructorArgs();
+ // A field declaration can leave a short forward type name in TYPES.
+ // Allocation happens in lexical context, so resolve that short name
+ // to the completed nested type before selecting its class helper.
+ String typeName = ts._name.indexOf('.') == -1 ? P.types.fullTypeName(ts._name) : ts._name;
+ Type resolved = Parser.TYPES.get(typeName);
+ if( resolved instanceof TypeStruct rts ) ts = rts;
+ TypeStruct clz = (TypeStruct)Parser.TYPES.get(Parser.addClzPrefix(typeName));
+ if( clz==null )
+ throw P.error("Unknown struct type '" + ts._name + "'");
+ Field afld = clz.field("<new>");
+ Node alloc;
+ if( afld != null ) {
+ TypeFunPtr tfp = (TypeFunPtr)afld._t;
+ FunNode fun = P._code.link(tfp);
+ alloc = fun==null
+ ? new FRefNode((Parser.addClzPrefix(typeName)+".<new>").intern(),P.loc())
+ : new FunPtrNode(tfp,P._code._start,fun.ret()).peephole();
+ } else {
+ if( !ts._fref )
+ throw P.error("Constructor arguments for '" + ts._name + "' but no constructor is defined");
+ Type[] sig = new Type[ctorArgs._len];
+ for( int i = 0; i<ctorArgs._len; i++ )
+ sig[i] = ctorArgs.at(i)._type;
+ alloc = new FRefNode((Parser.addClzPrefix(typeName)+".<new>").intern(),P.loc(),
+ TypeFunPtr.make(false,sig,TypeMemPtr.make(ts)));
+ }
+ Ary<Node> args = new Ary<>(Node.class);
+ args.add(P.ctrl()); args.add(P.mem());
+ for( Node arg : ctorArgs ) args.add(arg.unkeep());
+ args.add(alloc);
+ return P.functions.functionCall(args);
+ }
+
+ private Ary<Node> constructorArgs() {
+ Ary<Node> args = new Ary<>(Node.class);
+ if( !P.match("(") )
+ return args;
+ P.functions.arguments(args);
+ P.require(")");
+ return args;
+ }
+
+ private Node allocArray(TypeStruct ts, Node len) {
+ Field lenFld = ts.field("#" );
+ Field bodyFld = ts.field("[]");
+
+ ConFldOffNode base = Parser.off(ts, "[]");
+ int scale = ts.aryScale();
+ Node size = P.peep(new AddNode(base,P.peep(new ShlNode(null,len,P.con(scale)))));
+
+ // Build a NewNode; takes in and puts out all aliases.
+ NewNode nnn = new NewNode(ts, P.ctrl(), size ).init();
+ ProjNode self = new ProjNode(nnn,0,ts.str()).init().keep();
+ ProjNode smem = new ProjNode(nnn,1,"#selfMem").init().keep();
+
+ // Store length. Rest of array is zero'd via CALLOC during CodeGen.
+ // Length is casted to sanity.
+ // TODO: Needs runtime check.
+ Node st = P.peep(new StoreNode(null, "#", 1, TypeInteger.U32, null, smem, self, Parser.off(ts,"#"), len.unkeep(), true));
+
+ // TODO: Allow add-on "constructor" to init the array elements
+
+ Node prior = P.mem();
+ Node esc = P.peep(new EscapeNode(lenFld,self,st,prior));
+ P.mem(P.mergeAlias(prior,lenFld._alias,esc));
+ prior = P.mem();
+ esc = P.peep(new EscapeNode(bodyFld,self,smem,prior));
+ P.mem(P.mergeAlias(prior,bodyFld._alias,esc));
+ smem.unkeep();
+ return self.unkeep();
+ }
+
+ Node newString(String s) {
+ TypeStruct ts = P.types.typeAry(TypeInteger.U8,true);
+ int lenAlias = ts.field("#" )._alias;
+ int elemAlias = ts.field("[]")._alias;
+ TypeConAryB body = TypeConAryB.make(s);
+ TypeInteger slen = TypeInteger.constant(s.length());
+ // Make a TMP, not-null (byte)2, singleton (true) (requires text
+ // strings are hash-interned), with a TypeStruct having a constant
+ // array body.
+ TypeMemPtr str = TypeMemPtr.make((byte)2,TypeStruct.makeAry("[]u8", slen, lenAlias, body, elemAlias, true),true);
+ assert str.isConstant();
+ return P.con(str.makeRO());
+ }
+
+ // Build the declaration-side allocation helper. Allocation sites only
+ // call this function; all layout, initialization and publication choices
+ // are made here, where the complete struct and constructor are known.
+ TypeFunPtr makeAllocator(TypeStruct ts, ReturnNode initRet, Field ctorFld) {
+ TypeFunPtr ctor = ctorFld==null ? null : (TypeFunPtr)ctorFld._t;
+ int nargs = ctor==null ? 0 : ctor.nargs()-2; // Hide self and selfMem
+ Type[] atypes = new Type[nargs];
+ for( int i = 0; i<nargs; i++ ) atypes[i] = ctor.arg(i+2);
+ TypeMemPtr ptr = TypeMemPtr.make(ts);
+ TypeFunPtr sig = TypeFunPtr.make1((byte)2,true,atypes,ptr,P._code.fidx(P._ref._cname));
+ String name = (ts._name+".<new>").intern();
+
+ int oldUID = P._code.UID();
+ FunNode fun = (FunNode)new FunNode(P.loc(),sig,name,P._ref,null,P._ref._start).peephole();
+ P._code.link(fun);
+ Node rpc = new ParmNode("$rpc",0,TypeRPC.BOT,fun,ConstantNode.seed(TypeRPC.BOT).peephole()).peephole();
+ Node pub = new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,
+ new ProjNode(P._ref._start,1,ScopeNode.MEM0).peephole()).peephole();
+ Node[] parms = new Node[nargs];
+ for( int i = 0; i<nargs; i++ )
+ parms[i] = new ParmNode("arg"+i,i+2,atypes[i],fun,ConstantNode.seed(atypes[i]).peephole()).peephole();
+
+ NewNode nnn = new NewNode(ts,fun,Parser.off(ts," len")).init();
+ Node self = new ProjNode(nnn,0,ts.str()).init();
+ Node priv = new ProjNode(nnn,1,"#selfMem").init();
+ Node[] initArgs = new Node[]{fun,pub,self,priv,
+ new FunPtrNode(initRet.fun().sig(),P._code._start,initRet).peephole()};
+ CallEndNode cend = rawCall(initArgs,initRet.fun());
+ Node ctl = new CProjNode(cend,0,ScopeNode.CTRL).peephole();
+ pub = new ProjNode(cend,1,ScopeNode.MEM0).peephole();
+ priv = new ProjNode(cend,2,"#selfMem").peephole();
+
+ if( ctor != null ) {
+ Node[] cargs = new Node[nargs+5];
+ cargs[0] = ctl; cargs[1] = pub; cargs[2] = self; cargs[3] = priv;
+ for( int i = 0; i<nargs; i++ ) cargs[i+4] = parms[i];
+ FunNode cfun = P._code.link(ctor);
+ cargs[cargs.length-1] = new FunPtrNode(ctor,P._code._start,cfun.ret()).peephole();
+ cend = rawCall(cargs,cfun);
+ ctl = new CProjNode(cend,0,ScopeNode.CTRL).peephole();
+ pub = new ProjNode(cend,1,ScopeNode.MEM0).peephole();
+ priv = new ProjNode(cend,2,"#selfMem").peephole();
+ }
+
+ priv.keep();
+ for( Field field : ts._fields ) {
+ Type storage = field._t.makeStorage();
+ Field escaped = field._final ? field : field.makeFrom(storage).makeFrom(true);
+ pub.keep();
+ Node esc = new EscapeNode(escaped,self,priv,pub).peephole();
+ MemMergeNode merge = new MemMergeNode(null,pub);
+ merge.alias(field._alias,esc);
+ Node next = merge.peephole();
+ pub.unkeep();
+ pub = next;
+ }
+ priv.unkill();
+ ReturnNode ret = (ReturnNode)new ReturnNode(ctl,pub,self,rpc,fun).peephole();
+ fun.setRet(ret);
+ fun._approxUIDs = P._code.UID() - oldUID;
+ P._ref.addFun(P._code,fun);
+ return sig;
+ }
+
+ private CallEndNode rawCall(Node[] args, FunNode target) {
+ // Establish this known declaration-side edge immediately, but do not
+ // peephole or inline it while the surrounding factory is being built.
+ // IterPeeps therefore starts with the complete initializer caller set.
+ CallNode call = new CallNode(P.loc(),args).init();
+ CallEndNode cend = new CallEndNode(call,TypeRPC.constant(P._code.rpc(P._ref._cname))).init();
+ call.link(target);
+ return cend;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/ControlFlow.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/ControlFlow.java
new file mode 100644
index 00000000..caa73076
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/ControlFlow.java
@@ -0,0 +1,96 @@
+package com.seaofnodes.simple.parser;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.parser.Parser.ParseException;
+import com.seaofnodes.simple.type.*;
+
+import static com.seaofnodes.simple.util.Utils.TODO;
+
+/** Shared guarded branch/merge construction for if, ?:, && and ||. */
+final class ControlFlow {
+ private final Parser P;
+
+ ControlFlow(Parser P) { this.P = P; }
+
+ // Parse a conditional expression, merging results.
+ Node conditional( Node pred, String fside ) {
+ // IfNode takes current control and predicate
+ pred.keep();
+ Node ifNode = new IfNode(P.ctrl(), pred).peephole();
+ // Setup projection nodes
+ CFGNode ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
+ CFGNode ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole().keep();
+ // In if true branch, the ifT proj node becomes the ctrl
+ // But first clone the scope and set it as current
+ ScopeNode fScope = P._scope.dup(); // Duplicate current scope
+ fScope.ctrl(ifF); // The pending arm already starts at False
+ P._xScopes.push(fScope); // For graph visualization we need all scopes
+
+ // Parse the true side
+ P.ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection
+ P._scope.addGuards(ifT,pred,false); // Up-cast predicate
+ // Parse true-side flavor
+ Node lhs = switch( fside ) {
+ case "else" -> P.statements.parseStatement(); // if( pred ) stmts;
+ case ":" -> P.expressions.parseAssignment(); // pred ? asgn;
+ case "&&" -> P.expressions.parseLogical(); // pred && expr
+ case "||" -> pred; // pred || expr_is_ignored
+ default -> throw TODO();
+ };
+ lhs.keep();
+ P._scope.removeGuards(ifT);
+
+ // See if a one-sided def was made: "if(pred) int x = 1;" and throw.
+ // See if any forward-refs were made, and copy them to the other side:
+ // "pred ? n*fact(n-1) : 1"
+ fScope.balanceIf(P._scope);
+
+ ScopeNode tScope = P._scope;
+
+ // Parse the false side
+ P._scope = fScope; // Restore scope, then parse else block if any
+ ifF.unkeep(); // fScope already owns the false control
+ // Up-cast predicate, even if not else clause, because predicate can
+ // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;`
+ P._scope.addGuards(ifF,pred,true);
+ // Parse false-side flavor
+ boolean doRHS = false; // RHS is optional for if/else and trinary
+ Node rhs = switch( fside ) {
+ case "else" -> (doRHS = P.match(fside)) ? P.statements.parseStatement() : P.con(lhs._type.makeZero());
+ case ":" -> (doRHS = P.match(fside)) ? P.expressions.parseAssignment() : P.con(lhs._type.makeZero());
+ case "&&" -> P.con(lhs._type.makeZero()); // pred && expr_is_ignored
+ case "||" -> P.expressions.parseLogical(); // pred || expr
+ default -> throw TODO();
+ };
+ rhs.keep();
+ P._scope.removeGuards(ifF);
+ // The RHS of `||` is parsed on the false side, so retain its updated
+ // control, memory and locals just as an explicit else arm does.
+ if( doRHS || fside.equals("||") )
+ fScope = P._scope;
+ pred.unkeep();
+
+ // Check the trinary widening int/flt
+ if( !fside.equals("else") ) {
+ rhs = P.conversions.widenInt( rhs.unkeep(), lhs._type ).keep();
+ lhs = P.conversions.widenInt( lhs.unkeep(), rhs._type ).keep();
+ }
+
+ P._scope = tScope;
+ P._xScopes.pop(); // Discard pushed from graph display
+ // See if a one-sided def was made: "if(pred) int x = 1;" and throw.
+ // See if any forward-refs were made, and copy them to the other side:
+ // "pred ? n*fact(n-1) : 1"
+ tScope.balanceIf(fScope);
+
+ // Merge results
+ RegionNode r = P.ctrl(tScope.mergeScopes(fScope,P.loc()));
+ Node ret = P.peep(new PhiNode("",r,lhs.unkeep(),rhs.unkeep()));
+ // Immediately fail e.g. `arg ? 7 : ptr`
+ ParseException err;
+ if( !fside.equals("else") && (err = ret.err()) !=null ) throw err;
+ ret.keep();
+ r.peephole();
+ return ret.unkeep();
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Conversions.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Conversions.java
new file mode 100644
index 00000000..d50e2031
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Conversions.java
@@ -0,0 +1,72 @@
+package com.seaofnodes.simple.parser;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+
+/** Shared declaration, assignment, and return conversions. */
+final class Conversions {
+ private final Parser P;
+
+ Conversions(Parser P) { this.P = P; }
+
+ // Widen ints to floats; narrow wide int types.
+ // Early error if types do not match variable.
+ Node convertExpr( Node expr, Type t ) {
+ if( !expr._type.accessISA(t) )
+ throw P.error("Type "+expr._type.str()+" is not of declared type "+t.str());
+ if( t instanceof TypeMemPtr tmp && tmp.isFinal() )
+ expr = P.peep(new ReadOnlyNode(expr));
+ // A FunPtr carries the semantic edge to its function's Return. Do not
+ // hide that edge behind a Convert which can fold to a plain constant.
+ if( expr instanceof FunPtrNode && t instanceof TypeFunPtr )
+ return expr;
+ // Auto-widen array to i64 (cast ptr to raw int bits)
+ if( t == TypeInteger.BOT && expr._type instanceof TypeMemPtr tmp && tmp._obj.isAry() )
+ return P.peep(new AddNode(P.peep(new PtrToIntNode(expr)),Parser.off(tmp._obj,"[]")));
+ // Widen ints to floats, narrow wide types
+ return P.peep(new ConvertNode(t,expr));
+ }
+
+ // Inferred `var` declarations describe writable slots, not the exact
+ // value used to initialize them. Explicitly typed locals retain the
+ // user's nullability and numeric-width guarantees; `val` retains the
+ // initializer's precise type.
+ static Type mutableInferredType(Type t) {
+ return switch( t ) {
+ case TypeInteger ti -> TypeInteger.BOT;
+ case TypeFloat tf -> TypeFloat.F64;
+ case TypeMemPtr tmp -> tmp.makeVar();
+ case TypeFunPtr tfp -> tfp;
+ case TypeConAry<?> a -> a;
+ case TypePtr ptr -> TypePtr.PTR;
+ case TypeScalar ts -> TypeScalar.BOT;
+ default -> t;
+ };
+ }
+
+ Type constructorFieldType(Type t) {
+ if( t instanceof TypeMemPtr ptr && ptr._obj.isConAry() ) {
+ TypeStruct declared = (TypeStruct)Parser.TYPES.get(ptr._obj._name);
+ TypeStruct shape = P.types.typeAry(declared._fields[1]._t,ptr._obj._fields[1]._final);
+ return TypeMemPtr.make((byte)2,shape,false).withAccess(ptr._ro);
+ }
+ return mutableInferredType(t);
+ }
+
+ // Seed an inferred return merge with the declared numeric family instead
+ // of a transient constant/range. Pointer nullability remains part of the
+ // inferred function result and is combined by the later Phi.
+ static Type inferredReturnType(Type t) {
+ return switch( t ) {
+ case TypeInteger ti -> TypeInteger.BOT;
+ case TypeFloat tf -> TypeFloat.F64;
+ default -> t;
+ };
+ }
+
+ Node widenInt( Node expr, Type t ) {
+ return (expr._type instanceof TypeInteger || expr._type==Type.NIL) && t instanceof TypeFloat
+ ? P.peep(new ToFloatNode(expr)) : expr;
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Declarations.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Declarations.java
new file mode 100644
index 00000000..92e978b6
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Declarations.java
@@ -0,0 +1,164 @@
+package com.seaofnodes.simple.parser;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.ScopeNode.Kind;
+import com.seaofnodes.simple.type.*;
+
+/** Local/field bindings and native symbol declarations. */
+final class Declarations {
+ private final Parser P;
+
+ Declarations(Parser P) { this.P = P; }
+
+ /**
+ * Parse declaration or expression statement
+ * declStmt = type var['=' exprAsgn][, var['=' exprAsgn]]* ';' | exprAsgn ';'
+ * <p>
+ * exprAsgn = var '=' exprAsgn | expr
+ */
+ Node parseDeclarationStatement() {
+ int old = P.pos();
+ Type t = P.types.parseType();
+ if( P.peek('.') ) // Ambiguity static vars: "type.var", parse as expression
+ { P.pos(old); t = null; }
+ if( t == null )
+ return P.require(P.expressions.parseAssignment(),";");
+
+ // now parse var['=' asgnexpr] in a loop
+ Node n = parseDeclaration(t);
+ while( P.match(",") )
+ n = parseDeclaration(t);
+ return P.require(n,";");
+ }
+
+ // The binding modifier sits immediately before the name. It never
+ // changes permissions on the referenced object.
+ boolean bindingFinal(Type t) {
+ if( P.match("!") ) return false;
+ if( P.match("~") ) return true;
+ return t==Type.TOP || t instanceof TypeNil;
+ }
+
+ /** Parse final: [!|~]var['=' asgn]
+ */
+ private Node parseDeclaration(Type t) {
+ assert t!=null;
+ // Has var/val instead of a user-declared type
+ boolean inferType = t==Type.TOP || t==Type.BOTTOM;
+ boolean xfinal = bindingFinal(t);
+ Lexer loc = P.loc();
+ String name = P.requireId();
+ // Optional initializing expression follows
+ boolean fld_final = false; // Field is final, but not deeply final
+ boolean uninit = false; // Required non-null constructor field
+ Node expr;
+ if( P.match("=") ) {
+ if( isExternDecl() ) {
+ if( !(t instanceof TypeFunPtr) ) {
+ Node ptr = externData(name,t);
+ // The lexical binding is a fixed address; its contents are mutable.
+ if( !P._scope.define(name,t,true,ptr,loc) )
+ throw Parser.error("Redefining name '"+name+"'",loc);
+ P._scope.lookup(name)._extern = P._code.externAlias(name);
+ return P._code.ZERO;
+ }
+ expr = externDecl(name,t);
+ t = expr._type; // Upgrade declared type to the exact extern decl type
+ } else if( P.structs.isConstructorDecl(name) ) {
+ expr = P.functions.parseConstructor(name);
+ } else {
+ expr = P.expressions.parseAssignment();
+ }
+ // var/val, then type comes from expression
+ if( inferType ) {
+ if( expr._type==Type.NIL )
+ throw P.error("a not-null/non-zero expression");
+ t = expr._type;
+ if( !xfinal ) t = Conversions.mutableInferredType(t);
+ // Instance fields may change until the constructor finishes.
+ // Keep the declared family/access, not the default's constant
+ // value or string contents. Class constants remain static.
+ else if( P._scope.klast() instanceof Kind.Func fun && FunNode.isInstance(fun._name) && !(t instanceof TypeFunPtr) )
+ t = P.conversions.constructorFieldType(t);
+ }
+
+ // expr is a constant function
+ if( t instanceof TypeFunPtr decl && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
+ t = decl.makeFrom(tfp.fidx());
+ FunNode fun = P._code.link(tfp);
+ if( fun != null )
+ fun.setName(name); // Assign debug name to Simple function
+ }
+
+ } else {
+ // Since no initializer, not-final but might need an initializer
+
+ // Need an expression to infer the type.
+ // Also, if not-null then need an initializing expression.
+ // Allowed in a class def, because type/init will happen in the constructor.
+ if( (inferType || (t instanceof TypeNil tn && !tn.nullable() )) && !P._scope.inConstructor() )
+ throw P.errorSyntax("=expression");
+ // Initial value for uninitialized struct fields.
+ expr = switch( t ) {
+ // Required pointers retain their declared shape, but include
+ // null until every constructor path assigns them.
+ case TypeNil tn -> tn.nullable() ? P.con(Type.NIL) : P.con(t.meet(Type.NIL));
+ case TypeInteger ti -> xfinal ? P.con(Type.BOTTOM) : P._code.ZERO;
+ case TypeFloat tf -> P.con(xfinal ? Type.BOTTOM : TypeFloat.FZERO);
+ // Bottom signals type inference: they must be initialized in
+ // the constructor and that's when we'll discover the type.
+ case Type tt -> { assert tt==Type.BOTTOM; yield P.con(tt); }
+ };
+ // Every non-null pointer without a default is constructor-required.
+ // Without `!` it is also shallow-final; mutability and definite
+ // initialization are independent properties.
+ if( (xfinal && !(t instanceof TypeNil)) || (t instanceof TypeNil tn && !tn.nullable()) ) {
+ uninit = true;
+ if( xfinal ) fld_final = true;
+ }
+ }
+
+ // Lift expression, based on type
+ // Do not let an authoritative Convert hide the nullable
+ // not-yet-initialized state from constructor-local type flow.
+ Node lift = uninit ? expr : P.conversions.convertExpr(expr, t);
+
+ // Define a new name
+ if( !P._scope.define(name,t,xfinal || fld_final,lift, loc) )
+ throw P.error("Redefining name '" + name + "'", loc);
+ P._scope.lookup(name)._uninit = uninit;
+ return lift;
+ }
+
+ // Just after parsing "type foo = " can parse `"C"`
+ private boolean isExternDecl( ) {
+ int old = P.pos();
+ String s = P._lexer.parseString();
+ if( "C".equals(s) ) return true;
+ P.pos(old);
+ return false;
+ }
+ // External linked constant
+ private ConstantNode externDecl( String ex, Type t ) {
+ if( t instanceof TypeFunPtr tfp ) { // Generic TFP from type parse
+ int fidx = P._code.fidx(P._ref._cname); // Get a FIDX
+ tfp = tfp.makeFrom(fidx); // Become a constant
+ P._code.externFunc(fidx,ex); // Map fidx to extern name
+ t = tfp;
+ }
+ return (ExternNode)(new ExternNode(t,ex).peephole());
+ }
+
+ // A lexical C binding has a singleton namespace with one external field.
+ // The namespace occupies no storage; its field offset supplies the address.
+ private Node externData(String name, Type t) {
+ if( !(t instanceof TypeInteger) && !(t instanceof TypeFloat) )
+ throw P.error("C data binding requires an integer or float type");
+ String cname = ("C:"+name).intern();
+ TypeStruct ts = TypeStruct.make(cname,false,Field.make(name,t,P._code.externAlias(name),false,true));
+ Type old = Parser.TYPES.putIfAbsent(cname,ts);
+ if( old!=null && old!=ts )
+ throw P.error("Conflicting types for C data '"+name+"'");
+ return P.con(TypeMemPtr.make((byte)2,ts,true));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Expressions.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Expressions.java
new file mode 100644
index 00000000..c3971c63
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Expressions.java
@@ -0,0 +1,394 @@
+package com.seaofnodes.simple.parser;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.parser.Parser.ParseException;
+import com.seaofnodes.simple.type.*;
+
+import static com.seaofnodes.simple.util.Utils.TODO;
+
+/** Recursive-descent expression precedence, from assignment down to primaries. */
+final class Expressions {
+ private final Parser P;
+
+ Expressions(Parser P) { this.P = P; }
+
+ /** Parse: [name '='] expr
+ */
+ Node parseAssignment() {
+ int old = P.pos();
+ String name = P._lexer.matchId();
+ // Just a plain expression, no assignment.
+ // Distinguish `var==expr` from `var=expr`
+ if( name==null || Parser.KEYWORDS.contains(name) || !P.matchOpx('=','=') )
+ { P.pos(old); return parseExpression(); }
+
+ // Find variable to update
+ Var def = P._scope.lookup(name);
+ if( def==null )
+ throw P.error("Undefined name '" + name + "'");
+
+ if( def._extern!=0 ) // Parse the C binding as a memory lvalue
+ { P.pos(old); return parseExpression(); }
+
+ P.references.checkAssignment(def);
+ return P.references.assign(def,parseAssignment());
+ }
+
+ /**
+ * Parse an expression of the form:
+ *
+ * <pre>
+ * expression : logicalExpression ('?' assignment (':' assignment)?)?
+ * </pre>
+ * @return an expression {@link Node}, never {@code null}
+ */
+ private Node parseExpression() {
+ Node expr = parseLogical();
+ return P.match("?") ? P.controlflow.conditional(expr,":") : expr;
+ }
+
+ /**
+ * Parse a logical expression
+ *
+ * <pre>
+ * logicalExpression : bitwiseExpression (('&&' | '||') logicalExpression)?
+ * </pre>
+ * @return a bitwise expression {@link Node}, never {@code null}
+ */
+
+ Node parseLogical() {
+ Node lhs = parseBitwise();
+ while (true) {
+ if( P.match("&&") ) lhs = P.controlflow.conditional(lhs, "&&");
+ else if( P.match("||") ) lhs = P.controlflow.conditional(lhs, "||");
+ else break;
+ }
+ return lhs;
+ }
+
+ private Node parseBitwise() {
+ Node lhs = parseEquality();
+ while( true ) {
+ if( P.matchOp('&') ) lhs = new AndNode(P.loc(),lhs,null);
+ else if( P.matchOp('|') ) lhs = new OrNode(P.loc(),lhs,null);
+ else if( P.match ("^") ) lhs = new XorNode(P.loc(),lhs,null);
+ else break;
+ lhs.setDef(2,parseEquality());
+ lhs = P.peep(lhs);
+ }
+ return lhs;
+ }
+
+ /**
+ * Parse an eq/ne expression
+ */
+ private Node parseEquality() {
+ Node lhs = parseComparison();
+ while( true ) {
+ boolean eq = false;
+ if( P.match("==") ) eq = true;
+ else if( !P.match("!=") ) break;
+ lhs.keep();
+ Node rhs = parseComparison();
+ lhs = P.peep(new BoolNode.EQ(lhs.unkeep(),rhs));
+ if( !eq ) lhs = P.peep(new NotNode(lhs));
+ }
+ return lhs;
+ }
+
+ /**
+ * Parse an expression of the form:
+ *
+ * <pre>
+ * expr : shiftExpr < shiftExpr <= shiftExpr...
+ * expr : shiftExpr > shiftExpr >= shiftExpr...
+ * </pre>
+ * @return an comparator expression {@link Node}, never {@code null}
+ */
+ private Node parseComparison() {
+ Node lhs = parseShift();
+ int dir = parseCompDir();
+ // No compare
+ if( dir==0 ) return lhs;
+ lhs.keep();
+
+ // Compare
+ Node rhs = parseShift().keep();
+ Node cmp = makeCompBool(dir,lhs.unkeep(),rhs).keep(); // Convert to a bool
+
+ // Stacked compares?
+ int dir0 = parseCompDir();
+ if( dir0 == 0 ) {
+ rhs.unkill();
+ return cmp.unkeep();
+ }
+
+ // Keep the middle operand separately from the boolean result.
+ // The next test compares adjacent operands, not that boolean.
+ Node ifNode = new IfNode(P.ctrl(), cmp.unkeep()).peephole();
+ Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
+ Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole();
+ // False side does nothing but capture memory & side-effects
+ ScopeNode fail = P._scope.dup();
+ fail.ctrl(ifF);
+
+ // Loop over stacked compares. Each loop moves the old RHS to become
+ // the new LHS, so e.g. (a < b < c < d) compares (LHS:a < RHS:b) then
+ // compares (LHS:b < RHS:c) then (LHS:c < RHS:d), etc.
+ while( dir0 != 0 ) {
+ if( Math.abs(dir) != Math.abs(dir0) )
+ throw P.error("Mixing relational directions in a chained relational test");
+ // True side parses next arm of test
+ P.ctrl(ifT.unkeep());
+ lhs = rhs; // Reuse the previous RHS exactly once
+ rhs = parseShift().keep();
+ cmp = makeCompBool(dir0,lhs.unkeep(),rhs); // Convert to a bool
+ ifNode = new IfNode(P.ctrl(), cmp).peephole();
+ ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
+ ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole();
+ // Merge result into the fail case
+ P.ctrl(ifF);
+ fail.mergeScopes(P._scope.dup(), P.loc()).peephole();
+ // Check next stacked compare
+ dir0 = parseCompDir();
+ }
+ rhs.unkill();
+ P.ctrl(ifT.unkeep());
+ RegionNode r = fail.mergeScopes(P._scope, P.loc()).init();
+ P._scope = fail;
+ return new PhiNode("", r,P.con(0),P.con(1)).peephole();
+ }
+
+ private int parseCompDir() {
+ if( P.match("<=") ) return -1; // Negative for "equals-or-less"
+ if( P.match(">=") ) return -2; // 1 for <, 2 for >
+ if( P.match("<" ) ) return 1; // Positive for just "less"
+ if( P.match(">" ) ) return 2;
+ return 0;
+ }
+
+ private Node makeCompBool( int dir, Node lhs, Node rhs ) {
+ if( Math.abs(dir)==2 )
+ { Node tmp = lhs; lhs = rhs; rhs = tmp; }
+ Node cmp = dir < 0 ? new BoolNode.LE(lhs,rhs) : new BoolNode.LT(lhs,rhs);
+ return P.peep(cmp);
+ }
+
+ /**
+ * Parse an additive expression
+ *
+ * <pre>
+ * shiftExpr : additiveExpr (('<<' | '>>' | '>>>') additiveExpr)*
+ * </pre>
+ * @return a shift expression {@link Node}, never {@code null}
+ */
+ private Node parseShift() {
+ Node lhs = parseAddition();
+ while( true ) {
+ if( P.match("<<") ) lhs = new ShlNode(P.loc(),lhs,null);
+ else if( P.match(">>>")) lhs = new ShrNode(P.loc(),lhs,null);
+ else if( P.match(">>") ) lhs = new SarNode(P.loc(),lhs,null);
+ else break;
+ lhs.setDef(2,parseAddition());
+ ParseException err;
+ if( (err = lhs.err()) != null )
+ throw err;
+ lhs = P.peep(lhs);
+ }
+ return lhs;
+ }
+
+ /**
+ * Parse an additive expression
+ *
+ * <pre>
+ * additiveExpr : multiplicativeExpr (('+' | '-') multiplicativeExpr)*
+ * </pre>
+ * @return an add expression {@link Node}, never {@code null}
+ */
+ private Node parseAddition() {
+ Node lhs = parseMultiplication();
+ while( true ) {
+ if( P.match("+") ) lhs = new AddNode(lhs,null);
+ else if( P.match("-") ) lhs = new SubNode(lhs,null);
+ else break;
+ lhs.setDef(2,parseMultiplication());
+ lhs = P.peep(lhs);
+ }
+ return lhs;
+ }
+
+ /**
+ * Parse an multiplicativeExpr expression
+ *
+ * <pre>
+ * multiplicativeExpr : unaryExpr (('*' | '/') unaryExpr)*
+ * </pre>
+ * @return a multiply expression {@link Node}, never {@code null}
+ */
+ private Node parseMultiplication() {
+ var lhs = parseUnary();
+ while( true ) {
+ if( P.match("*") ) lhs = new MulNode(lhs,null);
+ else if( P.match("/") ) lhs = new DivNode(lhs,null);
+ else break;
+ lhs.setDef(2,parseUnary());
+ lhs = P.peep(lhs);
+ }
+ return lhs;
+ }
+
+ /**
+ * Parse a unary minus expression.
+ *
+ * <pre>
+ * unaryExpr : ('-') unaryExpr | '!') unaryExpr | postfixExpr | primaryExpr | '--' Id | '++' Id
+ * </pre>
+ * @return a unary expression {@link Node}, never {@code null}
+ */
+ private Node parseUnary() {
+ // Pre-dec/pre-inc
+ int old = P.pos();
+ if( P.match("--") || P.match("++") ) {
+ int delta = P._lexer.peek(-1)=='+' ? 1 : -1; // Inc vs Dec
+ int start = P.pos();
+ String name = P._lexer.matchId();
+ Var var = name==null ? null : P._scope.lookup(name);
+ if( var!=null && !(var.type() instanceof TypeMemPtr) ) {
+ if( var._extern==0 && var._final && !P._scope.canInit(var) )
+ throw P.error("Cannot reassign final '"+var._name+"'");
+ // Resolve fields just as a read or postfix update does. Updating
+ // their declaration's local slot would silently lose the Store.
+ P.pos(start);
+ return parsePrimary(delta);
+ }
+ // Reset, try again
+ P.pos(old);
+ }
+ if (P.match("-")) return P.peep(new MinusNode(parseUnary()));
+ if (P.match("!")) return P.peep(new NotNode(parseUnary()));
+ // Else be a primary expression
+ return parsePrimary();
+ }
+
+ /**
+ * Parse a primary expression:
+ *
+ * <pre>
+ * primaryExpr : integerLiteral | "string" | 'char' | true | false | null |
+ * new Type | '(' expression ')' | Id['++','--'] |
+ * type['.' Id]*
+ * </pre>
+ * @return a primary {@link Node}, never {@code null}
+ */
+ private Node parsePrimary() { return parsePrimary(0); }
+
+ // pre is +/-1 for a prefix update of a known name, zero otherwise.
+ private Node parsePrimary(int pre) {
+ if( P._lexer.isNumber(P._lexer.peek()) ) return parseLiteral();
+ if( P._lexer.peek('"') ) return parsePostfix(P.allocations.newString(P._lexer.parseString()));
+ if( P.matchx("true" ) ) return P.con(1);
+ if( P.matchx("false") ) return P._code.ZERO;
+ if( P.matchx("null" ) ) return P.con(Type.NIL);
+ if( P.match ("'" ) ) return parseChar();
+ if( P.match ("(" ) ) return parsePostfix(P.require(parseAssignment(), ")"));
+ if( P.matchx("new" ) ) return parsePostfix(P.allocations.parseNew());
+ if( P.match ("{" ) ) return parsePostfix(P.require(P.functions.parseFunction(),"}"));
+
+ int pos = P.pos();
+ // Expect an identifier now
+ String id = P._lexer.matchId();
+ if( id == null || Parser.KEYWORDS.contains(id) )
+ throw P.errorSyntax("an identifier or expression");
+ int pos2 = P.pos();
+ return P.references.parseName(id,pre,pos,pos2);
+ }
+
+ // Check for assign-update, "x += e0".
+ // Returns "x + e0"; the caller assigns value.
+ // if ch is postfix, then lhs is kept and caller will unkeep and return it.
+ Node opAssign( char ch, Node lhs, Type t ) {
+ // RHS of the update.
+ lhs.keep(); // Alive across parseAssignment
+ Node rhs =
+ (byte)ch == 1 ? P.con( 1) : // var++
+ (byte)ch == -1 ? P.con(-1) : // var--
+ parseAssignment(); // var op= rhs
+ if( !Parser.postfix(ch) ) lhs.unkeep(); // Allow to die in next peep
+ // 4 cases:
+ // int + int ==>> narrow int
+ // int + flt ==>> error, caller must fail assigning flt into int
+ // flt + int ==>> use float op, wrap toFloat()
+ // flt + flt ==>> use float op
+ Node op = switch(ch) {
+ case 1, (char)-1,
+ '+' -> new AddNode(lhs,rhs);
+ case '-' -> new SubNode(lhs,rhs);
+ case '*' -> new MulNode(lhs,rhs);
+ case '/' -> new DivNode(lhs,rhs);
+ case '|' -> new OrNode(null,lhs,rhs);
+ case '&' -> new AndNode(null,lhs,rhs);
+ case '^' -> new XorNode(P.loc(),lhs,rhs);
+ case Lexer.SHL -> new ShlNode(P.loc(),lhs,rhs);
+ case Lexer.SAR -> new SarNode(P.loc(),lhs,rhs);
+ case Lexer.SHR -> new ShrNode(P.loc(),lhs,rhs);
+ default -> throw TODO();
+ };
+ // Convert to float ops, or narrow int types; error if not declared type.
+ // Also, if postfix LHS is still keep()
+ return P.conversions.convertExpr(P.peep(op),t);
+ }
+
+ /**
+ * Parse postfix expression; this can be a field expression, an array
+ * lookup or a postfix operator like '#'
+ *
+ * <pre>
+ * expr ('.' FIELD)* [ = expr ] // Field reference
+ * expr '#' // Postfix unary read operator
+ * expr ['++' | '--' ] // Postfix unary write operator
+ * expr ('[' expr ']')* = [ = expr ] // Array reference
+ * expr '(' [args,]* ')' // Function call
+ * </pre>
+ */
+ Node parsePostfixMethod(Node expr, Node self) {
+ // TODO: This is too broad because the parser lacks strong types here.
+ // Some normal functions can end up with a "self" argument.
+
+ // Self method call? Self is a TMP-not-CLZ, then assume and pass self.
+ if( P.match("(") ) {
+ expr = parsePostfix(P.require(P.functions.functionCall(expr,self),")"));
+ } else if( expr != self )
+ self.isKill(); // Not a method call, no need for self
+ return parsePostfix(expr);
+ }
+ Node parsePostfix(Node expr) {
+ P._lexer.skipWhiteSpace();
+ Lexer site = P.loc();
+ String name;
+ if( P.match(".") ) name = P.requireId();
+ else if( P.match("#") ) name = "#";
+ else if( P.match("[") ) name = "[]";
+ // Can get here without an explicit receiver: "f()" or
+ // "self.method()(next)(next)(next)". Pass the current function self.
+ else if( P.match("(") ) return parsePostfix(P.require(P.functions.functionCall(expr,P.references.defaultSelf()),")"));
+ else return expr; // No postfix
+
+ return P.references.parseField(expr,name,0,site);
+ }
+
+ /**
+ * Parse integer literal
+ *
+ * <pre>
+ * integerLiteral: [1-9][0-9]* | [0]
+ * floatLiteral: [digits].[digits]?[e [digits]]?[f]?
+ * </pre>
+ */
+ private ConstantNode parseLiteral() { return P.con(P._lexer.parseNumber()); }
+
+ // Already parsed "'"
+ Node parseChar() {
+ return P.require(P.con(TypeInteger.constant(P._lexer.parseChar())),"'");
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Functions.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Functions.java
new file mode 100644
index 00000000..d848b0e3
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Functions.java
@@ -0,0 +1,346 @@
+package com.seaofnodes.simple.parser;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.ScopeNode.Kind;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Ary;
+import java.util.BitSet;
+
+import static com.seaofnodes.simple.util.Utils.TODO;
+
+/** Function expressions, parameters, calls, and function entry/exit graphs. */
+final class Functions {
+ private final Parser P;
+ // Merge all function exits here.
+ ScopeNode _returnScope;
+
+ Functions(Parser P) { this.P = P; }
+
+ /**
+ * Parse a function body; the caller will parse the surrounding "{}"
+ *
+ * <pre>
+ * { [type arg,]* -> expr }
+ * { -> statements } // The no-argument function
+ * </pre>
+ */
+ Node parseFunction() {
+ Ary<Type> ts = new Ary<>(Type.class);
+ Ary<String> ids = new Ary<>(String.class);
+ BitSet finals = new BitSet();
+ Type self = P.types.implicitSelfType();
+ ts.push(self);
+ ids.push(self == TypePtr.PTR ? "#self" : "self");
+
+ // Parse other arguments
+ P._lexer.skipWhiteSpace();
+ Lexer loc = P.loc(); // First argument location
+ parameters(ts,ids,finals);
+ // Make a concrete function type, with a fidx
+ TypeFunPtr tfp = TypeFunPtr.make1((byte)2,true,ts.asAry(),Type.BOTTOM,P._code.fidx(P._ref._cname));
+ ReturnNode ret = parseFunctionBody(tfp,loc,finals,ids.asAry());
+ return new FunPtrNode(tfp,P._code._start,ret).peephole();
+ }
+
+ Node parseConstructor(String name) {
+ P.require("{");
+ Ary<Type> ts = new Ary<>(Type.class);
+ Ary<String> ids = new Ary<>(String.class);
+ BitSet finals = new BitSet();
+ TypeStruct self = P._scope.constructorSelf();
+ assert self != null;
+ ts.push(TypeMemPtr.make(Structs.constructorRecv(self)));
+ ids.push("self");
+ // Explicit constructors use the same hidden allocation arguments as
+ // <init>: public memory is the call memory edge (index 1), followed by
+ // self (index 2) and the private, unescaped object memory (index 3).
+ ts.push(TypeMem.makePrivate(self));
+ ids.push("#selfMem");
+
+ P._lexer.skipWhiteSpace();
+ Lexer loc = P.loc();
+ parameters(ts,ids,finals);
+ TypeFunPtr tfp = TypeFunPtr.make1((byte)2,true,ts.asAry(),TypeMem.makePrivate(self),P._code.fidx(P._ref._cname));
+ Node[] preParse = P._scope.save();
+ ReturnNode ret = parseBody((self._name+"."+name).intern(),tfp,loc,finals,ids.asAry());
+ P._scope.restore(preParse);
+ P.require("}");
+ return new FunPtrNode(tfp,P._code._start,ret).peephole();
+ }
+
+ // parameterList followed by the function arrow; callers seed hidden parameters.
+ private void parameters(Ary<Type> ts, Ary<String> ids, BitSet finals) {
+ while( true ) {
+ Type t = P.types.parseType(); // Arg type
+ if( t==null ) break;
+ finals.set(ids.size(),P.declarations.bindingFinal(t));
+ String id = P.requireId();
+ ts .push(t ); // Push type/arg pairs
+ ids.push(id);
+ P.match(",");
+ }
+ P.require("->");
+ }
+
+ /**
+ * Parse function call arguments; caller will parse the surrounding "()"
+ * <pre>
+ * ( arg* )
+ * </pre>
+ */
+ Node functionCall(Node fcn, Node self) {
+ Lexer site = P.loc();
+ site._position--; // Opening '('; arguments can contain nested calls.
+ if( fcn._type == Type.NIL )
+ throw P.error("Calling a null function pointer");
+ fcn.keep(); // Keep while parsing args
+
+ Ary<Node> args = new Ary<>( Node.class );
+ args.push(null); // Space for ctrl,mem
+ args.push(null);
+ if( self != null ) // Method call has a self
+ args.push(self.keep());
+ arguments(args);
+ // Control & memory after parsing args
+ args.set(0,P.ctrl().keep());
+ args.set(1,P.mem().keep());
+ args.push(fcn); // Function pointer
+ // Unkeep them all
+ for( Node arg : args )
+ arg.unkeep();
+ return functionCall(args,site);
+ }
+
+ Node functionCall( Ary<Node> args ) {
+ return functionCall(args,P.loc());
+ }
+
+ Node functionCall( Ary<Node> args, Lexer site ) {
+ // Dead into the call? Skip all the node gen
+ if( P.ctrl()._type == Type.XCONTROL ) {
+ for( Node arg : args )
+ if( arg.isUnused() )
+ arg.kill();
+ return P.con(Type.TOP);
+ }
+
+ // Into the call
+ CallNode call = (CallNode)new CallNode(site, args.asAry()).peephole();
+ // Post-call setup
+ CallEndNode cend = (CallEndNode)new CallEndNode(call,TypeRPC.constant(P._code.rpc(P._ref._cname))).peephole();
+ call.peephole(); // Rerun peeps after CallEnd, allows early inlining
+ cend = (CallEndNode)cend.keep().peephole(); // TODO: Might inline
+ // Control from CallEnd
+ P.ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole());
+ // Memory from CallEnd
+ Node mem = new ProjNode(cend,1,ScopeNode.MEM0).peephole();
+ P.mem(mem);
+ // Call result
+ return new ProjNode(cend.unkeep(),2,"#2").peephole();
+ }
+
+ // argumentList; returns kept values and leaves the closing parenthesis to the caller.
+ void arguments(Ary<Node> args) {
+ while( !P.peek(')') ) {
+ Node arg = P.expressions.parseAssignment();
+ if( arg==null ) break;
+ args.push(arg.keep());
+ if( !P.match(",") ) break;
+ }
+ }
+
+ /**
+ * Parses a function body, assuming the header is parsed.
+ */
+
+ private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, BitSet finals, String[] ids) {
+ // Record & restore the existing scope vars. The function parse might
+ // update fields, so when the parse is done reset back to the pre-parse
+ // state.
+ Node[] preParse = P._scope.save();
+
+ // Parse function body normally
+ ReturnNode ret = parseBody(null, sig,loc,finals,ids);
+
+ // Reset all fields to pre-parse days
+ P._scope.restore(preParse);
+ return ret;
+ }
+
+ // Parse a function body
+
+ // Stack the {break,continue,return} scopes and a new lexical scope.
+ // Build a FunNode and parameters, extending scope by parm names.
+ // Parse the function body
+ // - while( not end-of-function )
+ // - - parseStatement
+ // - - If isInit, also incrementally extend the self-type.
+ // If isInit
+ // - upgrade and close the self-type
+ // - Gather fields and store values
+ // - <init> returns self, not last
+ // Unstack the {break,continue,return} scopes.
+
+ ReturnNode parseBody( String funName, TypeFunPtr sig, Lexer loc, String[] ids) {
+ return parseBody(funName, sig, loc, null, ids);
+ }
+
+ ReturnNode parseBody( String funName, TypeFunPtr sig, Lexer loc, BitSet finals, String[] ids) {
+ Structs.Constructor outerCtor = P.structs._pendingCtor;
+ P.structs._pendingCtor = null;
+ boolean isInit = FunNode.isInit(funName);
+ boolean isCtor = isInit || P.structs.isExplicitConstructorName(funName);
+ String typeName = isCtor ? ((TypeMemPtr)sig._sig[0])._obj._name : null;
+
+ // Stack parser state on the local Java stack, and unstack it later
+ P._scope.push(new Kind.Func(funName,isCtor && !funName.endsWith(".<clinit>")));
+ ScopeNode breakScope = P.statements._breakScope; P.statements._breakScope = null;
+ ScopeNode continueScope = P.statements._continueScope; P.statements._continueScope = null;
+ ScopeNode returnScope = _returnScope; _returnScope = null;
+
+ int oldUID = P._code.UID(); // Used to approximate function size
+ FunNode fun = enterFunction(funName,sig,loc,finals,ids);
+ Node rpc = fun.parm(0);
+ if( isCtor && !isInit ) P.structs.constructorDefaults(typeName,fun,loc);
+ if( fun.isClz() ) P.structs.beginClassInit(fun,loc);
+
+ // Parse the function body.
+ Node last = P._code.ZERO; // Last statement as the default result
+
+ int nvar = 0;
+ while (!P.peek('}') && !P._lexer.isEOF()) {
+ // Parse a statement; record last statement as default return
+ last = P._code.add(P.statements.parseStatement());
+ // For <init> and <clinit>, local vars are really struct fields and
+ // get exposed as discovered. This allows late local functions to
+ // use early declared fields.
+ if( isInit )
+ nvar = P.structs.updateSelfAsFieldsDiscovered( typeName, nvar );
+ }
+
+ if( isInit ) P.structs.finishConstructor();
+ P.structs._pendingCtor = outerCtor;
+
+ ParmNode self = fun.parm(2);
+ // Last expression is the return
+ if( P.ctrl()._type==Type.CONTROL ) {
+ // Constructors get special return types
+ if( fun.isClz() ) {
+ assert last!=null;
+ } else if( isCtor ) {
+ last = fun.parm(3);
+ }
+ addReturn(last);
+ // exit path is now dead (nothing live after a "return")
+ }
+ if( isCtor )
+ P.structs.upgradeSelfTypeAndStoreFields( typeName, fun.isClz(), self, fun.parm(3), isInit );
+
+ // Build a return from the _returnScope.
+ // Can be no returns for never-exit functions
+ Node rctl = _returnScope==null ? P._code.XCTRL : _returnScope.ctrl().peephole();
+ Node rmem = rctl==P._code.XCTRL ? P.con(TypeMem.TOP) : _returnScope.mem();
+ Node expr = rctl==P._code.XCTRL ? P.con(Type.TOP) : _returnScope._inputs.last().peephole();
+ ReturnNode ret = (ReturnNode)P.peep(new ReturnNode(rctl, rmem, expr, rpc, fun));
+ fun.setRet(ret);
+ // Approximate function size, for inlining heuristics
+ fun._approxUIDs = P._code.UID() - oldUID;
+ for( Node cend : ret.outs() )
+ if( cend instanceof CallEndNode )
+ throw TODO(); //_code.add(cend); // have call ends ALREADY? just barely made the Return
+
+ // Export (and keep-alive) functions until prove dead
+ P._ref.addFun(P._code,fun);
+
+ // Unstack parser state
+ if( _returnScope != null ) _returnScope.kill();
+ _returnScope = returnScope;
+ P.statements._continueScope = continueScope;
+ P.statements._breakScope = breakScope;
+ P._scope.pop();
+
+ return ret;
+ }
+
+ private FunNode enterFunction(String funName, TypeFunPtr sig, Lexer loc, BitSet finals, String[] ids) {
+ FunNode fun = (FunNode)P.peep(new FunNode(P.loc(),sig, funName, P._ref, null, P._ref._start));
+ // Once the function header is available, install in linker table -
+ // allowing recursive functions. Linker matches on declared args and
+ // exact fidx, and ignores the return (because the fidx will only match
+ // the exact single function).
+ P._code.link(fun);
+
+ new ParmNode("$rpc",0,TypeRPC.BOT,fun,ConstantNode.seed(TypeRPC.BOT).peephole()).peephole();
+
+ // Pre-call the function from Start, with worse-case arguments. This
+ // represents all the future, yet-to-be-parsed functions calls and
+ // external calls.
+ P._scope.ctrl(fun); // Scope control from function
+ // Private mem alias tracking per function
+ Node defaultMem = new ProjNode(P._ref._start,1,ScopeNode.MEM0).peephole();
+ Node privMem = new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,defaultMem).peephole();
+ P.mem(privMem);
+ // All args, "as-if" called externally
+ for( int i = 0; i<ids.length; i++ ) {
+ Type t = sig.arg(i);
+ // Args take a default input if called from Start/external-world
+ P._scope.define(ids[i], t, (i==0 && ids[0]=="self") || (finals!=null && finals.get(i)), new ParmNode(ids[i],i+2,t,fun,ConstantNode.seed(t).peephole()).peephole(), loc);
+ }
+
+ return fun;
+ }
+ Node addReturn(Node expr) {
+ expr.keep();
+ // Force the pending lazy bulk-memory Phi.
+ P.mem(); // Force a pending lazy bulk-memory Phi
+ // For <init> and <clinit> - all fields in scope will be live-on-exit
+ // and stored into `self` and need to have lazy-phi's inserted.
+ int lexN = P._scope.enclosingFuncOrDecl();
+ Kind.Func k = (Kind.Func)P._scope._kinds.at(lexN);
+ if( FunNode.isInit(k._name) ) {
+ int nestedLexSize = lexN+1 < P._scope.depth() ? P._scope._kinds.at(lexN+1)._lexSize : P._scope.nIns();
+ for( int i = k._lexSize; i<nestedLexSize; i++ )
+ P._scope.update(P._scope.var(i),null);
+ if( _returnScope != null ) {
+ Var vexpr = _returnScope._vars.pop();
+ Node oldX = _returnScope.removeLast();
+ int rlen = _returnScope.nIns();
+ while( rlen < nestedLexSize ) {
+ _returnScope._vars.add(P._scope.var(rlen));
+ _returnScope.addDef(P.con(P._scope.var(rlen++).type().makeZero()));
+ }
+ _returnScope._vars.add(vexpr);
+ _returnScope.addDef(oldX);
+ }
+ } else if( P.structs.isExplicitConstructorName(k._name) ) {
+ // Field bindings live in the enclosing declaration, not among the
+ // constructor's arguments/locals. Force lazy Phis on every exit.
+ int first = P._scope._kinds.at(lexN-1)._lexSize+2;
+ for( int i = first; i<k._lexSize; i++ )
+ P._scope.update(P._scope.var(i),null);
+ }
+
+ // No prior merge point? Just clone and hang on to it
+ if( _returnScope == null ) {
+ _returnScope = P._scope.dup();
+ while( lexN+1 < _returnScope.depth() )
+ _returnScope._pop(); // Pop a nested block scope until we hit the function scope
+ _returnScope.define("$expr", Conversions.inferredReturnType(expr._type), true, expr, null);
+
+ } else {
+ // For <init> and <clinit> - ALL FIELDS IN LAST SCOPE are live
+ // and need to have lazy-phi's inserted.
+
+ // And fields might not match.... will need matching Var/define with default values
+ int rlen = _returnScope.nIns()-1;
+ RegionNode r = P.ctrl(new RegionNode(null, null,_returnScope.ctrl(), P._scope.ctrl()).init().keep());
+ _returnScope._merge(P._scope, r, rlen);
+ Node oldExpr = _returnScope.in(rlen);
+ _returnScope.setDef(rlen,new PhiNode("$expr", r, oldExpr, expr).peephole());
+ P._code.add(r);
+ _returnScope.ctrl(r.unkeep());
+ }
+ P.ctrl(P._code.XCTRL); // Kill control
+ return expr.unkeep();
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Lexer.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Lexer.java
new file mode 100644
index 00000000..f2857c9f
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Lexer.java
@@ -0,0 +1,384 @@
+package com.seaofnodes.simple.parser;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.CompUnit;
+import com.seaofnodes.simple.codegen.Serialize;
+import com.seaofnodes.simple.parser.Parser.ParseException;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+import java.util.HashMap;
+
+// Lexer provides low level access to the raw file bytes, peeks and matches
+// short strings, parses numbers, skips comments and whitespace, tracks
+// line numbers, allows the parse position to be saved and restored, and
+// serves as a location indicator for errors.
+
+public class Lexer {
+
+ // Input buffer; an array of text bytes read from a file or a string
+ private final byte[] _input;
+ // Original source, retained across inlining.
+ public final CompUnit _cu;
+ // Tracks current position in input buffer
+ int _position = 0;
+
+ //
+ private int _line_number = 1;
+ // Start of current line
+ private int _line_start = 0;
+
+ /** True when this location precedes {@code that} in the same source. */
+ public boolean before(Lexer that) {
+ return that != null && (_cu!=null ? _cu==that._cu : _input==that._input) && _position < that._position;
+ }
+
+ public String position() { return _line_number+":"+(_position-_line_start+1); }
+ public int pos() { return _position; }
+
+ /**
+ * Record the source text for lexing
+ */
+ public Lexer(String source) {
+ this(source.getBytes());
+ }
+
+ /**
+ * Direct from disk file source
+ */
+ public Lexer(byte[] buf) {
+ _input = buf;
+ _cu = null;
+ }
+
+ Lexer(CompUnit cu) {
+ _input = cu._src.getBytes();
+ _cu = cu;
+ }
+
+ // Imported location: source text is only needed when reporting an error.
+ Lexer(CompUnit cu, int pos) {
+ _input = null;
+ _cu = cu;
+ _position = pos;
+ }
+
+ public static void gather(Lexer loc, HashMap<String,Integer> strs) {
+ if( loc==null || loc._cu==null ) return;
+ Serialize.gather(strs,loc._cu._cname);
+ String file = loc._cu.sourceFile();
+ if( file!=null ) Serialize.gather(strs,file);
+ }
+
+ public static void packed(Lexer loc, BAOS out, HashMap<String,Integer> strs) {
+ if( loc==null || loc._cu==null ) { out.packed4(0); return; }
+ out.packed4(loc._position+1);
+ out.packed4(strs.get(loc._cu._cname));
+ String file = loc._cu.sourceFile();
+ out.packed4(file==null ? 0 : strs.get(file));
+ }
+
+ public static Lexer unpack(BAOS in, String[] strs) {
+ int pos = in.packed4();
+ if( pos==0 ) return null;
+ String owner = strs[in.packed4()];
+ String file = strs[in.packed4()];
+ CompUnit cu = CodeGen.CODE._compunits.get(owner.replace('.','/'));
+ assert cu!=null : owner;
+ if( !file.isEmpty() ) cu._sourceFile = file;
+ return new Lexer(cu,pos-1);
+ }
+
+ /**
+ * Copy a lexer from a lexer
+ */
+ Lexer(Lexer l) {
+ _input = l._input;
+ _cu = l._cu;
+ _position = l._position;
+ _line_number = l._line_number;
+ _line_start = l._line_start;
+ }
+
+ // Very handy in the debugger, shows the unparsed program
+ @Override
+ public String toString() {
+ return _input==null ? _cu._cname+"@"+_position : new String(_input, _position, _input.length - _position);
+ }
+
+ // True if at EOF
+ boolean isEOF() {
+ return _position >= _input.length;
+ }
+
+ // Peek next character, or report EOF
+ char peek() {
+ return isEOF() ? Character.MAX_VALUE // Special value that causes parsing to terminate
+ : (char) _input[_position];
+ }
+ // Just crash if misused
+ public byte peek(int off) { return _input[_position+off]; }
+
+ private void inc() {
+ if( _position++ < _input.length && _input[_position-1] == '\n' ) {
+ _line_number++;
+ _line_start = _position;
+ }
+ }
+ // Does not honor LF, so caller must roll back position on a LF
+ private char nextChar() {
+ char ch = peek();
+ inc();
+ return ch;
+ }
+
+ // True if a white space
+ private boolean isWhiteSpace() {
+ return peek() <= ' '; // Includes all the use space, tab, newline, CR
+ }
+
+ /**
+ * Return the next non-white-space character
+ */
+ void skipWhiteSpace() {
+ while( true ) {
+ if( isWhiteSpace() ) inc();
+ // Skip // to end of line
+ else if( _position+2 < _input.length &&
+ _input[_position ] == '/' &&
+ _input[_position+1] == '/') {
+ inc(); inc();
+ while( !isEOF() && _input[_position] != '\n' )
+ inc();
+ } else if( _position+2 < _input.length &&
+ _input[_position ] == '/' &&
+ _input[_position+1] == '*') {
+ // Skip /*comment*/
+ while( !isEOF() && !(_input[_position-1] == '*' && _input[_position] == '/'))
+ inc();
+ inc();
+ } else break;
+ }
+ }
+
+ // Return true, if we find "syntax" after skipping white space; also
+ // then advance the cursor past syntax.
+ // Return false otherwise, and do not advance the cursor.
+ boolean match(String syntax) {
+ assert syntax.indexOf('\n')==-1; // No newlines in match
+ skipWhiteSpace();
+ int len = syntax.length();
+ if (_position + len > _input.length) return false;
+ for (int i = 0; i < len; i++)
+ if ((char) _input[_position + i] != syntax.charAt(i))
+ return false;
+ _position += len;
+ return true;
+ }
+
+ // Match must be exact and not followed by more ID characters.
+ // Prevents identifier "ifxy" from matching an "if" statement.
+ boolean matchx(String syntax) {
+ if( !match(syntax) ) return false;
+ if( !isIdLetter(peek()) ) return true;
+ _position -= syntax.length();
+ return false;
+ }
+ // Match this char, and the next char must be different.
+ // Handles '&&' vs '&'
+ boolean matchOp( char c0 ) {
+ skipWhiteSpace();
+ if( _position+1 >= _input.length || _input[_position]!=c0 || _input[_position+1]==c0 )
+ return false;
+ inc();
+ return true;
+ }
+ // Match these two characters in a row
+ boolean matchOpx(char c0, char c1) {
+ skipWhiteSpace();
+ if( _position+1 >= _input.length || _input[_position]!=c0 || _input[_position+1]==c1 )
+ return false;
+ inc();
+ return true;
+ }
+
+ boolean peek(char ch) {
+ skipWhiteSpace();
+ return peek()==ch;
+ }
+
+ boolean peekIsId() {
+ skipWhiteSpace();
+ return isIdStart(peek());
+ }
+
+ // Return an identifier or null
+ String matchId() {
+ return peekIsId() ? parseId() : null;
+ }
+
+ // Used for errors
+ String getAnyNextToken() {
+ if (isEOF()) return "EOF";
+ if (isIdStart(peek())) return parseId();
+ if (isNumber(peek())) return parseNumberString();
+ if (isPunctuation(peek())) return parsePunctuation();
+ return String.valueOf(peek());
+ }
+
+ boolean isNumber(char ch) {return Character.isDigit(ch);}
+
+ // Return a constant Type, either TypeInteger or TypeFloat
+ Type parseNumber() {
+ int old = _position;
+ int len = isLongOrDouble();
+ boolean f32 = peek()=='f';
+ if( f32 ) nextChar();
+ int n = Math.abs(len);
+ if( f32 )
+ return TypeFloat.constant(Float.parseFloat(new String(_input,old,n)));
+ if( len > 0 ) {
+ if( len > 1 && _input[old]=='0' )
+ throw Parser.error("Syntax error: integer values cannot start with '0'",this);
+ long i = Long.parseLong(new String(_input,old,len));
+ return TypeInteger.constant(i);
+ }
+ return TypeFloat.constant(Double.parseDouble(new String(_input,old,n)));
+ }
+ private String parseNumberString() {
+ int old = _position;
+ int len = Math.abs(isLongOrDouble());
+ if( peek()=='f' ) {
+ nextChar();
+ len++;
+ }
+ return new String(_input,old,len);
+ }
+
+ // Return +len that ends a long
+ // Return -len that ends a double
+ private int isLongOrDouble() {
+ int old = _position;
+ char c;
+ while( Character.isDigit(c = nextChar()) ) ;
+ if( !(c=='e' || c=='.') )
+ return --_position - old;
+ if( peek() == '-' ) nextChar();
+ while( Character.isDigit(c = nextChar()) || c=='e' || c=='.' ) ;
+ return -(--_position - old);
+ }
+
+ // First letter of an identifier
+ private boolean isIdStart(char ch) {
+ return Character.isAlphabetic(ch) || ch == '_';
+ }
+
+ // All characters of an identifier, e.g. "_x123"
+ private boolean isIdLetter(char ch) {
+ return Character.isLetterOrDigit(ch) || ch == '_';
+ }
+
+ private String parseId() {
+ int start = _position;
+ while (isIdLetter(nextChar())) ;
+ return new String(_input, start, --_position - start).intern();
+ }
+
+ //
+ private boolean isPunctuation(char ch) {
+ return "=;[]<>()+-/*&|^".indexOf(ch) != -1;
+ }
+
+ private String parsePunctuation() {
+ int start = _position;
+ return new String(_input, start, 1);
+ }
+
+ // Next oper= character, or 0.
+ // As a convenience, mark "++" as a char 1 and "--" as char -1 (65535)
+ // Distinct tags for the multi-character compound operators.
+ static final char SHL = 2, SAR = 3, SHR = 4;
+
+ public char matchOperAssign() {
+ skipWhiteSpace();
+ if( match("<<=" ) ) return SHL;
+ if( match(">>>=") ) return SHR;
+ if( match(">>=" ) ) return SAR;
+ if( _position+2 >= _input.length ) return 0;
+ char ch0 = (char)_input[_position];
+ if( "+-/*&|^".indexOf(ch0) == -1 ) return 0;
+ char ch1 = (char)_input[_position+1];
+ if( ch1 == '=' ) { _position += 2; return ch0; }
+ if( isIdLetter((char)_input[_position+2]) ) return 0;
+ if( ch0 == '+' && ch1 == '+' ) { _position += 2; return (char) 1; }
+ if( ch0 == '-' && ch1 == '-' ) { _position += 2; return (char)-1; }
+ return 0;
+ }
+
+ String parseString() {
+ if( !peek('"') ) return null;
+ inc();
+ StringBuilder sb = new StringBuilder();
+ while( !isEOF() ) {
+ char c = nextChar();
+ if( c=='"' ) return sb.toString();
+ if( c!='\\' ) {
+ sb.append(c);
+ continue;
+ }
+ if( isEOF() )
+ throw error("Unclosed string");
+ sb.append(escapedChar("string"));
+ }
+ throw error("Unclosed string");
+ }
+
+ private char escapedChar(String kind) {
+ char esc = nextChar();
+ return switch( esc ) {
+ case '0' -> '\u0000';
+ case 'n' -> '\n';
+ case 't' -> '\t';
+ case 'r' -> '\r';
+ case '\\' -> '\\';
+ case '"' -> '"';
+ case '\'' -> '\'';
+ default -> throw error("Unknown "+kind+" escape \\\\"+esc);
+ };
+ }
+
+ char parseChar() {
+ if( isEOF() ) throw error("Unclosed character");
+ char c = nextChar();
+ if( c=='\\' ) {
+ if( isEOF() ) throw error("Unclosed character");
+ c = escapedChar("character");
+ }
+ return c;
+ }
+
+ private ParseException error(String msg) { return Parser.error(msg,this); }
+
+ // Scan a deferred fragment, leaving its unmatched closing delimiter unread.
+ // Comments, quoted text, and escapes do not affect the nesting depth.
+ void skipTo(char open, char close, String what) {
+ int depth = 0;
+ while( true ) {
+ skipWhiteSpace();
+ if( isEOF() ) throw error("Unclosed "+what);
+ char ch = peek();
+ if( ch==close && depth==0 ) return;
+ nextChar();
+ if( ch==open ) depth++;
+ else if( ch==close ) depth--;
+ else if( ch=='"' || ch=='\'' ) {
+ char quote = ch;
+ while( !isEOF() ) {
+ ch = nextChar();
+ if( ch=='\\' ) nextChar();
+ else if( ch==quote ) break;
+ }
+ }
+ }
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Parser.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Parser.java
new file mode 100644
index 00000000..913fd2b2
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Parser.java
@@ -0,0 +1,298 @@
+package com.seaofnodes.simple.parser;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.CompUnit;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Ary;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Stack;
+
+/** Source-unit entry point and shared cursor, scope, and IR utilities.
+ * Grammar productions live in the package-local components below. */
+public class Parser {
+ final Statements statements = new Statements(this);
+ final ControlFlow controlflow = new ControlFlow(this);
+ final Expressions expressions = new Expressions(this);
+ final Declarations declarations = new Declarations(this);
+ final Types types = new Types(this);
+ final Functions functions = new Functions(this);
+ final Structs structs = new Structs(this);
+ final Allocations allocations = new Allocations(this);
+ final References references = new References(this);
+ final Conversions conversions = new Conversions(this);
+
+ // Compile driver
+ public final CodeGen _code;
+
+ // The Lexer. Thin wrapper over a byte[] buffer with a cursor.
+ Lexer _lexer;
+
+ // Class-object name, distinct from its instance type.
+ public static final String CLZ = "class:";
+
+ // Source file for compilation
+ public CompUnit _ref;
+
+ // Current classname being parsed
+ String _nestedType;
+ /**
+ * Current ScopeNode - ScopeNodes change as we parse code, but at any point of time
+ * there is one current ScopeNode. The reason the current ScopeNode can change is to do with how
+ * we handle branching. See {@link Statements#parseIf()}.
+ * <p>
+ * Each ScopeNode contains a stack of lexical scopes, each scope is a symbol table that binds
+ * variable names to Nodes. The top of this stack represents current scope.
+ * <p>
+ * We keep a list of all ScopeNodes so that we can show them in graphs.
+ * @see Statements#parseIf()
+ * @see #_xScopes
+ */
+ public ScopeNode _scope;
+
+ /**
+ * List of keywords disallowed as identifiers
+ */
+ public static final HashSet<String> KEYWORDS = new HashSet<>(){{
+ add("bool");
+ add("break");
+ add("byte");
+ add("continue");
+ add("else");
+ add("f32");
+ add("f64");
+ add("false");
+ add("flt");
+ add("i16");
+ add("i32");
+ add("i64");
+ add("i8");
+ add("if");
+ add("int");
+ add("new");
+ add("null");
+ add("return");
+ add("struct");
+ add("true");
+ add("u1");
+ add("u16");
+ add("u32");
+ add("u8");
+ add("while");
+ }};
+
+ /**
+ * We clone ScopeNodes when control flows branch; it is useful to have
+ * a list of all active ScopeNodes for purposes of visualization of the SoN graph
+ */
+ public final Stack<ScopeNode> _xScopes = new Stack<>();
+ // Layouts needed by field accesses in this source file. Resolve after
+ // parsing, since a later local struct declaration can satisfy the use.
+ public final LinkedHashSet<String> _requiredTypes = new LinkedHashSet<>();
+
+ // Mapping from a type name to a Type. The string name matches
+ // `type.str()` call.
+ public static final HashMap<String, Type> TYPES = new HashMap<>();
+ public static final HashMap<String, Type> INIT_TYPES =
+ new HashMap<>() {{
+ put("bool",TypeInteger.U1 );
+ put("byte",TypeInteger.U8 );
+ put("f32" ,TypeFloat .F32);
+ put("f64" ,TypeFloat .F64);
+ put("flt" ,TypeFloat .F64);
+ put("i16" ,TypeInteger.I16);
+ put("i32" ,TypeInteger.I32);
+ put("i64" ,TypeInteger.BOT);
+ put("i8" ,TypeInteger.I8 );
+ put("int" ,TypeInteger.BOT);
+ put("u1" ,TypeInteger.U1 );
+ put("u16" ,TypeInteger.U16);
+ put("u32" ,TypeInteger.U32);
+ put("u8" ,TypeInteger.U8 );
+ put("val" ,Type.TOP); // Marker type, indicates type inference
+ put("var" ,Type.BOTTOM); // Marker type, indicates type inference
+ }};
+
+ public static class ParseException extends RuntimeException {
+ public final Lexer _loc;
+ ParseException( String msg, Lexer loc ) { super(msg); _loc = loc; }
+ }
+
+ public Parser(CodeGen code ) {
+ _code = code;
+ }
+
+ public Ary<FRefNode> parse( CompUnit ref ) {
+ assert _scope == null && statements._breakScope == null && statements._continueScope == null && functions._returnScope == null;
+ assert references._frefs == null;
+ _lexer = new Lexer(ref);
+ references._frefs = new Ary<>(FRefNode.class);
+ _requiredTypes.clear();
+ // Starting Scope has control, memory, initial arguments
+ _scope = new ScopeNode();
+ _scope.define(ScopeNode.CTRL, Type.CONTROL , false, null, _lexer);
+ _scope.define(ScopeNode.MEM0, TypeMem.BOT , false, null, _lexer);
+ // Track active scopes for Graph display
+ _xScopes.push(_scope);
+ ctrl(_code.XCTRL);
+ mem(con(TypeMem.BOT));
+
+ // File-level struct declaration
+ _ref = ref;
+ _nestedType = ref._cname;
+ String typeName = addClzPrefix(ref._cname);
+
+ // Parse whole file, which is a struct-level class initializer
+ structs.parseInitializer( true, typeName );
+
+ if( !_lexer.isEOF() ) throw _errorSyntax("unexpected");
+
+ Ary<FRefNode> frefs = references.resolve();
+
+ // The class being published
+ ref._clz = (TypeStruct)TYPES.get(typeName);
+
+ // Clean up and reset
+ _xScopes.pop();
+ _scope.kill();
+ _scope = null;
+ references._frefs = null;
+
+ return frefs;
+ }
+
+ @Override
+ public String toString() { return _lexer==null ? "" : _lexer.toString(); }
+
+ // Debugging utility to find a Node by index
+ public Node f(int nid) { return _code.f(nid); }
+ public static String addClzPrefix( String x ) {
+ assert !x.startsWith(CLZ);
+ return (CLZ+x).intern();
+ }
+ public static boolean startsClzPrefix( String s ) {
+ return s.startsWith(CLZ);
+ }
+ public static boolean isPublicClassName( String name ) {
+ if( startsClzPrefix(name) ) name = name.substring(CLZ.length());
+ return !name.isEmpty() && name.charAt(0)!='_' && name.indexOf("._")==-1;
+ }
+
+ // Read and set CTRL
+ Node ctrl() { return _scope.ctrl(); }
+ <N extends Node> N ctrl(N n) { return _scope.ctrl(n); }
+ // Read and set Memory
+ Node mem() { return _scope.mem(1); }
+ void mem(Node mem) { _scope.mem(mem); }
+ // Structural label for precise memory Phis. The '$' prefix cannot be
+ // referenced in Simple source.
+ public static String memName(int alias) { return ("$"+alias).intern(); }
+
+ // Ordinary parsing carries one whole-memory value. Alias partitioning is
+ // graph optimization, not parser state.
+ void storeMem( Node st, int alias, Node prior ) {
+ mem(alias==1 ? st : mergeAlias(prior,alias,st));
+ }
+
+ // A semantic graph partition representing the complete memory after a
+ // precise alias update. ScopeNode treats this as one opaque value.
+ Node mergeAlias(Node bulk, int alias, Node precise) {
+ assert bulk != null : "Missing bulk memory for alias #"+alias;
+ MemMergeNode merge = new MemMergeNode(null,bulk);
+ merge.alias(alias,precise);
+ return peep(merge);
+ }
+ ConstantNode con( long con ) { return _code.con(con); }
+ ConstantNode con( Type t ) { return _code.con(t ); }
+ void classInit(Lexer site, Node ptr) {
+ // Ordinary load machinery resolves the flag alias and offset when
+ // the receiver is still a forward reference.
+ Field flag = ptr._type instanceof TypeMemPtr tmp ? tmp._obj.field("$init") : null;
+ int alias = flag==null ? 1 : flag._alias;
+ Node ready = new LoadNode(site,"$init",alias,null,mem(),ptr,fldoff(ptr,"$init")).peephole();
+ CLInitNode init = new CLInitNode(site,ctrl(),mem(),ptr,ready).peephole().keep();
+ ctrl(new CProjNode(init,0,ScopeNode.CTRL).peephole());
+ Node bulk = new ProjNode(init,1,ScopeNode.MEM0).peephole().keep();
+ flag = init.flag();
+ mem(flag==null ? bulk : mergeAlias(bulk,flag._alias,
+ new ProjNode(init,2,memName(flag._alias)).peephole()));
+ bulk.unkeep();
+ init.unkeep();
+ }
+
+ // Field offset for a known type (but unknown offset)
+ public static ConFldOffNode off( TypeStruct ts, String fname ) {
+ return (ConFldOffNode)(new ConFldOffNode(ts,fname).peephole());
+ }
+ // Field offset for an unknown type
+ public Node fldoff( Node n, String name ) {
+ if( n._type instanceof TypeMemPtr ptr && ptr._obj._fref )
+ _requiredTypes.add(ptr._obj._name);
+ return new FldOffNode(n,name,loc()).peephole();
+ }
+ public Node peep( Node n ) {
+ // Peephole, then improve with lexically scoped guards
+ return _scope.upcastGuard(n.peephole());
+ }
+
+ // Parse a string or null
+
+ //////////////////////////////////
+ // Utilities for lexical analysis
+
+ // Return true and skip if "syntax" is next in the stream.
+ boolean match (String syntax) { return _lexer.match (syntax); }
+ // Match must be "exact", not be followed by more id letters
+ boolean matchx(String syntax) { return _lexer.matchx(syntax); }
+ boolean matchOp(char c0 ) { return _lexer.matchOp(c0); }
+ boolean matchOpx(char c0, char c1) { return _lexer.matchOpx(c0,c1); }
+ // Return true and do NOT skip if 'ch' is next
+ boolean peek(char ch) { return _lexer.peek(ch); }
+
+ // ch is +/- 1, means oper++ or oper-- means postfix
+ static boolean postfix(char ch) {
+ return (byte)ch== 1 || (byte)ch== -1;
+ }
+
+ public int pos() { return _lexer._position; }
+ int pos(int pos) {
+ int old = _lexer._position;
+ _lexer._position = pos;
+ return old;
+ }
+ Type posT(int pos) { _lexer._position = pos; return null; }
+ // Source code location
+ Lexer loc() { return new Lexer(_lexer); }
+
+ // Require and return an identifier
+ String requireId() {
+ String id = _lexer.matchId();
+ if (id != null && !KEYWORDS.contains(id) ) return id.intern();
+ throw error("Expected an identifier, found '"+id+"'");
+ }
+
+ String matchId() {
+ int old = pos();
+ String id = _lexer.matchId();
+ if( id==null ) return null;
+ if( !KEYWORDS.contains(id) ) return id;
+ pos(old);
+ return null;
+ }
+
+ // Require an exact match
+ Parser require(String syntax) { require(null, syntax); return this; }
+ <N> N require(N n, String syntax) {
+ if (match(syntax)) return n;
+ throw errorSyntax(syntax);
+ }
+
+ ParseException errorSyntax(String syntax) { return _errorSyntax("expected `"+syntax+"`"); }
+ private ParseException _errorSyntax(String msg) {
+ return error("Syntax error, "+msg+" but found `" + _lexer.getAnyNextToken()+"`");
+ }
+ ParseException error(String msg) { return error(msg,_lexer); }
+ public static ParseException error(String msg, Lexer loc) { return new ParseException(msg,loc); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/README.md b/chapter29/src/main/java/com/seaofnodes/simple/parser/README.md
new file mode 100644
index 00000000..02a1a2d0
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/README.md
@@ -0,0 +1,26 @@
+# Parser layout
+
+The parser builds Sea-of-Nodes IR directly. Each component shares the cursor,
+active scope, and compilation state through its `Parser P` reference; there is no intermediate
+AST. The [grammar map](../../../../../../../docs/29-grammar.md) connects language
+productions to their entry points.
+
+| File | Responsibility |
+| --- | --- |
+| `Parser.java` | Source-unit entry point, shared state, type registry, and common token/IR helpers. |
+| `Lexer.java` | Tokens, literals, comments, source-location snapshots, and scanning deferred bodies. |
+| `Statements.java` | Statement dispatch, blocks, loops, and jumps. |
+| `Expressions.java` | Assignment and expression precedence, unary operators, primaries, and postfix dispatch. |
+| `Declarations.java` | Bindings, initializers, mutability modifiers, and native declarations. |
+| `Types.java` | Type syntax, qualified-name lookup, arrays, function types, and forward types. |
+| `Functions.java` | Function expressions, parameters, arguments, calls, and function entry/return graphs. |
+| `Structs.java` | Struct and constructor declarations, field discovery, and initialization checks. |
+| `References.java` | Bare-name and field resolution, implicit receivers, assignments, and forward references. |
+| `Allocations.java` | Allocation expressions, strings, and declaration-side allocation helpers. |
+| `ControlFlow.java` | Guarded scope merging shared by `if`, `?:`, `&&`, and `||`. |
+| `Conversions.java` | Shared binding, field, and result conversions. |
+| `Var.java` | Lexical binding metadata: name, type, scope slot, mutability, and initialization state. |
+
+Start with `Statements.parseStatement` or `Expressions.parseAssignment` to follow
+the grammar. Loop updates are parsed after their bodies, and declared constructor
+bodies after field discovery; `Lexer.skipTo` scans these deferred fragments.
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/References.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/References.java
new file mode 100644
index 00000000..4b474cb8
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/References.java
@@ -0,0 +1,369 @@
+package com.seaofnodes.simple.parser;
+
+import com.seaofnodes.simple.codegen.CompUnit;
+import com.seaofnodes.simple.codegen.ParseAll;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.ScopeNode.Kind;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Ary;
+import java.util.BitSet;
+
+/** Name and field access, receiver selection, and lvalue checks. */
+final class References {
+ private final Parser P;
+ // Forward references created during this unit.
+ Ary<FRefNode> _frefs;
+
+ References(Parser P) { this.P = P; }
+
+ // Resolve a primary name, retaining its use site before speculative type lookup.
+ Node parseName(String id, int pre, int pos, int pos2) {
+ // Attempt a local var lookup first
+ Var var = P._scope.lookup(id);
+ if( var==null ) {
+ // Not a local var, try again as a type name
+ P.pos(pos);
+ Type t = P.types.parseType();
+ if( t!=null ) {
+ // Static <clinit> typename lookup
+ if( P.peek('.') ) // type.fld, which is really a field lookup in the class object
+ return P.expressions.parsePostfix(P.con(t));
+ // Direct reference to a <clinit> type. Convert the instance
+ // type to a class type name
+ if( !(t instanceof TypeMemPtr tmp) ) {
+ //return new FRefNode(addClzPrefix(tmp._obj._name), loc()).peephole();
+ // Not a local var, but yes a bare type... disallow a local
+ // FREF and return null, not-a-primary parse.
+ P.pos(pos);
+ return null;
+ }
+ }
+
+ // Insert FREF outside enclosing Kind.Func scope.
+ P.pos(pos2);
+ t = FRefNode.FREF_TYPE;
+ // Check if this is a class.
+ CompUnit cu = ParseAll.findCompUnit(P._code,P._ref,id);
+ if( cu!=null ) {
+ // Must be a final class-type
+ String clzName = Parser.addClzPrefix(cu._cname);
+ TypeStruct clz = (TypeStruct)Parser.TYPES.get(clzName);
+ if( clz == null )
+ Parser.TYPES.put(clzName,clz = TypeStruct.make(clzName,true));
+ t = TypeMemPtr.make((byte)2,clz,true);
+ }
+ // Define the FRef
+ var = P._scope.defineFRef(id,t,true,P.loc());
+ P.references._frefs.push((FRefNode)P._scope.in(var));
+ }
+
+ // Load local value
+ Node rvalue = P._scope.in(var);
+ if( var._extern!=0 )
+ return parseField(rvalue,id,pre);
+ if( var._fref )
+ return P.expressions.parsePostfix(rvalue);
+ // A constructor may read the unfinished object, but each field it
+ // reads must already be initialized on every path. Read the current
+ // local value; other fields may still be waiting for their first write.
+ if( P.structs.inExplicitConstructor() && var._uninit && missingInit(rvalue,new BitSet()) )
+ throw Parser.error("Field '"+id+"' might not be initialized",var._loc);
+
+ // Check for an instance field load
+ int kx = P._scope.kindx(var); // Declaration scope
+ int fx = P._scope.enclosingFunction(); // Enclosing function scope
+ Kind kk = P._scope._kinds.at(kx);
+ Kind fk = P._scope._kinds.at(fx);
+ // Access instance field from 'self'
+ if( kx+1 == fx && kk instanceof Kind.Func inst && FunNode.isInstance(inst._name) && fk instanceof Kind.Func method && !P.structs.inExplicitConstructor() )
+ return parseField(P._scope.in(method._lexSize),id,pre);
+
+ // Check for a function-escaping variable; these require true
+ // closures. Final constants are OK; final vars require a hidden var
+ // argument; not-final fields can just use an explicit struct arg.
+ if( kx < fx && !P.structs.inExplicitConstructor() && // Out of scope
+ !(var._final && rvalue._type.isConstant()) ) { // Not a final constant
+ if( kx > 0 ) // Global scope is OK, only ever one of these (not one per function invoke)
+ throw P.error("Variable '"+var._name+"' is out of function scope and must be a final constant");
+
+ // var.idx==2 is self argument, 3 is args, 4 or more are now static globals
+ if( var._idx >= 4 ) {
+ // Read from global scope.
+ // Surely a nicer way to get the top-level scope compile name?
+ Var clzptr = P._scope.var( 2 );
+ // Get the most recent sharpen global
+ TypeStruct clz = (TypeStruct) Parser.TYPES.get( ((TypeMemPtr) clzptr.type())._obj._name );
+ TypeMemPtr clztmp = TypeMemPtr.make( (byte) 2, clz, true );
+ return parseField( P.con( clztmp ), id,pre );
+ //return parseField(_scope.in(2),id);
+ }
+ }
+
+ // Check for assign-update, x += e0;
+ char ch = pre==0 ? P._lexer.matchOperAssign() : (char)pre;
+ if( ch==0 ) { // Normal primary, check for postfix updates
+ int selfx = P._scope.kindFcn(var)._lexSize;
+ // Might be a method call from inside a method, so no explicit "self".
+ // Pass the in-scope "self"
+ return P.expressions.parsePostfixMethod(rvalue,P._scope.in(P._scope.update(P._scope.var(selfx),null)));
+ }
+ // Assign-update direct into Scope
+ if( var._final && !P._scope.canInit(var) )
+ throw P.error("Cannot reassign final '"+var._name+"'");
+ checkSharedFunction(var);
+ Node op = P.expressions.opAssign(ch,rvalue, var.type() );
+ P._scope.update(var,op);
+ if( Parser.postfix(ch) ) rvalue.unkeep();
+ return pre==0 && Parser.postfix(ch) ? rvalue : op;
+ }
+
+ void checkAssignment(Var def) {
+ String name = def._name;
+ // A final field may receive its first value in its constructor.
+ if( P._scope.in(def._idx)._type!=Type.TOP && def._final &&
+ // Inside an allocation, final assign is OK, outside nope.
+ // The parseNew() call added the allocation scope
+ !P._scope.canInit(def) )
+ throw P.error("Cannot reassign final '"+name+"'");
+ checkSharedFunction(def);
+
+ }
+
+ Node assign(Var def, Node expr) {
+ String name = def._name;
+ // Lift expression, based on type
+ Type decl = def.type(); // Declared type
+ Node lift = P.conversions.convertExpr(expr.keep(), decl).keep();
+ // Update
+ P._scope.update(name,lift);
+
+ // Check for storing into an instance field instead of a local var
+ int kx = P._scope.kindx(def); // Declaration scope
+ int fx = P._scope.enclosingFunction(); // Enclosing function scope
+ Kind kk = P._scope._kinds.at(kx);
+ Kind fk = P._scope._kinds.at(fx);
+ // Access instance field from 'self'
+ if( kx+1 == fx && kk instanceof Kind.Func inst && FunNode.isInstance(inst._name) &&
+ fk instanceof Kind.Func method && !P.structs.inExplicitConstructor() ) {
+ // Named field offset on an unknown struct type
+ TypeMemPtr self = (TypeMemPtr)P._scope.var(fk._lexSize).type();
+ TypeStruct sobj = (TypeStruct)Parser.TYPES.get(self._obj._name);
+ Field fld = sobj.field(name);
+ int alias = fld._alias;
+ Node ptr = P._scope.in(method._lexSize);
+ Node off = P.fldoff(ptr,name);
+ Node prior = P.mem().keep();
+ Node st = new StoreNode(P.loc(), name, alias, decl, P.ctrl(), prior, ptr, off, lift, P.structs.inExplicitConstructor()).peephole().keep();
+ P.storeMem(st,alias,prior);
+ st.unkeep();
+ prior.unkeep();
+ }
+ lift.unkill();
+
+ // Return un-lifted expr
+ return expr.unkeep();
+ }
+
+ /**
+ * Parse postfix expression; this can be a field expression, an array
+ * lookup or a postfix operator like '#'
+ *
+ * <pre>
+ * expr ['++' | '--' ] // Postfix unary write operator
+ * expr [ [op]= expr ] // Field reference
+ * expr ('[' expr ']')* [ [op]= expr ] // Array reference
+ * </pre>
+ */
+
+ // pre is +/-1 for a prefix update, otherwise parse a postfix operation.
+ Node parseField(Node expr, String name, int pre) {
+ return parseField(expr,name,pre,P.loc());
+ }
+
+ Node parseField(Node expr, String name, int pre, Lexer site) {
+ if( P.structs.inExplicitConstructor() && privateSelf(expr) ) return privateField(name,pre);
+ // Keep expr across possible updates
+ expr.keep();
+
+ // Get the best known TypeStruct from a pointer type.
+ TypeStruct ts = null, ts2;
+ if( expr._type instanceof TypeMemPtr tmp && (ts2 = ((TypeStruct)Parser.TYPES.get(tmp._obj._name))) != null )
+ ts = (TypeStruct)tmp._obj.join(ts2);
+ // A Load's flow type is intentionally not promised during parsing,
+ // but its declaring field type is still known. Recover that static
+ // type for chained accesses such as `_grow(1).buf[len++]`; otherwise
+ // the array Store is parsed as alias #1 and can hide the independent
+ // side effect of the postfix `len++`.
+ if( ts==null && expr instanceof LoadNode load ) {
+ TypeStruct ots = load.off() instanceof ConFldOffNode coff ? coff._ts : null;
+ if( ots==null && load.ptr()._type instanceof TypeMemPtr owner ) {
+ Type owner0 = Parser.TYPES.get(owner._obj._name);
+ ots = owner0 instanceof TypeStruct obj ? obj : owner._obj;
+ }
+ if( ots != null ) {
+ Field loaded = ots.field(load._name);
+ if( loaded != null && loaded._t instanceof TypeMemPtr ltmp ) {
+ Type loaded0 = Parser.TYPES.get(ltmp._obj._name);
+ ts = loaded0 instanceof TypeStruct obj ? obj : ltmp._obj;
+ }
+ }
+ }
+
+ Field fld = ts==null ? null : ts.field(name);
+ if( fld==null && ts!=null && !Parser.startsClzPrefix(ts._name) ) {
+ Type clz0 = Parser.TYPES.get(Parser.addClzPrefix(ts._name));
+ if( clz0 instanceof TypeStruct clz && (fld = P.types.latestStruct(clz).field(name)) != null ) {
+ expr.unkeep();
+ ts = P.types.latestStruct(clz);
+ expr = P.con(TypeMemPtr.make((byte)2,ts,true)).keep();
+ }
+ }
+ if( fld==null && ts!=null )
+ fld = P.types.fieldOrOpen(ts,name,false);
+ if( fld==null && ts!=null && !P.types.latestStruct(ts)._open )
+ throw P.error("Accessing unknown field '"+name+"' from '*"+ts._name+"'");
+
+ // We have to record "active touches" of a new class, to track when we
+ // need to force class initialization. Basically, we insert an CLInit,
+ // and if it does not fold away it'll turn into a runtime check.
+ Kind.Func func = (Kind.Func)P._scope._kinds.at(P._scope.enclosingFuncOrDecl());
+ if( (ts==null || ts==TypeStruct.BOT || ts.isHigh() || Parser.startsClzPrefix(ts._name)) &&
+ !(ts!=null && (ts._name+".<clinit>").equals(func._name)) )
+ P.classInit(site,expr);
+
+ // With no field, we will update the bulk memory
+ int alias = fld==null ? 1 : fld._alias;
+ Type decl = fld==null ? Type.BOTTOM : fld._t;
+
+ // Field offset; fixed for structs, computed for arrays
+ Node off = (name=="[]"
+ // Array element math, with unknown base offset and shift amount.
+ ? P.peep(new AddNode(P.fldoff(expr,"[]"),P.peep(new ShlNode(null,P.require(P.expressions.parseAssignment(),"]"),P.fldoff(expr,"<<")))))
+ // Named field offset on an unknown struct type
+ : P.fldoff(expr,name)).keep();
+
+ // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment
+ if( pre==0 && P.matchOpx('=','=') ) {
+ // Field assignment
+ Node val = P.expressions.parseAssignment().keep();
+ // Lift value for store
+ Node lift = fld==null ? val : new ConvertNode(fld._t,val).peephole();
+ // Memory for store, post assignment expression
+ Node mem = P.mem();
+ // Store to field
+ mem.keep();
+ if( expr._type instanceof TypeMemPtr view && view._ro && P.ctrl()._type!=Type.XCONTROL )
+ throw P.error("Cannot modify final field '"+name+"'");
+ Node st = new StoreNode(P.loc(), name, alias, decl, P.ctrl(), mem, expr.unkeep(), off.unkeep(), lift, false).peephole().keep();
+ P.storeMem(st,alias,mem);
+ st.unkeep();
+ mem.unkeep();
+
+ return val.unkeep(); // "obj.a = expr" returns the expression while updating memory
+ }
+
+ // Memory for load
+ Node mem = P.mem();
+ // Loading from a constant array, the declared type is the
+ // meet-over-elements and not the array itself.
+ if( name=="[]" && decl instanceof TypeConAry conary )
+ decl = conary.elem();
+
+ // Load field
+ Node load = P.peep(new LoadNode(P.loc(),name, alias, P.ctrl(), mem, expr, off));
+
+ // Check for assign-update, "ptr.fld += expr" or "ary[idx]++"
+ char ch = pre==0 ? P._lexer.matchOperAssign() : (char)pre;
+ if( ch!=0 ) {
+ if( fld!=null && decl==Type.BOTTOM )
+ throw P.error( "'" + ts._name + "' is not fully initialized, field '" + fld._fname + "' needs to be set in a constructor" );
+ Node op = P.expressions.opAssign(ch,load, decl ).keep();
+ mem.keep();
+ if( expr._type instanceof TypeMemPtr view && view._ro && P.ctrl()._type!=Type.XCONTROL )
+ throw P.error("Cannot modify final field '"+name+"'");
+ Node st = new StoreNode(P.loc(), name, alias, decl, P.ctrl(), mem, expr.unkeep(), off.unkeep(), op, false).peephole().keep();
+ P.storeMem(st,alias,mem);
+ st.unkeep();
+ mem.unkeep();
+
+ op.unkeep(); // A narrow Store may have folded away the expression's conversion.
+ if( Parser.postfix(ch) ) load.unkeep();
+ return pre==0 && Parser.postfix(ch) ? load : op;
+ }
+ off.unkill();
+ // Might be a method call, so pass expr as 'self'
+ return P.expressions.parsePostfixMethod(load,expr.unkeep());
+ }
+
+ Node defaultSelf() {
+ int selfx = P._scope._kinds.at(P._scope.enclosingFuncOrDecl())._lexSize;
+ return P._scope.in(P._scope.update(P._scope.var(selfx),null));
+ }
+
+ private boolean privateSelf(Node ptr) {
+ while( ptr instanceof ConvertNode || ptr instanceof ReadOnlyNode || ptr instanceof GuardNode ) ptr = ptr.in(1);
+ Node self = defaultSelf();
+ while( self instanceof ConvertNode || self instanceof ReadOnlyNode || self instanceof GuardNode ) self = self.in(1);
+ return ptr==self;
+ }
+
+ // An explicit self.x read sees the same current value as bare x. Loading
+ // the allocation here would miss updates still held in local SSA values.
+ private Node privateField(String name, int pre) {
+ int idx = P.structs.fieldVarIdxBefore(P._scope._kinds.at(P._scope.enclosingFuncOrDecl())._lexSize,name);
+ if( idx<0 ) throw P.error("Cannot resolve field '"+name+"'");
+ Var v = P._scope.var(idx);
+ if( pre==0 && P.matchOpx('=','=') ) {
+ checkSharedFunction(v);
+ Node value = P.expressions.parseAssignment().keep();
+ P._scope.update(v,P.conversions.convertExpr(value,v.type()));
+ return value.unkeep();
+ }
+ Node value = P._scope.in(P._scope.update(v,null));
+ if( v._uninit && missingInit(value,new BitSet()) )
+ throw Parser.error("Field '"+name+"' might not be initialized",v._loc);
+ char ch = pre==0 ? P._lexer.matchOperAssign() : (char)pre;
+ if( ch!=0 ) {
+ checkSharedFunction(v);
+ Node op = P.expressions.opAssign(ch,value,v.type());
+ P._scope.update(v,op);
+ if( pre!=0 && Parser.postfix(ch) ) value.unkeep();
+ return pre==0 && Parser.postfix(ch) ? value.unkeep() : op;
+ }
+ return P.expressions.parsePostfixMethod(value,defaultSelf());
+ }
+
+ private void checkSharedFunction(Var v) {
+ if( P._scope.canInit(v) && v._final && v.type() instanceof TypeFunPtr t && t.isConstant() )
+ throw P.error("Cannot reassign shared function '"+v._name+"'");
+ }
+
+ // A loop Phi is temporarily bottom before its backedge is parsed. That is
+ // not an unwritten field: follow its live inputs to the actual defaults.
+ private boolean missingInit(Node value, BitSet seen) {
+ if( value==null || seen.get(value._nid) ) return false;
+ seen.set(value._nid);
+ if( value instanceof PhiNode phi && !(value instanceof ParmNode) ) {
+ for( int i = 1; i<phi.nIns(); i++ )
+ if( phi.region().in(i)!=null && phi.region().in(i)._type!=Type.XCONTROL && missingInit(phi.in(i),seen) )
+ return true;
+ return false;
+ }
+ return !Structs.initialized(value._type);
+ }
+
+ Ary<FRefNode> resolve() {
+ // All still-unresolved forward refs
+ // should immediately resolve to a file-based name which is a Simple
+ // class type.
+ Ary<FRefNode> frefs = new Ary<>(FRefNode.class);
+ for( FRefNode fref : _frefs ) {
+ if( fref.nIns() != 1 && fref._con == FRefNode.FREF_TYPE ) continue;
+ CompUnit cu = ParseAll.findCompUnitOrThrow(P._code,P._ref,fref._name);
+ fref._con = TypeMemPtr.make((byte)2,TypeStruct.make(Parser.addClzPrefix(cu._cname),true),true);
+ frefs.push(fref);
+ }
+
+ return frefs;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Statements.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Statements.java
new file mode 100644
index 00000000..4ff4993b
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Statements.java
@@ -0,0 +1,272 @@
+package com.seaofnodes.simple.parser;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.ScopeNode.Kind;
+import com.seaofnodes.simple.type.*;
+
+/** Blocks, statements, and loops; builds their control-flow scopes. */
+final class Statements {
+ private final Parser P;
+
+ // Merge all the while-breaks here
+ ScopeNode _breakScope;
+ // Merge all loop continues here.
+ ScopeNode _continueScope;
+
+ Statements(Parser P) { this.P = P; }
+
+ /**
+ * Parses a statement
+ *
+ * <pre>
+ * returnStatement | declStatement | blockStatement | ifStatement | expressionStatement
+ * </pre>
+ * @return a {@link Node} or {@code null}
+ */
+ Node parseStatement() {
+ if( P.matchx("return") ) return parseReturn();
+ else if (P.matchx("if") ) return parseIf();
+ else if (P.matchx("while") ) return parseWhile();
+ else if (P.matchx("for") ) return parseFor();
+ else if (P.matchx("break") ) return parseBreak();
+ else if (P.matchx("continue")) return parseContinue();
+ else if (P.matchx("struct") ) return P.structs.parseStruct();
+ else if (P.structs.inStructDeclaration() && P.matchx("new")) return P.structs.parseConstructorDeclaration();
+ else if (P.matchx(";") ) return P._code.ZERO; // Empty statement
+ // Break ambiguity around leading function types and starting a block
+ else if (P.peek('{') && !P.types.isTypeFun() ) {
+ P.match("{");
+ return P.require(parseBlock(new Kind.Block()),"}");
+ }
+ // Declaration or normal assignment/expression
+ else return P.declarations.parseDeclarationStatement();
+ }
+
+ /**
+ * Parses a block
+ *
+ * <pre>
+ * '{' statements '}'
+ * </pre>
+ * Does not parse the opening or closing '{}'
+ * @return a {@link Node} or {@code null}
+ */
+ private Node parseBlock(Kind kind) {
+ // Enter a new scope
+ P._scope.push(kind);
+ Node last = P._code.ZERO;
+ while (!P.peek('}') && !P._lexer.isEOF()) {
+ P._code.add(last);
+ last = parseStatement();
+ }
+ // Exit scope
+ last.keep();
+ P._scope.pop();
+ return last.unkeep();
+ }
+
+ /**
+ * Parses a return statement; "return" already parsed.
+ * The $ctrl edge is killed.
+ *
+ * <pre>
+ * 'return' expr ;
+ * </pre>
+ * @return an expression {@link Node}, never {@code null}
+ */
+ private Node parseReturn() {
+ var expr = P.require(P.expressions.parseAssignment(), ";");
+ return P.functions.addReturn(expr);
+ }
+
+ /**
+ * Parses a statement
+ *
+ * <pre>
+ * if ( expression ) statement [else statement]
+ * </pre>
+ * @return a {@link Node}, never {@code null}
+ */
+ private Node parseIf() {
+ // Parse predicate
+ P.require("(");
+ var pred = P.require(P.expressions.parseAssignment(), ")");
+ return P.controlflow.conditional(pred,"else");
+ }
+
+ /**
+ * Parses a while statement
+ *
+ * <pre>
+ * while ( expression ) statement
+ * </pre>
+ * @return a {@link Node}, never {@code null}
+ */
+ private Node parseWhile() {
+ P.require("(");
+ return parseLooping(false);
+ }
+
+ /**
+ * Parses a for statement
+ *
+ * <pre>
+ * for( var x=init; test; incr ) body
+ * </pre>
+ * @return a {@link Node}, never {@code null}
+ */
+ private Node parseFor() {
+ // { var x=init,y=init,...;
+ // while( test ) {
+ // body;
+ // next;
+ // }
+ // }
+ P.require("(");
+ P._scope.push(new Kind.Block()); // Scope for the index variables
+ if( !P.match(";") ) // Can be empty init "for(;test;next) body"
+ P.declarations.parseDeclarationStatement(); // Non-empty init
+ Node rez = parseLooping(true);
+ P._scope.pop(); // Exit index variable scope
+ return rez;
+ }
+
+ // Shared by `for` and `while`
+ private Node parseLooping( boolean doFor ) {
+
+ var savedContinueScope = _continueScope;
+ var savedBreakScope = _breakScope;
+
+ // Loop region has two control inputs, the first is the entry
+ // point, and second is back edge that is set after loop is parsed
+ // (see end_loop() call below). Note that the absence of back edge is
+ // used as an indicator to switch off peepholes of the region and
+ // associated phis; see {@code inProgress()}.
+
+ P.ctrl(new LoopNode(P.loc(),null,P.ctrl()).peephole()); // Note we set back edge to null here
+
+ // At loop head, we clone the current Scope (this includes all
+ // names in every nesting level within the Scope).
+ // We create phis eagerly for all the names we find, see dup().
+
+ // Save the current scope as the loop head
+ ScopeNode head = P._scope.keep();
+ // Clone the head Scope to create a new Scope for the body.
+ // Create phis eagerly as part of cloning
+ P._xScopes.push(P._scope = P._scope.dup(true)); // The true argument triggers creating phis
+
+ // Parse predicate
+ var pred = P.peek(';') ? P.con(1) : P.expressions.parseAssignment();
+ P.require( doFor ? ";" : ")" );
+
+ // IfNode takes current control and predicate
+ Node ifNode = new IfNode(P.ctrl(), pred.keep()).peephole();
+ // Setup projection nodes
+ CFGNode ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
+ CFGNode ifF = (CFGNode)new CProjNode(ifNode.unkeep(), 1, "False").peephole();
+
+ // for( ;;next ) body
+ int nextPos = -1, nextEnd = -1;
+ if( doFor ) {
+ // Skip the next expression and parse it later
+ nextPos = P.pos();
+ P._lexer.skipTo('(',')',"for update");
+ nextEnd = P.pos();
+ P.require(")");
+ }
+
+ // Clone the body Scope to create the break/exit Scope which accounts for any
+ // side effects in the predicate. The break/exit Scope will be the final
+ // scope after the loop, and its control input is the False branch of
+ // the loop predicate. Note that body Scope is still our current scope.
+ P.ctrl(ifF);
+ P._xScopes.push(_breakScope = P._scope.dup());
+ _breakScope.addGuards(ifF,pred,true); // Up-cast predicate
+
+ // No continues yet
+ _continueScope = null;
+
+ // Parse the true side, which corresponds to loop body
+ // Our current scope is the body Scope
+ P.ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection
+ P._scope.addGuards(ifT,pred.unkeep(),false); // Up-cast predicate
+ parseStatement().isKill(); // Parse loop body
+ P._scope.removeGuards(ifT);
+
+ // Merge the loop bottom into other continue statements
+ if (_continueScope != null) {
+ _continueScope = jumpTo(_continueScope);
+ P._scope.kill();
+ P._scope = _continueScope;
+ }
+
+ // Now append the next code onto the body code
+ if( doFor ) {
+ int old = P.pos(nextPos);
+ if( !P.peek(')') )
+ P.expressions.parseAssignment();
+ if( P.pos() != nextEnd )
+ throw P.errorSyntax( "Unexpected code after expression" );
+ P.pos(old);
+ }
+
+ // The true branch loops back, so whatever is current _scope.ctrl gets
+ // added to head loop as input. endLoop() updates the head scope, and
+ // goes through all the phis that were created earlier. For each phi,
+ // it sets the second input to the corresponding input from the back
+ // edge. If the phi is redundant, it is replaced by its sole input.
+ var exit = _breakScope;
+ head.balanceLoopFRefs(P._scope);
+ exit.balanceLoopFRefs(P._scope);
+ head.endLoop(P._scope, exit);
+ head.unkeep().kill();
+
+ P._xScopes.pop(); // Cleanup
+ P._xScopes.pop(); // Cleanup
+
+ _continueScope = savedContinueScope;
+ _breakScope = savedBreakScope;
+
+ // At exit the false control is the current control, and
+ // the scope is the exit scope after the exit test.
+ // During sys parsing, there is no xscope here.
+ if( !P._xScopes.isEmpty() ) {
+ P._xScopes.pop();
+ P._xScopes.push( exit );
+ }
+ P._scope = exit;
+ return P._code.ZERO;
+ }
+ private Node parseBreak () {
+ checkLoopActive();
+ // At the time of the break, and loop-exit conditions are only valid if
+ // they are ALSO valid at the break. It is the intersection of
+ // conditions here, not the union.
+ _breakScope.removeGuards(_breakScope.ctrl());
+ _breakScope = P.require(jumpTo(_breakScope ),";");
+ _breakScope.addGuards((CFGNode)_breakScope.ctrl(), null, false);
+ return P._code.ZERO;
+ }
+
+ private Node parseContinue() { checkLoopActive(); _continueScope = P.require(jumpTo( _continueScope ),";"); return P._code.ZERO; }
+
+ private ScopeNode jumpTo(ScopeNode toScope) {
+ ScopeNode cur = P._scope.dup();
+ P.ctrl(P._code.XCTRL); // Kill current scope
+ // Prune nested lexical scopes that have depth > than the loop head
+ // We use _breakScope as a proxy for the loop head scope to obtain the depth
+ while( cur.depth() > _breakScope.depth() )
+ cur.pop();
+ // If this is a continue then first time the target is null
+ // So we just use the pruned current scope as the base for the
+ // "continue"
+ if( toScope == null )
+ return cur;
+ // toScope is either the break scope, or a scope that was created here
+ assert toScope.depth() <= _breakScope.depth();
+ toScope.ctrl(toScope.mergeScopes(cur,P.loc()).peephole());
+ return toScope;
+ }
+
+ private void checkLoopActive() { if (_breakScope == null) throw P.error("No active loop for a break or continue"); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Structs.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Structs.java
new file mode 100644
index 00000000..db17cea3
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Structs.java
@@ -0,0 +1,411 @@
+package com.seaofnodes.simple.parser;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.ScopeNode.Kind;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Ary;
+import java.util.IdentityHashMap;
+
+/** Struct and constructor declarations, field discovery, and initialization checks. */
+final class Structs {
+ private final Parser P;
+
+ // Constructor exit types survive scope teardown until all fields are
+ // known. A null entry denotes a constructor with no returning path.
+ Ary<IdentityHashMap<Var,Type>> _ctorExits;
+
+ // Constructor declaration: new TypeName = { args -> body };
+ record Constructor(String name, Lexer body) {}
+ Constructor _pendingCtor;
+
+ Structs(Parser P) { this.P = P; }
+
+ /**
+ * Parse a struct declaration, and return the following statement.
+ * Structs cannot be redefined, but can be nested.
+ *
+ * @return zero
+ */
+ Node parseStruct( ) {
+ // keyword "struct" already parsed, so expect the struct name next.
+ String typeName = P.requireId();
+ P.require("{");
+
+ // create type class:NESTED.typeName; no fields.
+
+ String old = P._nestedType;
+ String fullName = P._scope.depth()==1 && P._nestedType.endsWith(typeName) ? old : (old+"."+typeName).intern();
+ P._nestedType = fullName; // Recursive structs start with this as their basename
+
+ ReturnNode ret = parseInitializer( false, fullName );
+
+ P._nestedType = old;
+
+ // Insert a field into class:NESTED.typeName of "val typeName = fcn-ptr-to-init"
+ // Function does malloc internally.
+ // Future self: means subtyping has to deal with "who does the malloc"
+ String clzName = Parser.addClzPrefix(fullName);
+ TypeFunPtr sig = ret.fun().sig();
+ TypeStruct ts = (TypeStruct)Parser.TYPES.get(clzName);
+ String initName = initFieldName(fullName);
+ Field oldFld = ts==null ? null : ts.field(initName);
+ Field fld = oldFld==null
+ ? Field.make(initName,sig,P._code.alias(P._ref._cname),true)
+ : oldFld.makeFrom(sig);
+ Field ctorFld = ts==null ? null : ts.field("<ctor>");
+ // A same-named struct shares its file's class object and flag.
+ Field flag = ts==null ? null : ts.field("$init");
+ ts = ctorFld==null
+ ? TypeStruct.make(clzName,false,fld)
+ : TypeStruct.make(clzName,false,fld,ctorFld);
+ if( flag!=null ) ts = ts.add(flag);
+ TypeFunPtr alloc = P.allocations.makeAllocator((TypeStruct)Parser.TYPES.get(fullName),ret,ctorFld);
+ ts = ts.add(Field.make("<new>",alloc,P._code.alias(P._ref._cname),true));
+ Parser.TYPES.put(clzName,ts);
+
+ // Insert a field in the containing class with the nested class type.
+ // This is basically sugar for "val CLZNAME = <init>"
+ // and is a nicer version of updateSelfAsFieldsDiscovered.
+ P._scope.define(typeName, sig, true, new FunPtrNode(sig,P._code._start,ret).peephole(), P.loc());
+
+ P.require("}");
+ return P.require(P._code.ZERO,";");
+ }
+
+ boolean inStructDeclaration() {
+ return P._scope.klast() instanceof Kind.Func fun && FunNode.isInstance(fun._name);
+ }
+
+ Node parseConstructorDeclaration() {
+ TypeStruct self = P._scope.constructorSelf();
+ assert self != null;
+ String name = P.requireId();
+ String expect = initFieldName(self._name);
+ if( name != expect )
+ throw P.error("Constructor name '" + name + "' must match type name '" + expect + "'");
+ P.require("=");
+ if( _pendingCtor!=null ) throw P.error("Redefining constructor '"+name+"'");
+ _pendingCtor = new Constructor(name,P.loc());
+ // Parse the body after all field declarations are known, so self.x
+ // and x share a local slot even when x is declared below this body.
+ P.require("{");
+ P._lexer.skipTo('{','}',"constructor body");
+ P.require("}");
+ P.require(";");
+ return P._code.ZERO;
+ }
+
+ void finishConstructor() {
+ if( _pendingCtor==null ) return;
+ Constructor ctor = _pendingCtor;
+ _pendingCtor = null;
+ Lexer after = P._lexer;
+ P._lexer = new Lexer(ctor.body());
+ defineConstructor(ctor.name());
+ P._lexer = after;
+ }
+
+ private void defineConstructor(String name) {
+ TypeStruct self = P._scope.constructorSelf();
+ Node ctor = P.functions.parseConstructor(name);
+ TypeFunPtr sig = (TypeFunPtr)ctor._type;
+ String clzName = Parser.addClzPrefix(self._name);
+ TypeStruct clz = (TypeStruct)Parser.TYPES.get(clzName);
+ Field old = clz==null ? null : clz.field("<ctor>");
+ if( old != null )
+ throw P.error("Redefining constructor '" + name + "'");
+ Field fld = Field.make("<ctor>",sig,P._code.alias(P._ref._cname),true);
+ Parser.TYPES.put(clzName,clz==null ? TypeStruct.make(clzName,true,fld) : clz.add(fld));
+ P.require(ctor,";");
+ }
+
+ // Parse a struct declaration (not an allocation); file-level is a
+ // class-init, and scope structs are normal
+ ReturnNode parseInitializer( boolean isClz, String typeName ) {
+ var oldExits = _ctorExits;
+ _ctorExits = new Ary(IdentityHashMap.class);
+ // Record & restore global state set during parsing the <init> code
+ Node oldCtrl= P._scope.ctrl().keep();
+ Node oldMem = P._scope.mem ().keep();
+
+ // Make the future clazz/instance struct.
+ TypeStruct tself = (TypeStruct)Parser.TYPES.get(typeName);
+ assert tself==null || tself._open;
+ // Close an existing open struct, or define a forward ref
+ tself = tself==null ? TypeStruct.make(typeName,true) : tself.defined();
+
+ // Class object keeps a runtime flag `$init` to signal the <clinit> has run.
+ if( isClz )
+ tself = tself.add(Field.make("$init",TypeInteger.BOOL,P._code.alias(P._ref._cname),false));
+ Parser.TYPES.put(typeName, tself);
+
+ // Signature of the default constructor
+ TypeFunPtr sig = structInitSig(isClz,tself);
+ // Function and argument names of default constructor
+ String fname = (typeName + (isClz ? ".<clinit>" : ".<init>")).intern();
+ String[] ids = new String[]{"self", isClz ? "arg" : "#selfMem"};
+
+ // Struct decls look like function bodies. Parse function body normally.
+ ReturnNode ret = P.functions.parseBody(fname,sig,P.loc(),ids);
+ _ctorExits = oldExits;
+ // Unwind global state.
+ P.ctrl(oldCtrl.unkeep());
+ P.mem (oldMem .unkeep());
+ return ret;
+ }
+
+ // <cl/init> signature: { self arg/selfMem -> BOT/selfMem }
+ // Self-memory is the rare *private* (never aliased) memory for the new object.
+ private TypeFunPtr structInitSig( boolean isClz, TypeStruct tself ) {
+ // Private uninitialized memory from new object, no escaped values.
+ TypeMem privMem = isClz ? null : TypeMem.make(1,tself,true,isClz,false,null,null);
+ Type targ = isClz ? TypeInteger.BOT : privMem;
+ // Return for private memory has to name all escaped fields, which are not known yet.
+ // Escape ALL fields, and sharpen later.
+ Type tret = isClz ? Type.BOTTOM : TypeMem.makePrivate(tself);
+ Type[] targs = new Type[]{ TypeMemPtr.make((byte)2,tself,isClz), targ };
+ // FIDX for FREFs is in the global namespace, needs the correct clz name.
+ String clzBare = tself._name;
+ int order = 3; // 3 is always the <init> order, 2nd in the file just behind the <clinit>
+ if( isClz ) {
+ assert Parser.startsClzPrefix( clzBare );
+ clzBare = clzBare.substring(Parser.CLZ.length()).intern();
+ order = 2; // 2 is always the <clinit> order, first in file and skipping reserved #1
+ }
+ return TypeFunPtr.make1((byte)2, true, targs, tret, P._code.fidx(clzBare,order));
+ }
+
+ int updateSelfAsFieldsDiscovered( String typeName, int nvar ) {
+ int lex = P._scope.klast()._lexSize + 2/*Skip self and selfMem*/;
+ // Load self type in and out of the TYPES hashtable.
+ TypeStruct tself = (TypeStruct)Parser.TYPES.get(typeName);
+ // Start looking for newly declared vars at the lexical scope start,
+ // skipping two ("self" and "selfmem") and fields seen already.
+ while( lex + nvar < P._scope.nIns() ) {
+ Var v = P._scope.var(lex + nvar++);
+ // Field with sharper type
+ Field old = tself.field(v._name);
+ Field fld = v._extern!=0
+ ? Field.make(v._name,v.type(),v._extern,false,true)
+ : old == null
+ ? Field.make(v._name,v.type(),P._code.alias(P._ref._cname),v._final)
+ : old.makeFrom(v.type());
+ tself = tself.addOrUpdate(fld);
+ }
+ Parser.TYPES.put(typeName,tself);
+ return nvar;
+ }
+
+ void upgradeSelfTypeAndStoreFields( String typeName, boolean isClz, ParmNode self, ParmNode smem, boolean upgrade ) {
+ int lex = P._scope.klast()._lexSize;
+ assert P._scope.var(lex)._name=="self";
+ TypeStruct tself = (TypeStruct)Parser.TYPES.get(typeName);
+ int base = lex+2; // Skip ctrl, mem
+ if( upgrade ) {
+ // Default <init>/<clinit> defines the struct and can sharpen fields.
+ // A named constructor only initializes the already-closed struct.
+ for( int i = 0; i<tself.nkids(); i++ ) {
+ Field tfld = tself._fields[i];
+ int vidx = fieldVarIdx(base,tfld._fname);
+ if( vidx == -1 )
+ continue;
+ Var v = P._scope.var(vidx);
+ // Forward-ref fields where placed in tself but will get promoted
+ // to an outer scope - and should not be in tself.
+ if( v._fref )
+ tself = tself.remove(i--);
+ else if( v.type() != tfld._t ) {
+ assert v.type().isa(tfld._t);
+ tself = tself.replace( tfld.makeFrom( v.type() ) );
+ }
+ }
+
+ // Close Struct type after parsing struct body.
+ Parser.TYPES.put( tself._name, tself = tself.close() );
+ // Improve self, selfMem types in scope.
+ // Class self-type is a singleton pointer. Instance <init> receives the
+ // broad allocation pointer, while private memory tracks initialized fields.
+ self._declaredType = self._type = isClz ? TypeMemPtr.make((byte)2,tself,true) : TypeMemPtr.make(constructorRecv(tself));
+ FunNode fun = self.fun();
+ fun.setSig(fun.sig().makeFrom(self._type,0));
+ if( !isClz ) // <init> returns upgraded private memory
+ smem._declaredType = smem._type = TypeMem.makePrivate(tself);
+ }
+
+ if( !upgrade ) {
+ IdentityHashMap<Var,Type> exit = null;
+ if( P.functions._returnScope!=null && !P.functions._returnScope.ctrl()._type.isHigh() ) {
+ exit = new IdentityHashMap<>();
+ int first = P._scope._kinds.at(P._scope.depth()-2)._lexSize+2;
+ for( int i = first; i<lex; i++ )
+ exit.put(P._scope.var(i),P.functions._returnScope.in(i)._type);
+ }
+ _ctorExits.add(exit);
+ } else if( !isClz ) {
+ // Check that all fields are initialized
+ checkInitFields(tself,base);
+ }
+
+ // When can _returnScope be null here? A never-exit constructor will
+ // not have any returns, and thus no need to gather values and store
+ // them into the (never) constructed object
+ if( P.functions._returnScope==null )
+ return;
+
+ // A MemMerge to gather field updates as stores. Classes update the
+ // normal public memory. <init> updates the private memory that got
+ // passed in - is treated like a normal argument and not like memory.
+ Node mmm = isClz ? P.functions._returnScope.mem(1) : smem;
+
+ // Store constructor results into fields
+ for( int i = 0; i<tself.nkids(); i++ ) {
+ Field fld = tself._fields[i];
+ int vidx = upgrade
+ ? fieldVarIdx(base,fld._fname)
+ : fieldVarIdxBefore(lex,fld._fname);
+ if( vidx == -1 )
+ continue;
+ Var v = P._scope.var(vidx);
+ if( v._fref || v._extern!=0 ) continue; // C storage has no Simple initializer
+ // NewNode's TOP field is the zero-code poison for a required
+ // field. Leave it alone unless the declaration body supplied a
+ // value; otherwise a user constructor must initialize it.
+ Node val = P.functions._returnScope.in(vidx);
+ if( upgrade && v._uninit &&
+ !initialized(val._type) ) continue;
+ // A numeric merge can still be untyped while parsing. Retain its
+ // declared conversion at the helper boundary, as assignments do.
+ if( fld._t instanceof TypeInteger || fld._t instanceof TypeFloat )
+ val = P.conversions.convertExpr(val,fld._t);
+ // Store value into extended struct
+ Node prior = mmm.keep();
+ Node st = P.peep(new StoreNode(null, fld._fname, fld._alias, fld._t, null, prior, self, Parser.off(tself,fld._fname), val, fld._final));
+ mmm = P.mergeAlias(prior,fld._alias,st);
+ prior.unkeep();
+ }
+ mmm = mmm.peephole();
+
+ if( !isClz )
+ // Stuff private mem into normal expression return value.
+ P.functions._returnScope.setDef(P.functions._returnScope.nIns()-1,mmm);
+ else
+ // Parser memory is one opaque whole-memory edge. Publish the
+ // completed class-field aggregate as the <clinit> return memory.
+ P.functions._returnScope.mem(mmm);
+ //_code.add(mmm);
+ }
+
+ // Check that all fields are initialized on all constructor exit paths.
+ private void checkInitFields(TypeStruct ts, int base) {
+ if( P.functions._returnScope==null || P.functions._returnScope.ctrl()._type.isHigh() ) return;
+ boolean implicit = _ctorExits.isEmpty();
+ if( implicit ) _ctorExits.add(new IdentityHashMap<>());
+ for( IdentityHashMap<Var,Type> exit : _ctorExits ) {
+ if( exit==null ) continue;
+ for( Field fld : ts._fields ) {
+ int idx = fieldVarIdx(base,fld._fname);
+ if( idx == -1 ) continue;
+ Var v = P._scope.var(idx);
+ if( !v._uninit || v._fref ) continue;
+ Type t = exit.getOrDefault(v,P.functions._returnScope.in(idx)._type);
+ if( !initialized(t) )
+ throw Parser.error("'"+ts._name+"' is not fully initialized, field '"+fld._fname+
+ (implicit ? "' needs to be set in a constructor" :
+ "' is only partially set in the constructor"),v._loc);
+ }
+ }
+ }
+
+ // Seed the constructor-local SSA slots from the declaration initializer.
+ void constructorDefaults(String typeName, FunNode fun, Lexer loc) {
+ int lex = P._scope.klast()._lexSize;
+ TypeStruct shape = (TypeStruct)Parser.TYPES.get(typeName);
+ for( Field fld : shape._fields ) {
+ int idx = fieldVarIdxBefore(lex,fld._fname);
+ if( idx<0 ) continue;
+ Var v = P._scope.var(idx);
+ if( v._fref || v._extern!=0 || v._uninit || P._scope.in(v)._type.isConstant() ) continue;
+ Node value = P.conversions.convertExpr(
+ new LoadNode(loc,fld._fname,fld._alias,P.ctrl(),fun.parm(3),fun.parm(2),Parser.off(shape,fld._fname)).peephole(),v.type());
+ P._scope.update(v,value);
+ }
+ }
+
+ void beginClassInit(FunNode fun, Lexer loc) {
+ // Each initializer establishes its parent before setting its own
+ // run-once flag. Keep this in the body so every execution strategy
+ // shares the same ordering, including direct entry into a child.
+ if( P._ref._par!=null ) {
+ String pname = Parser.addClzPrefix(P._ref._par._cname);
+ TypeStruct parent = (TypeStruct)Parser.TYPES.get(pname);
+ if( parent==null ) parent = TypeStruct.make(pname,true);
+ Node ptr = P.con(TypeMemPtr.make((byte)2,parent,true)).keep();
+ P.classInit(loc,ptr);
+ ptr.unkeep();
+ }
+ Node self = fun.parm(2);
+ Field flag = ((TypeMemPtr)self._type)._obj.field("$init");
+ Node prior = P.mem().keep();
+ Node st = P.peep(new StoreNode(loc,"$init",flag._alias,flag._t,P.ctrl(),prior,self,
+ P.fldoff(self,"$init"),P.con(1),false));
+ P.storeMem(st,flag._alias,prior);
+ prior.unkeep();
+ }
+
+ // Validate each user constructor, or an implicit empty one, before the
+ // declaration scope goes away. Later fields use their declaration defaults.
+ static boolean initialized(Type t) {
+ // Can't be BOTTOM, that means explicit not initialized.
+ // Can't be high (initialized to an error)
+ // Can't be left null and declared not-null.
+ return t!=Type.BOTTOM && !t.isHigh() && (!(t instanceof TypeNil tn) || tn.notNull());
+ }
+
+ private int fieldVarIdx( int base, String name ) {
+ for( int i = base; i<P._scope.nIns(); i++ )
+ if( P._scope.var(i)._name == name )
+ return i;
+ return -1;
+ }
+
+ // Named constructors update field Vars from the enclosing initializer
+ // scope. Constructor arguments and locals begin at limit and must not
+ // shadow the field state gathered into private memory.
+ int fieldVarIdxBefore( int limit, String name ) {
+ for( int i = limit-1; i>=0; i-- )
+ if( P._scope.var(i)._name == name )
+ return i;
+ return -1;
+ }
+
+ boolean isConstructorDecl(String name) {
+ TypeStruct self = P._scope.constructorSelf();
+ return self != null && name == initFieldName(self._name);
+ }
+
+ boolean inExplicitConstructor() {
+ Kind.Func func = (Kind.Func)P._scope._kinds.at(P._scope.enclosingFuncOrDecl());
+ return isExplicitConstructorName(func._name);
+ }
+
+ boolean isExplicitConstructorName(String funName) {
+ TypeStruct self = P._scope.constructorSelf();
+ return self != null && funName == (self._name+"."+initFieldName(self._name)).intern();
+ }
+
+ static TypeStruct constructorRecv(TypeStruct self) {
+ Field[] fs = new Field[self._fields.length];
+ for( int i = 0; i<fs.length; i++ ) {
+ Field fld = self._fields[i];
+ fs[i] = fld.makeFrom(fld._t.makeStorage());
+ }
+ return TypeStruct.make(self._name,self._open,fs);
+ }
+
+ private static String initFieldName( String typeName ) {
+ if( Parser.startsClzPrefix(typeName) )
+ typeName = typeName.substring(Parser.CLZ.length());
+ int idx = typeName.lastIndexOf('.');
+ return (idx == -1 ? typeName : typeName.substring(idx+1)).intern();
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Types.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Types.java
new file mode 100644
index 00000000..79ac9d12
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Types.java
@@ -0,0 +1,244 @@
+package com.seaofnodes.simple.parser;
+
+import com.seaofnodes.simple.codegen.CompUnit;
+import com.seaofnodes.simple.codegen.ParseAll;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Ary;
+
+/** Type syntax, qualified type lookup, and forward type declarations. */
+final class Types {
+ private final Parser P;
+
+ Types(Parser P) { this.P = P; }
+
+ // Parse a type or restore the cursor and return null. Unknown names need
+ // a following binding to distinguish a forward type from an expression.
+
+ // Structs have nested names, and can be discovered with a partial name
+ // match. Example, searching for a type name "C.D" in the namespace "A.B".
+ // This will match "A.B.C.D", then "A.C.D", then "C.D".
+
+ // Will also match external names like "sys.io" or "externModule.subtype.subtype"
+
+ Type parseType() {
+ if( P.peek('{') ) return parseFunctionType();
+
+ int old1 = P.pos();
+ boolean writable = P.match("!");
+ boolean readonly = !writable && P.match("~");
+ String tname = P._lexer.matchId();
+ if( tname==null ) return P.posT(old1);
+
+ Type t0 = Parser.TYPES.get(tname);
+ if( Parser.KEYWORDS.contains(tname) ) {
+ if( t0==null ) return P.posT(old1);
+ } else {
+ if( t0==Type.BOTTOM || t0==Type.TOP ) return t0; // var/val
+ tname = typeName(tname).intern();
+ t0 = Parser.TYPES.get(tname);
+ }
+ Type t1 = t0==null ? TypeStruct.forward(tname) : t0;
+
+ // Structs are always actually references
+ if( t1 instanceof TypeStruct ts1 )
+ t1 = TypeMemPtr.make(ts1);
+
+ // A qualifier before the name applies to the struct reference, not
+ // to an enclosing array. Arrays have their own [] / [~] qualifier.
+ if( t1 instanceof TypeMemPtr tmp ) {
+ if( readonly || !writable ) t1 = tmp.makeRO();
+ } else if( writable || readonly )
+ return P.posT(old1);
+
+ // Nest arrays and '?' as needed
+ Type t2 = t1;
+ while( true ) {
+ if( P.match("?") ) {
+ if( t2 instanceof TypeMemPtr tmp ) {
+ if( tmp.nullable() ) throw P.error("Type "+t2+" already allows null");
+ t2 = tmp.makeNullable();
+ } else
+ throw P.error("Type "+t0+" cannot be null");
+ } else if( P.match("[~]") ) {
+ t2 = TypeMemPtr.make(typeAry(t2,true)).makeRO();
+ } else if( P.match("[]") ) {
+ t2 = TypeMemPtr.make(typeAry(t2,false));
+ } else
+ break;
+ }
+
+ // Check no forward ref
+ if( t0 != null ) return t2;
+ // Check valid forward ref, after parsing all the type extra bits.
+ // Cannot check earlier, because cannot find required 'id' until after "[]?" syntax
+ int old2 = P.pos();
+ if( !P.match("!") ) P.match("~");
+ String id = P._lexer.matchId();
+ if( !(P.peek(',') || P.peek(';') || P.peek('=') || P.match("->")) || id==null )
+ return P.posT(old1); // Reset lexer to reparse
+ P.pos(old2); // Reset lexer to reparse
+ // Yes a forward ref, so declare it
+ forwardRefType(tname,(TypeStruct)((TypeMemPtr)t1)._obj);
+ // Return the (array, final) type
+ return t2;
+ }
+
+ TypeMemPtr forwardRefType( String tname, TypeStruct tself ) {
+ Parser.TYPES.put(tname,tself);
+ String clzName = Parser.addClzPrefix( tname );
+ // Only a struct declaration publishes an instance initializer. A
+ // forward type may name a file with just <clinit>, so do not invent
+ // a constructor field (or overwrite an already parsed class).
+ Parser.TYPES.putIfAbsent(clzName,TypeStruct.make(clzName,true));
+ return TypeMemPtr.make(tself);
+ }
+
+ TypeMemPtr forwardRefType( String tname ) {
+ tname = fullTypeName(tname);
+ return forwardRefType(tname,TypeStruct.forward(tname));
+ }
+
+ String fullTypeName( String tname ) {
+ String nest = P._nestedType, fullq;
+ while( true ) {
+ fullq = nest+"."+tname;
+ if( Parser.TYPES.get(fullq) != null )
+ return fullq.intern();
+ int idx = nest.lastIndexOf('.');
+ if( idx== -1 ) return fullq.intern();
+ nest = nest.substring(0,idx);
+ }
+ }
+
+ // Make an array type of t. Always record a mutable version,
+ // but return the requested version.
+ TypeStruct typeAry( Type t, boolean efinal ) {
+ // Non-null reference arrays are valid types, even though an ordinary
+ // zero-filled `new T[len]` cannot construct one. Allocation retains
+ // that check in parseNew(); trusted runtimes (notably argv construction)
+ // may fill the private array completely before publishing it.
+ String tname = ("[]"+t.str()).intern();
+ TypeStruct ta = (TypeStruct)Parser.TYPES.get(tname);
+ if( ta==null ) {
+ // Primitive arrays use the null compunit
+ String rcname = Parser.TYPES.get(t) instanceof TypeStruct ? P._ref._cname : ("_."+t).intern();
+ ta = TypeStruct.makeAry(tname,TypeInteger.U32,P._code.alias(rcname),t,P._code.alias(rcname), false );
+ Parser.TYPES.put(tname,ta);
+ }
+ // Already have the aliases, just efinal is wrong
+ return TypeStruct.makeAry(tname,TypeInteger.U32,ta._fields[0]._alias,t,ta._fields[1]._alias,efinal );
+ }
+
+ Type implicitSelfType() {
+ TypeStruct self = P._scope.constructorSelf();
+ // Ordinary instance methods accept the declared object shape. The
+ // constructor-only receiver starts at the low/default field values,
+ // and is built explicitly by parseConstructor().
+ return self == null ? TypePtr.PTR : TypeMemPtr.make(self);
+ }
+
+ // A function type is `{ type... -> type }` or `{ type }`.
+ private Type parseFunctionType() {
+ int old = P.pos(); // Record lexer position
+ P.match("{"); // Skip already-peeked '{'
+ Type t0 = parseType(); // Either return or first arg
+ if( t0==null ) return P.posT(old); // Not a function
+ Ary<Type> ts = new Ary<>(Type.class);
+ // Always reserve a receiver slot. Outside constructor scopes it is
+ // broad and unused, so call sites can uniformly pass their self.
+ ts.push(implicitSelfType());
+ if( P.match("}") ) // No-arg function { -> type }
+ return TypeFunPtr.make(P.match("?"),ts.asAry(),t0);
+ ts.push(t0); // First argument
+ while( true ) {
+ if( P.match("->") ) { // End of arguments, parse return
+ Type ret = parseType();
+ if( ret==null || !P.match("}") )
+ return P.posT(old); // Not a function
+ return TypeFunPtr.make(P.match("?"),ts.asAry(),ret);
+ }
+ Type t1 = parseType();
+ if( t1==null ) return P.posT(old); // Not a function
+ ts.push(t1);
+ }
+ }
+
+ // True if a TypeFunPtr, without advancing parser
+ boolean isTypeFun() {
+ int old = P.pos();
+ if( parseFunctionType()==null ) return false;
+ P.pos(old);
+ return true;
+ }
+
+ TypeStruct latestStruct( TypeStruct ts ) {
+ Type t = Parser.TYPES.get(ts._name);
+ return t instanceof TypeStruct ts2 ? ts2 : ts;
+ }
+
+ Field fieldOrOpen( TypeStruct ts, String name, boolean xfinal ) {
+ ts = latestStruct(ts);
+ Field fld = ts.field(name);
+ if( fld != null ) return fld;
+ if( !ts._open || !ts._fref ) return null;
+ fld = Field.make(name,Type.BOTTOM,P._code.alias(P._ref._cname),xfinal);
+ Parser.TYPES.put(ts._name,ts = ts.add(fld));
+ return fld;
+ }
+
+ // typeName: walk known local types first, then enclosing namespaces, then
+ // file modules. A missing suffix of a known type belongs to field parsing.
+ private String typeName(String name) {
+ if( Parser.TYPES.containsKey(name) ) return dottedName(name,false);
+ String root = P._nestedType;
+ int dot = root.indexOf('.');
+ if( dot!=-1 ) root = root.substring(0,dot);
+ if( name.equals(root) ) return rootedName(name);
+
+ for( String nest = P._nestedType; ; ) {
+ String full = nest+"."+name;
+ if( Parser.TYPES.containsKey(full) ) return dottedName(full,false);
+ int idx = nest.lastIndexOf('.');
+ if( idx==-1 ) break;
+ nest = nest.substring(0,idx);
+ }
+ CompUnit cu = ParseAll.findCompUnit(P._code,P._ref,name);
+ return dottedName(cu==null ? name : cu._cname,true);
+ }
+
+ private String dottedName(String name, boolean forward) {
+ while( true ) {
+ int old = P.pos();
+ if( !P.match(".") ) return name;
+ String part = P.matchId();
+ if( part==null || (!forward && !Parser.TYPES.containsKey(name+"."+part)) ) {
+ P.pos(old);
+ return name;
+ }
+ name += "."+part;
+ }
+ }
+
+ // A root-qualified name may introduce an as-yet-undeclared nested type
+ // inside the current lexical namespace. Once forward, keep its full tail.
+ private String rootedName(String name) {
+ boolean forward = false;
+ while( true ) {
+ int old = P.pos();
+ if( !P.match(".") ) return name;
+ String part = P.matchId();
+ if( part==null ) { P.pos(old); return name; }
+ String full = name+"."+part;
+ boolean known = Parser.TYPES.containsKey(full);
+ if( !known && !forward &&
+ !(P._nestedType.equals(name) || P._nestedType.startsWith(name+".")) ) {
+ P.pos(old);
+ return name;
+ }
+ name = full;
+ forward |= !known;
+ }
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/Var.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/Var.java
new file mode 100644
index 00000000..7d3be3c2
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/Var.java
@@ -0,0 +1,54 @@
+package com.seaofnodes.simple.parser;
+
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Utils;
+
+
+/**
+ * The tracked fields are now complex enough to deserve a array-of-structs layout
+ */
+public class Var {
+
+ public final String _name; // Declared name
+ // These fields are not final for forward reference late updates or
+ // promotions.
+ public int _idx; // index in containing scope
+ private Type _type; // Declared type
+ public boolean _final; // Final field
+ public boolean _fref; // Forward ref
+ // Parser-only definite-initialization marker. The current binding's
+ // nullable type carries the path-sensitive state through SSA merges.
+ public boolean _uninit;
+ public int _extern; // C storage alias; zero for ordinary variables
+ public Lexer _loc; // Source location
+
+ public Var(int idx, String name, Type type, boolean xfinal, Lexer loc ) {
+ this(idx,name,type,xfinal,loc,false);
+ }
+ public Var(int idx, String name, Type type, boolean xfinal, Lexer loc, boolean fref) {
+ _idx = idx;
+ _name = name;
+ _type = type;
+ _final = xfinal;
+ _fref = fref;
+ _loc = loc;
+ }
+ public Type type() { return _type; }
+ public Type _type() { return _type; } // ScopeNode printer needs to have no side effects
+
+ // Forward reference variables (not types) must be BOTTOM and
+ // distinct from inferred variables
+ public boolean isFRef() { return _fref; }
+
+ public void defFRef( Type type, boolean xfinal, Lexer loc ) {
+ assert isFRef() && xfinal;
+ _type = type;
+ _final = true;
+ _fref = false;
+ _loc = loc;
+ }
+
+ @Override public String toString() {
+ return (_fref ? "FREF " : "")+(_uninit ? "UNINIT " : "")+_type.toString()+(_final ? " ": " !")+_name;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/parser/package-info.java b/chapter29/src/main/java/com/seaofnodes/simple/parser/package-info.java
new file mode 100644
index 00000000..7212e629
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/parser/package-info.java
@@ -0,0 +1,14 @@
+/**
+ * Handwritten recursive-descent parsing directly into Sea-of-Nodes IR.
+ *
+ * <p>{@link com.seaofnodes.simple.parser.Parser} owns the shared cursor and
+ * scope. Package-local components implement the grammar families: statements,
+ * expressions, declarations, types, functions, and structs. References handles
+ * use-side name/field resolution; Allocations, ControlFlow, and Conversions
+ * contain their shared IR construction. Lexer also supplies location snapshots.
+ *
+ * <p>See {@code chapter29/docs/29-grammar.md} for the production-to-method map.
+ * Evaluation order and scope lifetimes are explicit: loop updates and declared
+ * constructor bodies are the two deferred source fragments. There is no AST.
+ */
+package com.seaofnodes.simple.parser;
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java b/chapter29/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
new file mode 100644
index 00000000..e8218afd
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
@@ -0,0 +1,101 @@
+package com.seaofnodes.simple.print;
+
+import com.seaofnodes.print.AssemblyAdapter;
+import com.seaofnodes.print.ASMPrinter.Data;
+import com.seaofnodes.print.ASMPrinter.Pool;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import java.util.ArrayList;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.util.BAOS;
+import java.util.HashMap;
+
+/** Machine-specific facts; traversal, columns and byte display live in print/. */
+public final class ASMPrinter extends AssemblyAdapter<Node> {
+ private final CodeGen _code;
+ private final Encoding _enc;
+ private ASMPrinter(CodeGen code) {
+ super(new IRPrinter());
+ _code=code;
+ _enc=code._encoding;
+ }
+ public static SB print(SB sb, CodeGen code) {
+ return sb.p(new com.seaofnodes.print.ASMPrinter<>(new ASMPrinter(code)).print());
+ }
+ @Override public ArrayList<Node> blocks() {
+ var blocks=new ArrayList<Node>();
+ var cfg=_enc==null ? _code._cfg : _enc._cfg;
+ if( cfg!=null ) for( Node n : cfg ) blocks.add(n);
+ return blocks;
+ }
+ @Override public String op(Node n) { return n instanceof MachNode m ? m.op() : n.label(); }
+ @Override public String register(Node n) { return _code.reg(n); }
+ @Override public String comment(Node n) { return n.comment(); }
+ @Override public String phiName(Node n) { return ((PhiNode)n)._label; }
+ @Override public String operands(Node n) {
+ SB sb=new SB();
+ if( n instanceof MachNode m ) m.asm(_code,sb);
+ else if( !(n._type instanceof TypeMem) ) {
+ sb.p(n._nid).p(": ");
+ for( Node def : n._inputs ) sb.p(def==null ? "___" : Integer.toString(def._nid)).p(' ');
+ }
+ return sb.toString();
+ }
+ @Override public boolean labelBlock(Node n) {
+ return !(n instanceof FunNode) && !(n instanceof IfNode) && !(n instanceof CallNode) &&
+ !(n instanceof CallEndNode) && !(n instanceof CProjNode && ir.input0(n) instanceof CallEndNode);
+ }
+ @Override public boolean hidden(Node n) {
+ if( n instanceof PhiNode || n instanceof ProjNode )
+ if( n._type instanceof TypeMem || n._type instanceof TypeRPC ) return true;
+ if( postAlloc() && n instanceof CalleeSaveNode ) return true;
+ if( n instanceof MemMergeNode ) return true;
+ if( n instanceof EscapeNode ) return true;
+ return n.getClass()==ConstantNode.class;
+ }
+ @Override public boolean postAlloc() { return _code._phase.ordinal()>CodeGen.Phase.RegAlloc.ordinal() ||
+ (_code._phase==CodeGen.Phase.RegAlloc && _code._regAlloc!=null && _code._regAlloc.done()); }
+ @Override public boolean encoded() { return _enc!=null && _code._phase.ordinal()>=CodeGen.Phase.Encoding.ordinal(); }
+ @Override public int defaultSize() { return _code._mach==null ? 2 : _code._mach.defaultOpSize(); }
+ @Override public boolean littleEndian() { return _code._asmLittle; }
+ @Override public boolean prologue(Node n) { return ((FunNode)n)._frameAdjust!=0; }
+ @Override public byte[] bytes() { return encoded() ? _enc._bits.buf() : null; }
+ @Override public int offset(Node n) { return encoded() ? _enc.opStart(n) : -1; }
+ @Override public int size(Node n) { return encoded() ? _enc.opLen(n)&255 : 0; }
+ private static ArrayList<Data> entries(Iterable<Encoding.Relo> relos, boolean ro) {
+ var data=new ArrayList<Data>();
+ for( Encoding.Relo r : relos ) {
+ if( r.readOnly()!=ro ) continue;
+ int align=1<<r._align;
+ int size=r._t instanceof TypeStruct ? (r._structSize+align-1)&-align : align;
+ data.add(new Data(r._t,r._t.str(),r._align,size));
+ }
+ return data;
+ }
+ @Override public ArrayList<Pool> pools(int codeEnd) {
+ var pools=new ArrayList<Pool>();
+ if( !encoded() ) return pools;
+ int base=(codeEnd+15)&-16;
+ if( _enc._cpool.size()>0 )
+ pools.add(new Pool("Constant Pool",_enc._cpool.buf(),0,base,objects(true)));
+ base=(base+_enc._cpool.size()+15)&-16;
+ if( _enc._sdata.size()>0 )
+ pools.add(new Pool("Static Data",_enc._sdata.buf(),0,base,objects(false)));
+ return pools;
+ }
+ private ArrayList<Data> objects(boolean ro) {
+ var data=new ArrayList<Data>();
+ for( var obj : _enc._data.entries() )
+ if( !obj.external && obj.readOnly==ro )
+ data.add(new Data(obj,obj.symbol+": "+obj.type.str(),obj.alignment,obj.size));
+ return data;
+ }
+ // Retain this diagnostic entry point for checking that pool printing never lays out types.
+ static int printConstantPool(int address, SB sb, BAOS bits, HashMap<Node,Encoding.Relo> relos,
+ boolean ro, String name) {
+ sb.p(com.seaofnodes.print.ASMPrinter.pool(new Pool(name,bits.buf(),0,address,entries(relos.values(),ro))));
+ return address+bits.size();
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/print/IRPrinter.java b/chapter29/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
new file mode 100644
index 00000000..531a549a
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
@@ -0,0 +1,60 @@
+package com.seaofnodes.simple.print;
+
+import com.seaofnodes.print.IRAdapter;
+import com.seaofnodes.simple.node.*;
+import java.util.ArrayList;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.codegen.CodeGen;
+
+/** This chapter's read-only IR facts and the familiar debugger entry points. */
+public final class IRPrinter extends IRAdapter<Node> {
+ private static final com.seaofnodes.print.IRPrinter<Node> PRINT =
+ new com.seaofnodes.print.IRPrinter<>(new IRPrinter());
+ @Override public boolean global(Node n) { return n.isConst(); }
+ @Override public String type(Node n) { return n._type==null ? "" : n._type.str(); }
+ @Override public String inputMark(Node n, Node def) { return n instanceof MemMergeNode && def instanceof MemMergeNode ? "^" : " "; }
+ @Override public Kind kind(Node n) {
+ if( n instanceof StartCUNode ) return Kind.UNIT;
+ if( n instanceof StopCUNode ) return Kind.UNIT;
+ if( n instanceof StartNode ) return Kind.START;
+ if( n instanceof StopNode ) return Kind.STOP;
+ if( n instanceof FunNode ) return Kind.FUN;
+ if( n instanceof LoopNode ) return Kind.LOOP;
+ if( n instanceof RegionNode ) return Kind.REGION;
+ if( n instanceof ParmNode ) return Kind.PARM;
+ if( n instanceof PhiNode ) return Kind.PHI;
+ if( n instanceof CallEndNode ) return Kind.CALL_END;
+ if( n instanceof CallNode ) return Kind.CALL;
+ if( n instanceof ReturnNode ) return Kind.RETURN;
+ if( n instanceof ConstantNode ) return Kind.CONSTANT;
+ if( n instanceof CProjNode ) return Kind.CPROJ;
+ if( n instanceof ProjNode ) return n instanceof CFGNode ? Kind.CPROJ : Kind.PROJ;
+ if( n instanceof CFGNode ) return Kind.CTRL;
+ if( n instanceof MultiNode ) return Kind.MULTI;
+ return Kind.DATA;
+ }
+ @Override public int index(Node n) {
+ if( n instanceof ProjNode p ) return p._idx;
+ if( n instanceof CProjNode p ) return p._idx;
+ return n._nid;
+ }
+ @Override public String functionName(Node n) { return ((FunNode)n)._name==null ? "" : ((FunNode)n)._name; }
+ @Override public String signature(Node n) {
+ FunNode fun=(FunNode)n;
+ return (fun._name==null ? "" : fun._name)+" "+fun.sig().str();
+ }
+
+ public static String prettyPrint(Node n, int depth) { return PRINT.prettyPrint(n,depth); }
+ public static SB printLine(Node n, SB sb) { return sb.p(PRINT.line(n)); }
+
+ public static String prettyPrint(CodeGen code) {
+ var units=new ArrayList<com.seaofnodes.print.IRPrinter.Unit<Node>>();
+ for( var cu : code._compunits.values() ) {
+ var funs=new ArrayList<Node>();
+ for( FunNode fun : code._linker )
+ if( fun!=null && fun._compunit==cu ) funs.add(fun);
+ units.add(new com.seaofnodes.print.IRPrinter.Unit<>(cu._cname,cu._start,cu._stop,funs));
+ }
+ return PRINT.program(code._start,code._stop,units);
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter29/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java
new file mode 100644
index 00000000..9ca0ec51
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java
@@ -0,0 +1,80 @@
+package com.seaofnodes.simple.print;
+
+import com.seaofnodes.graph.GraphAdapter;
+import com.seaofnodes.graph.GraphSnapshot.*;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.type.TypeMem;
+
+/** Chapter 25's view of the IR, including partially constructed parse-time graphs. */
+public class SimpleGraphAdapter extends GraphAdapter<Node> {
+
+ @Override protected boolean show(Node n) {
+ if( n != CodeGen.CODE.ZERO && n != CodeGen.CODE.XCTRL ) return true;
+ for( int i=0; i<n.nOuts(); i++ )
+ if( n.out(i) != null ) return true;
+ return false; // The parser's keep is not a graph use.
+ }
+
+ @Override protected String edgeName(Node n, int i) {
+ return n instanceof ScopeNode scope && i < scope._vars.size()
+ ? scope.var(i)._name : null;
+ }
+ @Override protected int edgeJump(Node n, int i) {
+ return n instanceof CallEndNode && i>0 && n.in(i) instanceof ReturnNode ret
+ ? ref(ret.fun()) : 0;
+ }
+ @Override protected int projectionIndex(Node n) { return n instanceof Proj p ? p.idx() : -1; }
+
+ @Override protected boolean folding(Node n) {
+ if( n instanceof FunNode fun ) return fun.folding();
+ if( n instanceof ReturnNode ret ) return ret.fun().folding();
+ if( n instanceof CallEndNode cend ) return cend.folding();
+ if( n instanceof CallNode )
+ for( int i=0; i<n.nOuts(); i++ )
+ if( n.out(i) instanceof CallEndNode cend && cend.folding() ) return true;
+ return false;
+ }
+
+ @Override protected Kind kind(Node n) {
+ if( n instanceof StopNode ) return Kind.STOP;
+ if( n instanceof ScopeNode ) return Kind.SCOPE;
+ if( n instanceof StartCUNode ) return Kind.UNIT;
+ if( n instanceof FunNode ) return Kind.FUN;
+ // Start inherits Loop for whole-program analysis, but is not a source loop.
+ if( n instanceof StartNode ) return Kind.START;
+ if( n instanceof LoopNode ) return Kind.LOOP;
+ if( n instanceof RegionNode ) return Kind.REGION;
+ if( n instanceof PhiNode ) return Kind.PHI;
+ if( n instanceof CFGNode ) return Kind.CTRL;
+ return isMem(n) ? Kind.MEM : Kind.DATA;
+ }
+
+ @Override protected Role role(Node n, int i) {
+ if( n instanceof ScopeNode || n instanceof ConstantNode || n instanceof FunPtrNode ||
+ n instanceof CtrlNode || n instanceof XCtrlNode )
+ return Role.ASSOC;
+ if( n instanceof PhiNode )
+ return i == 0 ? Role.ASSOC : isMem(n) ? Role.MEM : Role.DATA;
+ if( n instanceof Proj )
+ return n instanceof CFGNode ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA;
+ // StartCU inherits Region, but slot 0 really connects it to Start.
+ if( n instanceof StartCUNode && i == 0 ) return Role.CTRL;
+ if( n instanceof RegionNode && i == 0 ) return Role.ASSOC;
+ // Preserve known slot roles even when an input is not attached/typed yet.
+ if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL;
+ if( n instanceof MemMergeNode ||
+ (i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode)) ||
+ (n instanceof EscapeNode && i >= 2) ) return Role.MEM;
+ Node def = n.in(i);
+ if( def instanceof CFGNode ) return Role.CTRL;
+ if( def != null && isMem(def) ) return Role.MEM;
+ return Role.DATA;
+ }
+
+ private boolean isMem(Node n) {
+ // These Phi subclasses are memory even before their first type computation.
+ return n instanceof MemPhiNode || n instanceof BulkMemPhiNode || n.isMem() || n._type instanceof TypeMem;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter29/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
new file mode 100644
index 00000000..f2ed2f72
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
@@ -0,0 +1,35 @@
+package com.seaofnodes.simple.print;
+
+import com.seaofnodes.graph.GraphCapture;
+import com.seaofnodes.graph.GraphViewer;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.Node;
+import java.util.ArrayList;
+import java.io.IOException;
+
+/** Chapter 25's compilation context; capture and event bookkeeping are shared. */
+public class SimpleGraphObserver extends GraphCapture<Node> {
+ private CodeGen _code;
+ public SimpleGraphObserver() { super(new SimpleGraphAdapter()); }
+ public static void main(String[] args) throws IOException {
+ GraphViewer.run(new SimpleGraphObserver());
+ }
+ @Override protected void compile(String src) {
+ _code = new CodeGen(src);
+ _code._obs = this;
+ _code.driver(CodeGen.Phase.TypeCheck);
+ }
+ @Override protected String phase() { return _code._phase.name(); }
+ @Override protected int pos() { return _code._phase == CodeGen.Phase.Parse ? _code.P.pos() : -1; }
+ @Override protected void detach() {
+ if( _code != null ) _code._obs = null;
+ _code = null;
+ }
+ @Override protected Node scope() { return _code.P._scope; }
+ @Override protected void roots(ArrayList<Node> roots) {
+ roots.add(_code._stop);
+ if( _code._phase == CodeGen.Phase.Parse ) {
+ roots.addAll(_code.P._xScopes);
+ }
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/sys.java b/chapter29/src/main/java/com/seaofnodes/simple/sys.java
new file mode 100644
index 00000000..45106770
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/sys.java
@@ -0,0 +1,25 @@
+package com.seaofnodes.simple;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+
+/** Default included namespaces for Simple.
+
+ You can think of this as {@code #include <stdio.h>} for C or {@code import java.lang.*} for Java.
+ */
+public abstract class sys {
+
+ public static final String SYS;
+
+ static {
+ try {
+ SYS = Files.readString(Path.of("src/main/smp/sys.smp"));
+ }
+ catch( Exception e ) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter29/src/main/java/com/seaofnodes/simple/type/Field.java
new file mode 100644
index 00000000..beaa719c
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/Field.java
@@ -0,0 +1,154 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.SB;
+import java.util.*;
+
+/**
+ * Represents a field in a struct. This is not a Type in the type system.
+ */
+public class Field extends Type {
+ static final Field[] EMPTY = new Field[0];
+
+ // The pair {fieldName,type} uniquely identifies a field.
+
+ // Field name
+ public String _fname;
+ // Type of the field
+ public Type _t;
+ // Unique memory alias, not sensibly part of a "type" but very convenient here.
+ public int _alias;
+ // Field is only written in the constructor
+ public boolean _final;
+ // Native C storage, outside the containing object's layout.
+ public boolean _extern;
+
+ private static final Ary<Field> FREE = new Ary<>(Field.class);
+ private Field(String fname, Type type, int alias, boolean xfinal, boolean external ) { super(TFLD); init(fname,type,alias,xfinal,external); }
+ private Field init(String fname, Type type, int alias, boolean xfinal, boolean external ) {
+ _fname = fname;
+ _t = type;
+ _alias = alias;
+ _final = xfinal;
+ _extern = external;
+ return this;
+ }
+
+ // Return a filled-in Field; either from free list or alloc new.
+ private static Field malloc(String fname, Type type, int alias, boolean xfinal, boolean external ) {
+ return FREE.isEmpty() ? new Field(fname,type,alias,xfinal,external) : FREE.pop().init(fname,type,alias,xfinal,external);
+ }
+ // Malloc-from
+ public Field malloc( ) { return malloc(_fname,null,_alias,_final,_extern); }
+ @Override Field free(Type t) {
+ Field f = (Field)t;
+ assert !f.isFree() && !f._terned;
+ f._t = null;
+ f._dual = null;
+ f._hash = 0;
+ FREE.push(f);
+ return this;
+ }
+ @Override boolean isFree() { return _t ==null; }
+
+ // Make and intern with listed fields
+ public static Field make( String fname, Type type, int alias, boolean xfinal ) {
+ return make(fname,type,alias,xfinal,false);
+ }
+ public static Field make( String fname, Type type, int alias, boolean xfinal, boolean external ) {
+ Field f = malloc(fname,type,alias,xfinal,external);
+ Field f2 = f.intern();
+ if( f2==f ) return f;
+ return VISIT.isEmpty() ? f2.free(f) : f2.delayFree(f);
+ }
+ public Field makeFrom( Type type ) {
+ return type == _t ? this : make(_fname,type,_alias,_final,_extern);
+ }
+ public Field makeFrom( boolean xfinal ) {
+ return xfinal == _final ? this : make(_fname,_t,_alias,xfinal,_extern);
+ }
+
+ // Cycle-making-breaking
+ void setType(Type t) { assert _t ==null; _t = t; }
+
+ public static final Field BOT = make(" ",Type.BOTTOM,-999,false);
+ public static final Field TEST = make("test",Type.NIL,-2,false);
+ public static final Field TEST2 = make("test",Type.NIL,-2,true);
+ public static final Field FLT = make("flt ",TypeFloat.F32,-2,false);
+ public static final Field EXT = make("test",TypeInteger.I32,-2,false,true);
+ public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(TEST2); ts.add(FLT); ts.add(EXT); }
+
+ @Override Field xmeet( Type that ) {
+ Field fld = (Field)that; // Invariant
+ if( this==BOT || fld==BOT ) return BOT;
+ if( this==BOT.dual() ) return fld ;
+ if( fld ==BOT.dual() ) return this;
+ if( _fname!=fld._fname ) { assert !_fname.equals(fld._fname); return BOT; }
+ assert _alias==fld._alias;
+ if( _extern!=fld._extern ) return BOT;
+ return make(_fname, _t.meet(fld._t ), _alias, _final | fld._final,_extern);
+ }
+
+ @Override
+ Field xdual() { return malloc(_fname, _t.dual(),_alias,!_final,_extern); }
+
+ @Override Field rdual() {
+ if( _dual!=null ) return dual();
+ assert !_terned;
+ Field d = malloc(_fname,null,_alias,!_final,_extern);
+ (_dual = d)._dual = this; // Cross link duals
+ d._t = _t._terned ? _t.dual() : _t.rdual();
+ return d;
+ }
+
+ @Override boolean _isConstant() { return _t._isConstant(); }
+ @Override boolean _isFinal() { return _final && _t._isFinal(); }
+ @Override Field _makeRO() { return _final ? this : make(_fname, _t._makeRO(),_alias,true,_extern); }
+ @Override Field _close( String name, HashMap<String, Type> TYPES ) { return makeFrom( _t._close(name, TYPES )); }
+
+ @Override Type _upgradeType(HashMap<String,Type> TYPES) {
+ return makeFrom(_t._upgradeType(TYPES));
+ }
+
+ // Override in subclasses
+ int hash() { return _fname.hashCode() ^ _t.hashCode() ^ _alias ^ (_final ? 1024 : 0) ^ (_extern ? 2048 : 0); }
+
+ private boolean static_eq( Field f ) {
+ return _fname.equals(f._fname) && _alias==f._alias && _final==f._final && _extern==f._extern && _t !=null && f._t !=null;
+ }
+ @Override boolean eq( Type t ) {
+ Field f = (Field)t;
+ return static_eq(f) && _t ==f._t;
+ }
+ @Override boolean cycle_eq(Type t) {
+ if( this==t ) return true;
+ Field f = (Field)t;
+ return static_eq(f) && _t.cycle_eq(f._t );
+ }
+
+ @Override public int nkids() { return 1; }
+ @Override public Type at( int idx ) { return _t; }
+ @Override public void set( int idx, Type t ) { _t = t; }
+ // Tags: final/!final; +alias+name+external-storage flag
+ @Override int TAGOFF() { return 2; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ baos.write(TAGOFFS[_type] + (_final ? 1 : 0));
+ baos.packed2(_alias);
+ baos.packed2(strs.get(_fname));
+ baos.write(_extern ? 1 : 0);
+ }
+ static Field packed( int tag, BAOS bais, String[] strs ) {
+ int alias = bais.packed2();
+ String fname = strs[bais.packed2()];
+ return malloc(fname,null,alias,(tag&1)==1,bais.read()!=0);
+ }
+
+ @Override
+ public SB _print( SB sb, BitSet visit, boolean html ) {
+ sb.p(_extern ? "extern C " : "").p(_final?"":"!").p(_fname).p(":").p(_alias).p(" : ");
+ return _t ==null ? sb.p("---") : _t.print(sb,visit,html);
+ }
+
+ @Override public String str() { return (_final?"":"!")+_fname; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter29/src/main/java/com/seaofnodes/simple/type/Type.java
new file mode 100644
index 00000000..6c8a3e8d
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/Type.java
@@ -0,0 +1,667 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.parser.Parser;
+import com.seaofnodes.simple.codegen.GlobalBits;
+import com.seaofnodes.simple.util.*;
+import java.util.*;
+
+/**
+ * These types are part of a Monotone Analysis Framework,
+ * @see <a href="https://www.cse.psu.edu/~gxt29/teaching/cse597s21/slides/08monotoneFramework.pdf">see for example this set of slides</a>.
+ * <p>
+ * The types form a lattice; @see <a href="https://en.wikipedia.org/wiki/Lattice_(order)">a symmetric complete bounded (ranked) lattice.</a>
+ * <p>
+ * This wild lattice theory will be needed later to allow us to easily beef up
+ * the analysis and optimization of the Simple compiler... but we don't need it
+ * now, just know that it is coming along in a later Chapter.
+ * <p>g
+ * One of the fun things here is that while the theory is deep and subtle, the
+ * actual implementation is darn near trivial and is generally really obvious
+ * what we're doing with it. Right now, it's just simple integer math to do
+ * simple constant folding e.g. 1+2 == 3 stuff.
+ */
+
+public class Type /*implements Cloneable*/ {
+ // Interning tables
+
+ // Main intern table; a collection of all unique types.
+ static final HashMap<Type,Type> INTERN = new HashMap<>();
+ // Starting intern table; used to rapidly reset the main table between tests
+ private static final HashMap<Type,Type> INTERN0= new HashMap<>();
+ // Lifetime management during cyclic installs is complex; this is used to
+ // track created-but-already-interned objects
+ private static final Ary<Type> FREES = new Ary<>(Type.class);
+ // "visit" bit management, based solely on _uid
+ static final BitSet BITS = new BitSet();
+
+ // "visit" bit management. Key varies by kind of visit:
+
+ // - "close" an open cyclic type. Used by the Parser, it will hit on
+ // same-named structs, even if the structs have different states of
+ // completion/fields.
+ // - "uid/name" - Either the UID or name works (unlike "close" which must
+ // use the name). Used to make cyclic copies with changes, like make
+ // read-only where there should be only 1 copy of a name in a cycle.
+ // - "pair of uids" - For recursive MEET, where we need to key off two types
+ static final HashMap<Object,Type> VISIT = new HashMap<>();
+
+ // Every Type has a UID to allow visit bits on cyclic visits
+ private static int UID=1;
+ public char _uid; // Unique ID for every type
+
+ // ----------------------------------------------------------
+ // Simple types are implemented fully here. "Simple" means: the code and
+ // type hierarchy are simple, not that the Type is conceptually simple.
+ static final byte TBOT = 0; // Bottom (ALL)
+ static final byte TTOP = 1; // Top (ANY)
+ static final byte TCTRL = 2; // Ctrl flow bottom
+ static final byte TXCTRL = 3; // Ctrl flow top (mini-lattice: any-xctrl-ctrl-all)
+ static final byte TNIL = 4; // low null of all flavors
+ static final byte TXNIL = 5; // high or choice null
+ static final byte TSIMPLE = 5; // End of the Simple Types
+
+ static final byte TSCALAR = 6; // Integer, float, memory pointer or function pointer
+ static final byte TPTR = 7; // All nil-able pointer values
+ static final byte TINT = 8; // All Integers; see TypeInteger
+ static final byte TFLT = 9; // All Floats ; see TypeFloat
+ static final byte TCONARY =10; // Constant array
+ static final byte TRPC =11; // Return Program Control (Return PC or RPC)
+
+ static final byte TCYCLIC =12; // Has internal pointers, needs recursive treatment
+ static final byte TMEMPTR =12; // Memory pointer to a struct type
+ static final byte TFUNPTR =13; // Function pointer; unique signature and code address (just a bit)
+ static final byte TMEM =14; // All memory (alias 0) or A slice of memory - with specific alias
+ static final byte TFLD =15; // Named fields in structs
+ static final byte TSTRUCT =16; // Structs; tuples with named fields
+ static final byte TTUPLE =17; // Tuples; finite collections of unrelated Types, kept in parallel
+
+ static final byte TMAX =18;
+
+ // Basic RTTI, useful for a lot of fast tests.
+ public final byte _type;
+ public boolean _terned;
+
+ public boolean is_simple() { return _type <= TSIMPLE; }
+ public boolean is_nokids() { return _type < TCYCLIC; }
+ private static final String[] STRS = new String[]{"Bot","Top","Ctrl","~Ctrl","null","~nil"};
+ protected Type(byte type) {
+ _type = type; // RTTI
+ _uid = (char)UID++; // A unique ID for every type
+ if( UID > 32000 )
+ type = type;
+ assert _uid!=0; // Overflow
+ }
+
+ public static final Type BOTTOM = new Type( TBOT ).intern(); // ALL
+ public static final Type TOP = BOTTOM.dual();
+ public static final Type CONTROL = new Type( TCTRL ).intern(); // Ctrl
+ public static final Type XCONTROL = CONTROL.dual();
+ public static final Type NIL = new Type( TNIL ).intern(); // low null of all flavors
+ public static final Type XNIL = NIL.dual();
+ public static Type[] gather() {
+ ArrayList<Type> ts = new ArrayList<>();
+ ts.add(BOTTOM);
+ ts.add(CONTROL);
+ ts.add(NIL);
+ TypeScalar.gather(ts);
+ TypeNil.gather(ts);
+ TypePtr.gather(ts);
+ TypeInteger.gather(ts);
+ TypeFloat.gather(ts);
+ TypeMemPtr.gather(ts);
+ TypeFunPtr.gather(ts);
+ TypeMem.gather(ts);
+ Field.gather(ts);
+ TypeStruct.gather(ts);
+ TypeTuple.gather(ts);
+ TypeRPC.gather(ts);
+ TypeConAry.gather(ts);
+
+ int sz = ts.size();
+ for( int i = 0; i < sz; i++ )
+ ts.add(ts.get(i).dual());
+ return ts.toArray(new Type[ts.size()]);
+ }
+
+ // Is high or on the lattice centerline.
+ public boolean isHigh () { return _type==TTOP || _type==TXCTRL || _type==TXNIL; }
+ public boolean isHighOrConst() { return isHigh() || isConstant(); }
+
+ // ----------------------------------------------------------
+
+ // Notes on Type interning: At the moment it is not easy to reset the
+ // interned types because we hold static references to several types and
+ // these are scattered around. This means the INTERN cache will retain all
+ // types from every run of the Parser. For this to work correctly types
+ // must be rigorous about defining when they are the same. Also types need
+ // to be immutable once defined. The rationale for interning is
+ // *correctness* with cyclic type definitions. Simple structural recursive
+ // checks go exponential with merely sharing, but with cycles they will
+ // stack overflow and crash. Interning means we do not need to have sharing
+ // checks with every type compare, only during interning.
+
+ // Factory method which interns "this"
+ @SuppressWarnings("unchecked")
+ public <T extends Type> T intern() {
+ //assert check();
+ assert !_terned; // Do not ask for already-interned
+ T t2 = (T)INTERN.get(this);
+ if( t2!=null ) {
+ assert t2._dual != null; // Prior is complete with dual
+ assert this != t2; // Do not hashcons twice, should not get self back
+ return t2; // Return prior
+ }
+ // Mid recursive type construction
+ if( !VISIT.isEmpty() && _type >= TCYCLIC ) {
+ // No intern attempt at all
+ return (T)this;
+ }
+ // Not in type table
+ _dual = null; // No dual yet
+ INTERN.put(this,this); // Put in table without dual
+ _terned = true;
+ //Util.hash_quality_check_per(INTERN,"INTERN");
+ T d = xdual(); // Compute dual without requiring table lookup, and not setting name
+ _dual = d;
+ if( this==d ) return d; // Self-symmetric? Dual is self
+ assert !equals(d); // Self-symmetric is handled by caller
+ assert d._dual==null; // Else dual-dual not computed yet
+ assert INTERN.get(d)==null;
+ d._dual = (T)this;
+ INTERN.put(d,d); // Install dual also
+ d._terned = true;
+ //assert check();
+ return (T)this;
+ }
+
+ // Recursive visit elsewhere, interns with dual already
+ <T extends Type> T _intern() {
+ //assert check();
+ assert !_terned;
+ Type t = INTERN.get(this);
+ if( t!=null ) return (T)t.delayFree(this).delayFree(_dual);
+ assert !INTERN.containsKey( _dual );
+ INTERN.put(this,this);
+ INTERN.put(_dual,_dual);
+ _terned = _dual._terned = true;
+ //assert check();
+ return (T)this;
+ }
+
+ private static boolean check() {
+ for( Type t : INTERN.keySet() ) {
+ Type t2 = INTERN.get(t);
+ assert t==t2;
+ }
+ return true;
+ }
+
+ public static Type find(int uid) {
+ for( Type t : INTERN.keySet() )
+ if( t._uid==uid ) return t;
+ return null;
+ }
+
+
+ int _hash; // Hash cache; not-zero when set.
+ @Override
+ public final int hashCode() {
+ if( _hash!=0 ) return _hash;
+ _hash = hash();
+ if( _hash==0 ) _hash = 0xDEADBEEF; // Bad hash from subclass; use some junk thing
+ return _hash;
+ }
+ // Override in subclasses
+ int hash() { return _type; }
+
+ @Override
+ public final boolean equals( Object o ) {
+ if( o==this ) return true;
+ if( !(o instanceof Type t)) return false;
+ if( _type != t._type ) return false;
+ return eq(t);
+ }
+ // Overridden in subclasses; subclass can assume "this!=t" and java classes are same
+ boolean eq(Type t) { return this==t; }
+ boolean cycle_eq(Type t) { assert is_nokids(); return _type==t._type && eq(t); }
+ // A pair of uids
+ int pid( Type that ) {
+ return _uid < that._uid ? (_uid<<16 | that._uid) : (that._uid<<16 | _uid);
+ }
+
+ // At/Set child at 'idx' to t
+ public Type at( int idx ) { throw Utils.TODO("Should not reach here: type has no children"); }
+ public void set( int idx, Type t ) { throw Utils.TODO("Should not reach here: type has no children"); }
+ public int nkids() { return 0; } // Number of kids
+
+
+ // Clear and re-insert the basic Type INTERN table
+ public static void reset() {
+ VISIT.clear();
+ FREES.clear();
+ if( INTERN0.isEmpty() ) {
+ // First time run all <clinit> and collect basic types
+ Type t = TypeRPC .BOT; // Force class load, intern
+ Type u = TypeTuple.BOT; // Force class load, intern
+ Type w = TypePtr .PTR; // Force class load, intern
+ INTERN0.putAll(INTERN);
+ } else {
+ // Later times, reset back to first time
+ VISIT.put(0,CONTROL); // Defeat assert
+ for( Iterator<Type> it = INTERN.keySet().iterator(); it.hasNext(); ) {
+ Type t = it.next();
+ if( !INTERN0.containsKey(t) )
+ { t._terned=false; t.free(t); }
+ it.remove();
+ }
+ VISIT.clear();
+ INTERN.putAll(INTERN0);
+ }
+ }
+
+ // ----------------------------------------------------------
+ public final Type meet(Type t) {
+ assert _terned && t._terned;
+ // Shortcut for the self case
+ if( t == this ) return this;
+ // Same-type is always safe in the subclasses
+ if( _type==t._type ) return xmeet(t);
+ // TypeNil vs TypeNil meet
+ if( this instanceof TypeNil ptr0 && t instanceof TypeNil ptr1 )
+ return ptr0.nmeet(ptr1);
+ // Reverse; xmeet 2nd arg is never "is_simple" and never equal to "this".
+ if( is_simple() ) return this.xmeet(t );
+ if( t.is_simple() ) return t .xmeet(this);
+ // TypeConAry meet its element type is OK
+ if( this instanceof TypeConAry tcon && t instanceof TypeInteger ti ) return tcon.ymeet(ti);
+ if( t instanceof TypeConAry tcon && this instanceof TypeInteger ti ) return tcon.ymeet(ti);
+ // Two distinct scalar families meet at the scalar envelope.
+ if( this instanceof TypeScalar scalar0 && t instanceof TypeScalar scalar1 )
+ return scalar0.smeet(scalar1);
+ // Mixing 2 unrelated types
+ return Type.BOTTOM;
+ }
+
+ // Compute meet right now. Overridden in subclasses.
+ // Handle cases where 'this.is_simple()' and unequal to 't'.
+ // Subclassed xmeet calls can assert that '!t.is_simple()'.
+ Type xmeet(Type t) {
+ assert is_simple(); // Should be overridden in subclass
+ // ANY meet anything is thing; thing meet ALL is ALL
+ if( _type==TBOT || t._type==TTOP ) return this;
+ if( _type==TTOP || t._type==TBOT ) return t;
+
+ // RHS TypeNil vs NIL/XNIL
+ if( _type==TNIL || _type==TXNIL ) {
+ if( t instanceof TypeNil ptr ) return _type==TNIL ? ptr.meet0() : ptr.meetX();
+ if( t instanceof TypeScalar scalar ) {
+ if( t._type==TSCALAR ) return t.isHigh() ? this : t;
+ return TypeScalar.BOT;
+ }
+ return t._type==(_type==TNIL ? TXNIL : TNIL) ? TypePtr.PTR : BOTTOM;
+ }
+
+ // 'this' is only {TCTRL,TXCTRL}
+ // Other non-simple RHS things bottom out
+ if( !t.is_simple() ) return BOTTOM;
+ // If RHS is NIL/XNIL
+ if( t._type==TNIL || t._type==TXNIL ) return BOTTOM;
+ // Both are {TCTRL,TXCTRL} and unequal to each other
+ return _type==TCTRL || t._type==TCTRL ? CONTROL : XCONTROL;
+ }
+
+
+ // The dual is pre-computed on creation and available "for free" thereafter
+ Type _dual;
+ public final <T extends Type> T dual() { return (T)_dual; }
+
+ <T extends Type> T xdual() {
+ return (T)new Type(switch( _type ) {
+ case TBOT -> TTOP;
+ case TTOP -> TBOT;
+ case TCTRL -> TXCTRL;
+ case TXCTRL-> TCTRL;
+ case TNIL -> TXNIL;
+ case TXNIL -> TNIL;
+ default -> throw Utils.TODO("Should not reach here: unknown simple type");
+ });
+ }
+
+
+ // ----------------------------------------------------------
+ // Our lattice is defined with a MEET and a DUAL.
+ // JOIN is dual of meet of both duals.
+ public final Type join(Type t) {
+ if( this==t ) return this;
+ return dual().meet(t.dual()).dual();
+ }
+
+ // True if this "isa" t; e.g. 17 isa TypeInteger.BOT
+ public boolean isa( Type t ) { return meet(t)==t; }
+
+ public Type nonZero() { return this==NIL ? TypePtr.XNPTR : TypePtr.NPTR; }
+
+ // Make a zero version of this type, 0 for integers and null for pointers.
+ public Type makeZero() { return Type.NIL; }
+
+ // Cap at limits
+ public Type oob() { return isHigh() ? TOP : BOTTOM; }
+
+ // ----------------------------------------------------------
+
+ // Cyclic types! Flag the start of a cyclic type by putting a sentinel in
+ // VISIT (and eventually clearing VISIT when done). Then do a normal
+ // recursive descent visit of all types; this will accumulate types without
+ // interning them. When done we have to visit the possibly cyclic type and
+ // intern the whole cycle, possibly hitting the entire cycle on a prior
+ // interned cycle. Any sub-part might also be interned, including whole
+ // disjoint cycles.
+
+ // E.g. a new type: "A<->B -> C1<->D1" gets created; the "C1<->D1" cycle
+ // already exists as "C0<->D0", but the "A<->B" cycle is new. The "A<->B"
+ // cycle will get interned - except the pointer to C1 needs to get replaced
+ // with C0. The "C1<->D1" cycle gets freed and "B" will point to the
+ // "C0<->D0" cycle.
+
+ Type recurOpen() { assert VISIT.isEmpty(); VISIT.put(0L,BOTTOM); return this; }
+ Type recurClose() { return recurClose(null); }
+ Type recurClose(Type[] types) {
+ VISIT.remove(0L); // Just ignore the sentinel
+ Ary<Type> ts = new Ary<>(Type.class);
+ ts.addAll(VISIT.values());
+ if( ts.find(this)== -1 ) ts.add(this);
+
+ // Pass #1: replace already interned fields
+ assert BITS.isEmpty();
+ for( int i=0; i<ts._len; i++ )
+ if( ts.at(i).tern()._terned )
+ ts.del(i--);
+ BITS.clear();
+
+ // Pass#2: compute recursive duals
+ for( Type t : ts )
+ t.rdual();
+
+ // Pass#3: Recursive install
+ for( Type t : ts )
+ if( !t._terned && INTERN.get(t)==null )
+ t.install();
+ // Upgrade the result
+ Type rez = INTERN.get(this);
+ assert rez!=null;
+ if( types!=null )
+ for( int i=0; i<types.length; i++ )
+ types[i] = INTERN.get(types[i]);
+
+ // Pass#4?: Free up any created-but-already interned
+ for( Type t : FREES )
+ t.rfree();
+ FREES.clear();
+ VISIT.clear();
+ return rez;
+ }
+
+ // Read-only recursive visits do not *make* cyclic types and so the cleanup
+ // is much easier.
+ static boolean recurClose(boolean rez) { VISIT.clear(); return rez; }
+
+ final Type tern() {
+ if( _terned ) return this;
+ if( !BITS.get(_uid) ) {
+ BITS.set(_uid);
+ int nkids = nkids();
+ for( int i=0; i<nkids; i++ )
+ set(i,at(i).tern());
+ }
+ Type told = INTERN.get(this);
+ return told==null ? this : told.delayFree(this);
+ }
+
+ // Recursive dual. Visit all types recursively. Each visited type is
+ // *new*, and hits in the intern table, or not. If not, we recursively
+ // call rdual to get its dual, and cross-link the duals. If hitting, we
+ // use the intern type and mark the prior type for freeing.
+ Type rdual() { assert !_terned; return xdual(); }
+
+ // Recursively free type and children
+ final void rfree() {
+ if( isFree() ) return;
+ int nkids = nkids();
+ for( int i=0; i<nkids; i++ )
+ if( !at(i)._terned )
+ at(i).rfree();
+ free(this);
+ }
+
+ Type free(Type free) {
+ return this;
+ }
+ boolean isFree() { return false; }
+
+ final <T extends Type> T delayFree(Type free) {
+ assert !free._terned;
+ FREES.push(free);
+ return (T)this;
+ }
+
+ final Type install() {
+ if( this instanceof TypeStruct ) {
+ Type x = _intern(); // Stop the recursion
+ if( x!=this ) return x.delayFree(this);
+ }
+ int nkids = nkids();
+ for( int i=0; i<nkids; i++ ) {
+ Type kid = at(i);
+ if( !kid._terned ) {
+ Type ikid = kid.install();
+ if( ikid != kid ) {
+ set(i,ikid);
+ _dual.set(i,ikid.dual());
+ }
+ }
+ }
+ return this instanceof TypeStruct || _terned ? this : _intern();
+ }
+
+ // Strict constant values, things on the lattice centerline.
+ // Excludes both high and low values
+ public final boolean isConstant() { return recurClose(recurOpen()._isConstant()); }
+ boolean _isConstant() { return _type==TNIL; }
+
+ // Are all reachable struct Fields are final?
+ // Writable array slots are invariant in their element access permissions.
+ // Otherwise an alias could insert a readonly pointer into a writable slot.
+ public boolean accessISA(Type dst) { return true; }
+
+ public final boolean isFinal() { return recurClose(recurOpen()._isFinal()); }
+ boolean _isFinal() { assert is_nokids(); return true; }
+
+ public final Type makeRO() {
+ if( isFinal() ) return this;
+ return recurOpen()._makeRO().recurClose();
+ }
+ Type _makeRO() { return this; }
+
+ /** Declared shape used when a value is published into mutable memory. */
+ public final Type makeStorage() {
+ return recurOpen()._makeStorage().recurClose();
+ }
+ Type _makeStorage() { assert is_simple(); return BOTTOM; }
+
+ // Bulk close-over all recursive types
+ public static TypeStruct[] closeOver(TypeStruct[] ts, HashMap<String,Type> TYPES) {
+ BOTTOM.recurOpen();
+ for( TypeStruct t : ts )
+ t._close(null, TYPES);
+ for( int i=0; i<ts.length; i++ ) {
+ // Parser only tracks the mutable name of arrays, so the closeOver
+ // returns that one. However, internal types might have immutable
+ // versions.
+ String name = ts[i].isAry() ? (ts[i]._name+"!").intern() : ts[i]._name;
+ ts[i] = (TypeStruct)VISIT.get(name);
+ }
+ BOTTOM.recurClose(ts);
+ return ts;
+ }
+
+ Type _close( String name, HashMap<String, Type> TYPES ) { return this; }
+ public Type widen() { return this; }
+
+ // Replace recursively all TypeBuilders with cyclic TypeStructs
+ public Type upgradeType(HashMap<String,Type> TYPES) {
+ return recurOpen()._upgradeType(TYPES).recurClose();
+ }
+ Type _upgradeType(HashMap<String,Type> TYPES) { assert is_nokids(); return this; }
+
+
+ // Recursively gather all types
+ public void gather(HashMap<Type,Integer> types ) {
+ if( types.containsKey(this) ) return; // TEST
+ types.put(this,types.size()); // SET
+ int nkids = nkids();
+ for( int i=0; i<nkids; i++ )
+ at(i).gather(types);
+ }
+
+ // Compute serialization byte tag compression space
+ final static int[] TAGOFFS = new int[TMAX+1];
+ public static int[] TAGOFFS() {
+ if( TAGOFFS[TMAX-1]==0 )
+ // One entry per unique tag-space, using the BOT value from that
+ // tag-space except for the first tag-space which uses 1 tag for
+ // multiple Types instead of 1 Type having multiple tags. Has to
+ // be ordered to match _type enum.
+ for( Type t : new Type[]{Type.XNIL,TypeScalar.BOT,TypePtr.PTR,TypeInteger.BOT,TypeFloat.F64,TypeConAryB.ABC,TypeRPC.BOT,TypeMemPtr.BOT,TypeFunPtr.BOT,TypeMem.BOT,Field.BOT,TypeStruct.BOT,TypeTuple.BOT} )
+ TAGOFFS[t._type+1] = TAGOFFS[t._type] + t.TAGOFF();
+ assert TAGOFFS[TMAX] <= 255;
+ return TAGOFFS;
+ }
+ // Reserve 6 tags, 0-5, for plain Types
+ int TAGOFF() { assert is_simple(); return 6; }
+
+ public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ assert is_simple();
+ baos.write(_type);
+ }
+ static Type packed( int tag ) {
+ return switch( tag ) {
+ case TBOT -> BOTTOM;
+ case TTOP -> TOP;
+ case TCTRL -> CONTROL;
+ case TXCTRL->XCONTROL;
+ case TNIL -> NIL;
+ default -> throw Utils.TODO("Should not reach here: unknown simple type tag");
+ };
+ }
+
+ // Produce a type with null child types
+ static Type packed( BAOS bais, String[] strs ) {
+ int x = bais.read();
+ int type = Arrays.binarySearch( TAGOFFS, x );
+ if( type < 0 ) type = -(type+1)-1;
+ int off = x - TAGOFFS[type];
+ return switch( type ) {
+ case 0, 1, 2, 3, 4, 5 -> Type.packed( off );
+ case TSCALAR -> TypeScalar .packed( off );
+ case TPTR -> TypePtr .packed( off );
+ case TINT -> TypeInteger.packed( off, bais );
+ case TFLT -> TypeFloat .packed( off, bais );
+ case TCONARY -> TypeConAry .packed( off, bais );
+ case TRPC -> TypeRPC .packed( off, bais );
+ case TMEMPTR -> TypeMemPtr .packed( off, bais );
+ case TFUNPTR -> TypeFunPtr .packed( off, bais );
+ case TMEM -> TypeMem .packed( off, bais );
+ case TFLD -> Field .packed( off, bais, strs );
+ case TSTRUCT -> TypeStruct .packed( off, bais, strs );
+ case TTUPLE -> TypeTuple .packed( off, bais );
+ default -> throw Utils.TODO("Should not reach here: unknown packed type tag");
+ };
+ }
+
+ // Read a packed Type array
+ public static Type[] packed( BAOS bais, String[] strs, int ntypes, HashMap<String,Type> existingTypes,
+ GlobalBits fileAliases, GlobalBits aliases,
+ GlobalBits fileFidxs , GlobalBits fidxs ,
+ GlobalBits fileRpcs , GlobalBits rpcs ) {
+ Type[] types = new Type[ntypes];
+ // Read Types in ID# order, no children
+ for( int i=0; i<ntypes; i++ )
+ types[i] = Type.packed(bais,strs);
+ // Start possibly recursive type collection
+ BOTTOM.recurOpen();
+ // Read Types in ID# order, only children
+ for( int i=0; i<ntypes; i++ ) {
+ VISIT.put(types[i]._uid,types[i]);
+ int nkids = types[i].nkids();
+ for( int j=0; j<nkids; j++ )
+ types[i].set(j,types[bais.packed4()]);
+ }
+ // Remap file-local aliases/fidxs/rpcs into this compilation unit's
+ // local dense indexes via their global identities.
+ for( int i=0; i<ntypes; i++ ) {
+ if( types[i] instanceof Field fld ) {
+ if( fld._alias >= GlobalBits.RESERVED )
+ fld._alias = aliases.map(fileAliases,fld._alias);
+ }
+ if( types[i] instanceof TypeFunPtr tfp )
+ tfp._fidxs = remapBits(fileFidxs,fidxs,tfp._fidxs,GlobalBits.RESERVED);
+ if( types[i] instanceof TypeRPC rpc )
+ rpc._rpcs = remapBits(fileRpcs,rpcs,rpc._rpcs,GlobalBits.RESERVED);
+ if( types[i] instanceof TypeMem mem ) {
+ if( mem._alias >= GlobalBits.RESERVED )
+ mem._alias = aliases.map(fileAliases,mem._alias);
+ mem._escFs = remapBits(fileFidxs ,fidxs ,mem._escFs,GlobalBits.RESERVED);
+ mem._escAs = remapBits(fileAliases,aliases,mem._escAs,GlobalBits.RESERVED);
+ }
+ }
+
+ // Intern them all at once
+ BOTTOM.recurClose(types);
+ return types;
+ }
+
+ private static int[] remapBits(GlobalBits fileBits, GlobalBits bits, int[] fileLocals, int firstMapped ) {
+ if( fileLocals == XInt.EMPTY || fileLocals == XInt.FULL )
+ return fileLocals;
+ assert !XInt.isHigh(fileLocals);
+ int[] locals = XInt.EMPTY;
+ for( int fileLocal = XInt.next(fileLocals,-1); fileLocal >=0; fileLocal = XInt.next(fileLocals,fileLocal) )
+ locals = XInt.make(locals,fileLocal < firstMapped ? fileLocal : bits.map(fileBits,fileLocal));
+ return locals;
+ }
+
+
+ // ----------------------------------------------------------
+
+ // Size in bits to hold an instance of this type.
+ // Sizes are expected to be between 1 and 64 bits.
+ // Size 0 means this either takes no space (such as a known-zero field)
+ // or isn't a scalar to be stored in memory.
+ public int log_size () { return 3; } // log-size of a type; log_size for a struct is usually undefined
+ public int size() { return 1<<log_size(); }
+ public int alignment() { return log_size(); } // alignment; for structs, max align of Fields
+
+ // ----------------------------------------------------------
+ // Useful in the debugger, which calls toString everywhere.
+ // This is a more verbose dev-friendly print.
+ @Override
+ public final String toString() {
+ return print(new SB(), new BitSet(), false).toString();
+ }
+ public final String gprint() {
+ return print(new SB(), new BitSet(), true).toString();
+ }
+ public final SB print(SB sb) { return print(sb, new BitSet(), false); }
+ public final SB gprint(SB sb) { return print(sb, new BitSet(), true ); }
+
+ // Used during recursive printing, or combined with SB Node printing
+ public final SB print(SB sb, BitSet visit, boolean html ) {
+ if( visit.get(_uid) ) return sb.p(str());
+ visit.set(_uid);
+ return _print(sb,visit,html);
+ }
+ SB _print(SB sb, BitSet visit, boolean html ) { return sb.p(str()); }
+
+ // This is used by error messages, and is a shorted print.
+ public String str() { return STRS[_type]; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeConAry.java b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
new file mode 100644
index 00000000..c9093e3b
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
@@ -0,0 +1,90 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Represents a constant array of primitives
+ */
+public abstract class TypeConAry<A> extends TypeScalar {
+ boolean _any;
+ byte _widen;
+ // One of byte or int array
+ public final A _ary;
+
+ TypeConAry( boolean any, byte widen, A ary ) { super(TCONARY); _any = any; _widen = widen; _ary = ary; }
+ public static void gather(ArrayList<Type> ts) {
+ ts.add(TypeConAryB.ABC);
+ ts.add(TypeConAryB.ABCD);
+ ts.add(TypeConAryI.I123);
+ }
+ // Fresh, uninterned type sharing this array; xdual must not intern.
+ abstract TypeConAry<A> _make(boolean any, byte widen);
+
+ @Override TypeConAry<A> xdual() { return _make( !_any, (byte)(3-_widen) ); }
+
+ @Override Type xmeet(Type t) {
+ TypeConAry ary = (TypeConAry)t; // Invariant
+ // Same base array but different?
+ if( _equals(ary) )
+ // Return array with larger widen
+ return _widen >= ary._widen ? this : ary;
+ // Unrelated constant arrays, falls to some int range
+ return elem().meet(ary.elem());
+ }
+
+ Type ymeet( TypeInteger ti ) {
+ // if i can isa *each* element, then maybe can keep.
+ // no good to i.isa(elem()) because fails the dual
+ for( int j=0; j<len(); j++ )
+ if( !ti.isa(TypeInteger.make(at8(j),at8(j),ti._widen)) )
+ return ti.meet(elem());
+ byte widen = (byte)Math.max(_widen,ti._widen);
+ return widen==_widen ? this : _make(_any,widen).intern();
+ }
+
+ private boolean _equals(TypeConAry ary) {
+ int len = len();
+ if( len != ary.len() ) return false;
+ for( int i=0; i<len; i++ )
+ if( at8(i) != ary.at8(i) )
+ return false;
+ return true;
+ }
+
+ @Override public boolean isHigh() { return this==TOP; }
+ @Override boolean _isConstant() { return true; }
+ @Override Type _makeStorage() { return this; }
+
+ // Meet-over-elements type
+ public Type elem() {
+ if( _ary==null )
+ return _any ? Type.TOP : BOTTOM;
+ int len = len();
+ long min = Long.MAX_VALUE;
+ long max = Long.MIN_VALUE;
+ for( int i=0; i<len; i++ ) {
+ min = Math.min(min,at8(i));
+ max = Math.max(max,at8(i));
+ }
+ return TypeInteger.make(min,max,_widen);
+ }
+ public abstract long at8(int idx);
+ public abstract int len();
+ @Override public abstract int log_size();
+ public void write( BAOS baos ) { throw Utils.TODO("Should not reach here: abstract constant array cannot be written"); }
+
+ // Reserve tags for u8 array
+ @Override int TAGOFF() { return 1; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ assert log_size()==0 && !_any;
+ baos.write(TAGOFFS[TCONARY]+0);
+ baos.packed4(len());
+ baos.write((byte[])_ary);
+ }
+ static TypeConAry packed( int tag, BAOS bais ) {
+ return TypeConAryB.make(bais.read(new byte[bais.packed4()]));
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java
new file mode 100644
index 00000000..5877c309
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.util.BAOS;
+import java.util.Arrays;
+
+public class TypeConAryB extends TypeConAry<byte[]> {
+ private TypeConAryB(boolean any, byte widen, byte[] bs) { super(any,widen,bs); }
+ @Override TypeConAryB _make( boolean any, byte widen ) { return new TypeConAryB(any,widen,_ary); }
+ public static TypeConAryB make( byte[] bs ) { return new TypeConAryB(false,(byte)0,bs).intern(); }
+ public static TypeConAryB make( String s ) { return make(s.getBytes()); }
+ static final TypeConAryB ABC = make("abc");
+ static final TypeConAryB ABCD = make("abcd");
+ @Override public long at8(int idx) { return _ary[idx]; }
+ @Override public int len() { return _ary.length; }
+ @Override public int log_size() { return 0; }
+ @Override public String str() { return "[\""+new String(_ary)+"\"]"; }
+ @Override boolean eq(Type t) { return t instanceof TypeConAryB ary && _widen==ary._widen && Arrays.equals(_ary,ary._ary); }
+ @Override int hash() { return Arrays.hashCode(_ary); }
+ @Override public void write( BAOS baos ) { baos.write(_ary); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java
new file mode 100644
index 00000000..b1b061de
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java
@@ -0,0 +1,16 @@
+package com.seaofnodes.simple.type;
+
+import java.util.Arrays;
+
+public class TypeConAryI extends TypeConAry<int[]> {
+ private TypeConAryI(boolean any, byte widen, int[] bs) { super(any,widen,bs); }
+ public static TypeConAryI make( int[] bs ) { return new TypeConAryI(false,(byte)0,bs).intern(); }
+ @Override TypeConAryI _make( boolean any, byte widen ) { return new TypeConAryI(any,widen,_ary); }
+ static final TypeConAryI I123 = make(new int[]{1,2,3});
+ @Override public long at8(int idx) { return _ary[idx]; }
+ @Override public int len() { return _ary.length; }
+ @Override public int log_size() { return 2; }
+ @Override public String str() { return "[i32]"; }
+ @Override boolean eq(Type t) { return t instanceof TypeConAryI ary && _widen==ary._widen && Arrays.equals(_ary,ary._ary); }
+ @Override int hash() { return Arrays.hashCode(_ary); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeFloat.java b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
new file mode 100644
index 00000000..d410e9cf
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
@@ -0,0 +1,132 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.AryInt;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Float Type
+ */
+public class TypeFloat extends TypeScalar {
+
+ // - high -64, high -32, con 0, low +32, low +64
+ public byte _sz;
+
+ /**
+ * The constant value or 0
+ */
+ public double _con;
+
+ private static final Ary<TypeFloat> FREE = new Ary<>(TypeFloat.class);
+ private TypeFloat(byte sz, double con) { super(TFLT); init(sz,con); }
+ private TypeFloat init( byte sz, double con ) { _sz = sz; _con = con; return this; }
+ public static TypeFloat malloc(byte sz, double con) { return FREE.isEmpty() ? new TypeFloat(sz,con) : FREE.pop().init(sz,con); }
+ private static TypeFloat make(int sz, double con) {
+ TypeFloat f = malloc((byte)sz,con);
+ TypeFloat t2 = f.intern();
+ return t2==f ? f : t2.free(f);
+ }
+ @Override TypeFloat free(Type t) {
+ TypeFloat f = (TypeFloat)t;
+ f._hash = 0;
+ f._dual=null;
+ FREE.push(f);
+ return this;
+ }
+
+ public static TypeFloat constant(double con) { return make(0, con); }
+
+ public final static TypeFloat ONE = constant(1.0);
+ public final static TypeFloat FZERO = constant(0.0);
+ public final static TypeFloat F32 = make(32, 0);
+ public final static TypeFloat F64 = make(64, 0);
+
+ public static void gather(ArrayList<Type> ts) { ts.add(F64); ts.add(F32); ts.add(constant(3.141592653589793)); }
+
+ @Override public String str() {
+ return switch( _sz ) {
+ case -64 -> "~flt";
+ case -32 -> "~f32";
+ case 0 -> ""+_con+((float)_con==_con ? "f" : "");
+ case 32 -> "f32";
+ case 64 -> "flt";
+ default -> throw Utils.TODO("Should not reach here: unknown float size");
+ };
+ }
+ private boolean isF32() { return ((float)_con)==_con; }
+
+ @Override public boolean isHigh () { return _sz< 0; }
+ @Override public int log_size() { return _sz==32 || _sz==-32 ? 2 : 3; }
+
+ public double value() { return _con; }
+
+ @Override
+ public TypeFloat xmeet(Type other) {
+ TypeFloat f = (TypeFloat)other;
+ // Invariant from caller: 'this' != 'other' and same class (TypeFloat).
+ TypeFloat i = (TypeFloat)other; // Contract
+ // Larger size in i1, smaller in i0
+ TypeFloat i0 = _sz < i._sz ? this : i;
+ TypeFloat i1 = _sz < i._sz ? i : this;
+
+ if( i1._sz== 64 ) return F64;
+ if( i0._sz==-64 ) return i1;
+ if( i1._sz== 32 )
+ return i0._sz==0 && !i0.isF32() ? F64 : F32;
+ if( i1._sz!= 0 ) return i1;
+ // i1 is a constant
+ if( i0._sz==-32 )
+ return i1.isF32() ? i1 : F64;
+ // Since both are constants, and are never equals (contract) unequals
+ // constants fall to bottom
+ return i0.isF32() && i1.isF32() ? F32 : F64;
+ }
+
+ @Override
+ TypeFloat xdual() {
+ return _isConstant() ? this : new TypeFloat((byte)-_sz,0); // Constants are a self-dual
+ }
+
+ @Override boolean _isConstant() { return _sz==0; }
+
+ @Override Type _makeStorage() {
+ if( _isConstant() ) return isF32() ? F32 : F64;
+ return isHigh() ? dual() : this;
+ }
+
+ @Override public Type makeZero() { return FZERO; }
+
+ // Reserve tags for F64,F32,constant
+ @Override int TAGOFF() { return 3; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ if( this==F64 ) baos.write(TAGOFFS[_type] + 0);
+ else if( this==F32 ) baos.write(TAGOFFS[_type] + 1);
+ else {
+ assert isConstant();
+ baos.write(TAGOFFS[_type] + 2);
+ baos.packed8(Double.doubleToLongBits(_con));
+ }
+ }
+
+ static Type packed( int tag, BAOS bais ) {
+ return switch( tag ) {
+ case 0 -> F64;
+ case 1 -> F32;
+ case 2 -> constant(Double.longBitsToDouble(bais.packed8()));
+ default -> throw Utils.TODO("Should not reach here: unknown float type tag");
+ };
+ }
+
+ @Override
+ int hash() { return (int)(Double.hashCode(_con) ^ _sz ^ (1<<17)); }
+ @Override
+ public boolean eq( Type t ) {
+ TypeFloat i = (TypeFloat)t; // Contract
+ // Allow NaN to check for equality
+ return _sz==i._sz && Double.doubleToLongBits(_con)==Double.doubleToLongBits(i._con);
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
new file mode 100644
index 00000000..56f47bff
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
@@ -0,0 +1,341 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.util.*;
+import java.util.*;
+
+/**
+ * Represents a Pointer to a function.
+ * <p></p>
+ * Functions have argument types and a return type, making a function
+ * signature. Within a signature there can be many instances of functions, and
+ * each function is labeled with a small integer constant. A TFP can represent
+ * a single signature and a set of functions; the set might contain only 1
+ * function or the zeroth function (the null ptr).
+ * <p></p>
+ * Functions with different signatures cannot be mixed, and will result in a
+ * bottom function type, which can only be null-checked.
+ */
+public class TypeFunPtr extends TypeNil {
+ // A TypeFunPtr is Signature and a set of functions.
+ public Type[] _sig;
+ public Type _ret;
+ boolean _open; // Extra args are true:scalar BOT, false:scalar TOP
+ // Set of functions. Bit zero is used for mutable/immutable safety. Sign
+ // bit repeats infinitely.
+ public int[] _fidxs;
+
+ private static final Ary<TypeFunPtr> FREE = new Ary<>(TypeFunPtr.class);
+ private TypeFunPtr(byte nil) { super(TFUNPTR,nil); }
+ private TypeFunPtr init(byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) {
+ assert sig != null;
+ _nil = nil;
+ _open = open;
+ _sig = sig;
+ _ret = ret;
+ assert _fidxs == null;
+ _fidxs= XInt.intern(fidxs);
+ return this;
+ }
+ static TypeFunPtr malloc( byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) {
+ return (FREE.isEmpty() ? new TypeFunPtr(nil) : FREE.pop()).init(nil,open,sig,ret,fidxs);
+ }
+ @Override TypeFunPtr free(Type t) {
+ TypeFunPtr fun = (TypeFunPtr)t;
+ assert !fun.isFree() && !fun._terned;
+ fun._sig = null;
+ fun._ret = null;
+ fun._dual = null;
+ fun._hash = 0;
+ fun._fidxs = null;
+ FREE.push(fun);
+ return this;
+ }
+ @Override boolean isFree() { return _sig==null; }
+
+ // All fields directly listed
+ public static TypeFunPtr make( byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) {
+ TypeFunPtr fun = malloc(nil,open,sig,ret,fidxs);
+ TypeFunPtr f2 = fun.intern();
+ if( f2==fun ) return fun;
+ return VISIT.isEmpty() ? f2.free(fun) : f2.delayFree(fun);
+ }
+ // Trim trailing args which match the default.
+ private static TypeFunPtr makeAdjusted( byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) {
+ Type def = open ? TypeScalar.BOT : TypeScalar.TOP;
+ int nargs = sig.length;
+ while( nargs > 0 && sig[nargs-1] == def )
+ nargs--;
+ if( nargs != sig.length )
+ sig = nargs == 0 ? ARG_EMPTY : Arrays.copyOf(sig,nargs);
+ return make(nil,open,sig,ret,fidxs);
+ }
+ public static TypeFunPtr make( boolean nil, Type[] sig, Type ret ) { return makeAdjusted((byte)(nil ? 3 : 2),true,sig,ret, XInt.FULL); }
+ public static TypeFunPtr make1( byte nil, boolean open, Type[] sig, Type ret, int fidx ) {
+ return makeAdjusted(nil,open,sig,ret,XInt.make(fidx));
+ }
+
+
+
+ @Override TypeFunPtr makeFrom( byte nil ) { return nil ==_nil ? this : make( nil,_open,_sig,_ret, _fidxs); }
+ public TypeFunPtr makeFrom( Type ret ) { return ret ==_ret ? this : make( _nil,_open,_sig, ret, _fidxs); }
+ public TypeFunPtr makeFrom( int fidx ) { return make1(_nil,_open,_sig,_ret,fidx); }
+ public TypeFunPtr makeFrom( Type arg, int idx ) {
+ // Alter named arg
+ Type[] sig = _sig.clone();
+ sig[idx] = arg;
+ return make(_nil,_open,sig,_ret,_fidxs);
+ }
+ @Override public Type nonZero() { return makeFrom((byte)(_nil <= 1 ? 1 : 2)); }
+
+ public static final Type[] TEMPTY = new Type[0];
+ static final Type[] TINT = new Type[]{TypeInteger.BOT};
+ static final Type[] TINTMEM = new Type[]{TypeInteger.BOT,TypeFloat.F32};
+ static final Type[] TINTINT = new Type[]{TypeInteger.BOT,TypeInteger.BOT};
+ public static TypeFunPtr BOT = make((byte)3,true,TEMPTY,Type.BOTTOM, XInt.FULL);
+ public static TypeFunPtr TEST = make((byte)2,true,TINTMEM,TypeInteger.BOT, XInt.SET1);
+ public static TypeFunPtr TEST0 = make((byte)3,true,TINTMEM,TypeInteger.BOT, XInt.SET3);
+ public static TypeFunPtr MAIN = make((byte)3,true,TINT ,Type.BOTTOM, XInt.FULL); // Main can return anything
+ public static TypeFunPtr CALLOC= make((byte)3,true,TINTINT,TypeMemPtr .BOT, XInt.FULL);
+ public static void gather(ArrayList<Type> ts) {
+ ts.add(TEST);
+ ts.add(TEST0);
+ ts.add(BOT);
+ ts.add(MAIN);
+ ts.add(CALLOC);
+ ts.add(make((byte)2,false,TINT,TypeInteger.I32, XInt.SET1));
+ ts.add(make((byte)2,true ,TINT,TypeInteger.BOT, XInt.SET3));
+ ts.add(make1((byte)2,true,
+ new Type[]{TypeInteger.BOT},
+ TypeInteger.BOT, 65));
+ ts.add(make1((byte)2,true,
+ new Type[]{TypeInteger.BOT, TypeScalar.BOT},
+ TypeInteger.BOT, 65));
+ // Explicit trailing defaults must agree with the implicit argument tail.
+ for( boolean open : new boolean[]{false,true} ) {
+ Type def = open ? TypeScalar.BOT : TypeScalar.TOP;
+ ts.add(make1((byte)2,open,TINT,TypeInteger.I32,1));
+ ts.add(make1((byte)2,open,new Type[]{TypeInteger.BOT,def,def},TypeInteger.I32,1));
+ ts.add(make1((byte)2,open,new Type[]{def,def},TypeInteger.I32,1));
+ ts.add(make1((byte)2,open,new Type[]{def,TypeInteger.BOT},TypeInteger.I32,1));
+ }
+ }
+
+ private static final Type[] ARG_EMPTY = new Type[0];
+ @Override
+ Type xmeet(Type t) {
+ TypeFunPtr that = (TypeFunPtr) t;
+ boolean open = _open | that._open;
+ Type def0 = this._open ? TypeScalar.BOT : TypeScalar.TOP;
+ Type def1 = that._open ? TypeScalar.BOT : TypeScalar.TOP;
+ Type def = open ? TypeScalar.BOT : TypeScalar.TOP;
+
+ int nargs = Math.max(_sig.length,that._sig.length);
+ Type[] args = new Type[nargs];
+
+ for( int i=0; i<nargs; i++ ) {
+ Type t0 = i < this._sig.length ? this._sig[i] : def0;
+ Type t1 = i < that._sig.length ? that._sig[i] : def1;
+ args[i] = t0.meet(t1);
+ }
+
+ while( nargs>0 && args[nargs-1] == def )
+ nargs--;
+
+ if( nargs != args.length )
+ args = nargs == 0 ? ARG_EMPTY : Arrays.copyOf(args,nargs);
+
+ return make(xmeet0(that), open, args, _ret.meet(that._ret), XInt.meet(_fidxs,that._fidxs));
+ }
+
+ static Type[] xmeet( Type[] ts0, Type[] ts1) {
+ if( ts0==ts1 ) return ts0;
+ assert ts0.length==ts1.length;
+ Type[] ts = new Type[ts0.length];
+ for( int i=0; i<ts0.length; i++ )
+ ts[i] = ts0[i].meet(ts1[i]);
+ return ts;
+ }
+
+ @Override TypeFunPtr xdual() { return malloc(dual0(), !_open, xdual(_sig), _ret.dual(), XInt.dual(_fidxs)); }
+ static Type[] xdual(Type[] ts) {
+ Type[] dual = new Type[ts.length];
+ for( int i=0; i<ts.length; i++ )
+ dual[i] = ts[i].dual();
+ return dual;
+ }
+
+ @Override TypeFunPtr rdual() {
+ if( _dual!=null ) return dual();
+ assert !_terned;
+ Type[] sigs = new Type[_sig.length];
+ TypeFunPtr d = malloc(dual0(),!_open,sigs,null, XInt.dual(_fidxs));
+ (_dual = d)._dual = this; // Cross link duals
+ // Return rdual
+ d._ret = _ret._terned ? _ret.dual() : _ret.rdual();
+ // Signature rdual
+ for( int i=0; i<_sig.length; i++ )
+ sigs[i] = _sig[i]._terned ? _sig[i].dual() : _sig[i].rdual();
+ return d;
+ }
+
+ // RHS is NIL; deep-dual when crossing the centerline
+ @Override public Type meet0() {
+ if( _nil==3 ) return this;
+ if( _nil==2 ) return make((byte)3,_open,_sig,_ret,_fidxs);
+
+ return make((byte)3,_open,_sig,_ret,_fidxs);
+ }
+
+ @Override public boolean isHigh() { return _nil <= 1 || (_nil==2 && _fidxs== XInt.EMPTY); }
+
+ @Override public boolean _isConstant() { return (_nil==2 && XInt.isConstant(_fidxs)) || (_nil==3 && _fidxs== XInt.EMPTY); }
+
+ @Override boolean _isFinal() { return true; }
+ @Override TypeFunPtr _makeStorage() { return this; }
+
+ @Override TypeFunPtr _close( String name, HashMap<String, Type> TYPES ) {
+ Type[] sig = new Type[_sig.length];
+ TypeFunPtr fun = malloc(_nil,true,sig,null,_fidxs);
+ // Now start the recursion
+ fun._ret = _ret._close(name, TYPES );
+ for( int i=0; i<sig.length; i++ )
+ sig[i] = _sig[i]._close(name, TYPES );
+ return fun;
+ }
+
+ // Replace recursively all TypeBuilders with cyclic TypeStructs
+ @Override Type _upgradeType(HashMap<String,Type> TYPES) {
+ Type[] sig = new Type[_sig.length];
+ TypeFunPtr fun = malloc(_nil,_open,sig,null,_fidxs);
+ // Now start the recursion
+ fun._ret = _ret._upgradeType(TYPES);
+ for( int i=0; i<sig.length; i++ )
+ sig[i] = _sig[i]._upgradeType(TYPES);
+ return fun;
+ }
+
+ @Override public int log_size() { return 2; } // (1<<2)==4-byte pointers
+
+ public Type arg(int i) { return _sig[i]; }
+ public int[] fidxs() { return _fidxs; }
+ public Type ret() { return _ret; }
+ public int nargs() { return _sig.length; }
+ public int fidx() { return XInt.onlyBit(_fidxs); }
+ public int nfcns() {
+ if( XInt.isHigh(_fidxs) ) return Integer.MAX_VALUE; // Infinite choices
+ return XInt.bitCount(_fidxs);
+ }
+
+
+ // Used to walk over type structures
+ @Override public int nkids() { return _sig.length+1; }
+ @Override public Type at( int idx ) {
+ return idx == _sig.length ? _ret : _sig[idx];
+ }
+ @Override public void set( int idx, Type t ) {
+ if( idx == _sig.length ) _ret = t;
+ else _sig[idx] = t;
+ }
+
+ // Reserve tags for null/not and open/close, one fidx/bitset, 0-6 args
+ // Tags 0-5 - not,close,1 fidx, 0-5 args (+fidx)
+ // Tag 6 - not ,close+fidxs+nargs
+ // Tag 7 - null,close+fidxs+nargs
+ @Override int TAGOFF() { return 8; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ assert _open; // Open: Passing extra args is fine, and extras ignored.
+ if( _nil==2 && nargs()<6 && nfcns() == 1 && fidx() < 64 ) {
+ baos.write(TAGOFFS[_type] + nargs());
+ baos.packed2(fidx());
+ } else {
+ baos.write(TAGOFFS[_type] + 6 + (_nil==2 ? 0 : 1));
+ baos.packed1(nargs());
+ XInt.packed(baos,_fidxs);
+ }
+ }
+ static TypeFunPtr packed( int tag, BAOS bais ) {
+ if( tag < 6 ) {
+ int fidx = bais.read();
+ assert fidx < 64; // Need a larger fidx space
+ return malloc((byte)2,true,new Type[tag],null, XInt.make(fidx));
+ }
+ byte nil = (byte)(tag==6 ? 2 : 3);
+ int nargs = bais.packed1();
+ int[] fidxs = XInt.packed(bais);
+ return malloc(nil,true,new Type[nargs],null,fidxs);
+ }
+
+ @Override
+ int hash() {
+ long hash = _ret.hashCode() ^ XInt.hash(_fidxs) ^ super.hash();
+ for( Type arg : _sig )
+ hash = hash*19 ^ arg.hashCode();
+ return Utils.fold(hash);
+ }
+
+ @Override
+ boolean eq(Type t) {
+ // Recursive; so use cyclic equals
+ if( !VISIT.isEmpty() ) {
+ assert TypeStruct.CEQUALS.isEmpty();
+ boolean rez = cycle_eq(t);
+ TypeStruct.CEQUALS.clear();
+ return rez;
+ }
+ TypeFunPtr fun = (TypeFunPtr)t; // Invariant
+ if( !super.eq(fun) || _open != fun._open || _fidxs != fun._fidxs || _sig.length != fun._sig.length ) return false;
+ if( _sig == fun._sig && _ret == fun._ret ) return true;
+
+ if( !_ret.eq(fun._ret) ) return false;
+ for( int i = 0; i < _sig.length; i++ )
+ if( !_sig[i].eq(fun._sig[i]) )
+ return false;
+ return true;
+ }
+ @Override boolean cycle_eq(Type t) {
+ if( t._type != TFUNPTR ) return false;
+ TypeFunPtr fun = (TypeFunPtr)t; // Invariant
+ if( !super.eq(fun) || _open != fun._open || _fidxs != fun._fidxs || _sig.length != fun._sig.length ) return false;
+ if( _sig == fun._sig && _ret == fun._ret ) return true;
+
+ // Check to see if we've ever compared this pair of types before;
+ // if so, then assume the cycle is equal here.
+ int pid = pid(fun);
+ if( TypeStruct.CEQUALS.find(pid)!= -1 ) return true;
+ TypeStruct.CEQUALS.push(pid);
+ if( !_ret.cycle_eq(fun._ret) ) return false;
+ for( int i = 0; i < _sig.length; i++ )
+ if( !_sig[i].cycle_eq(fun._sig[i]) )
+ return false;
+ return true;
+ }
+
+ @Override public String str() {
+ if( this==BOT ) return "FunBot";
+ if( this==BOT.dual() ) return "FunTop";
+ return "{"+printFIDX()+"}";
+ }
+
+ @Override
+ SB _print(SB sb, BitSet visit, boolean html ) {
+ if( this==BOT ) return sb.p("FunBot");
+ if( this==BOT.dual() ) return sb.p("FunTop");
+ sb.p(x()).p("{ ");
+ if( _sig!=null )
+ for( Type t : _sig )
+ sb.p(t==null ? "---" : t.str()).p(" "); // Short form in signature
+ if( !_open ) sb.p("^ ");
+ _ret.print(sb.p(html ? "&rarr; " : "-> "),visit,html).p(" #");
+ // Print fidxs
+ return sb.p(printFIDX()).p("}").p(q());
+ }
+ String printFIDX() {
+ String tilde = isHigh() ? "~" : "";
+ String fidx = XInt.str(_fidxs);
+ return tilde+fidx;
+ }
+
+ // Usage: for( long fidxs=fidxs(); fidxs!=0; fidxs=nextFIDX(fidxs) ) { int fidx = Long.numberOfTrailingZeros(fidxs); ... }
+ //public static long nextFIDX(long fidxs) { return fidxs & (fidxs-1); }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
new file mode 100644
index 00000000..a0c2266d
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
@@ -0,0 +1,217 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.AryInt;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Integer Type
+ */
+public class TypeInteger extends TypeScalar {
+ /**
+ * Describes an integer *range* - everything from min to max; both min and
+ * max are inclusive. If min==max, this is a constant.
+ * <br>
+ * If min <= max, this is a below center (towards bottom).
+ * If min > max, this is an above center (towards top).
+ */
+ public long _min, _max;
+
+ public byte _widen;
+
+ private static final Ary<TypeInteger> FREE = new Ary<>(TypeInteger.class);
+ private TypeInteger(long min, long max, byte widen) { super(TINT); init(min,max,widen); }
+ private TypeInteger init(long min, long max, byte widen) {
+ _min = min; _max = max; _widen=widen;
+ return this;
+ }
+ public static TypeInteger malloc(long lo, long hi, byte widen) { return FREE.isEmpty() ? new TypeInteger(lo,hi,widen) : FREE.pop().init(lo,hi,widen); }
+ public static TypeInteger make(long lo, long hi) { return make(lo,hi,(byte)0); }
+ public static TypeInteger make(long lo, long hi, byte widen) {
+ TypeInteger i = malloc(lo,hi,widen);
+ TypeInteger t2 = i.intern();
+ return t2==i ? i : t2.free(i);
+ }
+ public TypeInteger makeWide(byte widen) {
+ if( widen == _widen ) return this;
+ if( _min == Long.MIN_VALUE && _max == Long.MAX_VALUE )
+ return TypeInteger.BOT;
+ if( _min==_max ) return this; // Leave constants always narrow
+ return make(_min,_max,widen);
+ }
+ @Override TypeInteger free(Type t) {
+ TypeInteger i = (TypeInteger)t;
+ i._min = i._max = 0;
+ i._widen = 0;
+ i._hash = 0;
+ i._dual = null;
+ FREE.push(i);
+ return this;
+ }
+
+ public Type same_but_slightly_wider_than( ) {
+ return _widen < 3 ? make(_min,_max, (byte)(_widen+1)) : BOT;
+ }
+ // Force max widen
+ @Override public Type widen() {
+ return _widen==3 ? this : make(_min,_max,(byte)3);
+ }
+ public static TypeInteger constant(long con) { return make(con, con); }
+
+ public final static TypeInteger ZERO= make(0,0);
+ public final static TypeInteger FALSE=ZERO;
+ public final static TypeInteger TRUE= make(1,1);
+ public final static TypeInteger TWO = make(2,2); // Shows up in some Simple tests as the starting argument
+
+ public final static TypeInteger I1 = make(-1,0,(byte)3);
+ public final static TypeInteger I8 = make(-128,127,(byte)3);
+ public final static TypeInteger I16 = make(-32768,32767,(byte)3);
+ public final static TypeInteger I32 = make(-1L<<31,(1L<<31)-1,(byte)3);
+ public final static TypeInteger BOT = make(Long.MIN_VALUE,Long.MAX_VALUE,(byte)3);
+ public final static TypeInteger TOP = (TypeInteger)BOT.dual();
+
+ public final static TypeInteger U1 = make(0,1,(byte)3);
+ public final static TypeInteger BOOL= U1;
+ public final static TypeInteger U8 = make(0,255,(byte)3);
+ public final static TypeInteger U16 = make(0,65535,(byte)3);
+ public final static TypeInteger U32 = make(0,(1L<<32)-1,(byte)3);
+
+ public final static TypeInteger FATBOOL = make(0,1,(byte)2);
+
+ public static void gather(ArrayList<Type> ts) {
+ ts.add(I32); ts.add(BOT); ts.add(U1); ts.add(I1); ts.add(U8); ts.add(ZERO); ts.add(FATBOOL);
+ // Ranges before and after excluding zero, at each widening level.
+ for( byte widen=0; widen<=3; widen++ ) {
+ ts.add(make(0,255,widen));
+ ts.add(make(1,255,widen));
+ ts.add(make(-255,0,widen));
+ ts.add(make(-255,-1,widen));
+ }
+ }
+
+ @Override public String str() {
+ if( _isConstant() ) return ""+_min;
+ long lo = _min, hi = _max;
+ String x = "";
+ if( hi < lo ) {
+ lo = _max; hi = _min;
+ x = "~";
+ }
+ return x+_str(lo,hi);
+ }
+ private static String _str(long lo, long hi) {
+ if( lo==Long.MIN_VALUE && hi==Long.MAX_VALUE ) return "i64";
+ if( lo== 0 && hi== 1 ) return "bool";
+ if( lo== -1 && hi== 0 ) return "i1";
+ if( lo== -128 && hi== 127 ) return "i8";
+ if( lo== -32768 && hi== 32767 ) return "i16";
+ if( lo== -1L<<31 && hi==(1L<<31)-1 ) return "i32";
+ if( lo== 0 && hi== 255 ) return "u8";
+ if( lo== 0 && hi== 65535 ) return "u16";
+ if( lo== 0 && hi==(1L<<32)-1 ) return "u32";
+ return "["+lo+"-"+hi+"]";
+ }
+
+ @Override public boolean isHigh () { return _min > _max; }
+
+ @Override public int log_size() {
+ if( isHigh() ) return 0; // High types are dead, and should never hit code emission.
+ if( this==I8 || this==U8 || this==BOOL ) return 0; // 1<<0 == 1 bytes
+ if( this==I16 || this==U16 ) return 1; // 1<<1 == 2 bytes
+ if( this==I32 || this==U32 ) return 2; // 1<<2 == 4 bytes
+ if( this==BOT ) return 3; // 1<<3 == 8 bytes
+ if( _isConstant() ) return log(_min); // just const here
+ int lo = log(_min);
+ int hi = log(_max);
+ return Math.max(lo,hi);
+ }
+
+ private static int log(long x) {
+ if( -0xFFL <= x && x <= 0xFFL) return 0;
+ if( -0xFFFFL <= x && x <= 0xFFFFL) return 1;
+ if(-0xFFFFFFFFL <= x && x <= 0xFFFFFFFFL) return 2;
+ return 3;
+ }
+
+ public long value() { assert _isConstant(); return _min; }
+
+ // AND-mask of forced zeros. e.g. unsigned types will return their mask;
+ // u8 will return 0xFF. But also a range of 16-18 (0x10-0x12) will return
+ // 0x13 - no value in the range {16,17,18} will allow bit 0x04 to be set.
+ public long mask() {
+ if( isHigh() ) return 0;
+ if( isConstant() ) return _min;
+ // Those bit positions which differ min to max
+ long x = _min ^ _max;
+ // Highest '1' bit in the differ set. Since the range is from min to
+ // max, all values below here are possible.
+ long ff1 = Long.highestOneBit(x);
+ // Make a all-1's mask from ff1, and set over the same bits (either min
+ // or max is ok).
+ long mask = _min | (ff1-1) | ff1;
+ return mask;
+ }
+
+ @Override
+ public Type xmeet(Type other) {
+ // Invariant from caller: 'this' != 'other' and same class (TypeInteger)
+ TypeInteger i = (TypeInteger)other; // Contract
+ return make(Math.min(_min,i._min), Math.max(_max,i._max), (byte)Math.max(_widen,i._widen));
+ }
+
+ @Override TypeInteger xdual() { return malloc(_max,_min,(byte)(3-_widen)); }
+
+ @Override public TypeInteger nonZero() {
+ if( isHigh() ) return this;
+ if( this==ZERO ) return null; // No sane answer
+ if( _min==0 ) return make(1,Math.max(_max,1),_widen); // specifically good on BOOL
+ if( _max==0 ) return make(_min,-1,_widen);
+ return this;
+ }
+ @Override public Type makeZero() { return ZERO; }
+ @Override boolean _isConstant() { return _min == _max; }
+ @Override Type _makeStorage() {
+ if( _isConstant() ) return BOT;
+ return isHigh() ? dual() : this;
+ }
+ // Reserve tags for I64,I32,U32,0,constant,generic
+ @Override int TAGOFF() { return 7; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ if( this==BOT ) baos.write(TAGOFFS[_type] + 0);
+ else if( this==I32 ) baos.write(TAGOFFS[_type] + 1);
+ else if( this==U32 ) baos.write(TAGOFFS[_type] + 2);
+ else if( this==BOOL) baos.write(TAGOFFS[_type] + 3);
+ else if( this==ZERO) baos.write(TAGOFFS[_type] + 4);
+ else if( isConstant() ) {
+ baos.write(TAGOFFS[_type] + 5);
+ baos.packed8(_min);
+ } else {
+ baos.write(TAGOFFS[_type] + 6);
+ baos.packed8(_min);
+ baos.packed8(_max);
+ assert -3 <= _widen && _widen <= 3;
+ baos.packed1(_widen + 3);
+ }
+ }
+
+ static Type packed( int tag, BAOS bais ) {
+ return switch( tag ) {
+ case 0 -> BOT;
+ case 1 -> I32;
+ case 2 -> U32;
+ case 3 -> BOOL;
+ case 4 -> ZERO;
+ case 5 -> constant(bais.packed8());
+ case 6 -> make(bais.packed8(),bais.packed8(),(byte)(bais.packed1()-3));
+ default -> throw Utils.TODO("Should not reach here: unknown integer type tag");
+ };
+ }
+
+ @Override int hash() { return Utils.fold(_min) * Utils.fold(_max) + _widen; }
+ @Override public boolean eq( Type t ) {
+ return t instanceof TypeInteger i && _min==i._min && _max==i._max && _widen==i._widen;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMem.java
new file mode 100644
index 00000000..278a3ea0
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMem.java
@@ -0,0 +1,257 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.util.*;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashMap;
+
+
+/**
+ * Represents a slice of memory corresponding to a set of aliases
+ */
+public class TypeMem extends Type {
+
+ // Which slice of memory?
+ // 1 and TOP means no slice.
+ // 1 and BOT means all memory.
+ // N means alias slice#N.
+ public int _alias;
+ public Type _t; // Memory contents, some scalar type
+ public boolean _one; // Singleton memory, corresponding to TypeMemPtr._one.
+ public boolean _clz; // Class-object singleton memory; true singletons stay constant after construction.
+ public boolean _final;// Memory is only set in the constructor
+
+ // Escaped aliases and functions - escaped into this memory. These are
+ // reachable from some chain of indirections from `_t`. Bit zero is
+ // used for mutable/immutable safety. Sign bit repeats infinitely.
+ public int[] _escFs;
+ public int[] _escAs;
+
+ static public int CNT;
+
+ private static final Ary<TypeMem> FREE = new Ary<>(TypeMem.class);
+
+ private TypeMem() { super(TMEM); CNT++; }
+ private TypeMem init(int alias, Type t, boolean one, boolean clz, boolean xfinal, int[] fidxs, int[] aliases) {
+ _alias = alias;
+ _t = t;
+ _one = one;
+ _clz = clz;
+ // Alias 1 represents an aggregate, including a private object's fields.
+ // Its fields carry finality individually. Canonicalize the unused bit
+ // with _one so normalization commutes with dual, including named structs.
+ _final = alias==1 ? one : xfinal;
+
+ assert _escFs == null && _escAs == null;
+ _escFs = XInt.intern(fidxs);
+ _escAs = XInt.intern(aliases);
+ return this;
+ }
+ @Override TypeMem free(Type t) {
+ TypeMem mem = (TypeMem)t;
+ mem._alias= -99;
+ mem._dual = null;
+ mem._hash = 0;
+ mem._t = null;
+ mem._one = false;
+ mem._clz = false;
+ mem._final = false;
+ mem._escFs = null;
+ mem._escAs = null;
+ FREE.push(mem);
+ return this;
+ }
+ @Override boolean isFree() { return _alias == -99; }
+
+ public static TypeMem malloc(int alias, Type t, boolean one, boolean clz, boolean xfinal, int[] fidxs, int[] aliases) {
+ return (FREE.isEmpty() ? new TypeMem() : FREE.pop()).init(alias,t,one,clz,xfinal,fidxs,aliases);
+ }
+ public static TypeMem make(int alias, Type t, boolean one, boolean clz, boolean xfinal, int[] fidxs, int[] aliases) {
+ if( fidxs ==null ) fidxs = XInt.EMPTY;
+ if( aliases==null ) aliases= XInt.EMPTY;
+ TypeMem f = malloc(alias,t,one,clz,xfinal, fidxs, aliases);
+ TypeMem f2 = f.intern();
+ if( f2==f ) return f;
+ return VISIT.isEmpty() ? f2.free(f) : f2.delayFree(f);
+ }
+ public static TypeMem make(int alias, Type t) { return make(alias,t,false,false,false,null,null); }
+ // Make a private memory
+ public static TypeMem makePrivate(Type t) { return make(1,t,true,false,false,escapeFIDX(XInt.EMPTY,t), escapeAlias(XInt.EMPTY,t)); }
+
+ public TypeMem makeFrom(int alias) { return make(alias,_t,_one,_clz,_final,_escFs,_escAs); }
+ public TypeMem makeFrom(int[] fidxes, int[] aliases) {
+ return make(_alias,_t,_one,_clz,_final,fidxes,aliases);
+ }
+ // Existing escapes plus `t` escapes
+ public TypeMem escapes(Type t) {
+ return make(_alias,_t,_one,_clz,_final, escapeFIDX(_escFs,t), escapeAlias(_escAs,t));
+ }
+ // Existing escapes plus `t` escapes
+ public TypeMem escapesFrom(Type t) {
+ return make(_alias,_t,_one,_clz,_final, escapeFIDX(_escFs,t), escapeAlias(_escAs,t));
+ }
+ public TypeMem escapesAliases(Type t) {
+ return make(_alias,_t,_one,_clz,_final, _escFs, escapeAlias(_escAs,t));
+ }
+
+ public static final TypeMem TOP = make(1, Type.TOP ,true ,true, true , XInt.EMPTY, XInt.EMPTY);
+ public static final TypeMem BOT = make(1, Type.BOTTOM,false,false,true , XInt.FULL , XInt.FULL );
+ public static final TypeMem START= make(1,Type.BOTTOM,false,false,false, XInt.EMPTY, XInt.EMPTY);
+ public static final TypeMem SELF_MEM = make(1, TypeStruct.BOT);
+
+ public static void gather(ArrayList<Type> ts) {
+ ts.add(make(2,Type.NIL));
+ ts.add(make(2,TypeInteger.ZERO));
+ ts.add(BOT);
+ ts.add(SELF_MEM);
+ ts.add(START);
+ ts.add(make(2,TypeInteger.ZERO,true,false,true,XInt.EMPTY,XInt.make(2)));
+ ts.add(make(2,TypeInteger.BOT,false,false,false,XInt.EMPTY,XInt.FULL));
+ }
+
+ @Override
+ TypeMem xmeet(Type t) {
+ TypeMem that = (TypeMem) t; // Invariant: TypeMem and unequal
+ if( this==TOP ) return that;
+ if( that==TOP ) return this;
+ if( this==BOT ) return BOT;
+ if( that==BOT ) return BOT;
+ // Treat a alias-1 has either very-high or very-low, according to "_t"
+ int alias = _alias==that._alias ? _alias // Same-same
+ : this._alias==1 && this._t.isHigh() ? that._alias // this is high-1 alias, so take that
+ : that._alias==1 && that._t.isHigh() ? this._alias // that is high-1 alias, so take this
+ : 1;
+ Type mt = _t.meet(that._t);
+ int[] fidxs = XInt.meet( _escFs, that._escFs );
+ int[] aliases= XInt.meet( _escAs, that._escAs );
+ // Singleton and class facts are kept only when both sides agree.
+ // An aggregate has no field-wide finality. If meeting with it selects
+ // a precise alias, retain that slice's finality without mixing in the
+ // aggregate's canonical placeholder bit.
+ boolean xfinal = _alias==1 ? that._final : that._alias==1 ? _final : (_final | that._final);
+ return make(alias, mt, _one & that._one, _clz & that._clz, xfinal, fidxs, aliases);
+ }
+
+ @Override
+ Type xdual() { return malloc(_alias,_t.dual(),!_one,!_clz,!_final, XInt.dual(_escFs), XInt.dual(_escAs)); }
+
+ @Override TypeMem rdual() {
+ if( _dual!=null ) return dual();
+ assert !_terned;
+ TypeMem d = malloc(_alias,null,!_one,!_clz,!_final, XInt.dual(_escFs), XInt.dual(_escAs));
+ (_dual = d)._dual = this; // Cross link duals
+ d._t = _t._terned ? _t.dual() : _t.rdual();
+ return d;
+ }
+
+ @Override public TypeMem makeZero() { return TOP; }
+ @Override public boolean isHigh() { return _t.isHigh(); }
+ @Override boolean _isConstant() { return _one && _clz && _alias!= 1 && _t._isConstant(); }
+ @Override public int log_size() { throw Utils.TODO("Should not reach here: memory has no scalar storage size"); }
+ @Override boolean _isFinal() { throw Utils.TODO("Should not reach here"); }
+ @Override TypeMem _makeStorage() { throw Utils.TODO("Should not reach here"); /*return make(_alias,_t._makeStorage(),_one,_clz,_final,_escFs,_escAs);*/ }
+
+ @Override TypeMem _close( String name, HashMap<String, Type> TYPES ) { return malloc(_alias,_t._close(name, TYPES ),_one,_clz,_final,_escFs,_escAs); }
+
+ @Override Type _upgradeType(HashMap<String,Type> TYPES) {
+ return make(_alias,_t._upgradeType(TYPES),_one,_clz,_final,_escFs,_escAs);
+ }
+
+ @Override int hash() { return 9876543 + _alias + _t.hashCode() + (_one ? 8196 : 0) + (_clz ? 4098 : 0) + (_final ? 16392 : 0) + XInt.hash(_escFs) + XInt.hash(_escAs); }
+
+ @Override boolean eq(Type t) {
+ TypeMem that = (TypeMem) t; // Invariant
+ return _alias == that._alias && _t == that._t && _one == that._one && _clz == that._clz && _final == that._final && _escFs == that._escFs && _escAs == that._escAs;
+ }
+
+ @Override boolean cycle_eq(Type t) {
+ if( t._type!=TMEM ) return false;
+ TypeMem that = (TypeMem) t; // Invariant
+ return _alias == that._alias && _one == that._one && _clz == that._clz && _final == that._final && _escFs == that._escFs && _escAs == that._escAs && _t.cycle_eq(that._t);
+ }
+
+ @Override public int nkids() { return 1; }
+ @Override public Type at( int idx ) { return _t; }
+ @Override public void set( int idx, Type t ) { _t = t; }
+ // one tag for alias#1, one tag for generic alias.
+ // tag bit for _one, another bit for _final.
+ @Override int TAGOFF() { return 12; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ baos.write(TAGOFFS[_type]
+ + (_alias==1 ? 0 : 1)
+ + (!_final ? 0 : 2)
+ + (!_one ? 0 : (_clz ? 4 : 8)));
+ if( _alias!=1 ) {
+ // generic alias
+ assert _alias <= 255;
+ baos.packed1( _alias );
+ }
+ XInt.packed(baos,_escFs);
+ XInt.packed(baos,_escAs);
+ }
+ static TypeMem packed( int tag, BAOS bais ) {
+ int alias = (tag&1)==0 ? 1 : bais.packed1();
+ boolean xfinal= (tag&2)==2;
+ boolean one = (tag&12)!=0;
+ boolean clz = (tag&4)==4;
+ int[] escFs = XInt.packed(bais);
+ int[] escAs = XInt.packed(bais);
+ return malloc(alias,null,one,clz,xfinal,escFs,escAs);
+ }
+
+ @Override public SB _print(SB sb, BitSet visit, boolean html) {
+ if( this==BOT ) return sb.p("MemBot");
+ if( this==TOP ) return sb.p("MemTop");
+ sb.p('#');
+ if( !_final ) sb.p('!'); // Mutable memory
+ if( _one ) sb.p(_clz ? 'z' : '-');
+ if( _alias==0 ) return sb.p(_t._type==TTOP ? "TOP" : "BOT");
+ sb.p(_alias).p(":[");
+ _t.print(sb,visit,html).p(",");
+ XInt.str(sb,_escFs).p(",");
+ return XInt.str(sb,_escAs).p("]");
+ }
+
+ @Override public String str() {
+ if( this==BOT ) return "MemBot";
+ if( this==TOP ) return "MemTop";
+ SB sb = new SB().p('#');
+ if( !_final ) sb.p('!'); // Mutable memory
+ if( _one ) sb.p(_clz ? 'z' : '-');
+ if( _alias==0 ) return sb.p(_t._type==TTOP ? "TOP" : "BOT").toString();
+ sb.p(_alias).p(":[").p(_t.str()).p(",");
+ XInt.str(sb,_escFs).p(",");
+ return XInt.str(sb,_escAs).p("]").toString();
+ }
+
+
+ // Tracking bulk escaped aliases and fidxs
+
+ // Escape all FIDXs from t
+ private static int[] escapeFIDX( int[] xs, Type t ) {
+ return switch(t) {
+ case TypeMemPtr tmp -> escapeFIDX(xs,tmp._obj);
+ case TypeStruct ts -> ts._open && !ts.isAry()
+ ? XInt.FULL // Open structs can have any fields
+ : XInt.meet(xs,ts.fidxs());
+ case TypeFunPtr tfp -> XInt.meet(xs,tfp.fidxs());
+ case TypeMem mem -> XInt.meet(xs,mem._escFs);
+ // An abstract pointer or scalar can refine to a concrete escaped
+ // object or function. Until then it must allow all of their bits.
+ case TypePtr ptr -> ptr.isHigh() ? xs : XInt.FULL;
+ case Type tt -> tt == BOTTOM || tt == TypeScalar.BOT ? XInt.FULL : xs;
+ };
+ }
+
+ private static int[] escapeAlias( int[] xs, Type t ) {
+ return switch(t) {
+ case TypeMemPtr tmp -> escapeAlias(xs,tmp._obj);
+ case TypeStruct ts -> ts._open && !ts.isAry()
+ ? XInt.FULL // Open structs can have any fields
+ : XInt.meet(xs,ts.aliases());
+ case TypeMem mem -> XInt.meet(xs,mem._escAs);
+ case TypePtr ptr -> ptr.isHigh() ? xs : XInt.FULL;
+ case Type tt -> tt == BOTTOM || tt == TypeScalar.BOT ? XInt.FULL : xs;
+ };
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
new file mode 100644
index 00000000..175b1c63
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
@@ -0,0 +1,193 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.AryInt;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.BAOS;
+import java.util.*;
+
+/**
+ * Represents a Pointer to memory.
+ *
+ * Null is generic pointer to non-existent memory.
+ * *void is a non-Null pointer to all possible refs, both structs and arrays.
+ * Pointers can be to specific struct and array types, or a union with Null.
+ * The distinguished *$BOT ptr represents union of *void and Null.
+ * The distinguished *$TOP ptr represents the dual of *$BOT.
+ */
+public class TypeMemPtr extends TypeNil {
+ // A TypeMemPtr is pair (obj,nil)
+ // where obj is a TypeStruct, possibly TypeStruct.BOT/TOP
+ // where nil is an explicit null is allowed or not
+
+ // Examples:
+ // (Person,false) - a not-nil Person
+ // (Person,true ) - a Person or a nil
+ // (BOT ,false) - a not-nil void* (unspecified struct)
+ // (TOP ,true ) - a nil
+
+ public TypeStruct _obj;
+ // Deep read-only access through this pointer; independent of slot finality.
+ public boolean _ro;
+
+ public boolean _one; // Singleton pointer value; the pointer itself is constant.
+ public boolean _pub; // Escaped/public pointer. False means constructor-private.
+
+ private static final Ary<TypeMemPtr> FREE = new Ary<>(TypeMemPtr.class);
+ private TypeMemPtr(byte nil, TypeStruct obj, boolean one, boolean pub) { super(TMEMPTR,nil); init(nil,obj,one,pub); }
+ private TypeMemPtr init(byte nil, TypeStruct obj, boolean one, boolean pub) { _nil=nil; _obj=obj; _ro=obj==TypeStruct.BOT; _one=one; _pub=pub; return this; }
+ // Return a filled-in TypeMemPtr; either from free list or alloc new.
+ static TypeMemPtr malloc(byte nil, TypeStruct obj, boolean one, boolean pub) {
+ return FREE.isEmpty() ? new TypeMemPtr(nil,obj,one,pub) : FREE.pop().init(nil,obj,one,pub);
+ }
+
+ // All fields directly listed
+ public static TypeMemPtr make(byte nil, TypeStruct obj, boolean one) {
+ return make(nil,obj,one,true);
+ }
+ public static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean pub) {
+ return make(nil,obj,one, pub,obj==TypeStruct.BOT);
+ }
+ private static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean pub, boolean ro) {
+ TypeMemPtr tmp = malloc(nil, obj, one, pub).access(ro);
+ TypeMemPtr t2 = tmp.intern();
+ if( t2==tmp ) return tmp;
+ return VISIT.isEmpty() ? t2.free(tmp) : t2.delayFree(tmp);
+ }
+ @Override TypeMemPtr free(Type t) {
+ TypeMemPtr tmp = (TypeMemPtr)t;
+ tmp._nil = -99;
+ tmp._obj = null;
+ tmp._one = false;
+ tmp._pub = true;
+ tmp._dual = null;
+ tmp._hash = 0;
+ FREE.push(tmp);
+ return this;
+ }
+ @Override boolean isFree() { return _obj==null; }
+
+ static TypeMemPtr make(byte nil, TypeStruct obj) { return make(nil, obj, false); }
+ public static TypeMemPtr make (TypeStruct obj) { return make((byte)2, obj); }
+ public static TypeMemPtr makeNullable(TypeStruct obj) { return make((byte)3, obj); }
+ public static TypeMemPtr makePrivate (TypeStruct obj) { return make((byte)2, obj, false, false); }
+
+ public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj, _one, _pub,_ro); }
+ public TypeMemPtr makeNullable() { return makeFrom((byte)3); }
+ /** Inferred mutable local: nullable, public and non-singleton. */
+ public TypeMemPtr makeVar() { return (TypeMemPtr)makeStorage(); }
+ /** Public-memory shape: nullable and non-singleton, preserving the referent. */
+ @Override TypeMemPtr _makeStorage() { return make(_nil,_obj._makeStorage(),false,true,_ro); }
+ @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one, _pub,_ro); }
+ @Override public Type nonZero() { return makeFrom((byte)(_nil <= 1 ? 1 : 2)); }
+
+ private TypeMemPtr access(boolean ro) { _ro=ro; return this; }
+
+ public TypeMemPtr withAccess(boolean ro) {
+ if( _ro==ro ) return this;
+ TypeMemPtr ptr = malloc(_nil, _obj, _one, _pub);
+ ptr._ro = ro;
+ TypeMemPtr res = ptr.intern();
+ if( res==ptr ) return ptr;
+ return VISIT.isEmpty() ? res.free(ptr) : res.delayFree(ptr);
+ }
+
+ // Writable slots can load and store references, so their element access
+ // must agree in both directions. A read-only outer view can weaken access.
+ @Override public boolean accessISA(Type dst) {
+ if( !(dst instanceof TypeMemPtr ptr) ) return true;
+ if( _ro && !ptr._ro ) return false;
+ if( ptr._ro || _obj._fields==null || ptr._obj._fields==null || !_obj.isAry() || !ptr._obj.isAry() ) return true;
+ Type a = _obj._fields[1]._t, b = ptr._obj._fields[1]._t;
+ return a.accessISA(b) && b.accessISA(a);
+ }
+
+ // An abstract pointer, pointing to either a Struct or an Array.
+ // Can also be null or not, so 4 choices {TOP,BOT} x {nil,not}
+ public static final TypeMemPtr BOT = make((byte)3, TypeStruct.BOT);
+ public static final TypeMemPtr TOP = BOT.dual();
+ public static final TypeMemPtr NOTBOT = make((byte)2,TypeStruct.BOT);
+
+ public static final TypeMemPtr TEST= make((byte)2, TypeStruct.TEST);
+ public static void gather(ArrayList<Type> ts) { ts.add(NOTBOT); ts.add(BOT); ts.add(TEST); ts.add(TEST.makeRO()); }
+
+ @Override
+ public TypeNil xmeet(Type t) {
+ TypeMemPtr that = (TypeMemPtr) t;
+ assert !isFree() && !that.isFree();
+ // Can't keep _one if mixing two unequal singletons; the result is not
+ // either singleton. Once either side is public, the meet is public.
+ boolean one = _one && that._one && (_obj==that._obj || !(_obj.isConAry() || that._obj.isConAry()));
+ return make(xmeet0(that), (TypeStruct)_obj.meet(that._obj), one, _pub | that._pub,_ro | that._ro);
+ }
+
+ @Override
+ TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), !_one, !_pub).access(!_ro); }
+
+ @Override TypeMemPtr rdual() {
+ if( _dual!=null ) return dual();
+ assert !_terned;
+ TypeMemPtr d = malloc(dual0(), null, !_one, !_pub).access(!_ro);
+ (_dual = d)._dual = this; // Cross link duals
+ d._obj = _obj._terned ? _obj.dual() : _obj.rdual();
+ return d;
+ }
+
+ // RHS is NIL; do not deep-dual when crossing the center line
+ @Override Type meet0() { return _nil==3 ? this : make((byte)3,_obj,false,true,_ro); }
+
+ @Override boolean _isConstant() { return _one; }
+ @Override boolean _isFinal() { return _ro; }
+ @Override TypeMemPtr _makeRO() { return withAccess(true); }
+ @Override TypeMemPtr _close( String name, HashMap<String, Type> TYPES ) { return malloc(_nil,_obj._close(name, TYPES ),_one,_pub).access(_ro); }
+
+ @Override Type _upgradeType(HashMap<String,Type> TYPES) {
+ return makeFrom((TypeStruct)_obj._upgradeType(TYPES));
+ }
+
+ @Override public int log_size() { return 3; } // (1<<3)==8-byte pointers
+
+ @Override int hash() { return (_ro ? 8192 : 0) ^ _obj.hashCode() ^ super.hash() ^ (_one ? 2048 : 0) ^ (_pub ? 4096 : 0); }
+
+ @Override boolean eq(Type t) {
+ TypeMemPtr ptr = (TypeMemPtr)t; // Invariant
+ return _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _obj == ptr._obj;
+ }
+ @Override boolean cycle_eq(Type t) {
+ if( t._type != TMEMPTR ) return false;
+ TypeMemPtr ptr = (TypeMemPtr)t; // Invariant
+ return _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _obj.cycle_eq(ptr._obj);
+ }
+
+ @Override public int nkids() { return 1; }
+ @Override public Type at( int idx ) { return _obj; }
+ @Override public void set( int idx, Type t ) { _obj = (TypeStruct)t; }
+
+ // Reserve tags for null/not, one/general, public/private and read-only access
+ @Override int TAGOFF() { return 16; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ assert _nil>=2;
+ baos.write(TAGOFFS[_type]
+ + (_nil==2 ? 0 : 1)
+ + (!_one ? 0 : 2)
+ + (!_pub ? 0 : 4)
+ + (!_ro ? 0 : 8) );
+ }
+ static TypeMemPtr packed( int tag, BAOS bais ) {
+ return malloc((byte)((tag&1)+2),null,(tag&2)==2,(tag&4)==4).access((tag&8)==8);
+ }
+
+ @Override public String str() {
+ if( this==BOT ) return "PtrBot";
+ if( this==TOP ) return "PtrTop";
+ if( this== NOTBOT) return "*void";
+ return x()+"*"+(_obj==null?"---":_obj.str())+q();
+ }
+
+ @Override SB _print(SB sb, BitSet visit, boolean html ) {
+ if( this==BOT ) return sb.p("PtrBot");
+ if( this==TOP ) return sb.p("PtrTop");
+ if( this== NOTBOT) return sb.p("*void");
+ return _obj.print(sb.p(x()).p("*"),visit,html).p(q());
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeNil.java b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeNil.java
new file mode 100644
index 00000000..2c1c1d76
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeNil.java
@@ -0,0 +1,61 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+import java.util.ArrayList;
+
+/**
+ * Nil-able Scalar types
+ */
+public abstract class TypeNil extends TypeScalar {
+ // 0 = high-subclass choice nil
+ // 1 = high-subclass no nil
+ // 2 = low -subclass no nil
+ // 3 = low -subclass also nil
+ byte _nil;
+
+ TypeNil(byte t, byte nil ) { super(t); _nil = nil; }
+
+ public static void gather(ArrayList<Type> ts) { }
+
+ abstract TypeNil makeFrom(byte nil);
+
+ byte xmeet0(TypeNil that) { return (byte)Math.max(_nil,that._nil); }
+
+ byte dual0() { return (byte)(3-_nil); }
+
+ // RHS is NIL
+ abstract Type meet0();
+
+ // RHS is XNIL
+ Type meetX() {
+ return _nil==0 ? XNIL : (_nil<=2 ? TypePtr.NPTR : TypePtr.PTR);
+ }
+
+ Type nmeet(TypeNil tn) {
+ // Invariants: both are TypeNil subclasses and unequal classes.
+ // If this is TypePtr, we went to TypePtr.nmeet and not here.
+ // If that is TypePtr, this is not (invariant); reverse and go again.
+ if( tn instanceof TypePtr ts ) return ts.nmeet(this);
+
+ // Two mismatched TypeNil, no Scalar.
+ if( _nil==0 && tn._nil==0 ) return XNIL;
+ if( _nil<=2 && tn._nil<=2 ) return TypePtr.NPTR;
+ return TypePtr.PTR;
+ }
+
+ @Override public boolean isHigh () { return _nil <= 1; }
+ @Override public boolean isHighOrConst() { return isHigh() || isConstant(); }
+
+ public boolean notNull() { return _nil==1 || _nil==2; }
+ public boolean nullable() { return _nil==3; }
+
+ @Override boolean _isConstant() { return false; }
+
+ final String q() { return _nil == 1 || _nil == 2 ? "" : "?"; }
+ final String x() { return isHigh() ? "~" : ""; }
+
+ int hash() { return _nil<<17; }
+
+ @Override boolean eq(Type ptr) { return ptr instanceof TypeNil nil && _nil ==nil._nil; }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/TypePtr.java b/chapter29/src/main/java/com/seaofnodes/simple/type/TypePtr.java
new file mode 100644
index 00000000..38941ed1
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypePtr.java
@@ -0,0 +1,66 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.util.AryInt;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.util.BAOS;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashMap;
+
+/**
+ * Represents a Scalar; a single register-sized value.
+ */
+public class TypePtr extends TypeNil {
+ private static final String[] STRS = new String[]{"~ptr","~nptr","nptr","ptr"};
+
+ private TypePtr(byte nil) { super(TPTR,nil); }
+
+ // An abstract pointer, pointing to either a Struct or an Array.
+ // Can also be null or not, so 4 choices {TOP,BOT} x {nil,not}
+ public static TypePtr XPTR = new TypePtr((byte)0).intern();
+ public static TypePtr XNPTR= new TypePtr((byte)1).intern();
+ public static TypePtr NPTR = (TypePtr)XNPTR.dual();
+ public static TypePtr PTR = (TypePtr)XPTR.dual();
+ private static final TypePtr[] PTRS = new TypePtr[]{XPTR,XNPTR,NPTR,PTR};
+
+ public static void gather(ArrayList<Type> ts) { ts.add(PTR); ts.add(NPTR); }
+
+ TypeNil makeFrom(byte nil) { throw Utils.TODO("Should not reach here: plain pointer nil state is canonical"); }
+
+ @Override public TypeNil xmeet(Type t) {
+ TypePtr that = (TypePtr) t;
+ return PTRS[xmeet0(that)];
+ }
+
+ @Override TypePtr xdual() { return new TypePtr((byte)(3-_nil)); }
+
+ // High scalar loses, low scalar wins
+ @Override TypeNil nmeet(TypeNil tn) {
+ if( _nil==0 ) return tn; // High scalar loses
+ if( _nil==1 ) return tn.makeFrom(xmeet0(tn)); // High scalar loses
+ if( _nil==2 ) return tn._nil==3 ? PTR : NPTR; // Low scalar wins
+ return PTR; // this
+ }
+
+
+ // RHS is NIL
+ @Override Type meet0() { return isHigh() ? NIL : PTR; }
+ // RHS is XNIL
+ // 0->xscalar, 1->nscalar, 2->nscalar, 3->scalar
+ @Override Type meetX() { return _nil==0 ? XNIL : (_nil==3 ? PTR : NPTR); }
+
+ @Override TypePtr _makeStorage() { return PTR; }
+
+
+ // Reserve 2 tags, PTR and NPTR
+ @Override int TAGOFF() { return 2; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ assert _nil>=2;
+ baos.write(TAGOFFS[_type] + (_nil==2 ? 0 : 1));
+ }
+ static TypePtr packed( int tag ) { return tag==0 ? NPTR : PTR; }
+
+ @Override public String str() { return STRS[_nil]; }
+ @Override public SB _print(SB sb, BitSet visit, boolean html) { return sb.p(str()); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeRPC.java b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeRPC.java
new file mode 100644
index 00000000..8a12f50a
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeRPC.java
@@ -0,0 +1,106 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.BAOS;
+import java.util.*;
+
+/**
+ * Return Program Control or Return PC or RPC
+ */
+public class TypeRPC extends Type {
+
+ // A set of CallEndNode IDs (or StopNode); commonly just one. High-bit
+ // XInt sets represent complements/infinite sets.
+ public int[] _rpcs;
+
+ private static final Ary<TypeRPC> FREE = new Ary<>(TypeRPC.class);
+ private TypeRPC init(int[] rpcs) { _rpcs = XInt.intern(rpcs); return this; }
+ private TypeRPC() { super(TRPC); }
+ private static TypeRPC malloc(int[] rpcs) {
+ return (FREE.isEmpty() ? new TypeRPC() : FREE.pop()).init(rpcs);
+ }
+ private static TypeRPC make(int[] rpcs) {
+ TypeRPC rpc = malloc(rpcs);
+ TypeRPC t2 = rpc.intern();
+ return t2==rpc ? rpc : t2.free(rpc);
+ }
+ @Override TypeRPC free(Type t) {
+ TypeRPC rpc = (TypeRPC)t;
+ rpc._rpcs = null;
+ rpc._dual = null;
+ rpc._hash = 0;
+ FREE.push(rpc);
+ return this;
+ }
+ @Override boolean isFree() { return _rpcs==null; }
+
+ public static TypeRPC constant(int cend) {
+ return make(XInt.make(cend));
+ }
+
+ public int rpc() {
+ return XInt.onlyBit(_rpcs);
+ }
+
+
+ public static final TypeRPC BOT = make(XInt.FULL);
+ private static final TypeRPC TEST2 = constant(2);
+ private static final TypeRPC TEST3 = constant(2);
+ public static final TypeRPC EMPTY = make(XInt.EMPTY);
+
+ public static void gather(ArrayList<Type> ts) { ts.add(BOT); ts.add(TEST2); ts.add(TEST3); }
+
+ @Override public TypeRPC makeZero() { return EMPTY; }
+
+
+ @Override public String str() {
+ if( _rpcs == XInt.FULL ) return "$[ALL]";
+ if( _rpcs == XInt.EMPTY ) return "$[]";
+ return "$"+XInt.str(_rpcs);
+ }
+
+ @Override
+ public TypeRPC xmeet(Type other) {
+ TypeRPC rpc = (TypeRPC)other;
+ return make(XInt.meet(_rpcs,rpc._rpcs));
+ }
+
+ @Override TypeRPC xdual() { return malloc(XInt.dual(_rpcs)); }
+ @Override Type rdual() {
+ TypeRPC d = xdual();
+ (_dual = d)._dual = this;
+ return d;
+ }
+
+ @Override boolean _isConstant() { return XInt.isConstant(_rpcs); }
+ @Override TypeRPC _makeStorage() { return this; }
+
+ // Reserve tags for ALL, singleton, generic
+ @Override int TAGOFF() { return 3; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ if( this==BOT ) {
+ baos.write( TAGOFFS[_type] + 0 );
+ } else if( XInt.isConstant(_rpcs) ) { // Singleton
+ baos.write(TAGOFFS[_type] + 1);
+ baos.packed2(rpc());
+ } else { // Generic
+ assert !XInt.isHigh(_rpcs); // Not serializing above-center RPC
+ baos.write(TAGOFFS[_type] + 2);
+ XInt.packed(baos,_rpcs);
+ }
+ }
+ static TypeRPC packed( int tag, BAOS bais ) {
+ if( tag==0 ) return BOT;
+ if( tag==1 ) return malloc(XInt.make(bais.packed2()));
+ return malloc(XInt.packed(bais));
+ }
+
+ @Override
+ int hash() { return XInt.hash(_rpcs); }
+ @Override
+ public boolean eq( Type t ) {
+ TypeRPC rpc = (TypeRPC)t; // Contract
+ return _rpcs==rpc._rpcs;
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeScalar.java b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeScalar.java
new file mode 100644
index 00000000..eaba0a1c
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeScalar.java
@@ -0,0 +1,57 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.SB;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashMap;
+
+/**
+ * The envelope for values which can live in an ordinary register or memory
+ * field: integers, floats, memory pointers, and function pointers.
+ *
+ * `BOT` means any scalar value and `TOP` means no scalar value. They sit
+ * strictly inside the global {@link Type#BOTTOM}/{@link Type#TOP} pair, which
+ * also includes non-values such as control and memory.
+ */
+public class TypeScalar extends Type {
+ protected TypeScalar(byte type) { super(type); }
+ private TypeScalar() { this(TSCALAR); }
+
+ public static final TypeScalar BOT = new TypeScalar().intern();
+ public static final TypeScalar TOP = BOT.dual();
+
+ public static void gather(ArrayList<Type> ts) { ts.add(BOT); }
+
+ /** Meet two different scalar families. */
+ final Type smeet(TypeScalar scalar) {
+ if( _type == TSCALAR ) return isHigh() ? scalar : this;
+ if( scalar._type == TSCALAR ) return scalar.isHigh() ? this : scalar;
+ return BOT;
+ }
+
+ @Override Type xmeet(Type t) {
+ TypeScalar scalar = (TypeScalar)t;
+ return isHigh() ? scalar : this;
+ }
+
+ @Override TypeScalar xdual() { return new TypeScalar(); }
+ @Override public boolean isHigh() { return this == TOP; }
+ @Override Type _makeStorage() { return BOT; }
+
+ // Reserve tags for both lattice endpoints. Unlike most concrete scalar
+ // families, generic scalar TOP can survive into a serialized graph.
+ @Override int TAGOFF() { return 2; }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs) {
+ assert this == BOT || this == TOP;
+ baos.write(TAGOFFS[_type] + (this == BOT ? 0 : 1));
+ }
+ static TypeScalar packed(int tag) {
+ assert tag == 0 || tag == 1;
+ return tag == 0 ? BOT : TOP;
+ }
+
+ @Override public String str() { return isHigh() ? "~scalar" : "scalar"; }
+ @Override SB _print(SB sb, BitSet visit, boolean html) { return sb.p(str()); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
new file mode 100644
index 00000000..4e73e148
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
@@ -0,0 +1,656 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.util.*;
+import java.util.*;
+
+/**
+ * Represents a struct type.
+ */
+public class TypeStruct extends Type {
+
+ // A Struct has a name and a set of fields; the fields themselves have
+ // names, types and aliases. The name is essentially another field,
+ // immutable and type ignored (BOT).
+
+ /**
+ * `_open` and `_fref` are independent lattice coordinates.
+ *
+ * `_open` says the structural tail is not known yet - more fields may be
+ * added. On the low side an open struct has infinitely many implicit BOT
+ * fields; its dual has an implicit TOP tail. A real declaration is open
+ * while its body is being parsed:
+ *
+ * ```text
+ * struct S { int x; /* more declared fields may follow *\/ }
+ * ```
+ *
+ * `_fref` says no actual declaration has contributed to the type. The
+ * parser may still invent fields demanded by uses, leaving a useful open
+ * structural constraint. Here `S` is an unknown struct, which has an
+ * unknown `x` field:
+ *
+ * ```text
+ * S? p; p.x; // fref S { BOT x; ... }
+ * ```
+ *
+ * Meeting that constraint with a real definition ANDs `_fref` off while
+ * retaining and checking the invented field. Closing recursive types must
+ * not by itself clear `_fref`; closure discovers shape, not a declaration.
+ */
+
+ public String _name; // Struct name
+ public boolean _open;
+ public boolean _fref;
+ public Field[] _fields;
+
+ private TypeStruct() { super(TSTRUCT); }
+ private static final Ary<TypeStruct> FREE = new Ary<>(TypeStruct.class);
+ private TypeStruct init(String name, boolean open, boolean fref, Field[] fields) {
+ // Check for const array element name with mutable field
+ //assert !name.startsWith("[~]") || fields.length!=2 || fields[1]._t==null || (fields[1]._final != fields[1]._t.isHigh());
+ _name = name;
+ _open = open;
+ _fref = fref;
+ _fields = fields;
+ return this;
+ }
+ // Return a filled-in TypeStruct; either from free list or alloc new.
+ private static TypeStruct malloc(String name, boolean open, boolean fref, Field[] fields) {
+ return (FREE.isEmpty() ? new TypeStruct() : FREE.pop()).init(name,open,fref,fields);
+ }
+ // Free ts; return this.
+ @Override TypeStruct free(Type t) {
+ TypeStruct ts = (TypeStruct)t;
+ assert !ts.isFree() && !ts._terned;
+ ts._name = null;
+ ts._fields = null;
+ ts._offs = null;
+ ts._dual = null;
+ ts._hash = 0;
+ ts._aliases = null;
+ ts._fidxs = null;
+ FREE.push(ts);
+ return this;
+ }
+ @Override boolean isFree() { return _fields==null; }
+
+
+ // All fields directly listed
+ public static TypeStruct make(String name, boolean open, Field... fields) {
+ return make(name,open,false,fields);
+ }
+ public static TypeStruct make(String name, boolean open, boolean fref, Field... fields) {
+ TypeStruct ts = malloc(name, open, fref, fields);
+ TypeStruct t2 = ts.intern();
+ if( t2==ts ) return ts;
+ return VISIT.isEmpty() ? t2.free(ts) : ts.delayFree(ts);
+ }
+
+ // Summary aliases, after interning. Skips private
+ // fields which need to be explicitly loaded.
+ private int[] _aliases;
+ public int[] aliases() {
+ if( _aliases != null ) return _aliases;
+ if( _fields.length==0 ) return (_aliases=XInt.EMPTY);
+ assert _terned;
+ int maxAlias = 0;
+ for( Field f : _fields )
+ maxAlias = Math.max(maxAlias,f._alias);
+ int len = XInt.idx(maxAlias)+1;
+ if( (maxAlias&31)==31 ) len++;
+ int[] xs = XInt.free(len);
+ for( Field f : _fields )
+ if( f._fname.charAt(0) != '_' && // Skip private fields.
+ !hiddenInit(f) && // Skip private allocation helper.
+ f._t != Type.TOP ) // Skip uninit fields
+ xs[XInt.idx(f._alias)] |= XInt.mask(f._alias);
+ return (_aliases=XInt.intern(xs));
+ }
+
+ // Summary fidxs, after interning. Skips private
+ // fields which need to be explicitly loaded.
+ private int[] _fidxs;
+ public int[] fidxs() {
+ if( _fidxs != null ) return _fidxs;
+ assert _terned;
+ int[] xs = XInt.EMPTY;
+ for( Field f : _fields )
+ if( f._fname.charAt(0) != '_' && // Skip private fields.
+ !hiddenInit(f) && // Skip private allocation helper.
+ f._t instanceof TypeFunPtr tfp ) // Looking for TFP
+ xs = XInt.meet(xs,tfp._fidxs);
+ return (_fidxs = xs);
+ }
+
+ // With a user constructor, the same-named function field is the hidden
+ // default <init> allocation helper, not part of the public class surface.
+ public boolean hiddenInit(Field fld) {
+ if( field("<ctor>") == null || !_name.startsWith("class:") ) return false;
+ int idx = _name.lastIndexOf('.');
+ String name = _name.substring(idx<0 ? "class:".length() : idx+1);
+ return fld._fname.equals(name);
+ }
+
+ // New open struct with no fields
+ public static TypeStruct open( String name ) { return make(name,true); }
+ public static TypeStruct forward( String name ) { return make(name,true,true); }
+ public TypeStruct defined() { return _fref ? make(_name,_open,false,_fields) : this; }
+
+ // Array, variant body
+ public static TypeStruct makeAry(String name, TypeInteger len, int lenAlias, Type body, int bodyAlias, boolean efinal) {
+ return make(name,false,
+ Field.make("#" , len, lenAlias, true ),
+ Field.make("[]",body,bodyAlias,efinal));
+ }
+ // Array, known constant body
+ public static TypeStruct makeAry(String name, int lenAlias, int bodyAlias, TypeConAry con) {
+ Type len = TypeInteger.constant(con.len());
+ return make(name,false,
+ Field.make("#" ,len , lenAlias,true),
+ Field.make("[]" ,con.elem(),bodyAlias,true),
+ Field.make("[con]",con ,bodyAlias,true));
+ }
+
+ public TypeStruct makeInit() {
+ Field[] fs = new Field[_fields.length];
+ for( int i=0; i<_fields.length; i++ ) {
+ if( _fields[i]._extern ) { fs[i] = _fields[i]; continue; }
+ Type zero = _fields[i]._t.makeZero();
+ fs[i] = _fields[i].makeFrom(_fields[i]._t != Type.BOTTOM && zero.isa(_fields[i]._t) ? zero : Type.TOP);
+ }
+ return make(_name,_open,_fref,fs);
+ }
+
+ public TypeStruct add( Field f ) {
+ assert find(f._fname)==-1; // No double field names
+ Field[] flds = Arrays.copyOf(_fields,_fields.length+1);
+ flds[_fields.length] = f;
+ return make(_name,_open,_fref,flds);
+ }
+ public TypeStruct replace( Field f ) {
+ Field[] flds = Arrays.copyOf(_fields,_fields.length);
+ flds[find(f._fname)] = f;
+ return make(_name,_open,_fref,flds);
+ }
+ public TypeStruct remove( int i ) {
+ Field[] flds = new Field[_fields.length-1];
+ System.arraycopy(_fields, 0,flds,0,i);
+ System.arraycopy(_fields,i+1,flds,i,flds.length-i);
+ return make(_name,_open,_fref,flds);
+ }
+
+ public TypeStruct addOrUpdate(Field fld) {
+ int idx = find(fld._fname);
+ int len = idx == -1 ? (idx=_fields.length)+1 : _fields.length;
+ Field[] flds = Arrays.copyOf(_fields,len);
+ flds[idx] = fld;
+ return make(_name,_open,_fref,flds);
+ }
+
+ public final TypeStruct close() {
+ return (TypeStruct)recurOpen()._close(_name, null).recurClose();
+ }
+ @Override TypeStruct _close( String name, HashMap<String, Type> TYPES ) {
+ assert (name==null) != (TYPES==null); // One or the other
+ String namePlusFinal = isAry() ? (_name+(_fields[1]._final ? "~" : "!")).intern() : _name;
+ TypeStruct ts = (TypeStruct)VISIT.get(namePlusFinal);
+ if( ts!=null ) return ts;
+ TypeStruct base = name==null ? (TypeStruct)TYPES.get(_name) : this;
+ assert base != null;
+ boolean open = base._fref ? base._open : name != _name && name != null && _open;
+ ts = base.recurPre(namePlusFinal, _name, open, base._fref );
+ Field[] flds = ts._fields;
+ // Now start the recursion
+ for( int i=0; i<flds.length; i++ )
+ flds[i].setType(base._fields[i]._t._close(name, TYPES));
+
+ // Note: this whole complex shenanigans here is because there are
+ // actually two flavors of any array (non-/final contents) but I
+ // allow the parser to represent both with the same name string.
+ if( isAry() && _fields[1]._final && !flds[1]._final ) // This is a final array variant
+ flds[1] = flds[1].makeFrom(true); // Add final to the base TYPES non-final array
+
+ return ts;
+ }
+
+ // Needs full recursive treatment; e.g.
+ // struct LL { LL? next; int !q; } // Q is mutable field.
+ // Stored into a not-mutable, so need that version also:
+ // struct LL { LL? next; int q; } // Q is NOT mutable field.
+ // Now upgrade:
+ // it is not the default version from TYPES.
+ // it is fully cyclic
+ //
+ // Evil question: has no *open* internal fields, so can I assume it is fully correct and never needs upgrade?
+ //
+ // upgrades are for well-formed types with internal ptrs to early open
+ // versions of other types, as happens in all the CoRecur tests.
+ // During the co-recur tests, can I make non-default cycles with
+ // weaker versions of other early types?
+ // Seems plausible...
+ @Override Type _upgradeType(HashMap<String,Type> TYPES) {
+ TypeStruct base = (TypeStruct)TYPES.get(_name);
+ if( this == base ) return this; // Already the good form
+ TypeStruct ts = (TypeStruct)VISIT.get(_uid);
+ if( ts!=null ) return ts;
+ // Main work: replace open with closed
+ if( _open )
+ return base;
+ // Build recursive version
+ ts = recurPre(_uid,_name,false,_fref);
+ Field[] flds = ts._fields;
+ // Now start the recursion
+ for( int i=0; i<flds.length; i++ )
+ flds[i].setType(_fields[i]._t._upgradeType(TYPES));
+ return ts;
+ }
+
+ static final AryInt CEQUALS = new AryInt();
+
+ public static final String TOPNAME = "$TOP", BOTNAME = "$BOT";
+ public static final TypeStruct TOP = make("$TOP",false);
+ public static final TypeStruct BOT = TOP.dual();
+ public static final TypeStruct TEST= make("test",false,Field.TEST);
+ private static final TypeStruct ARY = makeAry("[]i64",TypeInteger.U32,-1,TypeInteger.BOT,-2,false);
+ private static final TypeStruct STR = makeAry("[]u8" ,TypeInteger.U32,-1,TypeInteger.U8 ,-4,false);
+ private static final TypeStruct ABC = makeAry("[]u8",-1,-4,TypeConAryB.ABC);
+ // Representative unresolved forms for the exhaustive lattice tests:
+ // bare forward ref, forward ref constrained by a field use, and definition.
+ private static final TypeStruct FREF0 = forward("%FREF");
+ private static final TypeStruct FREF1 = FREF0.add(Field.make("x",Type.BOTTOM,-20,false));
+ private static final TypeStruct FDEF1 = make("%FREF",false,Field.make("x",TypeInteger.BOT,-20,false));
+
+ // A pair of self-cyclic types
+ private static final TypeStruct SINT0 = make("%SINT",false);
+ private static final TypeStruct SFLT0 = make("%SFLT",false);
+ public static final TypeStruct SINT1 = SINT0.add(Field.make("a", TypeInteger.U32, -1, false)).add(Field.make("s2",TypeMemPtr.make((byte)2,SFLT0),-2, false));
+ public static final TypeStruct SFLT1 = SFLT0.add(Field.make("b", TypeFloat .F32, -3, false)).add(Field.make("s1",TypeMemPtr.make((byte)2,SINT1),-4, false));
+
+ @Override public boolean isHigh() { return _name==TOPNAME; }
+
+ public static void gather(ArrayList<Type> ts) {
+ ts.add(BOT);
+ ts.add(TEST);
+ ts.add(ARY);
+ ts.add(STR);
+ ts.add(ABC);
+ ts.add(FREF0);
+ ts.add(FREF1);
+ ts.add(FDEF1);
+ ts.add(SINT0);
+ ts.add(SFLT0);
+ ts.add(SINT1);
+ ts.add(SFLT1);
+ // Break cyclic init: built a struct
+ Field fcalloc = Field.make("calloc",TypeFunPtr.CALLOC,-2,true);
+ TypeStruct scalloc = make("calloc",false,fcalloc);
+ ts.add(scalloc);
+
+ }
+
+ // Find field index by name
+ public int find(String fname) {
+ for( int i=0; i<_fields.length; i++ )
+ if( _fields[i]._fname==fname )
+ return i;
+ return -1;
+ }
+ public Field field(String fname) {
+ int idx = find(fname);
+ return idx== -1 ? null : _fields[idx];
+ }
+
+ // Find field index by alias
+ public int findAlias( int alias ) {
+ for( int i=0; i<_fields.length; i++ )
+ if( _fields[i]._alias==alias )
+ return i;
+ return -1;
+ }
+
+ @Override
+ TypeStruct xmeet(Type t) {
+ TypeStruct that = (TypeStruct) t;
+ assert !isFree() && !that.isFree();
+ if( this==TOP ) return that;
+ if( that==TOP ) return this;
+ if( this==BOT ) return BOT;
+ if( that==BOT ) return BOT;
+
+ // if equal, no matters.
+ // if short is BOT, chop ; recurPre on short.
+ // if short is TOP, copy long; recurPre on long.
+ TypeStruct min = _fields.length < that._fields.length ? this : that;
+ if( _fields.length != that._fields.length && (!min._open ^ min!=this) )
+ return that.xmeet(this);
+
+ assert (_name==that._name) == (_name.equals(that._name)); // Strings are interned
+ String name =
+ that._name==TOPNAME ? _name :
+ _name==TOPNAME ? that._name :
+ that._name==_name ? _name :
+ BOTNAME;
+ if( name==BOTNAME ) return BOT;
+
+ // Check all other fields are sanely similar; same struct type but
+ // different field contents (e.g. field "age" is either 'int' or '18').
+ int len = Math.min(_fields.length,that._fields.length);
+ for( int i=0; i<len; i++ ) {
+ Field f0 = _fields[i], f1 = that._fields[i];
+ if( !f0._fname.equals(f1._fname) || f0._alias != f1._alias || f0._extern != f1._extern )
+ return BOT;
+ }
+
+ // See if we get a mid-recursion hit; breaks cycles
+ Integer pid = pid(that);
+ TypeStruct ts = (TypeStruct)VISIT.get(pid);
+ if( ts!=null ) return ts;
+ // If not mid-recursion, we are now; flag recursion.
+ boolean notRecur = VISIT.isEmpty();
+ if( notRecur ) recurOpen();
+
+ // Setup and install the type, prior to recursing, so we can find our
+ // recursive self again.
+ ts = recurPre(pid,name,_open | that._open,_fref & that._fref);
+
+ // Recurse all common fields
+ Field[] flds = ts._fields;
+ for( int i=0; i<len; i++ ) {
+ Field f0 = _fields[i], f1 = that._fields[i];
+ flds[i].setType(f0._t.meet(f1._t ));
+ flds[i]._final = f0._final | f1._final;
+ }
+
+ // Handle extra fields via `_open`
+ for( int i=len; i<_fields.length; i++ )
+ flds[i].setType(_fields[i]._t );
+
+ // Cleanup post-recursion; intern/install at cycle end
+ if( notRecur ) ts = (TypeStruct)ts.recurClose();
+ return ts;
+ }
+
+ @Override
+ TypeStruct xdual() {
+ Field[] flds = new Field[_fields.length];
+ for( int i=0; i<_fields.length; i++ )
+ flds[i] = _fields[i].dual();
+ return malloc(sdual(),!_open,!_fref,flds);
+ }
+
+ // Recursive dual
+ @Override TypeStruct rdual() {
+ if( _dual!=null ) return dual();
+ assert !_terned;
+ Field[] flds = new Field[_fields.length];
+ TypeStruct d = malloc(sdual(),!_open,!_fref,flds);
+ (_dual = d)._dual = this; // Cross link duals
+ for( int i=0; i<_fields.length; i++ )
+ flds[i] = _fields[i]._terned ? _fields[i].dual() : _fields[i].rdual();
+ return d;
+ }
+
+ private String sdual() {
+ return
+ _name==TOPNAME ? BOTNAME :
+ _name==BOTNAME ? TOPNAME :
+ _name;
+ }
+
+ @Override boolean _isConstant() {
+ assert !_open; // who is asking about constant open structs? if( _open ) return false; // Infinite BOT fields
+ assert !VISIT.containsKey(_uid); //expect constants to not include self-cycles. if( VISIT.containsKey(_uid) ) return true; // Cycles assume constant
+ VISIT.put(_uid,this);
+ // Check all fields for being constant
+ for( Field field : _fields )
+ if( !field._isConstant() )
+ return false;
+ return true;
+ }
+
+ // All fields are final
+ @Override boolean _isFinal() {
+ if( _open ) return false; // May have more non-final fields
+ if( VISIT.containsKey(_uid) ) // Test: been here before?
+ return true; // Cycles assume final
+ VISIT.put(_uid,this); // Set: don't do this again
+ for( Field fld : _fields )
+ if( !fld._isFinal() )
+ return false;
+ return true;
+ }
+
+ private TypeStruct recurPre(Object key, String name, boolean open, boolean fref) {
+ // Make a clone of original; suitable for hashing so can build
+ // e.g. TMPs to clone as part of cycles - but will fail the 'eq' check
+ // until the entire cycle is built.
+ Field[] flds = new Field[_fields.length];
+ for( int i=0; i<flds.length; i++ )
+ flds[i] = _fields[i].malloc(); // Blank copy, but can be hashed
+ TypeStruct ts = malloc(name, open, fref, flds );
+ ts._aliases = aliases();
+ VISIT.put(key,ts);
+ return ts;
+ }
+
+ private interface FieldTransform { Type apply(Type t); }
+
+ private TypeStruct recurTransform(Object key, String name, boolean open, boolean fref, boolean xfinal, FieldTransform transform) {
+ TypeStruct ts = (TypeStruct)VISIT.get(key);
+ if( ts!=null ) return ts; // Already visited
+ ts = recurPre(key,name,open,fref);
+ Field[] flds = ts._fields;
+ for( Field fld : flds )
+ fld._final |= xfinal;
+
+ // Now start the recursion
+ for( int i=0; i<flds.length; i++ )
+ flds[i].setType(transform.apply(_fields[i]._t));
+
+ return ts;
+ }
+
+ // Make a read-only version
+ @Override TypeStruct _makeRO() {
+ return recurTransform(_name,_name,_open,_fref,true,Type::_makeRO);
+ }
+
+ @Override TypeStruct _makeStorage() {
+ return recurTransform(_uid,_name,_open,false,false,Type::_makeStorage);
+ }
+
+ // log_size for a struct is not defined, unless its exactly some power of
+ // 2. *Total size* is well-defined, and is available in the offsets.
+ @Override public int log_size() { throw Utils.TODO("Should not reach here: structs have no scalar log size"); }
+ @Override public int size() { return offset(_fields.length); }
+ @Override public int alignment() {
+ int align = 0;
+ for( Field f : _fields )
+ if( !f._extern ) align = Math.max(align, f._t.alignment());
+ return align;
+ }
+
+ // If false, always false.
+ // If true , maybe true, need to check recursive fields.
+ private boolean static_eq( TypeStruct ts ) {
+ assert (_name==ts._name) == (_name.equals(ts._name)); // Strings are interned
+ return _name==ts._name && _open==ts._open && _fref==ts._fref && _fields.length==ts._fields.length;
+ }
+
+ @Override boolean eq(Type t) {
+ TypeStruct ts = (TypeStruct)t; // Invariant
+
+ // Recursive; so use cyclic equals
+ if( !VISIT.isEmpty() ) {
+ assert CEQUALS.isEmpty();
+ boolean rez = cycle_eq(t);
+ CEQUALS.clear();
+ return rez;
+ }
+ // Normal equals
+ if( !static_eq(ts) ) return false;
+ if( _fields==ts._fields ) return true;
+ for( int i = 0; i < _fields.length; i++ )
+ if( !_fields[i].eq(ts._fields[i]) )
+ return false;
+ return true;
+ }
+
+ @Override boolean cycle_eq(Type t) {
+ if( this==t ) return true;
+ TypeStruct ts = (TypeStruct)t; // Invariant
+ if( !static_eq(ts) ) return false;
+ if( _fields==ts._fields ) return true;
+ // Check to see if we've ever compared this pair of types before;
+ // if so, then assume the cycle is equal here.
+ int pid = pid(ts);
+ if( CEQUALS.find(pid)!= -1 ) return true;
+ CEQUALS.push(pid);
+ // Recursively check fields
+ for( int i = 0; i < _fields.length; i++ )
+ if( !_fields[i].cycle_eq(ts._fields[i]) )
+ return false;
+ return true;
+ }
+
+ @Override
+ int hash() {
+ long hash = _name.hashCode() ^ (_open ? 4 : 0) ^ (_fref ? 8 : 0);
+ for( Field f : _fields )
+ hash = Utils.rot(hash,13) ^ ((long)f._fname.hashCode() * f._alias);
+ return Utils.fold(hash);
+ }
+
+ @Override public int nkids() { return _fields.length; }
+ @Override public Field at( int idx ) { return _fields[idx]; }
+ @Override public void set( int idx, Type t ) { _fields[idx] = (Field)t; }
+ // Tags 0-4 - closed +#nflds (+name)
+ // Tags 5 - closed(+ nflds +name)
+ // Tags 6-10- open +#nflds (+name)
+ // Tags 11 - open (+ nflds +name)
+ @Override int TAGOFF() { return 24; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ baos.write(TAGOFFS[_type]+ (_fref ? 12 : 0) + (_open ? 6 : 0) + Math.min(5,_fields.length));
+ if( _fields.length >= 5 )
+ baos.packed2(_fields.length);
+ baos.packed2(strs.get(_name));
+ // TODO: Write aliases summary
+ }
+ static TypeStruct packed( int tag, BAOS bais, String[] strs ) {
+ boolean fref = tag >= 12;
+ if( fref ) tag -= 12;
+ int ntag = tag >= 6 ? tag-6 : tag;
+ int nflds = ntag < 5 ? ntag : bais.packed2();
+ String name = strs[bais.packed2()];
+ return malloc(name,ntag!=tag,fref,new Field[nflds]);
+ }
+
+ @Override
+ SB _print(SB sb, BitSet visit, boolean html ) {
+ if( this==BOT ) return sb.p("StructBot");
+ if( this==TOP ) return sb.p("StructTop");
+ if( isFree() ) return sb.p("FREE:").p(_name);
+ if( isAry() && field("[]")._t instanceof TypeConAry con )
+ return sb.p(con.str());
+ sb.p(_name);
+ if( isAry() )
+ // Array names deliberately canonicalize mutable and immutable
+ // element variants. The verbose debugger print must retain the
+ // otherwise invisible lattice coordinate.
+ return sb.p(_fields[1]._final ? "[final]" : "[mutable]");
+ if( html )
+ return sb;
+ sb.p(" {");
+ for( Field f : _fields )
+ (f._t ==null ? sb.p("---") : f._t.print(sb,visit,html)).p(f._final ? " " : " !").p(f._fname).p("; ");
+ if( _open && !_fref ) sb.p("... ");
+ return sb.p("}");
+ }
+
+ @Override public String str() {
+ if( this==BOT ) return "StructBot";
+ if( this==TOP ) return "StructTop";
+ return (isFree() ? "FREE:":"")+_name;
+ }
+
+
+ public boolean isAry() { return find("[]")!=-1; }
+ public boolean isConAry() {
+ return isAry() && _fields[1]._t instanceof TypeConAry;
+ }
+
+ public int aryBase() {
+ assert isAry();
+ if( _offs==null ) _offs = offsets();
+ return _offs[1];
+ }
+ public int aryScale() {
+ assert isAry();
+ return _fields[1]._t.log_size();
+ }
+
+
+ // Field offsets as packed byte offsets
+ private int[] _offs; // Lazily computed and not part of the type semantics
+ public int offset(int idx) {
+ if( _offs==null ) _offs = offsets();
+ return _offs[idx];
+ }
+ private int[] offsets() { // Field byte offsets
+ assert CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.FinalizeIR.ordinal();
+ // Compute a layout for a collection of fields
+ assert _fields != null; // No forward refs
+
+ // Compute a layout
+ _offs = new int[_fields.length+1];
+ if( isAry() ) { // Arrays used fixed u32 len, padding to elems, var length
+ assert _fields[1]._fname=="[]";
+ // Pad out to the element alignment
+ int align = _fields[1]._t.alignment();
+ _offs[1] = (4 + ((1<<align)-1)) & -(1<<align);
+ if( _fields[1]._t instanceof TypeConAry ary ) // Array length is well-defined for constants, not defined for runtime arrays
+ _offs[2] = _offs[1]+ary.len();
+
+ } else {
+ int[] cnts = new int[5]; // Count of fields at log field size
+ int flen = _fields.length;
+ for( int i=0; i<flen; i++ )
+ if( !_fields[i]._extern )
+ cnts[_fields[i]._t.log_size()]++; // Log size is 0(byte), 1(i16/u16), 2(i32/f32), 3(i64/dbl)
+ int off = 0, idx = 0; // Base common struct fields go here, e.g. Mark/Klass
+ // Compute offsets to the start of each power-of-2 aligned fields.
+ int[] offs = new int[4];
+ for( int i=3; i>=0; i-- ) {
+ offs[i] = off;
+ off += cnts[i]<<i;
+ }
+ // Assign offsets to all fields.
+ // Really a hidden radix sort.
+ for( int i=0; i<flen; i++ ) {
+ if( _fields[i]._extern ) { _offs[idx++] = -1; continue; }
+ int log = _fields[i]._t.log_size();
+ _offs[idx++] = offs[log]; // Field offset
+ offs[log] += 1<<log; // Next field offset at same alignment
+ cnts[log]--; // Count down, should be all zero at end
+ }
+ _offs[flen] = off; // Max struct size, no trailing padding
+ }
+ return _offs;
+ }
+
+ // Field order with increasing offset
+ public int[] layout() {
+ offset(0);
+ int len = _offs.length;
+ int[] is = new int[len];
+ for( int i=0; i<len; i++ ) is[i] = i;
+ // Stoopid hand rolled bubble sort
+ for( int i=0; i<len; i++ )
+ for( int j=i+1; j<len; j++ )
+ if( _offs[is[j]] < _offs[is[i]] )
+ { int tmp = is[i]; is[i] = is[j]; is[j] = tmp; }
+ return is;
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
new file mode 100644
index 00000000..075698dc
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
@@ -0,0 +1,172 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.util.*;
+import java.util.*;
+
+public class TypeTuple extends Type {
+
+ public Type[] _types;
+
+ private static final Ary<TypeTuple> FREE = new Ary<>(TypeTuple.class);
+ private TypeTuple(Type[] types) { super(TTUPLE); _types = types; }
+ private TypeTuple init( Type[] types ) { _types = types; return this; }
+ private static TypeTuple malloc(Type[] types) {
+ return FREE.isEmpty() ? new TypeTuple(types) : FREE.pop().init(types);
+ }
+ // All fields directly listed
+ public static TypeTuple make(Type... types) {
+ TypeTuple tt = malloc(types);
+ TypeTuple t2 = tt.intern();
+ return t2==tt ? tt : t2.free(tt);
+ }
+ @Override TypeTuple free(Type t) {
+ TypeTuple tt = (TypeTuple)t;
+ tt._types = null;
+ tt._dual = null;
+ tt._hash = 0;
+ FREE.push(tt);
+ return this;
+ }
+ @Override boolean isFree() { return _types==null; }
+
+ public TypeTuple makeFrom(int idx, Type tx ) {
+ if( _types[idx]==tx ) return this;
+ Type[] types = _types.clone();
+ types[idx] = tx;
+ return make(types);
+ }
+ public TypeTuple meetFrom(int idx, Type t ) { return makeFrom(idx,_types[idx].meet(t)); }
+
+ public static final TypeTuple BOT = malloc(new Type[0]).intern();
+ public static final TypeTuple TOP = BOT.dual();
+ // The default state, a triple of CONTROL, Memory, and Value, with bottoms
+ // for all. This is the default StopNode type, when new functions are
+ // being created and we cannot tell what the future holds. This is the
+ // default ReturnNode type, returning what the worst a function can do.
+ public static final TypeTuple STATE = make(Type.CONTROL,TypeMem.BOT,Type.BOTTOM);
+ public static final TypeTuple STOP_HIGH = make(Type.CONTROL,TypeMem.START,Type.TOP);
+
+ public static final TypeTuple TEST = make(TypeInteger.BOT,TypeMemPtr.TEST);
+ public static final TypeTuple DEAD_NEW = make(TypeMemPtr.TOP,TypeMem.TOP);
+
+ public static final TypeTuple IF_BOTH = make(Type. CONTROL,Type. CONTROL);
+ public static final TypeTuple IF_NEITHER = make(Type.XCONTROL,Type.XCONTROL);
+ public static final TypeTuple IF_TRUE = make(Type. CONTROL,Type.XCONTROL);
+ public static final TypeTuple IF_FALSE = make(Type.XCONTROL,Type. CONTROL);
+
+ public static void gather(ArrayList<Type> ts) { ts.add(BOT); ts.add(TEST); ts.add(STATE); ts.add(IF_TRUE); }
+
+ @Override
+ Type xmeet(Type other) {
+ TypeTuple that = (TypeTuple)other; // contract from xmeet
+ if( this==TOP ) return that;
+ if( that==TOP ) return this;
+ if( _types.length != that._types.length )
+ return BOT;
+ return make(TypeFunPtr.xmeet(_types,that._types));
+ }
+
+ @Override TypeTuple xdual() {
+ if( _types.length==0 ) return malloc(null);
+ boolean isCon=true;
+ for( Type t : _types )
+ if( t._dual!=t )
+ // Not a constant
+ return malloc(TypeFunPtr.xdual(_types));
+ return this;
+ }
+ @Override TypeTuple rdual() {
+ if( _dual!=null ) return dual();
+ assert !_terned;
+ assert _types.length>0;
+ Type[] ts = new Type[_types.length];
+ TypeTuple d = malloc(ts);
+ (_dual = d)._dual = this; // Cross link duals
+ // Collection rdual
+ for( int i=0; i<_types.length; i++ )
+ ts[i] = _types[i]._terned ? _types[i].dual() : _types[i].rdual();
+ return d;
+ }
+
+ @Override boolean _isConstant() {
+ for( Type t : _types )
+ if( !t._isConstant() )
+ return false;
+ return true;
+ }
+ @Override boolean _isFinal() { throw Utils.TODO("Should not reach here: tuples are not stored object values"); }
+
+ @Override TypeMemPtr _makeRO() { throw Utils.TODO("Should not reach here: tuples cannot be made read-only"); }
+
+ @Override Type _upgradeType(HashMap<String,Type> TYPES) {
+ Type[] ts = new Type[_types.length];
+ for( int i=0; i<ts.length; i++ )
+ ts[i] = _types[i]._upgradeType(TYPES);
+ return make(ts);
+ }
+
+ @Override public int log_size() { throw Utils.TODO("Should not reach here: tuples have no scalar storage size"); }
+ @Override public int alignment() {
+ assert isConstant();
+ int align = 0;
+ for( Type t : _types )
+ align = Math.max(align,t.alignment());
+ return align;
+ }
+
+ public Type ctl() { assert _types.length>=3; return _types[0]; }
+ public Type ret() { assert _types.length>=3; return _types[2]; }
+ public TypeMem mem() { assert _types.length>=3;
+ return _types[1] instanceof TypeMem tmem ? tmem : (_types[1].isHigh() ? TypeMem.START : TypeMem.BOT);
+ }
+
+ @Override public int nkids() { return _types.length; }
+ @Override public Type at( int idx ) { return _types[idx]; }
+ @Override public void set( int idx, Type t ) { _types[idx] = t; }
+
+ // Reserve tags for tuples with 2,3,generic
+ @Override int TAGOFF() { return 4; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ if( _types.length==2 ) baos.write(TAGOFFS[_type] + 0);
+ else if( _types.length==3 ) baos.write(TAGOFFS[_type] + 1);
+ else if( _types.length==4 ) baos.write(TAGOFFS[_type] + 2);
+ else baos.write(TAGOFFS[_type] + 3).packed2(_types.length);
+ }
+ static TypeTuple packed( int tag, BAOS bais ) {
+ if( tag==0 ) return malloc(new Type[2]);
+ if( tag==1 ) return malloc(new Type[3]);
+ if( tag==2 ) return malloc(new Type[4]);
+ return malloc(new Type[bais.packed2()]);
+ }
+
+ @Override SB _print(SB sb, BitSet visit, boolean html ) {
+ if( this==TOP ) return sb.p("[TOP]");
+ if( this==BOT ) return sb.p("[BOT]");
+ sb.p("[ ");
+ for( Type t : _types )
+ t.print(sb,visit,html).p(", ");
+ return sb.unchar(2).p("]");
+ }
+
+ @Override public String str() { return print(new SB(),new BitSet(),false).toString(); }
+
+ @Override
+ int hash() {
+ int sum = 0;
+ if( _types!=null ) for( Type type : _types ) sum ^= type.hashCode();
+ return sum;
+ }
+
+ @Override
+ boolean eq( Type t ) {
+ TypeTuple that = (TypeTuple)t; // Contract
+ if( _types==null && that._types==null ) return true;
+ if( _types==null || that._types==null ) return false;
+ if( _types.length != that._types.length ) return false;
+ for( int i=0; i<_types.length; i++ )
+ if( _types[i]!=that._types[i] )
+ return false;
+ return true;
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/type/XInt.java b/chapter29/src/main/java/com/seaofnodes/simple/type/XInt.java
new file mode 100644
index 00000000..3e74df84
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/type/XInt.java
@@ -0,0 +1,274 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.util.*;
+import java.lang.Integer;
+import java.util.Arrays;
+
+// Stupidly overkill interned immutable int[]'s
+public abstract class XInt {
+ private static final Ary<Ary<int[]>> FREES = new Ary(Ary.class);
+ private static final Ary<Ary<int[]>> INTERNS = new Ary(Ary.class);
+ private static int PROBES, HITS, NEWS;
+
+ public static final int[] EMPTY = intern(new int[]{0, 0});
+ public static final int[] FULL = intern(new int[]{0,-1});
+ public static final int[] SET1 = make(1);
+ public static final int[] SET3 = make(SET1, 2);
+
+ static int idx(int x) { return (x>>5)+1; }
+ static int mask(int x) { return 1<<(x&31); }
+
+ // No hash set, still mutable
+ public static boolean isMutable(int[] xs) { return xs[0]==0; }
+ // Return hash code; must be set hence frozen
+ public static int hash(int[] xs) { return xs[0]; }
+
+ // High bit set (infinite 1's extension)
+ public static boolean isHigh(int[] xs) { return xs[xs.length-1] < 0; }
+
+ // Check for a bit
+ public static boolean bit(int[] xs, int x) {
+ int idx = idx(x);
+ if( idx >= xs.length )
+ return isHigh(xs);
+ return (xs[idx] & mask(x))!=0;
+ }
+
+ // Number of bits set
+ public static int bitCount(int[] xs) {
+ assert !isHigh(xs); // Dont ask if infinite
+ int cnt=0;
+ for( int i=1; i<xs.length; i++ )
+ cnt += Integer.bitCount(xs[i]);
+ return cnt;
+ }
+
+ // Largest bit
+ public static int max(int[] xs) {
+ assert !isHigh(xs);
+ return Integer.numberOfTrailingZeros(xs[xs.length-1])+((xs.length-1-1)<<5);
+ }
+
+ // Has 1 bit set
+ public static boolean isConstant(int[] xs) { return !isHigh(xs) && bitCount(xs)==1; }
+ // Return the 1 set bit or assert
+ public static int onlyBit(int[] xs) {
+ assert isConstant(xs);
+ for( int i=1; i<xs.length; i++ )
+ if( xs[i] != 0 )
+ return Integer.numberOfTrailingZeros(xs[i]) + ((i-1)<<5);
+ throw Utils.TODO("Should not reach here");
+ }
+
+ // Subset check: ys is a subset of xs
+ public static boolean subset(int[] xs, int[] ys) {
+ assert !isMutable(ys);
+ if( xs==ys ) return true;
+ if( xs==FULL ) return true;
+ if( ys==EMPTY ) return true;
+ int min = Math.min(xs.length,ys.length);
+ for( int i=1; i<min; i++ )
+ if( (xs[i] | ys[i])!=xs[i] )
+ return false;
+ if( xs.length == ys.length )
+ return true;
+ if( ys.length < xs.length )
+ return !isHigh(ys); // If ys is infinite, it'll hit one of the xs extra zeros
+ return isHigh(xs);
+ }
+
+
+ // Construct a set with 1 bit set
+ public static int[] make(int x ) { return make(EMPTY,x); }
+
+ // Add a bit
+ public static int[] make(int[] xs, int x ) {
+ assert !isMutable(xs);
+ int idx = idx(x);
+ if( idx >= xs.length && isHigh(xs) )
+ return xs; // Set in the default infinite
+ if( idx < xs.length && (mask(x) & xs[idx])!=0 )
+ return xs; // Already set
+ int len = Math.max(xs.length,idx+1);
+ if( (x&31)==31 && idx+1==len )
+ len++;
+ int[] ys = free(len);
+ System.arraycopy(xs,1,ys,1,xs.length-1);
+ ys[idx] |= mask(x);
+ return intern(ys);
+ }
+
+ // Interning, and life-cycle management
+ // Intern the array
+ public static int[] intern(int[] xs) {
+ if( !isMutable(xs) ) return xs; // Already interned
+ PROBES++;
+
+ // Trim trailing 0s or -1s, use the infinite extend bit
+ int len = xs.length;
+ while( len > 2 &&
+ ((xs[len-1]== 0 && xs[len-2]>=0) ||
+ (xs[len-1]== -1 && xs[len-2]< 0) ) )
+ len--;
+ // Replace with trimmed
+ if( len < xs.length ) {
+ var ys = free(len);
+ System.arraycopy(xs,1,ys,1,len-1);
+ xs = _free(xs,ys);
+ }
+
+ // Compute lame hash code
+ int hash = 0;
+ for( int i=1; i<len; i++ )
+ hash += xs[i];
+ if( hash == 0 ) hash = 0xCAFEBABE;
+ xs[0] = hash; // Set lame hashcode, and mark immutable
+
+ // Get length-specific INTERN table
+ var xss = INTERNS.atX(len);
+ if( xss==null )
+ INTERNS.setX( xs.length, xss = new Ary<>(int[].class));
+
+ // Probe for a hit
+ for( int[] ys : xss )
+ if( Arrays.equals(xs,ys) ) // Hit!
+ // Free new array and return old array
+ { HITS++; return _free(xs,ys); }
+
+ // Intern new array and return
+ return xss.push(xs);
+ }
+
+ // Return Ary of free int[]s of fixed size
+ private static Ary<int[]> _xss(int len) {
+ Ary<int[]> xss = FREES.atX(len);
+ if( xss==null )
+ FREES.setX(len, xss = new Ary<>(int[].class));
+ return xss;
+ }
+ // Pull free array from free list
+ static int[] free(int len) {
+ int[] xs = _xss(len).pop();
+ if( xs==null )
+ { NEWS++; xs = new int[len]; }
+ assert isMutable(xs);
+ return xs;
+ }
+ // Return ys. Put xs on free list
+ static int[] _free(int[] xs, int[] ys) {
+ Arrays.fill(xs,0); // Mutable again
+ _xss(xs.length).push(xs);
+ return ys;
+ }
+
+ // Meet: Set union
+ public static int[] meet(int[] xs, int[] ys) {
+ if( xs==ys ) return xs;
+ boolean isX=true, isY=true;
+ int min = Math.min(xs.length,ys.length);
+ int max = Math.max(xs.length,ys.length);
+ for( int i=1; i<max; i++ ) {
+ int x = i < xs.length ? xs[i] : isHigh(xs) ? -1 : 0;
+ int y = i < ys.length ? ys[i] : isHigh(ys) ? -1 : 0;
+ if( (x | y)!=x ) isX=false;
+ if( (x | y)!=y ) isY=false;
+ }
+ if( isX ) return xs;
+ if( isY ) return ys;
+ // Get a free one
+ int[] ms = free(max);
+ for( int i=1; i<min; i++ )
+ ms[i] = xs[i] | ys[i];
+ for( int i=min; i<ms.length; i++ ) {
+ int x = i < xs.length ? xs[i] : isHigh(xs) ? -1 : 0;
+ int y = i < ys.length ? ys[i] : isHigh(ys) ? -1 : 0;
+ ms[i] = x | y;
+ }
+ return intern(ms);
+ }
+
+ // Dual: flip all the bits
+ public static int[] dual(int[] xs) {
+ int[] ds = free(xs.length);
+ for( int i=1; i<xs.length; i++ )
+ ds[i] = ~xs[i];
+ return intern(ds);
+ }
+
+
+ public static void packed(BAOS baos, int[] xs) {
+ baos.packed1(xs.length);
+ for( int i=1; i<xs.length; i++ )
+ baos.packed4(xs[i]);
+ }
+ public static int[] packed(BAOS bais) {
+ int len = bais.packed1();
+ int[] xs = free(len);
+ for( int i=1; i<len; i++ )
+ xs[i] = bais.packed4();
+ return intern(xs);
+ }
+
+ // Next set bit, or -1
+ // for( int bit = XInt.next(bits,0); bit >=0; bit = XInt.next(bits,bit) ) {...bit...}
+ public static int next(int[] xs, int x) {
+ assert !isHigh(xs); // No iterating infinite bitsets
+ return next(xs,x,Integer.MAX_VALUE);
+ }
+
+ // Next set bit below limit, or -1. Allows bounded walks of infinite sets.
+ public static int next(int[] xs, int x, int limit) {
+ while( ++x < limit ) {
+ if( bit(xs,x) ) return x;
+ if( idx(x)>=xs.length )
+ return -1;
+ }
+ return -1;
+ }
+
+ // Remap all bits
+ public static int[] remap( int[] xs, AryInt map ) {
+ if( xs==FULL ) return xs;
+ assert !isHigh(xs);
+ int[] ys = free(xs.length);
+ for( int bit = next(xs,0); bit >=0; bit = next(xs,bit) ) {
+ int bat = map.at(bit);
+ ys[idx(bat)] |= mask(bat);
+ }
+ return intern(ys);
+ }
+
+
+
+ public static String str(int[] xs) { return str(new SB(),xs).toString(); }
+ public static SB str(SB sb, int[] xs) {
+ sb.p("[ ");
+ // Printing ranges;
+ // 1,2,3 prints 1-3
+ // 1,2,4 prints 1,2,4
+ //
+ int max = (xs.length-1)<<5;
+ int low = 0; // No low range
+ for( int i=1; i<max; i++ ) {
+ if( bit(xs,i) ) { // Set bit, start or extend range
+ if( low==0 ) low=i; // No range, start one
+ } else { // Break in sequence, ending a range
+ range(sb,low,i-1); // Print prior range, and start new one
+ low=0; // Reset range
+ }
+ }
+ // Print any partial range
+ range(sb,low,max-1).unchar();
+ if( isHigh(xs) )
+ sb.p(",...");
+ return sb.p("]");
+ }
+
+ private static SB range( SB sb, int low, int mid ) {
+ if( low==0 ) ;
+ else if( low==mid ) sb.p(low).p(',');
+ else if( low==mid-1 ) sb.p(low).p(',').p(mid).p(',');
+ else sb.p(low).p('-').p(mid).p(',');
+ return sb;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/util/Ary.java b/chapter29/src/main/java/com/seaofnodes/simple/util/Ary.java
new file mode 100644
index 00000000..d67801c2
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/util/Ary.java
@@ -0,0 +1,229 @@
+package com.seaofnodes.simple.util;
+
+import java.lang.StringBuilder;
+import java.lang.reflect.Array;
+import java.util.*;
+
+// ArrayList with saner syntax
+public class Ary<E> extends AbstractList<E> implements List<E> {
+ public E[] _es;
+ public int _len;
+
+ public Ary(E[] es) { this(es,es.length); }
+ public Ary(E[] es, int len) { if( es.length==0 ) es=Arrays.copyOf(es,1); _es=es; _len=len; }
+ @SuppressWarnings("unchecked")
+ public Ary(Class<E> clazz) { this((E[]) Array.newInstance(clazz, 1),0); }
+
+ /** @return list is empty */
+ public boolean isEmpty() { return _len==0; }
+ /** @return active list length */
+ public int len() { return _len; }
+ public int size() { return len(); }
+
+ /** @param i element index
+ * @return element being returned; throws if OOB
+ * @exception AIOOBE if !(0 <= i < _len)
+ */
+ public E get( int i ) { return at(i); }
+ public E at( int i ) {
+ range_check(i);
+ return _es[i];
+ }
+ public E atX( int i ) {
+ if( i >= _len ) return null;
+ return _es[i];
+ }
+
+ /** @return last element */
+ public E last( ) { return at(_len-1); }
+ public E last(int x ) { return at(_len-1+x); }
+
+ /** Add element in amortized constant time
+ * @param e Element to add at end of list
+ **/
+ public E push( E e ) {
+ if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1));
+ return (_es[_len++] = e);
+ }
+ /** @return remove and return last element */
+ public E pop( ) {
+ return _len==0 ? null : _es[--_len];
+ }
+ @Override public E removeLast() { return pop(); }
+
+
+ /** Add element in amortized constant time
+ * @param e Element to add at end of list
+ * @return true */
+ public boolean add( E e ) {
+ if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1));
+ _es[_len++] = e;
+ return true;
+ }
+
+ /** Fast, constant-time, element removal. Does not preserve order
+ * @param i element to be removed
+ * @return element removed */
+ public E del( int i ) {
+ range_check(i);
+ E tmp = _es[i];
+ _es[i]=_es[--_len];
+ return tmp;
+ }
+
+ /** Slow, linear-time, element removal. Preserves order.
+ * @param i element to be removed
+ * @return element removed */
+ @Override public E remove( int i ) {
+ range_check(i);
+ E e = _es[i];
+ System.arraycopy(_es,i+1,_es,i,(--_len)-i);
+ return e;
+ }
+
+ /** Slow, linear-time, element insertion. Preserves order.
+ * @param i where to insert
+ * @return element inserted */
+ public E insert( E e, int i ) {
+ setLen(_len+1);
+ System.arraycopy(_es,i,_es,i+1,_len-i);
+ _es[i] = e;
+ return e;
+ }
+
+
+ /** Remove all elements */
+ public void clear( ) { _len=0; }
+
+ /** Set existing element
+ * @param i element to set
+ * @param e value to set
+ * @return old value
+ * @exception AIOOBE if !(0 <= i < _len)
+ */
+ public E set( int i, E e ) {
+ range_check(i);
+ E old = _es[i];
+ _es[i] = e;
+ return old;
+ }
+
+ /** Set existing element
+ * @param i element to set
+ * @param e value to set
+ * @return old value
+ * @exception AIOOBE if !(0 <= i < _len)
+ */
+ public E setX( int i, E e ) {
+ if( i >= _len ) {
+ while( i >= _es.length ) _es = Arrays.copyOf( _es, _es.length << 1 );
+ _len = i+1;
+ }
+ E old = _es[i];
+ _es[i] = e;
+ return old;
+ }
+
+ public Ary<E> setLen( int len ) {
+ if( len > _len )
+ while( len>= _es.length ) _es = Arrays.copyOf(_es,_es.length<<1);
+ _len = len;
+ while( _es.length > (len<<1) ) // Shrink if hugely too large
+ _es = Arrays.copyOf(_es,_es.length>>1);
+ Arrays.fill(_es,len,_es.length,null);
+ return this;
+ }
+
+ public E swap( int i, int j ) {
+ range_check(i);
+ range_check(j);
+ E tmp = _es[i];
+ if( i==j ) return tmp;
+ _es[i] = _es[j];
+ _es[j] = tmp;
+ return tmp;
+ }
+
+
+ /** @param c Collection to be added */
+ public boolean addAll( Collection<? extends E> c ) {
+ if( c==null ) return false;
+ for( E e : c ) add(e);
+ return true;
+ }
+ public Ary<E> addAll( Iterable<? extends E> it ) { for( E e : it ) add(e); return this; }
+
+ /** @param es Array to be added */
+ public <F extends E> Ary<E> addAll( F[] es ) {
+ if( es==null || es.length==0 ) return this;
+ while( _len+es.length > _es.length ) _es = Arrays.copyOf(_es,_es.length<<1);
+ System.arraycopy(es,0,_es,_len,es.length);
+ _len += es.length;
+ return this;
+ }
+
+ /** @param c Collection to be added */
+ public Ary<E> addAll( Ary<? extends E> c ) {
+ if( c==null || c._len==0 ) return this;
+ while( _len+c._len > _es.length ) _es = Arrays.copyOf(_es,_es.length<<1);
+ System.arraycopy(c._es,0,_es,_len,c._len);
+ _len += c._len;
+ return this;
+ }
+
+ /** @return compact array version */
+ public E[] asAry() { return Arrays.copyOf(_es,_len); }
+
+ /** Find the first matching element using ==, or -1 if none. Note that
+ * most del calls shuffle the list, so the first element might be random.
+ * @param e Element to find
+ * @return index of first matching element, or -1 if none */
+ public int find( E e ) {
+ for( int i=0; i<_len; i++ ) if( _es[i]==e ) return i;
+ return -1;
+ }
+
+
+ /** @return an iterator */
+ @Override public Iterator<E> iterator() { return new Iter(); }
+ private class Iter implements Iterator<E> {
+ int _i=0;
+ @Override public boolean hasNext() { return _i<_len; }
+ @Override public E next() { return _es[_i++]; }
+ }
+
+ @Override public String toString() {
+ StringBuilder sb = new StringBuilder().append('{');
+ for( int i=0; i<_len; i++ ) {
+ if( i>0 ) sb.append(',');
+ if( _es[i] != null ) sb.append(_es[i].toString());
+ }
+ return sb.append('}').toString();
+ }
+
+ private void range_check( int i ) {
+ if( i < 0 || i>=_len )
+ throw new ArrayIndexOutOfBoundsException(""+i+" >= "+_len);
+ }
+
+ // Note that the hashCode() and equals() are not invariant to changes in the
+ // underlying array. If the hashCode() is used (e.g., inserting into a
+ // HashMap) and the then the array changes, the hashCode() will change also.
+ @Override public boolean equals( Object o ) {
+ if( this==o ) return true;
+ if( !(o instanceof Ary ary) ) return false;
+ if( _len != ary._len ) return false;
+ if( _es == ary._es ) return true;
+ for( int i=0; i<_len; i++ )
+ if( !(_es[i]==null ? (ary._es[i] == null) : _es[i].equals(ary._es[i])) )
+ return false;
+ return true;
+ }
+ @Override public int hashCode( ) {
+ int sum=_len;
+ for( int i=0; i<_len; i++ )
+ sum += _es[i]==null ? 0 : _es[i].hashCode();
+ return sum;
+ }
+
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/util/AryInt.java b/chapter29/src/main/java/com/seaofnodes/simple/util/AryInt.java
new file mode 100644
index 00000000..b498fd78
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/util/AryInt.java
@@ -0,0 +1,192 @@
+package com.seaofnodes.simple.util;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.function.IntUnaryOperator;
+
+// ArrayList with saner syntax
+public class AryInt {
+ public int[] _es;
+ public int _len;
+ public AryInt(int[] es) { this(es,es.length); }
+ public AryInt(int[] es, int len) { _es=es; _len=len; }
+ public AryInt() { this(new int[1],0); }
+
+ /** @return list is empty */
+ public boolean isEmpty() { return _len==0; }
+ /** @return active list length */
+ public int len() { return _len; }
+ /** @param i element index
+ * @return element being returned; throws if OOB */
+ public int at( int i ) {
+ range_check(i);
+ return _es[i];
+ }
+ /** @param i element index
+ * @return element being returned, or 0 if OOB */
+ public int atX( int i ) {
+ return i < _len ? _es[i] : 0;
+ }
+ /** @return last element */
+ public int last( ) { return at(_len-1); }
+ public int last(int x ) { return at(_len-1+x); }
+
+ /** @return remove and return last element */
+ public int pop( ) {
+ range_check(0);
+ return _es[--_len];
+ }
+
+ /** Add element in amortized constant time
+ * @param e element to add at end of list
+ * @return 'this' for flow-coding */
+ public AryInt push( int e ) {
+ if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1));
+ _es[_len++] = e;
+ return this;
+ }
+
+ /** Slow, linear-time, element insert. Preserves order.
+ * @param i index to insert at, between 0 and _len inclusive.
+ * @param e intlement to insert
+ */
+ public void insert( int i, int e ) {
+ if( i < 0 || i>_len )
+ throw new ArrayIndexOutOfBoundsException(""+i+" >= "+_len);
+ if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1));
+ System.arraycopy(_es,i,_es,i+1,(_len++)-i);
+ _es[i] = e;
+ }
+
+ /** Fast, constant-time, element removal. Does not preserve order
+ * @param i element to be removed
+ * @return element removed */
+ public int del( int i ) {
+ range_check(i);
+ int tmp = _es[i];
+ _es[i]=_es[--_len];
+ return tmp;
+ }
+
+ /** Slow, linear-time, element removal. Preserves order.
+ * @param i element to be removed
+ * @return element removed */
+ public int remove( int i ) {
+ range_check(i);
+ int e = _es[i];
+ System.arraycopy(_es,i+1,_es,i,(--_len)-i);
+ return e;
+ }
+
+ /** Remove all elements */
+ public void clear( ) { Arrays.fill(_es,0,_len,0); _len=0; }
+
+ // Extend and set
+ public int setX( int i, int e ) {
+ while( i>= _es.length ) _es = Arrays.copyOf(_es,_es.length<<1);
+ if( i >= _len ) _len = i+1;
+ return (_es[i] = e);
+ }
+
+ public int set( int i, int e ) {
+ range_check(i);
+ return (_es[i] = e);
+ }
+
+ public AryInt set_as( int e ) { _es[0] = e; _len=1; return this; }
+ public AryInt set_len( int len ) {
+ if( len > _len )
+ while( len>= _es.length ) _es = Arrays.copyOf(_es,_es.length<<1);
+ _len = len;
+ while( _es.length > (len<<1) ) // Shrink if hugely too large
+ _es = Arrays.copyOf(_es,_es.length>>1);
+ return this;
+ }
+
+ /** @param c Collection to be added */
+ public AryInt addAll( Collection<? extends Integer> c ) { for( int e : c ) push(e); return this; }
+
+ /** @param es Array to be added */
+ public AryInt addAll( int[] es ) {
+ if( es.length==0 ) return this;
+ while( _len+es.length > _es.length ) _es = Arrays.copyOf(_es,_es.length<<1);
+ System.arraycopy(es,0,_es,_len,es.length);
+ _len += es.length;
+ return this;
+ }
+
+ /** @param es Array to be added */
+ public AryInt addAll( AryInt ary ) {
+ if( ary._len==0 ) return this;
+ while( _len+ary._len > _es.length ) _es = Arrays.copyOf(_es,_es.length<<1);
+ System.arraycopy(ary._es,0,_es,_len,ary._len);
+ _len += ary._len;
+ return this;
+ }
+
+ public AryInt map_update( IntUnaryOperator f ) { for( int i = 0; i<_len; i++ ) _es[i] = f.applyAsInt(_es[i]); return this; }
+
+ /** @return compact array version, using the internal base array where possible. */
+ public int[] asAry() { return _len==_es.length ? _es : Arrays.copyOf(_es,_len); }
+
+ /** Sorts in-place */
+ public void sort_update() { Arrays.sort(_es, 0, _len); }
+ /** Find the first matching element using ==, or -1 if none. Note that
+ * most del calls shuffle the list, so the first element might be random.
+ * @param e intlement to find
+ * @return index of first matching element, or -1 if none */
+ public int find( int e ) {
+ for( int i=0; i<_len; i++ ) if( _es[i]==e ) return i;
+ return -1;
+ }
+
+ @Override public String toString() {
+ SB sb = new SB().p('[');
+ for( int i=0; i<_len; i++ )
+ sb.p(_es[i]).p(',');
+ return sb.unchar().p(']').toString();
+ }
+
+ private void range_check( int i ) {
+ if( i < 0 || i>=_len )
+ throw new ArrayIndexOutOfBoundsException(""+i+" >= "+_len);
+ }
+
+ // Binary search sorted _es. Returns insertion point.
+ // Undefined results if _es is not sorted.
+ public int binary_search( int e ) {
+ int lo=0, hi=_len-1;
+ while( lo <= hi ) {
+ int mid = (hi + lo) >>> 1; // midpoint, rounded down
+ int mval = _es[mid];
+ if( e==mval ) {
+ // If dups, get to the first.
+ while( mid>0 && e==_es[mid-1] ) mid--;
+ return mid;
+ }
+ if( e >mval ) lo = mid+1;
+ else hi = mid-1;
+ }
+ return lo;
+ }
+
+ // Note that the hashCode() and equals() are not invariant to changes in the
+ // underlying array. If the hashCode() is used (e.g., inserting into a
+ // HashMap) and the then the array changes, the hashCode() will change also.
+ @Override public boolean equals( Object o ) {
+ if( this==o ) return true;
+ if( !(o instanceof AryInt ary) ) return false;
+ if( _len != ary._len ) return false;
+ if( _es == ary._es ) return true;
+ for( int i=0; i<_len; i++ )
+ if( _es[i] != ary._es[i] )
+ return false;
+ return true;
+ }
+ @Override public int hashCode( ) {
+ int sum=_len;
+ for( int i=0; i<_len; i++ )
+ sum += _es[i];
+ return sum;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/util/BAOS.java b/chapter29/src/main/java/com/seaofnodes/simple/util/BAOS.java
new file mode 100644
index 00000000..61ce7387
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/util/BAOS.java
@@ -0,0 +1,162 @@
+package com.seaofnodes.simple.util;
+
+import java.util.HashMap;
+import java.util.Arrays;
+
+// "ByteArrayOutputStream", and Input Stream and not synchronized and has
+// matching pack/unpack strategies.
+public class BAOS {
+
+ private byte[] _buf;
+ private int _len;
+
+ public BAOS() { _buf = new byte[1]; }
+ public BAOS(byte[] buf) { _buf = buf; }
+ public BAOS(byte[] buf, int x) { _buf = buf; _len = x; }
+
+ public byte[] toByteArray() {
+ return Arrays.copyOf(_buf,_len);
+ }
+
+ public byte[] buf() { return _buf; }
+ public int size() { return _len; }
+ public void set( byte[] buf, int len ) { _buf=buf; _len=len; }
+ public void clear() { _len=0; }
+
+ // -----------------------------------------------------------------------
+ // Basic read, write (expand)
+
+ private void grow(int len) {
+ while( _len+len >= _buf.length )
+ _buf = Arrays.copyOf(_buf,_buf.length*2);
+ }
+
+ // Write a byte, grow as needed; silent chop
+ public BAOS write(int b) {
+ grow(1);
+ _buf[_len++] = (byte)b; // Silent chop
+ return this;
+ }
+
+ // Read an UNSIGNED bytes at INDEX, AIOOBE if off end
+ public int read1(int idx) { return _buf[idx] & 0xFF; }
+
+ // Read an UNSIGNED byte, AIOOBE if off end
+ public int read() { return read1(_len++); }
+
+ // write 2 bytes
+ public BAOS write2( int x ) {
+ grow(2);
+ _buf[_len++] = (byte)(x>> 0);
+ _buf[_len++] = (byte)(x>> 8);
+ return this;
+ }
+
+ // Read a u16 in 2 bytes
+ public int read2(int idx) { return read1(idx) | (read1(idx+1)<<8); }
+ public int read2() { int rez = read2(_len); _len+=2; return rez; }
+
+ // write 4 bytes
+ public BAOS write4( int x ) {
+ grow(4);
+ _buf[_len++] = (byte)(x>> 0);
+ _buf[_len++] = (byte)(x>> 8);
+ _buf[_len++] = (byte)(x>>16);
+ _buf[_len++] = (byte)(x>>24);
+ return this;
+ }
+
+ // Read a i32 in 4 bytes
+ public int read4(int idx) { return read2(idx) | (read2(idx+2)<<16); }
+ public int read4() { int rez = read4(_len); _len+=4; return rez; }
+
+ // write 8 bytes
+ public BAOS write8( long x ) {
+ write4((int) x );
+ write4((int)(x>>32));
+ return this;
+ }
+
+ // Read a i64 in 8 bytes
+ public long read8(int idx) { return ((long)read4(idx) & 0xFFFFFFFFL) | ((long)read4(idx+4)<<32); }
+ public long read8() { long rez = read8(_len); _len+=8; return rez; }
+
+ // Write byte array
+ public void write( byte[] bs ) {
+ grow(bs.length);
+ System.arraycopy(bs,0,_buf,_len,bs.length);
+ _len += bs.length;
+ }
+
+ // Read/fill byte array
+ public byte[] read( byte[] bs ) {
+ System.arraycopy(_buf,_len,bs,0,bs.length);
+ _len += bs.length;
+ return bs;
+ }
+
+ // -----------------------------------------------------------------------
+ // Write a packed integer to the BAOS. Track a histogram to make more
+ // intelligent choices in the future.
+ private static final HashMap<Long,Integer> PACKED_HISTOGRAM = new HashMap<>();
+ private BAOS histo(long x) {
+ Integer cnt = PACKED_HISTOGRAM.get(x);
+ PACKED_HISTOGRAM.put(x,cnt==null ? 1 : cnt+1);
+ return this;
+ }
+
+ // Write a u8 (checked)
+ public void packed1(int x) {
+ assert 0 <= x && x <= 255;
+ histo(x).write(x);
+ }
+
+ public int packed1() { return read(); }
+
+ // Write a u16 (checked)
+ public void packed2(int x) {
+ assert 0 <= x && x <= 65535;
+ (( x <= 254 ) ? write(x) : write(0xFF).write2(x)).histo(x);
+ }
+
+ // Read a packed u16
+ public int packed2() {
+ int x = read();
+ return x==0xFF ? read2() : x;
+ }
+
+ // Write a packed i32
+ public void packed4(int x) {
+ ((-127 <= x && x <= 127 ) ? write(x) : write(-128).write4(x)).histo(x);
+ }
+
+ // Read a packed i32
+ public int packed4() {
+ int x = _buf[_len++]; // Signed byte read
+ return x== -128 ? read4() : x;
+ }
+
+ // Write a packed i64
+ public void packed8(long x) {
+ ((-127 <= x && x <= 127 ) ? write((int)x) : write(-128).write8(x)).histo(x);
+ }
+
+ // Read a packed i64
+ public long packed8() {
+ int x = _buf[_len++]; // Signed byte read
+ return x== -128 ? read8() : x;
+ }
+
+ public void packedS(String s) {
+ packed4(s.length());
+ for( int i=0; i<s.length(); i++ )
+ packed4(s.charAt(i));
+ }
+
+ public String packedS() {
+ int len = packed4();
+ String s = new String(_buf,_len,len).intern();
+ _len += len;
+ return s;
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/util/IntHashMap.java b/chapter29/src/main/java/com/seaofnodes/simple/util/IntHashMap.java
new file mode 100644
index 00000000..b356ec31
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/util/IntHashMap.java
@@ -0,0 +1,87 @@
+package com.seaofnodes.simple.util;
+
+import java.util.Arrays;
+
+// Int-Int Hash Map
+public class IntHashMap {
+ private int[] _es;
+ private int _size;
+ private final int _sentinel;
+
+ // Pick a sentinel value; not allowed for keys or values. Sentinel is
+ // returned on a miss. Writing sentinel counts as a remove.
+ public IntHashMap(int sentinel) {
+ _sentinel = sentinel;
+ _es = new int[4];
+ Arrays.fill(_es,sentinel);
+ }
+
+ public int size() { return _size; }
+ public boolean isEmpty() { return _size==0; }
+ public void clear() {
+ _size = 0;
+ Arrays.fill(_es,_sentinel);
+ }
+
+ private int cap() { return _es.length>>1; }
+
+ public int put( int key, int val ) {
+ int slot = probe(key);
+ int old = _es[(slot<<1)+1];
+ if( old == _sentinel && val != _sentinel ) _size++;
+ if( old != _sentinel && val == _sentinel ) _size--;
+ // Check cap, resize at 3/4 full
+ if( _size > ((cap()>>1) + (cap()>>2)) ) {
+ grow();
+ slot = probe(key);
+ }
+
+ _es[ slot<<1 ] = key;
+ _es[(slot<<1)+1] = val;
+ return old;
+ }
+
+ public int get( int key ) {
+ int slot = probe(key);
+ return _es[(slot<<1)+1];
+ }
+
+ private int probe( int key ) {
+ int mask = cap()-1;
+ int slot = key & mask;
+ while( _es[slot<<1] != _sentinel && _es[slot<<1] != key )
+ slot = (slot + (key|1)) & mask;
+ return slot;
+ }
+
+ // Double underlying array size
+ private void grow() {
+ int[] old = _es;
+ _es = new int[_es.length<<1];
+ Arrays.fill(_es,_sentinel);
+ for( int i=0; i<old.length; i+=2 )
+ if( old[i] != _sentinel ) {
+ int slot = probe(old[i]);
+ _es[ slot<<1 ] = old[i ];
+ _es[(slot<<1)+1] = old[i+1];
+ }
+ }
+
+ // Just all the keys
+ public int[] keySet() {
+ int[] keys = new int[_size];
+ int j=0;
+ for( int i=0; i<_es.length; i+=2 )
+ if( _es[i] != _sentinel )
+ keys[j++] = _es[i];
+ return keys;
+ }
+
+ @Override public String toString() {
+ SB sb = new SB().p("[ ");
+ for( int i=0; i<_es.length; i+=2 )
+ if( _es[i] != _sentinel )
+ sb.p('(').p(_es[i]).p(',').p(_es[i+1]).p(')').p(',');
+ return sb.unchar().p(']').toString();
+ }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/util/SB.java b/chapter29/src/main/java/com/seaofnodes/simple/util/SB.java
new file mode 100644
index 00000000..cc59f2a6
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/util/SB.java
@@ -0,0 +1,99 @@
+package com.seaofnodes.simple.util;
+
+/** Tight/tiny StringBuilder wrapper.
+ * Short short names on purpose; so they don't obscure the printing.
+ * Can't believe this wasn't done long, long ago. */
+public final class SB {
+ private final StringBuilder _sb;
+ private int _indent = 0;
+ public SB( ) { _sb = new StringBuilder( ); }
+ public SB(String s) { _sb = new StringBuilder(s); }
+ public SB p( String s ) { _sb.append(s); return this; }
+ public SB p( float s ) {
+ if( Float.isNaN(s) )
+ _sb.append( "Float.NaN");
+ else if( Float.isInfinite(s) ) {
+ _sb.append(s > 0 ? "Float.POSITIVE_INFINITY" : "Float.NEGATIVE_INFINITY");
+ } else _sb.append(s);
+ return this;
+ }
+ public SB p( double s ) {
+ if( Double.isNaN(s) )
+ _sb.append("Double.NaN");
+ else if( Double.isInfinite(s) ) {
+ _sb.append(s > 0 ? "Double.POSITIVE_INFINITY" : "Double.NEGATIVE_INFINITY");
+ } else _sb.append(s);
+ return this;
+ }
+ public SB p( char s ) { _sb.append(s); return this; }
+ public SB p( int s ) { _sb.append(s); return this; }
+ public SB p( long s ) { _sb.append(s); return this; }
+ public SB p( boolean s) { _sb.append(s); return this; }
+ // 1 byte, 2 hex digits, 8 bits
+ public SB hex1(int s) {
+ int digit = (s>>4) & 0xf;
+ _sb.append((char)((digit <= 9 ? '0' : ('A'-10))+digit));
+ digit = s & 0xf;
+ _sb.append((char)((digit <= 9 ? '0' : ('A'-10))+digit));
+ return this;
+ }
+ // 2 bytes, 4 hex digits, 16 bits, Big Endian
+ public SB hex2(int s) { return hex1(s>> 8).hex1(s); }
+ // 4 bytes, 8 hex digits, 32 bits, Big Endian
+ public SB hex4(int s) { return hex2(s>>16).hex2(s); }
+ // 8 bytes, 16 hex digits, 64 bits, Big Endian
+ public SB hex8(long s) { return hex4((int)(s>>32)).hex4((int)s); }
+
+ // Fixed width field
+ public SB fix( int sz, String s ) {
+ for( int i=0; i<sz; i++ )
+ _sb.append( i < s.length() ? s.charAt(i) : ' ');
+ return this;
+ }
+ public char at(int idx ) { return _sb.charAt(idx); }
+
+ // Not spelled "p" on purpose: too easy to accidentally say "p(1.0)" and
+ // suddenly call the autoboxed version.
+ public SB pobj( Object s ) { _sb.append(s.toString()); return this; }
+ public SB i( int d ) { for( int i=0; i<d+_indent; i++ ) p(" "); return this; }
+ public SB i( ) { return i(0); }
+ public SB ip(String s) { return i().p(s); }
+ public SB s() { _sb.append(' '); return this; }
+
+ // Increase indentation
+ public SB ii( int i) { _indent += i; return this; }
+ public SB ii() { return ii(1); }
+ // Decrease indentation
+ public SB di( int i) { _indent -= i; return this; }
+ public SB di() { return di(1); }
+
+ public SB nl( ) { return p(System.lineSeparator()); }
+ // Last printed a nl
+ public boolean was_nl() {
+ int len = _sb.length();
+ String nl = System.lineSeparator();
+ int nlen = nl.length();
+ if( len < nlen ) return false;
+ for( int i=0; i<nlen; i++ )
+ if( _sb.charAt(len-nlen+i)!=nl.charAt(i) )
+ return false;
+ return true;
+ }
+
+ // Delete last char. Useful when doing string-joins and JSON printing and an
+ // extra separater char needs to be removed:
+ //
+ // sb.p('[');
+ // for( Foo foo : foos )
+ // sb.p(foo).p(',');
+ // sb.unchar().p(']'); // remove extra trailing comma
+ //
+ public SB unchar() { return unchar(1); }
+ public SB unchar(int x) { _sb.setLength(_sb.length()-x); return this; }
+ public SB setLen(int len) { _sb.setLength(len); return this; }
+ public String subString(int start, int end ) { return _sb.substring(start,end); }
+
+ public SB clear() { _sb.setLength(0); return this; }
+ public int len() { return _sb.length(); }
+ @Override public String toString() { return _sb.toString(); }
+}
diff --git a/chapter29/src/main/java/com/seaofnodes/simple/util/Utils.java b/chapter29/src/main/java/com/seaofnodes/simple/util/Utils.java
new file mode 100644
index 00000000..577a60af
--- /dev/null
+++ b/chapter29/src/main/java/com/seaofnodes/simple/util/Utils.java
@@ -0,0 +1,53 @@
+package com.seaofnodes.simple.util;
+
+import java.util.ArrayList;
+
+public class Utils {
+ public static RuntimeException TODO() { return TODO("Not yet implemented"); }
+ public static RuntimeException TODO(String msg) { return new RuntimeException(msg); }
+
+ /**
+ * Fast, constant-time, element removal. Does not preserve order
+ *
+ * @param array ArrayList to modify
+ * @param i element to be removed
+ * @return element removed
+ */
+ public static <E> void del(ArrayList<E> array, int i) {
+ E last = array.removeLast();
+ if (i < array.size()) array.set(i, last);
+ }
+
+ /**
+ * Search a list for an element by reference
+ *
+ * @param ary List to search in
+ * @param x Object to be searched
+ * @return >= 0 on success, -1 on failure
+ */
+ public static <E> int find( ArrayList<E> ary, E x ) {
+ for( int i=0; i<ary.size(); i++ )
+ if( ary.get(i)==x )
+ return i;
+ return -1;
+ }
+ /**
+ * Search a list for an element by reference
+ *
+ * @param ary List to search in
+ * @param x Object to be searched
+ * @return >= 0 on success, -1 on failure
+ */
+ public static <E> int find( E[] ary, E x ) {
+ for( int i=0; i<ary.length; i++ )
+ if( ary[i]==x )
+ return i;
+ return -1;
+ }
+
+ // Rotate a long, nice for hashes
+ public static long rot( long x, int n ) { return (x<<n) | (x>>>n); }
+
+ // Fold a 64bit hash into 32 bits
+ public static int fold( long x ) { return (int)((x>>32) ^ x); }
+}
diff --git a/chapter29/src/main/smp/sys.smp b/chapter29/src/main/smp/sys.smp
new file mode 100644
index 00000000..7705a187
--- /dev/null
+++ b/chapter29/src/main/smp/sys.smp
@@ -0,0 +1,11 @@
+
+// top-level default standard library import
+
+val adt = adt; // Abstract Data Types
+val ary = ary; // Array utilities
+val aryi64 = aryi64; // Variable sized array of i64
+val aryu8 = aryu8; // Variable sized array of u8
+val char = char; // Character utilities
+val io = io; // IO library
+val libc = libc; // libc FFI bindings
+val scan = scan; // A simple string parser
diff --git a/chapter29/src/main/smp/sys/adt.smp b/chapter29/src/main/smp/sys/adt.smp
new file mode 100644
index 00000000..a4fe40bf
--- /dev/null
+++ b/chapter29/src/main/smp/sys/adt.smp
@@ -0,0 +1,3 @@
+
+// Abstract data types.
+val bitset = bitset;
diff --git a/chapter29/src/main/smp/sys/adt/bitset.smp b/chapter29/src/main/smp/sys/adt/bitset.smp
new file mode 100644
index 00000000..d02cd2fe
--- /dev/null
+++ b/chapter29/src/main/smp/sys/adt/bitset.smp
@@ -0,0 +1,57 @@
+
+// A set of non-negative integers, represented one bit per value.
+struct bitset {
+ i64[] !_bits;
+
+ new bitset = { ->
+ _bits = new i64[1];
+ };
+
+ // Return 0 for missing, non-zero for true
+ val get = { int bit ->
+ if( (bit >> 6) >= _bits# ) return false;
+ i64 mask = 1 << (bit & 63);
+ return (_bits[bit >> 6] & mask);
+ };
+
+ val _grow = { int bit ->
+ int x = bit >> 6;
+ int len = _bits#;
+ if( x < len ) return self;
+ while( x >= len ) len *= 2;
+ var bits = new i64[len];
+ for( int i=0; i<_bits#; i++ )
+ bits[i] = _bits[i];
+ _bits = bits;
+ return self;
+ };
+
+ // Set
+ val set = { int bit ->
+ i64 mask = 1 << (bit & 63);
+ _grow(bit)._bits[bit >> 6] |= mask;
+ return self;
+ };
+
+ // Test and set
+ val tset = { int bit ->
+ int idx = bit >> 6;
+ i64 mask = 1 << (bit & 63);
+ if( _grow(bit)._bits[idx] & mask )
+ return true; // Already set
+ _bits[idx] |= mask;
+ return false; // Was false (and now set)
+ };
+
+ val clr = { int bit ->
+ if( (bit >> 6) >= _bits# ) return self;
+ i64 mask = 1 << (bit & 63);
+ _bits[bit >> 6] &= (mask ^ -1);
+ return self;
+ };
+
+ val clear = { ->
+ for( int i=0; i<_bits#; i++ ) _bits[i] = 0;
+ return self;
+ };
+};
diff --git a/chapter29/src/main/smp/sys/ary.smp b/chapter29/src/main/smp/sys/ary.smp
new file mode 100644
index 00000000..8312e94c
--- /dev/null
+++ b/chapter29/src/main/smp/sys/ary.smp
@@ -0,0 +1,24 @@
+
+// Array utilities
+
+// Copy byte array contents. Copies the shorter of the buffers and len
+val copy = { u8[] dst, int doff, u8[~] src, int soff, int len ->
+ if( len > dst# - doff ) len = dst# - doff;
+ if( len > src# - soff ) len = src# - soff;
+ for( int i=0; i<len; i++ )
+ dst[i+doff] = src[i+soff];
+ return dst;
+};
+
+// Compare lexigraphically array contents. Compares up to shorter array or
+// len. Returns <0 or >0 for first non-matching byte. Returns <0 or >0 for
+// the larger array after that, or 0 if both arrays are fully equal.
+val cmp = { u8[~] src1, u32 off1, u8[~] src2, u32 off2, u32 len ->
+ u32 len1 = len > src1# - off1 ? src1# - off1 : len;
+ u32 len2 = len > src2# - off2 ? src2# - off2 : len;
+ u32 max = len1 > len2 ? len2 : len1;
+ for( int i=0; i<max; i++ )
+ if( src1[off1+i] != src2[off2+i] )
+ return src1[off1+i] - src2[off2+i]; // Bytes determine compare
+ return len1 - len2; // Length determines compare
+};
diff --git a/chapter29/src/main/smp/sys/aryi64.smp b/chapter29/src/main/smp/sys/aryi64.smp
new file mode 100644
index 00000000..98b72605
--- /dev/null
+++ b/chapter29/src/main/smp/sys/aryi64.smp
@@ -0,0 +1,82 @@
+
+// Ary/Vector of i64s
+struct aryi64 {
+ u32 !len = 0;
+ i64[] !buf;
+
+ new aryi64 = { i64[] b ->
+ buf = b;
+ };
+
+ // Grow as needed
+ val _grow = { int len2 ->
+ if( len+len2 < buf# ) return self;
+ int newlen = buf# * 2;
+ while( len+len2 >= newlen ) newlen *= 2;
+ i64[] !buf2 = new i64[newlen];
+ for( int i=0; i<buf#; i++ )
+ buf2[i] = buf[i];
+ buf = buf2;
+ self;
+ };
+
+ // Add a i64 to aryu8
+ val add = { i64 c ->
+ _grow(1).buf[len++] = c;
+ self;
+ };
+
+ val get = { int index -> return buf[index]; };
+
+ // Write the bracketed array-list form to aryu8.
+ val str = { ->
+ // TODO: clunky default constructor syntax
+ var str = new aryu8().add('[');
+ for( int i=0; i<len; i++ )
+ str.num(buf[i]).add(',').add(' ');
+ if( len ) str.undo(2);
+ return str.add(']');
+ };
+
+ val swap = { int i, int j ->
+ i64 tmp = buf[i];
+ buf[i] = buf[j];
+ buf[j] = tmp;
+ self;
+ };
+};
+
+
+
+// Parse a comma-separated list of numbers, wrapped with square brackets,
+// with optional whitespace e.g. "[ -17, 0,99,12456789 ] ".
+// parseAryI64 & aryi64.str are bijections modulo whitespace.
+// Returns null if the parse failed, without giving a clue as to why.
+val parseAryI64 = { u8[~] str ->
+ !scan !s = new scan(str);
+ if( !s.match('[') ) return null;
+ // Array of parsed results
+ var nums = new aryi64(new i64[1]);
+
+ // Parse a list of numbers
+ if( s.match(']') ) // Handle the empty list
+ return nums;
+
+ // Parse number commas "123, " until something else
+ while( s.x < s.buf# ) {
+ nums.add(s.number());
+ if( s.bad ) return null;
+ if( !s.match(',') )
+ break;
+ };
+ // Close the list
+ if( !s.match(']') ) return null;
+ s.skip();
+ return s.x == s.buf# ? nums : null;
+};
+
+val copy = { i64[] dst, int doff, i64[~] src, int soff, int len ->
+ for( int i=0; i<len; i++ )
+ dst[i+doff] = src[i+soff];
+ return dst;
+};
diff --git a/chapter29/src/main/smp/sys/aryu8.smp b/chapter29/src/main/smp/sys/aryu8.smp
new file mode 100644
index 00000000..21cf920b
--- /dev/null
+++ b/chapter29/src/main/smp/sys/aryu8.smp
@@ -0,0 +1,87 @@
+
+// Ary/Vector of u8s - a resizable array of bytes.
+struct aryu8 {
+ u32 !len = 0;
+ u8[] !buf;
+
+ new aryu8 = { -> buf = new u8[1]; };
+
+ // Grow as needed
+ val _grow = { int len2 ->
+ if( len+len2 < buf# ) return self;
+ int newlen = buf# * 2;
+ while( len+len2 >= newlen ) newlen *= 2;
+ u8[] !buf2 = new u8[newlen];
+ ary.copy(buf2,0,buf,0,buf#);
+ buf = buf2;
+ self;
+ };
+
+ val clear = { -> len = 0; self; };
+
+ // Add a character to aryu8
+ val add = { u8 c ->
+ _grow(1).buf[len++] = c;
+ self;
+ };
+
+ // Add all of a plain array to the aryu8
+ val addAry = { u8[~] xs ->
+ _grow(xs#);
+ ary.copy(buf,len,xs,0,xs#);
+ len += xs#;
+ self;
+ };
+
+ // Remove x bytes from the tail.
+ val undo = { int x ->
+ len = len >= x ? len-x : 0;
+ self;
+ };
+
+ // Print ASCII number into aryu8
+ val num = { i64 x ->
+ if( !x ) { add('0'); return self; }
+ if( x < 0 ) { add('-'); x = -x; }
+ i64 ten = 1, log = 0;
+ while( ten <= x ) { ten *= 10; log++; }
+ for( int i=0; i<log; i++ ) {
+ ten /= 10;
+ i64 q = x/ten;
+ u8 digit = q+'0';
+ add(digit);
+ x -= q*ten;
+ }
+ self;
+ };
+
+ // Write to fd, returns the number of bytes written (len).
+ val write = { i32 fd ->
+ i64 ptr = buf; // Cast buffer pointer to i64 for native C call
+ libc.write(fd,ptr,len);
+ };
+};
+
+
+// Parse one integer, ignore trailing data; returns 0 with an empty string.
+// If you want a more robust parsing, use the `scan` class
+val atoi = { u8[~] str ->
+ int x = 0;
+ while( x < str# && str[x] <= ' ' ) x++;
+ if( x == str# ) return 0;
+ bool neg = false;
+ if( str[x]=='-' ) { neg=true; x++; }
+ i64 n = 0;
+ while( x < str# && '0' <= str[x] <= '9' )
+ n = n*10 + str[x++]-'0';
+ return neg ? -n : n;
+};
+
+// Zero-terminate string for libc strings. No error checking, just copies and
+// appends a zero.
+{ u8[~] -> u8[] } str0 = { u8[~] str ->
+ u8[] !strz = new u8[str#+1];
+ ary.copy(strz,0,str,0,str#);
+ strz[str#] = '\0';
+ strz;
+};
diff --git a/chapter29/src/main/smp/sys/char.smp b/chapter29/src/main/smp/sys/char.smp
new file mode 100644
index 00000000..22ca4213
--- /dev/null
+++ b/chapter29/src/main/smp/sys/char.smp
@@ -0,0 +1,8 @@
+
+val toupper = { u8 c ->
+ return c >= 'a' && c <= 'z' ? c - ('a' - 'A') : c;
+};
+
+val tolower = { u8 c ->
+ return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c;
+};
diff --git a/chapter29/src/main/smp/sys/io.smp b/chapter29/src/main/smp/sys/io.smp
new file mode 100644
index 00000000..d6263ffe
--- /dev/null
+++ b/chapter29/src/main/smp/sys/io.smp
@@ -0,0 +1,64 @@
+
+// Simple I/O Library
+
+// Write byte array to stdout
+val p = { u8[~] str -> write(1,str); };
+val p_noInline = { u8[~] str -> write(1,str); };
+
+// Return fd contents as a u8[].
+val read = { i32 fd ->
+ var buf = new u8[8*1024];
+ i64 ptr = buf;
+ int len = 0;
+ while( true ) {
+ i32 rez = libc.read(fd,ptr,buf#);
+ if( rez > buf# ) libc._exit(libc.errno());
+ len += rez;
+ if( rez < buf# )
+ break;
+ libc._exit(-2); // Need to implement for more than 8K input
+ }
+ var out = new u8[len];
+ ary.copy(out,0,buf,0,len);
+};
+
+// Write all of str to fd.
+val write = { i32 fd, u8[~] str ->
+ i64 ptr = str; // Convert array pointer to a i64 for the native call
+ libc.write(fd,ptr,str#);
+};
+
+// Write all of str to fd.
+val writeOff = { i32 fd, u8[~] str, u32 off, u32 len ->
+ i64 ptr = str; // Convert array pointer to a i64 for the native call
+ ptr += off; // Skip offset
+ // Cap at available length
+ if( len > str# - off ) len = str# - off;
+ // Do the write
+ libc.write(fd,ptr,len);
+};
+
+// Return stdin as a u8[].
+val stdin = { -> read(0); };
+
+// Create file for writing. Return fd, or -1 and errno has the error code
+val create = { u8[~] str ->
+ i64 ptr = sys.aryu8.str0(str);
+ i32 perms = 6*64 + 6*8 + 6; // 0666
+ return libc.creat(ptr, perms);
+};
+
+// Open file for reading. Return fd, or -1 and errno has the error code
+val open = { u8[~] str ->
+ i64 ptr = sys.aryu8.str0(str);
+ return libc.open(ptr, sys.libc.O_RDONLY, 0);
+};
+
+// Close an open fd. Return 0, or -1 and errno has the error code
+val close = { i32 fd -> return libc.close(fd); };
+
+// Remove a file. Return 0, or -1 and errno has the error code
+val remove = { u8[~] str ->
+ i64 ptr = sys.aryu8.str0(str);
+ return libc.unlink(ptr);
+};
diff --git a/chapter29/src/main/smp/sys/libc.smp b/chapter29/src/main/smp/sys/libc.smp
new file mode 100644
index 00000000..17ee65cb
--- /dev/null
+++ b/chapter29/src/main/smp/sys/libc.smp
@@ -0,0 +1,44 @@
+
+// libc bindings:
+// https://www.man7.org/linux/man-pages/man2/<libc call>.2.html
+
+// The buffers are int-versions of zero-terminated strings.
+// int buf = aryu8.str0("some_file");
+// buf mode perm-> fd
+{ i64 i32 i32 -> i32 } open = "C";
+// buf perm -> fd
+{ i64 i32 -> i32 } creat = "C";
+
+// fd buf len -> len
+{ i32 i64 u32 -> i32 } write = "C";
+// fd buf len -> len
+{ i32 i64 u32 -> i32 } read = "C";
+// fd -> err
+{ i32 -> i32 } close = "C";
+// path -> err
+{ i64 -> i32 } unlink = "C";
+
+i32 O_RDONLY = 0;
+i32 O_WRONLY = 1;
+i32 O_RDWR = 2;
+i32 O_CREAT = 512; // 02000 on x86_64-pc-cygwin
+i32 O_TRUNC = 1024; // 04000 on x86_64-pc-cygwin
+
+i32 STDIN = 0;
+i32 STDOUT = 1;
+i32 STDERR = 2;
+
+// status
+{ i32 -> i32 } _exit = "C";
+// errno is not an ordinary C data symbol; read it through the native runtime.
+{ i32 } simple_errno = "C";
+val errno = { -> simple_errno(); };
+// addr len prot flags fd off -> void*
+{ i64 i64 i32 i32 i32 i32 -> i64 } mmap = "C";
+// mmap flags
+i32 PROT_EXEC = 4;
+i32 PROT_READ = 1;
+i32 PROT_WRITE= 2;
+i32 PROT_NONE = 0;
+i32 MAP_PRIVATE = 2;
+i32 MAP_ANON = 32;
diff --git a/chapter29/src/main/smp/sys/scan.smp b/chapter29/src/main/smp/sys/scan.smp
new file mode 100644
index 00000000..44bedaaa
--- /dev/null
+++ b/chapter29/src/main/smp/sys/scan.smp
@@ -0,0 +1,39 @@
+
+// Scanner; a simple string parser.
+struct scan {
+ int !x; // Cursor
+ bool !bad; // True after a malformed token
+ u8[~] buf; // Buffer to parse
+
+ new scan = { u8[~] b -> buf = b; };
+
+ // Skip whitespace
+ val skip = { ->
+ while( x < buf# && buf[x] <= ' ' )
+ x++;
+ return self;
+ };
+
+ // True if find required character, and skip it; else False.
+ val match = { u8 c ->
+ skip();
+ if( x >= buf# ) return false;
+ if( buf[x] != c ) return false;
+ x++;
+ return true;
+ };
+
+ // Parse a simple number
+ val number = { ->
+ skip();
+ bool neg = match('-');
+ if( x >= buf# || buf[x] < '0' || '9' < buf[x] ) {
+ bad = true;
+ return 0;
+ }
+ i64 n = 0;
+ while( x < buf# && '0' <= buf[x] & buf[x] <= '9' )
+ n = n*10 + (buf[x++] - '0');
+ return neg ? -n : n;
+ };
+};