summaryrefslogtreecommitdiff
path: root/chapter25/src/main/java/com
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-08-10 11:11:18 -0700
committerCliff Click <cliffc@acm.org>2026-08-10 11:11:18 -0700
commit51f1f8f4b871e3b52eb0c5d18b03f362a2b37791 (patch)
tree0d734f9d53fa58acfd0ad2875f6c753c78d523a1 /chapter25/src/main/java/com
parentac3cfbfd58a2e7fdc61995c83a587778cb4e2919 (diff)
downloadSimple-51f1f8f4b871e3b52eb0c5d18b03f362a2b37791.tar.gz
Simple-51f1f8f4b871e3b52eb0c5d18b03f362a2b37791.zip
Chapter 25: add separate compilation and simplify SSA construction
Introduce Chapter 25 with separately compiled Simple modules, serialized IR, cross-compilation-unit linking, and native system-library integration. This also completes a broad parser and type-system cleanup needed to make graph construction independent of incomplete parse-time type information. Separate compilation and linking - Add compilation-unit Start/Stop nodes and track functions, classes, aliases, and global indices across compilation-unit boundaries. - Serialize canonical types and ideal graph nodes into ELF objects, including dependency metadata, relocations, global/local bitset remapping, and dense memory-alias information. - Load dependent objects on demand, recursively resolve source and object paths, relink calls and function pointers, and preserve external C symbols. - Distinguish the post-optimization ownership hook from unknown callers so private call graphs can remain precise while public functions remain externally callable. - Add command-line and build support for compiling modules and the reusable `sys` object library. Parser, constructors, and SSA - Replace inline constructor expansion with a hidden default `<init>` followed by an explicit user constructor declared as `new Type = { ... }`. - Keep the default constructor private, pass private object memory through constructor calls, and require user constructors to initialize non-default fields before the object escapes. - Track possibly-uninitialized fields in parser `Var` state and diagnose early reads without encoding parser-only constraints in Load and Store nodes. - Make ordinary parser memory bulk and recover precise alias slices in the optimizer using `MemMerge`, `MemPhi`, and `BulkMemPhi` nodes. - Remove parser-supplied type constraints from Loads and ordinary Phis, defer integer/float arithmetic selection, and keep call shape driven by syntax instead of provisional inferred types. - Preserve chained field declarations as static parser information when flow types are not yet available, including postfix field updates through calls. Types, functions, and escape analysis - Add `TypeScalar` as the common lattice family for integer, float, memory pointer, and function-pointer values, with scalar TOP/BOT values for generic Loads, Stores, parameters, and invalid intermediate expressions. - Represent unresolved struct definitions directly with the `TypeStruct` forward-reference flag while retaining the separate open-structure concept; close and canonicalize recursive structures without side maps. - Add explicit `FunPtrNode` references so constant function pointers retain a graph edge to their Return, and keep referenced functions alive until Opto proves the pointer dead. - Refine public-function and class-initializer escape handling, including public-only fallback escape sets and monotone compilation-unit summaries. - Add guards, proof casts, read-only propagation, pointer-to-integer conversion for non-null FFI arguments, and consistent deep mutability checks at calls. Optimization and code generation - Make weak and unresolved graph inputs compute conservatively and monotonically throughout iterative peepholes and SCCP. - Preserve independent memory slices when sharpening Stores, retain control on conditional Stores, and freeze Store widths from their target field types. - Extend serialization for new node kinds, scalar types, Store modes, function pointers, and memory partitions, preserving serialize/deserialize bijection. - Complete native selection and encoding support across x86-64, RISC-V, and ARM, including FFI pointer conversion and missing integer operations. - Fix x86 immediate multiply REX encoding and assorted scheduling, register allocation, spilling, relocation, and constant-pool issues exposed by the larger linked graphs. System library, tests, and documentation - Compile and link the Simple `sys` library, including libc bindings, I/O, scanners, strings, vectors, array helpers, and executable entry points. - Add Chapter 25 module, serialization, constructor, forward-reference, postfix-update, system, Hello World, and Bubble Sort coverage; update golden graphs and spill expectations for the new IR shape. - Add module and type-system documentation, parser-simplification design notes, grammar updates, examples, diagrams, and graph visualization assets. The resulting compiler passes the raw Chapter 1-24 suite and the Chapter 25 system and separate-compilation tests, including a freshly rebuilt `sys.o` and the native Bubble Sort example.
Diffstat (limited to 'chapter25/src/main/java/com')
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java230
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/Parser.java2748
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/Var.java53
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java96
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java105
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java1050
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java138
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java87
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java234
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java387
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Encoding.java606
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java171
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java376
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java556
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/LRG.java238
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java33
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java261
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Machine.java115
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Module.java98
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java360
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java379
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java14
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java646
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/RegMask.java125
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java36
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java617
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/AddNode.java179
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/AndNode.java50
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java198
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/BoolNode.java195
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java312
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java227
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java84
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CallEndMach.java38
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java275
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java192
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java23
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CheckCastMach.java20
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CheckCastNode.java82
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java73
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java88
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java96
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CtrlNode.java16
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/DivNode.java40
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/EscapeNode.java114
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ExternNode.java40
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/FRefNode.java45
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/FldOffNode.java50
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java448
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java65
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/GuardMach.java17
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/GuardNode.java61
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/IfNode.java99
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java332
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/LoopNode.java110
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java27
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MachNode.java60
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java258
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java131
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java72
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java78
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ModeNode.java12
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MulNode.java65
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java29
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/NeverNode.java23
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/NewNode.java104
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/Node.java828
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/NotNode.java31
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/OrNode.java46
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java87
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java294
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/Proj.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java71
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java17
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java40
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java19
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java30
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/RegionNode.java175
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java134
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java37
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java38
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ScopeNode.java562
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java51
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java34
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/SplitNode.java29
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/StartCUNode.java33
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/StartNode.java87
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java122
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/StopNode.java100
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java321
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/StructNode.java60
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/SubNode.java45
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java31
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/TypeNode.java33
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java15
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java44
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java20
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java22
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java27
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java21
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java24
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java18
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java31
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java79
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java49
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java32
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java22
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java22
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java40
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java19
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java19
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java44
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java22
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java25
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java70
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java39
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java19
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java33
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java19
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java28
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java71
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java19
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java18
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java25
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java38
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java27
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java20
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java24
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java17
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java15
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java15
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java35
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java59
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java43
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java19
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java18
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java28
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java50
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java48
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java49
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java19
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java26
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java875
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java41
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java17
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java21
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java21
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java12
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java16
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java93
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java61
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java31
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java19
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java17
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java47
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java22
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java23
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java53
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java48
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java30
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java32
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java64
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java50
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java17
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java17
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java20
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java48
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java20
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java12
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java16
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java17
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java15
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java21
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java33
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java17
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java20
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java12
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java17
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java60
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java14
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java17
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java12
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java17
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java53
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java17
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java16
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java55
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java44
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java47
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java13
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java16
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java566
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java44
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java33
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java24
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java34
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java11
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java11
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java11
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java33
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java54
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java32
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java44
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java78
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java32
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java34
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java33
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java50
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java35
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java31
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java38
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java79
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java81
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java55
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java57
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java40
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java89
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java32
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java34
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java29
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java22
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java26
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java39
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java11
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java11
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java17
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java15
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java27
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java19
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java39
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java24
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java55
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java39
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java24
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java34
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java24
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java100
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java85
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java31
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java10
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java55
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java40
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java54
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java11
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java11
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java680
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java302
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java288
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/print/IRPrinter.java172
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java404
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java149
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/sys.java25
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/Field.java148
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/Type.java682
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAry.java95
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java22
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java18
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeFloat.java134
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java304
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeInteger.java217
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeMem.java252
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java162
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeNil.java62
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypePtr.java66
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeRPC.java106
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeScalar.java56
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java660
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeTuple.java172
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/XInt.java266
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/util/Ary.java229
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/util/AryInt.java192
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/util/BAOS.java162
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/util/IntHashMap.java65
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/util/SB.java99
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/util/Utils.java53
275 files changed, 30095 insertions, 0 deletions
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java
new file mode 100644
index 00000000..608f368d
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -0,0 +1,230 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Opto;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.print.JSViewer;
+import java.util.Arrays;
+import java.util.BitSet;
+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;
+
+ public IterPeeps( long seed ) { _work = 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 to a fixed point
+ */
+ public void iterate( CodeGen code ) {
+ assert progressOnList(code, _work, true);
+ int cnt=0;
+
+ Node n;
+ while( (n=_work.pop()) != null ) {
+ if( n.isDead() ) continue;
+ cnt++; // Useful for debugging, searching which peephole broke things
+ Node x = n.peepholeOpt();
+ if( x != null ) {
+ if( x.isDead() ) continue;
+ // 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);
+ // 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. _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();
+ JSViewer.show(); // Show again
+ assert progressOnList(code, _work, true); // Very expensive assert
+ }
+ if( n.isUnused() && !(n instanceof StopNode) )
+ n.kill(); // Just plain dead
+ }
+
+ }
+
+ // 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, boolean dir ) {
+ code._midAssert = true;
+ Node changed = code._stop.walk( n -> {
+ Node m = n;
+ Type nval = n.compute();
+
+ // Types must be forwards, even if on the worklist.
+ boolean checkType = !n.iskeep() ||
+ n._nid <= 8 ||
+ (n instanceof ProjNode && n.in(0) instanceof StartNode);
+
+ boolean monotonic = dir
+ ? nval.isa(n._type) // Pesi: new value lifts over old
+ : n._type.isa(nval); // Opto: new value falls over old
+ assert !checkType || monotonic : "Non-monotonic peep: "+n+"#"+n._nid+" old="+n._type+" new="+nval+" inputs="+inputTypes(n)+" peep="+m;
+ if( list.on(n) )
+ return null;
+
+ assert n.nOuts() > 0 : "Unused live node: "+n;
+ 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();
+ }
+
+ /**
+ * 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); }
+ boolean isEmpty() { return _len==0; }
+ Node[] asAry() { return Arrays.copyOf(_es,_len); }
+
+ /**
+ * 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
+ _on.clear(x._nid);
+ return x;
+ }
+
+ public void clear() {
+ _len = 0;
+ _on.clear();
+ _R.setSeed(_seed);
+ _totalWork = 0;
+ }
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
new file mode 100644
index 00000000..98f8698f
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
@@ -0,0 +1,2748 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.print.GraphVisualizer;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.node.ScopeNode.Kind;
+
+import static com.seaofnodes.simple.util.Utils.TODO;
+
+import java.util.*;
+
+/**
+ * The Parser converts a Simple source program to the Sea of Nodes intermediate
+ * representation directly in one pass. There is no intermediate Abstract
+ * Syntax Tree structure.
+ * <p>
+ * This is a simple recursive descent parser. All lexical analysis is done here as well.
+ */
+public class Parser {
+
+ // Compile driver
+ public final CodeGen _code;
+
+ // The Lexer. Thin wrapper over a byte[] buffer with a cursor.
+ private Lexer _lexer;
+
+ // Class prefix string; TODO: use something short: "%"
+ public static final String CLZ = "class:";
+ private static final String clzPrefix = CLZ;
+ public static String addClzPrefix( String x ) {
+ assert !x.startsWith(clzPrefix);
+ return (clzPrefix+x).intern();
+ }
+ public static boolean startsClzPrefix( String s ) {
+ return s.startsWith(clzPrefix);
+ }
+ public static boolean isPublicClassName( String name ) {
+ if( startsClzPrefix(name) ) name = name.substring(clzPrefix.length());
+ return !name.isEmpty() && name.charAt(0)!='_' && name.indexOf("._")==-1;
+ }
+
+ // Source file for compilation
+ public CompUnit _ref;
+
+ // Current classname being parsed
+ private 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 #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 #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<>();
+
+ ScopeNode _breakScope; // Merge all the while-breaks here
+ ScopeNode _continueScope; // Merge all the while-continues here
+ ScopeNode _returnScope; // Merge all the function exits here
+ TypeStruct _ctorOpenStruct; // Open struct being initialized by an allocation constructor block.
+
+ // 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 Parser(CodeGen code ) {
+ _code = code;
+ }
+
+ @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); }
+
+ // Read and set CTRL
+ private Node ctrl() { return _scope.ctrl(); }
+ private <N extends Node> N ctrl(N n) { return _scope.ctrl(n); }
+ // Read and set Memory
+ private Node mem() { return _scope.mem(1); }
+ private 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.
+ private 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.
+ private Node mergeAlias(Node bulk, int alias, Node precise) {
+ assert bulk != null : "Missing bulk memory for alias #"+alias;
+ MemMergeNode merge = new MemMergeNode(false,null,bulk);
+ merge.alias(alias,precise);
+ return peep(merge);
+ }
+
+
+ public Ary<FRefNode> parse( CompUnit ref ) {
+ assert _scope == null && _breakScope == null && _continueScope == null && _returnScope == null;
+ _lexer = new Lexer(ref._src);
+ // 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);
+
+ ReturnNode clzret = parseStruct( true, typeName );
+
+ if( !_lexer.isEOF() ) throw _errorSyntax("unexpected");
+
+ // At the top scope and every new var should be a FRef, and these
+ // should immediately resolve to a file-based name which is a Simple
+ // class type.
+ Ary<FRefNode> frefs = new Ary<>(FRefNode.class);
+ for( int i=2; i<_scope._vars._len; i++ ) {
+ FRefNode fref = (FRefNode)_scope._inputs.at(i);
+ CompUnit cu = ParseAll.findCompUnitOrThrow(_code,_ref,fref._name);
+ fref._con = TypeMemPtr.make((byte)2,TypeStruct.make(addClzPrefix(cu._cname),true),true);
+ frefs.push(fref);
+ }
+
+ // The class being published
+ ref._clz = (TypeStruct)TYPES.get(typeName);
+
+ // Clean up and reset
+ _xScopes.pop();
+ _scope.kill();
+ _scope = null;
+
+ return frefs;
+ }
+
+
+
+ /**
+ * Parses a function body, assuming the header is parsed.
+ */
+ private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, 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 = _scope.save();
+
+ // Parse function body normally
+ ReturnNode ret = _parseFunctionBody(null, sig,loc,ids);
+
+ // Reset all fields to pre-parse days
+ _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.
+
+ private ReturnNode _parseFunctionBody( String funName, TypeFunPtr sig, Lexer loc, String[] ids) {
+ boolean isInit = FunNode.isInit(funName);
+ boolean isCtor = isInit || isExplicitConstructorName(funName);
+ String typeName = isCtor ? ((TypeMemPtr)sig._sig[0])._obj._name : null;
+
+ // Stack parser state on the local Java stack, and unstack it later
+ _scope.push(new Kind.Func(funName));
+ ScopeNode breakScope = _breakScope; _breakScope = null;
+ ScopeNode continueScope = _continueScope; _continueScope = null;
+ ScopeNode returnScope = _returnScope; _returnScope = null;
+
+ int oldUID = _code.UID(); // Used to approximate function size
+ FunNode fun = (FunNode)peep(new FunNode(loc(),sig, funName, _ref, null, _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).
+ _code.link(fun);
+
+ Node rpc = 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.
+ _scope.ctrl(fun); // Scope control from function
+ // Private mem alias tracking per function
+ Node defaultMem = new ProjNode(_ref._start,1,ScopeNode.MEM0).peephole();
+ Node privMem = new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,defaultMem).peephole();
+ 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
+ _scope.define(ids[i], t, i==0 && ids[0]=="self", new ParmNode(ids[i],i+2,t,fun,ConstantNode.seed(t).peephole()).peephole(), loc);
+ }
+
+ // Parse the function body.
+ Node last=_code.ZERO; // Last statement as the default result
+
+ int nvar = 0;
+ while (!peek('}') && !_lexer.isEOF()) {
+ // Parse a statement; record last statement as default return
+ last = _code.add(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 = updateSelfAsFieldsDiscovered( typeName, nvar );
+ }
+
+ ParmNode self = fun.parm(2);
+ // Last expression is the return
+ if( ctrl()._type==Type.CONTROL ) {
+ // Constructors get special return types
+ if( fun.isClz() ) {
+ // <clinit> only returns an integer, which becomes an OS exit code
+ // if the <clinit> is run directly
+ //if( !(last._type instanceof TypeInteger) )
+ // last = _code.ZERO;
+ assert last!=null;
+ } else if( isCtor ) {
+ last = fun.parm(3);
+ }
+ addReturn(last);
+ // exit path is now dead (nothing live after a "return")
+ }
+ if( isCtor )
+ 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 ? _code.XCTRL : _returnScope.ctrl().peephole();
+ Node rmem = rctl==_code.XCTRL ? con(TypeMem.TOP) : _returnScope.mem();
+ Node expr = rctl==_code.XCTRL ? con(Type.TOP) : _returnScope._inputs.last().peephole();
+ ReturnNode ret = (ReturnNode)peep(new ReturnNode(rctl, rmem, expr, rpc, fun));
+ fun.setRet(ret);
+ // Approximate function size, for inlining heuristics
+ fun._approxUIDs = _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
+ _ref.addFun(_code,fun);
+
+ // Unstack parser state
+ if( _returnScope != null ) _returnScope.kill();
+ _returnScope = returnScope;
+ _continueScope = continueScope;
+ _breakScope = breakScope;
+ _scope.pop();
+
+ return ret;
+ }
+
+ private int updateSelfAsFieldsDiscovered( String typeName, int nvar ) {
+ int lex = _scope.klast()._lexSize + 2/*Skip self and selfMem*/;
+ // Load self type in and out of the TYPES hashtable.
+ TypeStruct tself = (TypeStruct)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 < _scope.nIns() ) {
+ Var v = _scope.var(lex + nvar++);
+ // Field with sharper type
+ Field old = tself.field(v._name);
+ Field fld = old == null
+ ? Field.make(v._name,v.type(),_code.alias(_ref._cname),v._final)
+ : Field.make(v._name,v.type(), old._alias, old._final);
+ tself = tself.addOrUpdate(fld);
+ }
+ TYPES.put(typeName,tself);
+ return nvar;
+ }
+
+
+ void upgradeSelfTypeAndStoreFields( String typeName, boolean isClz, ParmNode self, ParmNode smem, boolean upgrade ) {
+ int lex = _scope.klast()._lexSize;
+ assert _scope.var(lex)._name=="self";
+ TypeStruct tself = (TypeStruct)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 = _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.
+ 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);
+ }
+
+ // 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( _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 ? _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 = _scope.var(vidx);
+ if( v._fref ) continue; // Forward refs not declared here
+ // NewNode's TOP field is the zero-code poison for a required
+ // field. The parser Var retains the typed nullable state for
+ // early-read diagnostics; only a user constructor writes memory.
+ if( upgrade && v._uninit ) continue;
+ Node val = _returnScope.in(vidx);
+ if( !upgrade && v._uninit && val._type instanceof TypeNil tn && tn.nullable() )
+ throw error("'"+tself._name+"' is not fully initialized, field '"+
+ fld._fname+"' is only partially set in the constructor",v._loc);
+ // Store value into extended struct
+ Node prior = mmm.keep();
+ Node st = peep(new StoreNode(null, fld._fname, fld._alias, fld._t, null, prior, self, off(tself,fld._fname), val, fld._final));
+ mmm = mergeAlias(prior,fld._alias,st);
+ prior.unkeep();
+ }
+ mmm = mmm.peephole();
+
+ if( !isClz )
+ // Stuff private mem into normal expression return value.
+ _returnScope.setDef(_returnScope.nIns()-1,mmm);
+ else
+ // Parser memory is one opaque whole-memory edge. Publish the
+ // completed class-field aggregate as the <clinit> return memory.
+ _returnScope.mem(mmm);
+ //_code.add(mmm);
+ }
+
+ private int fieldVarIdx( int base, String name ) {
+ for( int i=base; i<_scope.nIns(); i++ )
+ if( _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.
+ private int fieldVarIdxBefore( int limit, String name ) {
+ for( int i=limit-1; i>=0; i-- )
+ if( _scope.var(i)._name == name )
+ return i;
+ return -1;
+ }
+
+ private boolean isConstructorDecl(String name) {
+ TypeStruct self = _scope.constructorSelf();
+ return self != null && name == initFieldName(self._name);
+ }
+
+ private boolean inExplicitConstructor() {
+ if( !(_scope.klast() instanceof Kind.Func func) )
+ return false;
+ return isExplicitConstructorName(func._name);
+ }
+
+ private boolean isExplicitConstructorName(String funName) {
+ TypeStruct self = _scope.constructorSelf();
+ return self != null && funName == (self._name+"."+initFieldName(self._name)).intern();
+ }
+
+ private 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._t instanceof TypeFunPtr ? fld : fld.makeFrom(fld._t.glb(true));
+ }
+ return TypeStruct.make(self._name,self._open,fs);
+ }
+
+
+ /**
+ * 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
+ _scope.push(kind);
+ Node last = _code.ZERO;
+ while (!peek('}') && !_lexer.isEOF()) {
+ _code.add(last);
+ last = parseStatement();
+ }
+ // Exit scope
+ last.keep();
+ _scope.pop();
+ return last.unkeep();
+ }
+
+ /**
+ * Parses a statement
+ *
+ * <pre>
+ * returnStatement | declStatement | blockStatement | ifStatement | expressionStatement
+ * </pre>
+ * @return a {@link Node} or {@code null}
+ */
+ private Node parseStatement() {
+ if( matchx("return") ) return parseReturn();
+ else if (matchx("if") ) return parseIf();
+ else if (matchx("while") ) return parseWhile();
+ else if (matchx("for") ) return parseFor();
+ else if (matchx("break") ) return parseBreak();
+ else if (matchx("continue")) return parseContinue();
+ else if (matchx("struct") ) return parseStruct();
+ else if (inStructDeclaration() && matchx("new")) return parseConstructorDeclaration();
+ else if (matchx("#showGraph")) return require(showGraph(),";");
+ else if (matchx(";") ) return _code.ZERO; // Empty statement
+ // Break ambiguity around leading function types and starting a block
+ else if (peek('{') && !isTypeFun() ) {
+ match("{");
+ return require(parseBlock(new Kind.Block()),"}");
+ }
+ // Declaration or normal assignment/expression
+ else return parseDeclarationStatement();
+ }
+
+ private boolean inStructDeclaration() {
+ return _scope.klast() instanceof Kind.Func fun && FunNode.isInstance(fun._name);
+ }
+
+ // Constructor declaration: new TypeName = { args -> body };
+ private Node parseConstructorDeclaration() {
+ TypeStruct self = _scope.constructorSelf();
+ assert self != null;
+ String name = requireId();
+ String expect = initFieldName(self._name);
+ if( name != expect )
+ throw error("Constructor name '" + name + "' must match type name '" + expect + "'");
+ require("=");
+ Node ctor = constructorFunc(name);
+ TypeFunPtr sig = (TypeFunPtr)ctor._type;
+ String clzName = addClzPrefix(self._name);
+ TypeStruct clz = (TypeStruct)TYPES.get(clzName);
+ Field old = clz==null ? null : clz.field("<ctor>");
+ if( old != null )
+ throw error("Redefining constructor '" + name + "'");
+ Field fld = Field.make("<ctor>",sig,_code.alias(_ref._cname),true);
+ TYPES.put(clzName,clz==null ? TypeStruct.make(clzName,true,fld) : clz.add(fld));
+ return require(ctor,";");
+ }
+
+ /**
+ * Parses a while statement
+ *
+ * <pre>
+ * while ( expression ) statement
+ * </pre>
+ * @return a {@link Node}, never {@code null}
+ */
+ private Node parseWhile() {
+ 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;
+ // }
+ // }
+ require("(");
+ _scope.push(new Kind.Block()); // Scope for the index variables
+ if( !match(";") ) // Can be empty init "for(;test;next) body"
+ parseDeclarationStatement(); // Non-empty init
+ Node rez = parseLooping(true);
+ _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()}.
+
+ ctrl(new LoopNode(loc(),null,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 = _scope.keep();
+ // Clone the head Scope to create a new Scope for the body.
+ // Create phis eagerly as part of cloning
+ _xScopes.push(_scope = _scope.dup(true)); // The true argument triggers creating phis
+
+ // Parse predicate
+ var pred = peek(';') ? con(1) : parseAsgn();
+ require( doFor ? ";" : ")" );
+
+ // IfNode takes current control and predicate
+ Node ifNode = new IfNode(ctrl(), pred.keep()).peephole();
+ // Setup projection nodes
+ Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
+ Node ifF = 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 = pos();
+ skipAsgn();
+ nextEnd = pos();
+ 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.
+ ctrl(ifF);
+ _xScopes.push(_breakScope = _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
+ ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection
+ _scope.addGuards(ifT,pred.unkeep(),false); // Up-cast predicate
+ parseStatement().isKill(); // Parse loop body
+ _scope.removeGuards(ifT);
+
+ // Merge the loop bottom into other continue statements
+ if (_continueScope != null) {
+ _continueScope = jumpTo(_continueScope);
+ _scope.kill();
+ _scope = _continueScope;
+ }
+
+ // Now append the next code onto the body code
+ if( doFor ) {
+ int old = pos(nextPos);
+ if( !peek(')') )
+ parseAsgn();
+ if( pos() != nextEnd )
+ throw errorSyntax( "Unexpected code after expression" );
+ 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.endLoop(_scope, exit);
+ head.unkeep().kill();
+
+ _xScopes.pop(); // Cleanup
+ _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( !_xScopes.isEmpty() ) {
+ _xScopes.pop();
+ _xScopes.push( exit );
+ }
+ _scope = exit;
+ return _code.ZERO;
+ }
+
+ private ScopeNode jumpTo(ScopeNode toScope) {
+ ScopeNode cur = _scope.dup();
+ ctrl(_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,loc()).peephole());
+ return toScope;
+ }
+
+ private void checkLoopActive() { if (_breakScope == null) throw error("No active loop for a break or continue"); }
+
+ private Node parseContinue() { checkLoopActive(); _continueScope = require(jumpTo( _continueScope ),";"); return _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 = require(jumpTo(_breakScope ),";");
+ _breakScope.addGuards(_breakScope.ctrl(), null, false);
+ return _code.ZERO;
+ }
+
+ // Look for an unbalanced `)`, skipping balanced
+ private void skipAsgn() {
+ int paren=0;
+ while( true )
+ // Next X char handles skipping complex comments
+ switch( _lexer.nextXChar() ) {
+ case Character.MAX_VALUE:
+ throw TODO();
+ case ')':
+ if( --paren<0 ) {
+ posT( pos() - 1 );
+ return; // Leave the `)` behind
+ }
+ break;
+ case '(': paren ++; break;
+ default: break;
+ }
+ }
+
+
+ /**
+ * Parses a statement
+ *
+ * <pre>
+ * if ( expression ) statement [else statement]
+ * </pre>
+ * @return a {@link Node}, never {@code null}
+ */
+ private Node parseIf() {
+ // Parse predicate
+ require("(");
+ var pred = require(parseAsgn(), ")");
+ return parseTrinary(pred,"else");
+ }
+
+ // Parse a conditional expression, merging results.
+ private Node parseTrinary( Node pred, String fside ) {
+ // IfNode takes current control and predicate
+ pred.keep();
+ Node ifNode = new IfNode(ctrl(), pred).peephole();
+ // Setup projection nodes
+ Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
+ Node 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 = _scope.dup(); // Duplicate current scope
+ _xScopes.push(fScope); // For graph visualization we need all scopes
+
+ // Parse the true side
+ ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection
+ _scope.addGuards(ifT,pred,false); // Up-cast predicate
+ // Parse true-side flavor
+ Node lhs = switch( fside ) {
+ case "else" -> parseStatement(); // if( pred ) stmts;
+ case ":" -> parseAsgn(); // pred ? asgn;
+ case "&&" -> parseLogical(); // pred && expr
+ case "||" -> pred; // pred || expr_is_ignored
+ default -> throw TODO();
+ };
+ lhs.keep();
+ _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(_scope);
+
+ ScopeNode tScope = _scope;
+
+ // Parse the false side
+ _scope = fScope; // Restore scope, then parse else block if any
+ ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection
+ // 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;`
+ _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=match(fside)) ? parseStatement() : con(lhs._type.makeZero());
+ case ":" -> (doRHS=match(fside)) ? parseAsgn() : con(lhs._type.makeZero());
+ case "&&" -> rhs = pred;
+ case "||" -> rhs = parseLogical();
+ default -> throw TODO();
+ };
+ rhs.keep();
+ _scope.removeGuards(ifF);
+ if( doRHS )
+ fScope = _scope;
+ pred.unkeep();
+
+ // Check the trinary widening int/flt
+ if( !fside.equals("else") ) {
+ rhs = widenInt( rhs.unkeep(), lhs._type ).keep();
+ lhs = widenInt( lhs.unkeep(), rhs._type ).keep();
+ }
+
+ _scope = tScope;
+ _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 = ctrl(tScope.mergeScopes(fScope,loc()));
+ Node ret = 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;
+ r.peephole();
+ return ret;
+ }
+
+ /**
+ * 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 = require(parseAsgn(), ";");
+ return addReturn(expr);
+ }
+ private Node addReturn(Node expr) {
+ expr.keep();
+ // Need default memory, since it can be lazy, need to force
+ // a non-lazy Phi
+ 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 = _scope.enclosingFuncOrDecl();
+ Kind.Func k = (Kind.Func)_scope._kinds.at(lexN);
+ if( FunNode.isInit(k._name) ) {
+ int nestedLexSize = lexN+1 < _scope.depth() ? _scope._kinds.at(lexN+1)._lexSize : _scope.nIns();
+ for( int i=k._lexSize; i<nestedLexSize; i++ )
+ _scope.update(_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(_scope.var(rlen));
+ _returnScope.addDef(con(_scope.var(rlen++).type().makeZero()));
+ }
+ _returnScope._vars.add(vexpr);
+ _returnScope.addDef(oldX);
+ }
+ }
+
+ // No prior merge point? Just clone and hang on to it
+ if( _returnScope == null ) {
+ _returnScope = _scope.dup();
+ while( lexN+1 < _returnScope.depth() )
+ _returnScope._pop(); // Pop a nested block scope until we hit the function scope
+ _returnScope.define("$expr", expr._type.glb(false), 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 = ctrl(new RegionNode(null, null,_returnScope.ctrl(), _scope.ctrl()).init().keep());
+ _returnScope._merge(_scope, r, rlen);
+ Node oldExpr = _returnScope.in(rlen);
+ _returnScope.setDef(rlen,new PhiNode("$expr", r, oldExpr, expr).peephole());
+ _code.add(r);
+ _returnScope.ctrl(r.unkeep());
+ }
+ ctrl(_code.XCTRL); // Kill control
+ return expr.unkeep();
+ }
+
+ /**
+ * Dumps out the node graph
+ * @return {@code null}
+ */
+ Node showGraph() {
+ System.out.println(new GraphVisualizer().generateDotOutput(_ref,_scope,_xScopes));
+ return null;
+ }
+
+ /** Parse: [name '='] expr
+ */
+ private Node parseAsgn() {
+ int old = pos();
+ String name = _lexer.matchId();
+ // Just a plain expression, no assignment.
+ // Distinguish `var==expr` from `var=expr`
+ if( name==null || KEYWORDS.contains(name) || !matchOpx('=','=') )
+ { pos(old); return parseExpression(); }
+
+ // Find variable to update
+ Var def = _scope.lookup(name);
+ if( def==null )
+ def = defineOpenConstructorField(name);
+ if( def==null )
+ throw error("Undefined name '" + name + "'");
+
+ // TOP fields are for late-initialized fields; these have never
+ // been written to, and this must be the final write. Other writes
+ // outside the constructor need to check the final bit.
+ if( _scope.in(def._idx)._type!=Type.TOP && def._final &&
+ // Inside an allocation, final assign is OK, outside nope.
+ // The alloc() call added the allocation scope
+ !(_scope.inConstructor() && def._idx >= _scope.klast()._lexSize) &&
+ !inExplicitConstructor() )
+ throw error("Cannot reassign final '"+name+"'");
+
+ // Parse assignment expression
+ Node expr = parseAsgn();
+
+ // Lift expression, based on type
+ Type decl = def.type(); // Declared type
+ Node lift = convertExpr(expr.keep(), decl).keep();
+ // Update
+ _scope.update(name,lift);
+
+ // Check for storing into an instance field instead of a local var
+ int kx = _scope.kindx(def); // Declaration scope
+ int fx = _scope.enclosingFunction(); // Enclosing function scope
+ Kind kk = _scope._kinds.at(kx);
+ Kind fk = _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 && !inExplicitConstructor() ) {
+ // Named field offset on an unknown struct type
+ TypeMemPtr self = (TypeMemPtr)_scope.var(fk._lexSize).type();
+ TypeStruct sobj = (TypeStruct)TYPES.get(self._obj._name);
+ Field fld = sobj.field(name);
+ int alias = fld._alias;
+ Node ptr = _scope.in(method._lexSize);
+ Node off = fldoff(ptr,name);
+ Node prior = mem().keep();
+ Node st = new StoreNode(loc(), name, alias, decl, ctrl(), prior, ptr, off, lift, inExplicitConstructor()).peephole().keep();
+ storeMem(st,alias,prior);
+ st.unkeep();
+ prior.unkeep();
+ }
+ lift.unkill();
+
+
+ // Return un-lifted expr
+ return expr.unkeep();
+ }
+
+ // Widen ints to floats; narrow wide int types.
+ // Early error if types do not match variable.
+ private Node convertExpr( Node expr, Type t ) {
+ // 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 peep(new AddNode(peep(new PtrToIntNode(expr)),off(tmp._obj,"[]")));
+ // Null check has to eventually pass
+ if( t == Type.NIL && expr._type instanceof TypeMemPtr tmp && tmp.notNull() )
+ return peep(new CheckCastNode(t,null,expr));
+ // Widen ints to floats, narrow wide types
+ return peep(new ConvertNode(t,expr));
+ }
+
+ private Node widenInt( Node expr, Type t ) {
+ return (expr._type instanceof TypeInteger || expr._type==Type.NIL) && t instanceof TypeFloat
+ ? peep(new ToFloatNode(expr)) : expr;
+ }
+
+ /**
+ * Parse declaration or expression statement
+ * declStmt = type var['=' exprAsgn][, var['=' exprAsgn]]* ';' | exprAsgn ';'
+ * <p>
+ * exprAsgn = var '=' exprAsgn | expr
+ */
+ private Node parseDeclarationStatement() {
+ int old = pos();
+ Type t = type();
+ if( peek('.') ) // Ambiguity static vars: "type.var", parse as expression
+ { pos(old); t=null; }
+ if( t == null )
+ return require(parseAsgn(),";");
+
+ // now parse var['=' asgnexpr] in a loop
+ Node n = parseDeclaration(t);
+ while( match(",") )
+ n = parseDeclaration(t);
+ return require(n,";");
+ }
+
+ /** 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 hasBang = match("!");
+ Lexer loc = loc();
+ String name = requireId();
+ // Optional initializing expression follows
+ boolean xfinal = false;
+ boolean fld_final = false; // Field is final, but not deeply final
+ boolean uninit = false; // Required non-null constructor field
+ Node expr;
+ if( match("=") ) {
+ if( isExternDecl() ) {
+ expr = externDecl(name,t);
+ t = expr._type; // Upgrade declared type to the exact extern decl type
+ } else if( isConstructorDecl(name) ) {
+ expr = constructorFunc(name);
+ } else {
+ expr = parseAsgn();
+ }
+ // TOP means val and val is always final
+ xfinal = (t==Type.TOP) ||
+ expr instanceof ExternNode ||
+ // BOTTOM is var and var is always not-final
+ (t!=Type.BOTTOM &&
+ // no Bang AND
+ !hasBang &&
+ // not-null (expecting null to be set to not-null)
+ expr._type != Type.NIL &&
+ // Pointers are final by default; int/flt are not-final by default.
+ (t instanceof TypeNil));
+
+ // var/val, then type comes from expression
+ if( inferType ) {
+ if( expr._type==Type.NIL )
+ throw error("a not-null/non-zero expression");
+ t = expr._type;
+ if( !xfinal ) t = t.glb(false); // Widen if not final
+ }
+
+ // Final is deep on ptrs
+ if( xfinal && t instanceof TypeMemPtr tmp ) {
+ t = tmp.makeRO();
+ expr = peep(new ReadOnlyNode(expr));
+ }
+
+ // expr is a constant function
+ if( t instanceof TypeFunPtr && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
+ FunNode fun = _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() )) && !_scope.inConstructor() )
+ throw 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() ? con(Type.NIL) : con(t.meet(Type.NIL));
+ case TypeInteger ti -> _code.ZERO;
+ case TypeFloat tf -> con(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 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( t instanceof TypeNil tn && !tn.nullable() ) {
+ uninit = true;
+ if( !hasBang ) 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 : convertExpr(expr, t);
+
+ // Define a new name
+ if( !_scope.define(name,t,xfinal || fld_final,lift, loc) )
+ throw error("Redefining name '" + name + "'", loc);
+ _scope.lookup(name)._uninit = uninit;
+ return lift;
+ }
+
+
+
+ /**
+ * Parse a struct declaration, and return the following statement.
+ * Structs cannot be redefined, but can be nested.
+ *
+ * @return zero
+ */
+ private Node parseStruct( ) {
+ // keyword "struct" already parsed, so expect the struct name next.
+ String typeName = requireId();
+ require("{");
+
+ // create type class:NESTED.typeName; no fields.
+
+ String old = _nestedType;
+ String fullName = _scope.depth()==1 && _nestedType.endsWith(typeName) ? old : (old+"."+typeName).intern();
+ _nestedType = fullName; // Recursive structs start with this as their basename
+
+ ReturnNode ret = parseStruct( false, fullName );
+
+ _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 = addClzPrefix(fullName);
+ TypeFunPtr sig = ret.fun().sig();
+ TypeStruct ts = (TypeStruct)TYPES.get(clzName);
+ String initName = initFieldName(fullName);
+ Field oldFld = ts==null ? null : ts.field(initName);
+ Field fld = oldFld==null
+ ? Field.make(initName,sig,_code.alias(_ref._cname),true)
+ : oldFld.makeFrom(sig);
+ Field ctorFld = ts==null ? null : ts.field("<ctor>");
+ ts = ctorFld==null
+ ? TypeStruct.make(clzName,false,fld)
+ : TypeStruct.make(clzName,false,fld,ctorFld);
+ 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.
+ _scope.define(typeName, sig, true, new FunPtrNode(sig,_code._start,ret).peephole(), loc());
+
+ require("}");
+ return require(_code.ZERO,";");
+ }
+
+ // Parse a struct declaration (not an allocation); file-level is a class-init, and scope structs are normal
+ private ReturnNode parseStruct( boolean isClz, String typeName ) {
+ // Record & restore global state set during parsing the <init> code
+ Node oldCtrl= _scope.ctrl().keep();
+ Node oldMem = _scope.mem ().keep();
+
+ // Make the future clazz/instance struct.
+ TypeStruct tself = (TypeStruct)TYPES.get(typeName);
+ if( tself==null )
+ TYPES.put(typeName, tself = TypeStruct.make(typeName,true));
+ else {
+ assert tself._open;
+ TYPES.put(typeName,tself = tself.defined());
+ }
+
+ // 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 = _parseFunctionBody(fname,sig,loc(),ids);
+ // Unwind global state.
+ ctrl(oldCtrl.unkeep());
+ 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 startsClzPrefix( clzBare );
+ clzBare = clzBare.substring(clzPrefix.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, _code.fidx(clzBare,order));
+ }
+
+ // A class type carries the constructor function as a required final field.
+ private TypeStruct classStruct( String typeName, TypeFunPtr init, boolean open ) {
+ return TypeStruct.make(addClzPrefix(typeName),open,Field.make(initFieldName(typeName),init,_code.alias(_ref._cname),true));
+ }
+
+ private static String initFieldName( String typeName ) {
+ if( startsClzPrefix(typeName) )
+ typeName = typeName.substring(clzPrefix.length());
+ int idx = typeName.lastIndexOf('.');
+ return (idx == -1 ? typeName : typeName.substring(idx+1)).intern();
+ }
+
+ private TypeStruct latestStruct( TypeStruct ts ) {
+ Type t = TYPES.get(ts._name);
+ return t instanceof TypeStruct ts2 ? ts2 : ts;
+ }
+
+ private 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,_code.alias(_ref._cname),xfinal);
+ TYPES.put(ts._name,ts = ts.add(fld));
+ if( _ctorOpenStruct != null && _ctorOpenStruct._name == ts._name )
+ _ctorOpenStruct = ts;
+ return fld;
+ }
+
+ private Var defineOpenConstructorField( String name ) {
+ if( _ctorOpenStruct==null ) return null;
+ Field fld = fieldOrOpen(_ctorOpenStruct,name,true);
+ if( fld == null ) return null;
+ _scope.define(name,fld._t,fld._final,con(Type.TOP),loc());
+ return _scope.lookup(name);
+ }
+
+
+ // Parse and return a type or null. Valid types always are followed by an
+ // 'id' which the caller must parse. This lets us distinguish forward ref
+ // types (which ARE valid here) from local vars in an (optional) forward
+ // ref type position.
+
+ // t = int|i8|i16|i32|i64|u8|u16|u32|u64|byte|bool | flt|f32|f64 | val | var | struct[?]
+
+ // 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"
+
+ private Type type() {
+ // Only type with a leading `{` is a function pointer...
+ if( peek('{') ) return typeFunPtr();
+
+ // Otherwise you get a type name
+ int old1 = pos();
+ String tname = _lexer.matchId();
+ if( tname==null ) return null;
+
+ // Convert the type name to a type.
+ Type t0 = TYPES.get(tname), t1 = t0;
+ // No new types as keywords
+ if( KEYWORDS.contains(tname) ) {
+ if( t0 == null ) return posT(old1);
+ // Something like "int" or "f64" or "var".
+ // Can be "int[]" so still need to check array-ness
+ } else {
+ if( t0 == Type.BOTTOM || t0 == Type.TOP ) return t0; // var/val type inference
+
+ if( t0 != null ) {
+ while( true ) {
+ int old2 = pos();
+ if( !match(".") ) break;
+ String sname = matchId();
+ if( sname==null ) { pos(old2); break; }
+ sname = tname+"."+sname;
+ Type t = TYPES.get(sname);
+ if( t==null ) { pos(old2); break; }
+ tname = sname;
+ t0 = t;
+ }
+ } else {
+ // Type A.B.C starts with just A. Search our nested type path,
+ // e.g. path "X.Y.Z", in order:
+
+ // If A==X its now a local type.
+ // If another dot, repeat for B==Y then C==Z
+ // Else if AX.B exists, done
+ // Else AX.B is a local forward ref.
+ //
+ // Else its a short cut form, one of:
+ // X.Y.Z.A
+ // X.Y.A
+ // X.A
+ // Else this all failed, assume a forward ref with A as an external symbol:
+ // A
+
+ String root = _nestedType;
+ int ridx = root.indexOf('.');
+ if( ridx != -1 ) root = root.substring(0,ridx);
+ if( tname.equals(root) ) {
+ boolean localForward = false;
+ while( true ) {
+ int old2 = pos();
+ if( !match(".") ) break;
+ String sname = matchId();
+ if( sname==null ) { pos(old2); break; }
+ sname = tname+"."+sname;
+ Type t = TYPES.get(sname);
+ if( t==null && !(_nestedType.equals(tname) || _nestedType.startsWith(tname+".")) && !localForward ) {
+ pos(old2);
+ break;
+ }
+ tname = sname;
+ t0 = t;
+ localForward |= t==null;
+ }
+ } else {
+ String nest = _nestedType, fullq = tname;
+ while( true ) {
+ fullq = nest+"."+tname; // Full qualified name
+ t0 = TYPES.get(fullq);
+ if( t0 != null )
+ { tname = fullq; break; } // Search succeeded
+ // Subtract a layer from nested typenames
+ int idx = nest.lastIndexOf('.');
+ if( idx== -1 ) break; // Search failed; leave as an external symbol
+ nest = nest.substring(0,idx);
+ }
+ if( t0==null ) {
+ CompUnit cu = ParseAll.findCompUnit(_code,_ref,tname);
+ if( cu != null ) {
+ tname = cu._cname;
+ t0 = TYPES.get(tname);
+ }
+ while( true ) {
+ int old2 = pos();
+ if( !match(".") ) break;
+ String sname = matchId();
+ if( sname==null ) { pos(old2); break; }
+ tname = tname+"."+sname;
+ t0 = TYPES.get(tname);
+ }
+ } else {
+ while( true ) {
+ int old2 = pos();
+ if( !match(".") ) break;
+ String sname = matchId();
+ if( sname==null ) { pos(old2); break; }
+ sname = tname+"."+sname;
+ Type t = TYPES.get(sname);
+ if( t==null ) { pos(old2); break; }
+ tname = sname;
+ t0 = t;
+ }
+ }
+ }
+ }
+
+ tname = tname.intern();
+ // Still no type found? Assume forward reference
+ t1 = t0 == null
+ ? TypeStruct.forward(tname) // Null: assume a forward ref type
+ : t0;
+ }
+
+ // Structs are always actually references
+ if( t1 instanceof TypeStruct ts1 )
+ t1 = TypeMemPtr.make(ts1);
+
+ // Nest arrays and '?' as needed
+ Type t2 = t1;
+ while( true ) {
+ if( match("?") ) {
+ if( t2 instanceof TypeMemPtr tmp ) {
+ if( tmp.nullable() ) throw error("Type "+t2+" already allows null");
+ t2 = tmp.makeNullable();
+ } else
+ throw error("Type "+t0+" cannot be null");
+ } else if( match("[~]") ) {
+ t2 = TypeMemPtr.make(typeAry(t2,true));
+ } else if( 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 = pos();
+ match("!");
+ String id = _lexer.matchId();
+ if( !(peek(',') || peek(';') || peek('=') || match("->")) || id==null )
+ return posT(old1); // Reset lexer to reparse
+ 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;
+ }
+
+ private TypeMemPtr forwardRefType( String tname, TypeStruct tself ) {
+ TYPES.put(tname,tself);
+ String clzName = addClzPrefix( tname );
+ TYPES.put(clzName, classStruct(tname,structInitSig(false,tself),true));
+ return TypeMemPtr.make(tself);
+ }
+
+ private TypeMemPtr forwardRefType( String tname ) {
+ tname = fullTypeName(tname);
+ return forwardRefType(tname,TypeStruct.forward(tname));
+ }
+
+ private String fullTypeName( String tname ) {
+ String nest = _nestedType, fullq;
+ while( true ) {
+ fullq = nest+"."+tname;
+ if( 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.
+ private TypeStruct typeAry( Type t, boolean efinal ) {
+ if( t instanceof TypeMemPtr tmp && tmp.notNull() )
+ throw error("Arrays of reference types must always be nullable");
+ String tname = ("[]"+t.str()).intern();
+ TypeStruct ta = (TypeStruct)TYPES.get(tname);
+ if( ta==null ) {
+ // Primitive arrays use the null compunit
+ String rcname = TYPES.get(t) instanceof TypeStruct ? _ref._cname : ("_."+t).intern();
+ ta = TypeStruct.makeAry(tname,TypeInteger.U32,_code.alias(rcname),t,_code.alias(rcname), false );
+ TYPES.put(tname,ta);
+ }
+ if( !efinal ) return ta;
+ // Already have the aliases, just efinal is wrong
+ return TypeStruct.makeAry(tname,TypeInteger.U32,ta._fields[0]._alias,t,ta._fields[1]._alias,true );
+ }
+
+ private Type implicitSelfType() {
+ TypeStruct self = _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 constructorFunc().
+ return self == null ? TypePtr.PTR : TypeMemPtr.make(self);
+ }
+
+ // A function type is `{ type... -> type }` or `{ type }`.
+ private Type typeFunPtr() {
+ int old = pos(); // Record lexer position
+ match("{"); // Skip already-peeked '{'
+ Type t0 = type(); // Either return or first arg
+ if( t0==null ) return 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( match("}") ) // No-arg function { -> type }
+ return TypeFunPtr.make(match("?"),ts.asAry(),t0);
+ ts.push(t0); // First argument
+ while( true ) {
+ if( match("->") ) { // End of arguments, parse return
+ Type ret = type();
+ if( ret==null || !match("}") )
+ return posT(old); // Not a function
+ return TypeFunPtr.make(match("?"),ts.asAry(),ret);
+ }
+ Type t1 = type();
+ if( t1==null ) return posT(old); // Not a function
+ ts.push(t1);
+ }
+ }
+
+ // True if a TypeFunPtr, without advancing parser
+ private boolean isTypeFun() {
+ int old = pos();
+ if( typeFunPtr()==null ) return false;
+ pos(old);
+ return true;
+ }
+
+ /**
+ * Parse an expression of the form:
+ *
+ * <pre>
+ * expr : bitwise [? expr [: expr]]
+ * </pre>
+ * @return an expression {@link Node}, never {@code null}
+ */
+ private Node parseExpression() {
+ Node expr = parseLogical();
+ return match("?") ? parseTrinary(expr,":") : expr;
+ }
+
+ /**
+ * Parse an bitwise expression
+ *
+ * <pre>
+ * bitwise : compareExpr (('&' | '|' | '^') compareExpr)*
+ * </pre>
+ * @return a bitwise expression {@link Node}, never {@code null}
+ */
+
+ private Node parseLogical() {
+ Node lhs = parseBitwise();
+ while (true) {
+ if( false ) ;
+ else if( match("&&") ) lhs = parseTrinary(lhs, "&&");
+ else if( match("||") ) lhs = parseTrinary(lhs, "||");
+ else break;
+ }
+ return lhs;
+ }
+
+ private Node parseBitwise() {
+ Node lhs = parseEquality();
+ while( true ) {
+ if( false ) ;
+ else if( matchOp('&') ) lhs = new AndNode(loc(),lhs,null);
+ else if( matchOp('|') ) lhs = new OrNode(loc(),lhs,null);
+ else if( match ("^") ) lhs = new XorNode(loc(),lhs,null);
+ else break;
+ lhs.setDef(2,parseEquality());
+ lhs = peep(lhs);
+ }
+ return lhs;
+ }
+
+
+
+ /**
+ * Parse an eq/ne expression
+ */
+ private Node parseEquality() {
+ Node lhs = parseComparison();
+ while( true ) {
+ boolean eq = false;
+ if( match("==") ) eq = true;
+ else if( !match("!=") ) break;
+ lhs.keep();
+ Node rhs = parseComparison();
+ lhs = peep(new BoolNode.EQ(lhs.unkeep(),rhs));
+ if( !eq ) lhs = 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;
+
+ // Compare
+ Node rhs = parseShift().keep();
+ Node cmp = makeCompBool(dir,lhs,rhs); // Convert to a bool
+
+ // Stacked compares?
+ int dir0 = parseCompDir();
+ if( dir0 == 0 ) {
+ rhs.unkeep();
+ if( rhs != cmp ) rhs.isKill();
+ return cmp;
+ }
+
+ // rhs is keeped() and becomes lhs
+ // cmp is NOT keeped() and is the last test
+ Node ifNode = new IfNode(ctrl(), cmp).peephole();
+ Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole();
+ Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole();
+ // False side does nothing but capture memory & side-effects
+ ScopeNode fail = _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 error("Mixing relational directions in a chained relational test");
+ // True side parses next arm of test
+ ctrl(ifT);
+ lhs = rhs; // lhs is keeped() and is old RHS
+ rhs = parseShift().keep();
+ cmp = makeCompBool(dir,lhs.unkeep(),rhs); // Convert to a bool
+ ifNode = new IfNode(ctrl(), cmp).peephole();
+ ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole();
+ ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole();
+ // Merge result into the fail case
+ ctrl(ifF);
+ fail.mergeScopes(_scope.dup(), loc()).peephole();
+ // Check next stacked compare
+ dir0 = parseCompDir();
+ }
+ rhs.unkill();
+ ctrl(ifT);
+ RegionNode r = fail.mergeScopes(_scope, loc()).init();
+ _scope = fail;
+ return new PhiNode("", r,_code.ZERO,con(1)).peephole();
+ }
+
+ private int parseCompDir() {
+ if( match("<=") ) return -1; // Negative for "equals-or-less"
+ if( match(">=") ) return -2; // 1 for <, 2 for >
+ if( match("<" ) ) return 1; // Positive for just "less"
+ if( match(">" ) ) return 2;
+ return 0;
+ }
+
+ private Node makeCompBool( int dir0, Node lhs, Node rhs ) {
+ // Convert to a bool
+ if( Math.abs(dir0)==2 ) // Swap direction
+ { Node tmp = lhs; lhs = rhs; rhs = tmp; }
+ lhs = peep(dir0 < 0 ? new BoolNode.LE(lhs,rhs) : new BoolNode.LT(lhs,rhs));
+ return peep(lhs);
+ }
+
+ /**
+ * 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( false ) ;
+ else if( match("<<") ) lhs = new ShlNode(loc(),lhs,null);
+ else if( match(">>>")) lhs = new ShrNode(loc(),lhs,null);
+ else if( match(">>") ) lhs = new SarNode(loc(),lhs,null);
+ else break;
+ lhs.setDef(2,parseAddition());
+ ParseException err;
+ if( (err=lhs.err()) != null )
+ throw err;
+ lhs = 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( false ) ;
+ else if( match("+") ) lhs = new AddNode(lhs,null);
+ else if( match("-") ) lhs = new SubNode(lhs,null);
+ else break;
+ lhs.setDef(2,parseMultiplication());
+ lhs = 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( false ) ;
+ else if( match("*") ) lhs = new MulNode(lhs,null);
+ else if( match("/") ) lhs = new DivNode(lhs,null);
+ else break;
+ lhs.setDef(2,parseUnary());
+ lhs = 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 = pos();
+ if( match("--") || match("++") ) {
+ int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Inc vs Dec
+ String name = _lexer.matchId();
+ if( name!=null ) {
+ Var n = _scope.lookup(name);
+ if( n != null && !(n.type() instanceof TypeMemPtr) ) {
+ if( n._final )
+ throw error("Cannot reassign final '"+n._name+"'");
+ Node expr = zsMask(peep(new AddNode(_scope.in(n),con(delta))),n.type());
+ _scope.update(n,expr);
+ return expr;
+ }
+ }
+ // Reset, try again
+ pos(old);
+ }
+ if (match("-")) return peep(new MinusNode(parseUnary()));
+ if (match("!")) return 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() {
+ if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral();
+ if( _lexer.peek('"') ) return newString(parseString());
+ if( matchx("true" ) ) return con(1);
+ if( matchx("false") ) return _code.ZERO;
+ if( matchx("null" ) ) return con(Type.NIL);
+ if( match ("'" ) ) return parseChar();
+ if( match ("(" ) ) return parsePostfix(require(parseAsgn(), ")"));
+ if( matchx("new" ) ) return parsePostfix(alloc());
+ if( match ("{" ) ) return parsePostfix(require(func(),"}"));
+
+ int pos = pos();
+ // Expect an identifier now
+ String id = _lexer.matchId();
+ if( id == null || KEYWORDS.contains(id) )
+ throw errorSyntax("an identifier or expression");
+ int pos2 = pos();
+ // Attempt a local var lookup first
+ Var var = _scope.lookup(id);
+ if( var==null ) {
+ // Not a local var, try again as a type name
+ pos(pos);
+ Type t = type();
+ if( t!=null ) {
+ // Static <clinit> typename lookup
+ if( peek('.') ) // type.fld, which is really a field lookup in the class object
+ return parsePostfix(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.
+ pos(pos);
+ return null;
+ }
+ }
+
+ // Insert FREF outside enclosing Kind.Func scope.
+ pos(pos2);
+ t = FRefNode.FREF_TYPE;
+ // Check if this is a class.
+ CompUnit cu = ParseAll.findCompUnit(_code,_ref,id);
+ if( cu!=null ) {
+ // Must be a final class-type
+ String clzName = addClzPrefix(cu._cname);
+ TypeStruct clz = (TypeStruct)TYPES.get(clzName);
+ if( clz == null )
+ TYPES.put(clzName,clz = TypeStruct.make(clzName,true));
+ t = TypeMemPtr.make((byte)2,clz,true);
+ }
+ // Define the FRef
+ var = _scope.defineFRef(id,t,cu!=null,loc());
+ }
+
+ // Load local value
+ Node rvalue = _scope.in(var);
+ if( var._fref )
+ return parsePostfix(rvalue);
+ // Required constructor fields begin as their declared pointer type
+ // widened to include null. Inside the explicit constructor, any read
+ // before all paths replace that value observes a partially initialized
+ // object. Ordinary methods run only after construction is complete;
+ // their enclosing declaration scope still carries the same Var marker.
+ if( inExplicitConstructor() && var._uninit && rvalue._type instanceof TypeNil tn && tn.nullable() )
+ throw error("Field '"+id+"' might not be initialized",var._loc);
+
+ // Check for an instance field load
+ int kx = _scope.kindx(var); // Declaration scope
+ int fx = _scope.enclosingFunction(); // Enclosing function scope
+ Kind kk = _scope._kinds.at(kx);
+ Kind fk = _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 )
+ return parsePostfixName(_scope.in(method._lexSize),id);
+
+ // 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 && // 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 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 = _scope.var( 2 );
+ // Get the most recent sharpen global
+ TypeStruct clz = (TypeStruct) TYPES.get( ((TypeMemPtr) clzptr.type())._obj._name );
+ TypeMemPtr clztmp = TypeMemPtr.make( (byte) 2, clz, true );
+ return parsePostfixName( con( clztmp ), id );
+ //return parsePostfixName(_scope.in(2),id);
+ }
+ }
+
+ // Check for assign-update, x += e0;
+ char ch = _lexer.matchOperAssign();
+ if( ch==0 ) { // Normal primary, check for postfix updates
+ int selfx = _scope.kindFcn(var)._lexSize;
+ // Might be a method call from inside a method, so no explicit "self".
+ // Pass the in-scope "self"
+ return parsePostfixMethod(rvalue,_scope.in(selfx));
+ }
+ // Assign-update direct into Scope
+ Node op = opAssign(ch,rvalue, var.type() );
+ _scope.update(var,op);
+ return postfix(ch) ? rvalue.unkeep() : op;
+ }
+
+ // 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.
+ private Node opAssign( char ch, Node lhs, Type t ) {
+ // RHS of the update.
+ lhs.keep(); // Alive across parseAsgn
+ Node rhs =
+ (byte)ch == 1 ? con( 1) : // var++
+ (byte)ch == -1 ? con(-1) : // var--
+ parseAsgn(); // var op= rhs
+ if( !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);
+ default -> throw TODO();
+ };
+ // Convert to float ops, or narrow int types; error if not declared type.
+ // Also, if postfix LHS is still keep()
+ return convertExpr(peep(op),t.glb(false));
+ }
+
+
+ /**
+ Parse an allocation
+ */
+ private Node alloc() {
+ Type t = type();
+ if( t==null ) {
+ String tname = _lexer.matchId();
+ if( tname==null || KEYWORDS.contains(tname) )
+ throw error("Expected a type");
+ t = forwardRefType(tname);
+ }
+ // Parse ary[ length_expr ]
+ if( match("[") ) {
+ if( !t.makeZero().isa(t) )
+ throw error("Cannot allocate a non-nullable, since arrays are always zero/null filled");
+ Node len = parseAsgn().keep();
+ require("]");
+ return allocArray(typeAry(t,false),len);
+ } else {
+ if( !(t instanceof TypeMemPtr tmp) )
+ throw error("Cannot allocate a "+t.str());
+ return allocStruct(tmp._obj);
+ }
+ }
+
+ private Node allocStruct(TypeStruct ts) {
+ Ary<Node> ctorArgs = constructorArgs(); // CNC TODO- BAD THREADING SELF-MEM
+ Node size = off(ts, " len");
+
+ // Build a NewNode; takes in ctrl and size.
+ // Produces a ptr and a private mem.
+ NewNode nnn = new NewNode(ts, ctrl(), size ).init();
+ ProjNode self = new ProjNode(nnn,0,ts.str()).init().keep();
+ ProjNode smem = new ProjNode(nnn,1,"#selfMem").init();
+
+ // Find a "class:XXX" struct, with field "XXX" function ptr as the <init>
+ TypeStruct clz = (TypeStruct)TYPES.get(addClzPrefix(ts._name));
+ if( clz==null )
+ throw error("Unknown struct type '" + ts._name + "'");
+ TypeFunPtr init = (TypeFunPtr)clz.field(initFieldName(ts._name))._t;
+
+ // Call construct <init>($ctrl,$mem,NewNode.self,NewNode.#selfMem) and
+ // encourage inlining
+ Node initSelf = self._type.isa(init.arg(0)) ? self : peep(new CheckCastNode(init.arg(0),ctrl(),self));
+ Ary<Node> args = new Ary<>(Node.class){{add(ctrl()); add(mem()); add(initSelf); add(smem); add(ConstantNode.seed(init)); }};
+ Node selfMem = functionCall( args );
+ // The returned value is a merge of private *Memory* and NOT some Scalar
+
+ // Optional user constructor; same as the default constructor; private
+ // memory going in and out.
+ Field ctor = clz.field("<ctor>");
+ if( ctor != null )
+ selfMem = constructorCall((TypeFunPtr)ctor._t,self,selfMem,ctorArgs);
+ else if( !ctorArgs.isEmpty() )
+ throw error("Constructor arguments for '" + ts._name + "' but no constructor is defined");
+ if( match("{") )
+ throw error("Inline constructor blocks are no longer supported; define a constructor in '" + ts._name + "'");
+
+ // Might be TOP if parsing in dead/unreachable code
+ if( selfMem._type == Type.TOP ) {
+ _code.add(selfMem);
+ return self.unkeep();
+ }
+
+ // Escape all new aliases. EscapeNode inputs are the self pointer, the
+ // merged private memory, then all the named public aliases. The
+ // output is all the newly merged public aliases - but not actually
+ // bulk memory.
+ selfMem.keep();
+ for( Field fld : ts._fields ) {
+ Field fld2 = fld._final ? fld : (Field)fld.glb(true);
+ Node prior = mem();
+ Node esc = peep(new EscapeNode(fld2,self,selfMem,prior));
+ mem(mergeAlias(prior,fld._alias,esc));
+ }
+
+ if( selfMem.unkeep().isUnused() ) selfMem.kill();
+ else _code.add(selfMem);
+ return self.unkeep();
+ }
+
+ private Ary<Node> constructorArgs() {
+ Ary<Node> args = new Ary<>(Node.class);
+ if( !match("(") )
+ return args;
+ while( !peek(')') ) {
+ Node arg = parseAsgn();
+ if( arg==null ) break;
+ args.push(arg.keep());
+ if( !match(",") ) break;
+ }
+ require(")");
+ return args;
+ }
+
+ // Constructors are called with two extra arguments: self and selfMem.
+ private Node constructorCall(TypeFunPtr ctor, Node self, Node selfMem, Ary<Node> ctorArgs) {
+ Ary<Node> args = new Ary<>(Node.class);
+ Node ctorSelf = self._type.isa(ctor.arg(0))
+ ? self
+ : peep(new CheckCastNode(ctor.arg(0),ctrl(),self));
+ args.add(ctrl());
+ args.add(mem());
+ args.add(ctorSelf);
+ args.add(selfMem);
+ for( Node arg : ctorArgs )
+ args.add(arg.unkeep());
+ args.add(ConstantNode.seed(ctor));
+
+ CallNode call = (CallNode)new CallNode(loc(), args.asAry()).peephole();
+ CallEndNode cend = (CallEndNode)new CallEndNode(call,TypeRPC.constant(_code.rpc(_ref._cname))).peephole();
+ call.peephole();
+ cend = (CallEndNode)cend.keep().peephole();
+ ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole());
+ mem (new ProjNode(cend,1,ScopeNode.MEM0).peephole());
+ return new ProjNode(cend.unkeep(),2,"#selfMem").peephole();
+ }
+
+ private Node allocArray(TypeStruct ts, Node len) {
+ Field lenFld = ts.field("#" );
+ Field bodyFld = ts.field("[]");
+
+ ConFldOffNode base = off(ts, "[]");
+ int scale = ts.aryScale();
+ Node size = peep(new AddNode(base,peep(new ShlNode(null,len,con(scale)))));
+
+ // Build a NewNode; takes in and puts out all aliases.
+ NewNode nnn = new NewNode(ts, 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 = peep(new StoreNode(null, "#", 1, TypeInteger.U32, null, smem, self, off(ts,"#"), len.unkeep(), true));
+
+ // TODO: Allow add-on "constructor" to init the array elements
+
+ Node prior = mem();
+ Node esc = peep(new EscapeNode(lenFld,self,st,prior));
+ mem(mergeAlias(prior,lenFld._alias,esc));
+ prior = mem();
+ esc = peep(new EscapeNode(bodyFld,self,smem,prior));
+ mem(mergeAlias(prior,bodyFld._alias,esc));
+ smem.unkeep();
+ return self.unkeep();
+ }
+
+ private Node newString(String s) {
+ TypeStruct ts = 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 con(str);
+ }
+
+ /**
+ * 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>
+ */
+ private 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( match("(") ) {
+ expr = parsePostfix(require(functionCall(expr,self),")"));
+ } else
+ self.isKill(); // Not a method call, no need for self
+ return parsePostfix(expr);
+ }
+ private Node parsePostfix(Node expr) {
+ String name;
+ if( match(".") ) name = requireId();
+ else if( match("#") ) name = "#";
+ else if( match("[") ) name = "[]";
+ // Can get here without an explicit receiver: "f()" or
+ // "self.method()(next)(next)(next)". Pass the current function self.
+ else if( match("(") ) return parsePostfix(require(functionCall(expr,defaultSelf()),")"));
+ else return expr; // No postfix
+
+ return parsePostfixName(expr,name);
+ }
+
+ private Node defaultSelf() {
+ return _scope.in(_scope._kinds.at(_scope.enclosingFuncOrDecl())._lexSize);
+ }
+
+ /**
+ * 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>
+ */
+
+ private Node parsePostfixName(Node expr, String name) {
+ // 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)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 = 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 = 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 )
+ fld = fieldOrOpen(ts,name,false);
+ // 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.
+ ? peep(new AddNode(fldoff(expr,"[]"),peep(new ShlNode(null,require(parseAsgn(),"]"),fldoff(expr,"<<")))))
+ // Named field offset on an unknown struct type
+ : fldoff(expr,name)).keep();
+
+ // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment
+ if( matchOpx('=','=') ) {
+ // Field assignment
+ Node val = parseAsgn().keep();
+ // Lift value for store
+ Node lift = fld==null ? val : new ConvertNode(fld._t,val).peephole();
+ // Memory for store, post assignment expression
+ Node mem = mem();
+ // Store to field
+ mem.keep();
+ Node st = new StoreNode(loc(), name, alias, decl, ctrl(), mem, expr.unkeep(), off.unkeep(), lift, false).peephole().keep();
+ 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 = 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 = peep(new LoadNode(loc(),name, alias, ctrl(), mem, expr, off));
+
+ // Check for assign-update, "ptr.fld += expr" or "ary[idx]++"
+ char ch = _lexer.matchOperAssign();
+ if( ch!=0 ) {
+ if( fld!=null && decl==Type.BOTTOM )
+ throw error( "'" + ts._name + "' is not fully initialized, field '" + fld._fname + "' needs to be set in a constructor" );
+ Node op = opAssign(ch,load, decl );
+ mem.keep();
+ Node st = new StoreNode(loc(), name, alias, decl.glb(true), ctrl(), mem, expr.unkeep(), off.unkeep(), op, false).peephole().keep();
+ storeMem(st,alias,mem);
+ st.unkeep();
+ mem.unkeep();
+
+ load = postfix(ch) ? load.unkeep() : op;
+ // And use the original loaded value as the result
+ return load;
+ }
+ off.unkill();
+ // Might be a method call, so pass expr as 'self'
+ return parsePostfixMethod(load,expr.unkeep());
+ }
+
+
+ // zero/sign extend. "i" is limited to either classic unsigned (min==0) or
+ // classic signed (min=minus-power-of-2); max=power-of-2-minus-1.
+ private Node zsMask(Node val, Type t ) {
+ if( !(val._type instanceof TypeInteger tval && t instanceof TypeInteger t0 && !tval.isa(t0)) ) {
+ if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) )
+ return val;
+ // Float rounding
+ return peep(new RoundF32Node(val));
+ }
+ if( t0._min==0 ) // Unsigned
+ return peep(new AndNode(null,val,con(t0._max)));
+ // Signed extension
+ int shift = Long.numberOfLeadingZeros(t0._max)-1;
+ Node shf = con(shift);
+ if( shf._type==TypeInteger.ZERO )
+ return val;
+ return peep(new SarNode(null,peep(new ShlNode(null,val,shf.keep())),shf.unkeep()));
+ }
+
+ /**
+ * Parse a function body; the caller will parse the surrounding "{}"
+ *
+ * <pre>
+ * { [type arg,]* -> expr }
+ * { expr } // The no-argument function
+ * </pre>
+ */
+ private Node func() {
+ Ary<Type> ts = new Ary<>(Type.class);
+ Ary<String> ids = new Ary<>(String.class);
+ Type self = implicitSelfType();
+ ts.push(self);
+ ids.push(self == TypePtr.PTR ? "#self" : "self");
+
+ // Parse other arguments
+ _lexer.skipWhiteSpace();
+ Lexer loc = loc(); // First argument location
+ while( true ) {
+ Type t = type(); // Arg type
+ if( t==null ) break;
+ String id = requireId();
+ ts .push(t ); // Push type/arg pairs
+ ids.push(id);
+ match(",");
+ }
+ require("->");
+ // Make a concrete function type, with a fidx
+ TypeFunPtr tfp = TypeFunPtr.make1((byte)2,true,ts.asAry(),Type.BOTTOM,_code.fidx(_ref._cname));
+ ReturnNode ret = parseFunctionBody(tfp,loc,ids.asAry());
+ return new FunPtrNode(tfp,_code._start,ret).peephole();
+ }
+
+ private Node constructorFunc(String name) {
+ require("{");
+ Ary<Type> ts = new Ary<>(Type.class);
+ Ary<String> ids = new Ary<>(String.class);
+ TypeStruct self = _scope.constructorSelf();
+ assert self != null;
+ ts.push(TypeMemPtr.make(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");
+
+ _lexer.skipWhiteSpace();
+ Lexer loc = loc();
+ while( true ) {
+ Type t = type();
+ if( t==null ) break;
+ String id = requireId();
+ ts .push(t );
+ ids.push(id);
+ match(",");
+ }
+ require("->");
+ TypeFunPtr tfp = TypeFunPtr.make1((byte)2,true,ts.asAry(),TypeMem.makePrivate(self),_code.fidx(_ref._cname));
+ Node[] preParse = _scope.save();
+ ReturnNode ret = _parseFunctionBody((self._name+"."+name).intern(),tfp,loc,ids.asAry());
+ _scope.restore(preParse);
+ require("}");
+ return new FunPtrNode(tfp,_code._start,ret).peephole();
+ }
+
+ /**
+ * Parse function call arguments; caller will parse the surrounding "()"
+ * <pre>
+ * ( arg* )
+ * </pre>
+ */
+ private Node functionCall(Node fcn, Node self) {
+ if( fcn._type == Type.NIL )
+ throw 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());
+ while( !peek(')') ) {
+ Node arg = parseAsgn();
+ if( arg==null ) break;
+ args.push(arg.keep());
+ if( !match(",") ) break;
+ }
+ // Control & memory after parsing args
+ args.set(0,ctrl().keep());
+ args.set(1,mem().keep());
+ args.push(fcn); // Function pointer
+ // Unkeep them all
+ for( Node arg : args )
+ arg.unkeep();
+ return functionCall(args);
+ }
+
+ private Node functionCall( Ary<Node> args ) {
+ // Dead into the call? Skip all the node gen
+ if( ctrl()._type == Type.XCONTROL ) {
+ for( Node arg : args )
+ if( arg.isUnused() )
+ arg.kill();
+ return con(Type.TOP);
+ }
+
+ // Into the call
+ CallNode call = (CallNode)new CallNode(loc(), args.asAry()).peephole();
+ // Post-call setup
+ CallEndNode cend = (CallEndNode)new CallEndNode(call,TypeRPC.constant(_code.rpc(_ref._cname))).peephole();
+ call.peephole(); // Rerun peeps after CallEnd, allows early inlining
+ cend = (CallEndNode)cend.keep().peephole(); // TODO: Might inline
+ // Control from CallEnd
+ ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole());
+ // Memory from CallEnd
+ Node mem = new ProjNode(cend,1,ScopeNode.MEM0).peephole();
+ mem(mem);
+ // Call result
+ return new ProjNode(cend.unkeep(),2,"#2").peephole();
+ }
+
+ // Just after parsing "type foo = " can parse `"C"`
+ private boolean isExternDecl( ) {
+ int old = pos();
+ String s = parseString();
+ if( "C".equals(s) ) return true;
+ 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 = _code.fidx(_ref._cname); // Get a FIDX
+ tfp = tfp.makeFrom(fidx); // Become a constant
+ _code.externFunc(fidx,ex); // Map fidx to extern name
+ t = tfp;
+ }
+ return (ExternNode)(new ExternNode(t,ex).peephole());
+ }
+
+ /**
+ * Parse integer literal
+ *
+ * <pre>
+ * integerLiteral: [1-9][0-9]* | [0]
+ * floatLiteral: [digits].[digits]?[e [digits]]?[f]?
+ * </pre>
+ */
+ private ConstantNode parseLiteral() { return con(_lexer.parseNumber()); }
+ ConstantNode con( long con ) { return _code.con(con); }
+ ConstantNode con( Type t ) { return _code.con(t ); }
+ // 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 ) {
+ 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
+ private String parseString() {
+ if( !peek('"') ) return null;
+ _lexer.inc();
+ int start = pos();
+ while( !_lexer.isEOF() && _lexer.nextChar()!= '"' ) ;
+ if( _lexer.isEOF() )
+ throw error("Unclosed string");
+ return new String(_lexer._input,start,pos()-start-1);
+ }
+
+ // Already parsed "'"
+ private Node parseChar() {
+ return require(con(TypeInteger.constant(_lexer.nextChar())),"'");
+ }
+
+ private boolean noLowerCase(String s) {
+ for( int i=0; i<s.length(); i++ )
+ if( Character.isLowerCase(s.charAt(i)) )
+ return false;
+ return true;
+ }
+
+
+ //////////////////////////////////
+ // Utilities for lexical analysis
+
+ // Return true and skip if "syntax" is next in the stream.
+ private boolean match (String syntax) { return _lexer.match (syntax); }
+ // Match must be "exact", not be followed by more id letters
+ private boolean matchx(String syntax) { return _lexer.matchx(syntax); }
+ private boolean matchOp(char c0 ) { return _lexer.matchOp(c0); }
+ private boolean matchOpx(char c0, char c1) { return _lexer.matchOpx(c0,c1); }
+ // Return true and do NOT skip if 'ch' is next
+ private boolean peek(char ch) { return _lexer.peek(ch); }
+ private boolean peekIsId() { return _lexer.peekIsId(); }
+
+ // ch is +/- 1, means oper++ or oper-- means postfix
+ private static boolean postfix(char ch) {
+ return (byte)ch== 1 || (byte)ch== -1;
+ }
+
+ public int pos() { return _lexer._position; }
+ private int pos(int pos) {
+ int old = _lexer._position;
+ _lexer._position = pos;
+ return old;
+ }
+ private Type posT(int pos) { _lexer._position = pos; return null; }
+ // Source code location
+ Lexer loc() { return new Lexer(_lexer); }
+
+
+ // Require and return an identifier
+ private String requireId() {
+ String id = _lexer.matchId();
+ if (id != null && !KEYWORDS.contains(id) ) return id.intern();
+ throw error("Expected an identifier, found '"+id+"'");
+ }
+
+ private 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
+ private Parser require(String syntax) { require(null, syntax); return this; }
+ private <N> N require(N n, String syntax) {
+ if (match(syntax)) return n;
+ throw errorSyntax(syntax);
+ }
+
+ ////////////////////////////////////
+ // Lexer components
+
+ // 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 static class Lexer {
+
+ // Input buffer; an array of text bytes read from a file or a string
+ private final byte[] _input;
+ // Tracks current position in input buffer
+ private 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 && _input == that._input && _position < that._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;
+ }
+
+ /**
+ * Copy a lexer from a lexer
+ */
+ private Lexer(Lexer l) {
+ _input = l._input;
+ _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 new String(_input, _position, _input.length - _position);
+ }
+
+ // True if at EOF
+ private boolean isEOF() {
+ return _position >= _input.length;
+ }
+
+ // Peek next character, or report EOF
+ private 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
+ */
+ private 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;
+ }
+ }
+
+ // Next non-white-space character, or EOF
+ public char nextXChar() { skipWhiteSpace(); return nextChar(); }
+
+ // 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;
+ }
+
+ private 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
+ private 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 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)
+ public char matchOperAssign() {
+ skipWhiteSpace();
+ 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;
+ }
+ }
+
+ 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); }
+
+ public static class ParseException extends RuntimeException {
+ public final Lexer _loc;
+ // file:line:charoff err
+ //String msg = "src:"+_line_number+":"+(_position-_line_start)+" "+errorMessage;
+ ParseException( String msg, Lexer loc ) { super(msg); _loc = loc; }
+ }
+
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/Var.java b/chapter25/src/main/java/com/seaofnodes/simple/Var.java
new file mode 100644
index 00000000..1906ce15
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/Var.java
@@ -0,0 +1,53 @@
+package com.seaofnodes.simple;
+
+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 Parser.Lexer _loc; // Source location
+
+ public Var(int idx, String name, Type type, boolean xfinal, Parser.Lexer loc ) {
+ this(idx,name,type,xfinal,loc,false);
+ }
+ public Var(int idx, String name, Type type, boolean xfinal, Parser.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, Parser.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/chapter25/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java
new file mode 100644
index 00000000..911e69f6
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java
@@ -0,0 +1,96 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+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() ) {
+ if( n instanceof PhiNode phi && !(phi._type instanceof TypeMem) ) {
+ if( phi instanceof MachNode mach && mach.outregmap()==null )
+ continue;
+ // All 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++ )
+ 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( !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/chapter25/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java
new file mode 100644
index 00000000..689ceceb
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
new file mode 100644
index 00000000..85407ddc
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
@@ -0,0 +1,1050 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.IterPeeps;
+import com.seaofnodes.simple.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.nio.file.Files;
+import java.nio.file.Path;
+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, // Run ideal optimizations
+ TypeCheck, // Last check for bad programs
+ LoopTree, // Build a loop tree; break infinite loops
+ Serialize, // Serialize public IR for future compiles to link
+ 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;
+
+ // ---------------------------
+ // 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 in this compile; one per source/object file
+ 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;
+ throw Utils.TODO();
+ }
+
+ // True when fidx is published through a public field of a public class.
+ // TypeStruct.fidxs() already excludes private (underscore) fields.
+ public boolean publicFIDX(int fidx) {
+ // A public class initializer is itself an external root; unlike
+ // constructors and methods, it is not reached through a class field.
+ FunNode fun = _linker.atX(fidx);
+ if( fun != null && fun.isClz() && fun.isPublic() )
+ return true;
+ // Constructors and methods live on their class objects, including
+ // nested classes which do not have their own CompUnit.
+ for( Type t : Parser.TYPES.values() )
+ if( t instanceof TypeStruct clz &&
+ Parser.startsClzPrefix(clz._name) &&
+ Parser.isPublicClassName(clz._name) &&
+ resolvedClass(clz) &&
+ publicClassFIDX(clz,fidx) )
+ return true;
+ return false;
+ }
+
+ private static boolean publicClassFIDX(TypeStruct clz, int fidx) {
+ boolean hasCtor = clz.field("<ctor>") != null;
+ for( Field fld : clz._fields )
+ if( fld._fname.charAt(0)!='_' && fld._t instanceof TypeFunPtr tfp &&
+ // With a user constructor, class:N.N is the private <init>.
+ !(hasCtor && clz.hiddenInit(fld)) &&
+ XInt.bit(tfp._fidxs,fidx) )
+ return true;
+ return false;
+ }
+
+ 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 gets a class object and constructor signature so
+ // parsing can proceed, but it has no callable 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;
+
+ /**
+ * Freeze the externally visible portion of the program after ParseAll has
+ * loaded every compilation unit and closed/upgraded all types. Later
+ * optimization can remove linker entries and create inline-only fidxs;
+ * neither operation changes this interface.
+ */
+ void freezePublicInterface() {
+ assert _publicFIDXs==null && _publicAliases==null;
+ int[] fidxs = XInt.EMPTY;
+ for( int fidx=1; fidx<_linker._len; fidx++ )
+ if( publicFIDX(fidx) )
+ fidxs = XInt.make(fidxs,fidx);
+ int[] aliases = XInt.EMPTY;
+ for( Type t : Parser.TYPES.values() )
+ if( t instanceof TypeStruct clz &&
+ Parser.startsClzPrefix(clz._name) &&
+ Parser.isPublicClassName(clz._name) &&
+ resolvedClass(clz) ) {
+ boolean hasCtor = clz.field("<ctor>") != null;
+ for( Field fld : clz._fields )
+ if( fld._fname.charAt(0)!='_' && fld._t!=Type.TOP &&
+ !(hasCtor && clz.hiddenInit(fld)) )
+ aliases = XInt.make(aliases,fld._alias);
+ }
+ _publicFIDXs = fidxs;
+ _publicAliases = aliases;
+ }
+
+ 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.Opto.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 ); }
+
+ // 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, arg);
+ }
+ // Test setup; no module nor file; can alter seed & argument; can re-run same CodeGen
+ public CodeGen( String src, long workListSeed ) {
+ this(null,null,null,null, src, workListSeed, TypeInteger.BOT);
+ }
+
+ // Generic CodeGen, including full module setup
+ public CodeGen( String modDir, String buildDir, Ary<String> externPaths,
+ String srcName, String src, long workListSeed, 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;
+ 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<>();
+ _iter = new IterPeeps(workListSeed);
+ // 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.
+
+ // 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); }
+ // Generic driver
+ public CodeGen driver( Phase phase, String cpu, String callingConv, boolean inMemory, boolean emitEntrySymbol, int dump ) {
+ int p1 = phase.ordinal(), p2 = _phase==null ? -1 : _phase.ordinal();
+ if( p2 < p1 && p2 < Phase.Parse .ordinal() ) { parse(); p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.Iter .ordinal() ) { iter(); p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.Opto .ordinal() ) { opto(); p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.TypeCheck .ordinal() ) { typeCheck(); p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.LoopTree .ordinal() ) { loopTree(); p2 = dump(dump); }
+ if( p2 < p1 && p1 >= Phase.Encoding .ordinal() ) { unlinkImports(); p2 = dump(dump); }
+ if( p2 < p1 && p1 >= Phase.Encoding .ordinal() ) { serialize(); p2 = dump(dump); } // Include ideal graph in object file
+ if( p2 < p1 && p2 < Phase.Unlink .ordinal() ) { unlink(); p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.Select .ordinal() && cpu != null ) { instSelect(cpu,callingConv); p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.Schedule .ordinal() ) { GCM(); p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.LocalSched.ordinal() ) { localSched();p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.RegAlloc .ordinal() ) { regAlloc(); p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.Encoding .ordinal() ) { encode(); p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.Export .ordinal() ) { exportELF(inMemory,emitEntrySymbol); p2 = dump(dump); }
+ return this;
+ }
+
+ public String entryClinitName() {
+ return Parser.addClzPrefix(_srcName==null ? "Test" : _srcName)+".<clinit>";
+ }
+
+ // Verbose printing during compilation
+ private int dump(int dump) {
+ int p2 = _phase.ordinal();
+ if( (dump & (1<<p2)) == 0 ) return p2;
+ if( (dump & (1<<29)) != 0 ) {
+ String fn = ""+p2+"-"+_phase+".dot";
+ try {
+ Files.writeString(Path.of(fn),
+ new GraphVisualizer().generateDotOutput(compunit(), null, null));
+ } catch(IOException e) { throw Utils.TODO("Cannot write DOT file"); }
+ return p2;
+ }
+
+ if( (dump & (1<<30)) != 0 )
+ System.err.println("After "+_phase+":");
+ System.err.println(IRPrinter.prettyPrint(this));
+ return p2;
+ }
+
+
+ // 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); }
+
+ // 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); }
+
+
+ // idepths are cached and valid until *inserting* CFG edges (deleting is
+ // OK). This happens with inlining, which bumps the version to bulk
+ // invalidate the idepth caches.
+ private int _iDepthVersion = 0;
+ public void invalidateIDepthCaches() { _iDepthVersion++; }
+ public boolean validIDepth(int idepth) {
+ if( idepth==0 ) return false;
+ if( _iDepthVersion==0 ) return true;
+ return (idepth%100)==_iDepthVersion;
+ }
+ public int iDepthAt(int idepth) {
+ return 100*idepth+_iDepthVersion;
+ }
+ public int iDepthFrom(int idepth) {
+ assert idepth==0 || validIDepth(idepth);
+ return idepth+100;
+ }
+
+ // 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;
+
+ // 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 lookupFun(int fidx) { return _linker.atX(fidx); }
+ public FunNode lookupFun(TypeFunPtr tfp) { return lookupFun(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;
+ }
+
+ // 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);
+ }
+
+ // "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);
+ }
+
+ public boolean owns(FunNode fun) {
+ return fun._compunit != null && fun._compunit._src != null;
+ }
+
+
+ // ---------------------------
+ // Parser object
+ public final Parser P;
+
+ // Parse ASCII text into Sea-of-Nodes IR
+ public CodeGen parse() {
+ assert _phase == null;
+ _phase = Phase.Parse;
+ long t0 = System.currentTimeMillis();
+
+ Parser.TYPES.clear();
+ Parser.TYPES.putAll(Parser.INIT_TYPES);
+ if( _src != null ) {
+ // No source file, just the source itself.
+ ParseAll.parseSource(this,_srcName==null ? "Test" : _srcName,_src);
+ } else {
+ // Path from module root to source file
+ ParseAll.parsePath(this,_srcName);
+ }
+
+ _times[Phase.Parse.ordinal()] = System.currentTimeMillis() - t0;
+ JSViewer.show();
+ return this;
+ }
+
+
+ // ---------------------------
+ // 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() {
+ assert _phase == Phase.Parse;
+ _phase = Phase.Iter;
+ long t0 = System.currentTimeMillis();
+
+ // 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: non-singleton
+ // memory must restart at FULL so newly discovered private escapes can
+ // only be removed, never added. Seed the complete graph once.
+ _start.walk(n -> {
+ n._type = iterEscapeReset(n._type);
+ add(n);
+ return null;
+ });
+ _iter.iterate(this);
+ assert Opto.fixedPointCheck(this);
+
+ _times[Phase.Iter.ordinal()] = System.currentTimeMillis() - t0;
+ return this;
+ }
+
+ 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;
+ }
+
+
+ // Run ideal optimizations
+ public CodeGen opto() {
+ if( _phase == Phase.Parse )
+ iter();
+ assert _phase == Phase.Iter;
+ _phase = Phase.Opto;
+ long t0 = System.currentTimeMillis();
+
+ Opto.opto(this);
+
+ _times[Phase.Opto.ordinal()] = System.currentTimeMillis() - t0;
+ return this;
+ }
+ 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() {
+ // Demand phase Opto for cleaning up dead control flow at least,
+ // required for the following GCM.
+ assert _phase.ordinal() <= Phase.Opto.ordinal();
+ _phase = Phase.TypeCheck;
+ long t0 = System.currentTimeMillis();
+
+ final Ary<Node> errs = new Ary<>(Node.class);
+ _stop.walk( n -> {
+ if( n.err() != null )
+ errs.add(n);
+ return null;
+ });
+ String unresolved = unresolvedTypeName();
+ _times[Phase.TypeCheck.ordinal()] = System.currentTimeMillis() - t0;
+ 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;
+ throw min.err();
+ }
+ if( unresolved != null )
+ throw Parser.error("Unknown struct type '"+unresolved+"'",null);
+ return this;
+ }
+
+ 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;
+ }
+
+ // Forward-ref structs are open while parsing. They must all be resolved
+ // to closed structs before leaving type checking.
+ private String unresolvedTypeName() {
+ String[] unresolved = new String[1];
+ _stop.walk(n -> {
+ TypeStruct ts = requiredStruct(n);
+ if( unresolved[0]==null && ts!=null && ts._fref )
+ unresolved[0] = ts._name;
+ return null;
+ });
+ return unresolved[0];
+ }
+
+ public static TypeStruct requiredStruct(Node n) {
+ if( n instanceof NewNode alloc ) return alloc._ts;
+ if( n instanceof ConFldOffNode off ) return off._ts;
+ if( n instanceof FldOffNode off && off.in(0)._type instanceof TypeMemPtr ptr ) return ptr._obj;
+ return null;
+ }
+
+ // ---------------------------
+ // Build the loop tree; break never-exit loops
+ public CodeGen loopTree() {
+ assert _phase.ordinal() <= Phase.Serialize.ordinal();
+ _phase = Phase.LoopTree;
+ long t0 = System.currentTimeMillis();
+ // Build the loop tree, fix never-exit loops
+ _start.buildLoopTree( _linker, _stop);
+ // Invariant theory: No more type or code changes after Opto. The
+ // LoopTree stuff is used to do layouts now, but not core opts. TODO:
+ // at some future date we'll do loop opts (e.g. peeling, unrolling)
+ // that will totally impact code shape, types and core opts - but this
+ // will be done in tandem with Opto changes.
+ //_iter.iterate(this);
+ _times[Phase.LoopTree.ordinal()] = System.currentTimeMillis() - t0;
+ return this;
+ }
+
+ // ---------------------------
+ public BAOS _serial;
+ public void serialize() {
+ assert _phase.ordinal() <= Phase.Serialize.ordinal();
+ _phase = Phase.Serialize;
+ long t0 = System.currentTimeMillis();
+ // Does not change compiler phase; just records IR
+ Serialize.serialize(this);
+ _times[Phase.Serialize.ordinal()] = System.currentTimeMillis() - t0;
+ }
+
+ // Unlink imported function bodies before serialization. Calls to them
+ // remain as normal calls through their function pointer, and codegen later
+ // turns them into external relocations.
+ private void unlinkImports() {
+ assert _phase.ordinal() <= Phase.Serialize.ordinal();
+ for( FunNode fun : _linker ) {
+ if( fun==null || fun.isDead() || owns(fun) )
+ continue;
+ for( int i=1; i<fun.nIns(); i++ )
+ if( fun.in(i) instanceof CallNode call )
+ call.unlink(fun,i--);
+ }
+ //_iter.iterate(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 ) {
+ assert _phase.ordinal() <= Phase.Unlink.ordinal();
+ _phase = Phase.Select;
+
+ _callingConv = callingConv;
+
+ // Not timing the class load...
+ // Look for CPU in fixed named place:
+ // com.seaofnodes.simple.node.cpus."cpu"."cpu.class"
+ String clzFile = base+"."+cpu+"."+cpu;
+ try { _mach = ((Class<Machine>) Class.forName( clzFile )).getDeclaredConstructor(new Class[]{CodeGen.class}).newInstance(this); }
+ catch( Exception e ) { throw new RuntimeException(e); }
+
+ _cCallingConv = _mach.cCallingConv(callingConv);
+
+ // Build global copies of common register masks.
+ long callerSave = _mach.callerSave();
+ long cCallerSave= _mach.callerSave(_cCallingConv);
+ long neverSave = _mach. neverSave();
+ int maxReg = Math.min(64,_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
+ _callerSave = new RegMask(callerSave);
+ _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
+ _retMasks = new RegMask[(maxReg-_callerSave.size()-Long.bitCount( neverSave ))+4];
+ for( int reg=0, i=4; reg<maxReg; reg++ )
+ if( !_callerSave.test(reg) && ((1L<<reg)&neverSave)==0 )
+ _retMasks[i++] = new RegMask(reg);
+ _rpcMask = new RegMask(_mach.rpc());
+ _retMasks[3] = _rpcMask;
+
+ // Convert to machine ops
+ long t0 = System.currentTimeMillis();
+ _uid = 1; // All new machine nodes reset numbering
+ var map = new IdentityHashMap<Node,Node>();
+ _instSelect( _stop, map );
+ _stop = (StopNode)map.get(_stop );
+ StartNode start = (StartNode)map.get(_start);
+ _start = start==null ? new StartNode(_start) : start;
+ _instOuts(_stop,visit());
+ _visit.clear();
+
+ // Replace the CompUnit stop (and list of functions)
+ // with hardware-specific ones
+ for( CompUnit cu : _compunits.values() ) {
+ cu._start= (StartCUNode)map.get(cu._start);
+ cu._stop = (StopCUNode )map.get(cu._stop );
+ }
+
+ _times[Phase.Select.ordinal()] = System.currentTimeMillis() - t0;
+ return this;
+ }
+
+ // Walk all ideal nodes, recursively mapping ideal to machine nodes, then
+ // make a machine node for "this".
+ 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.
+ map.put(n, x=_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(this); // Post selection action
+ }
+
+ return x;
+ }
+
+ // Walk all machine Nodes, and set their output edges
+ 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);
+ }
+ }
+
+ // ---------------------------
+ public void unlink() {
+ assert _phase.ordinal() <= Phase.Select.ordinal();
+ _phase = Phase.Unlink;
+ long t0 = System.currentTimeMillis();
+
+ // 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 : _linker ) {
+ // Already linked to start, not going dead
+ if( fun==null || fun.isDead() || fun._type.isHigh() )
+ continue;
+
+ // Imported functions are not emitted by this object. Unlink any
+ // concrete calls so they become normal external relocations.
+ if( !owns(fun) ) {
+ 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 = _stop._inputs.find(stop);
+ if( idx != -1 )
+ _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.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--);
+ if( fun.rpc()==null ) { // Ensure valid RPC
+ // First make sure a valid RPC; single-call into multi-fun will have each
+ // function having a single call site, so the RPC becomes a constant and
+ // folds - but since multiple targets, the Call never inlines. Recreate a
+ // valid RPC so codegen (and Eval2) understands the calling convention.
+ ParmNode rpc = new ParmNode( "$rpc", 0, ret.rpc()._type, fun );
+ rpc.addDef(ret.rpc());
+ ret.setDef( 3, rpc.init() );
+ }
+ }
+
+ _times[Phase.Unlink.ordinal()] = System.currentTimeMillis() - t0;
+ }
+
+ // ---------------------------
+ // Control Flow Graph in Reverse Post Order.
+ public Ary<CFGNode> _cfg = new Ary<>(CFGNode.class);
+
+ // Global schedule (code motion) nodes
+ public CodeGen GCM() { return GCM(false); }
+ public CodeGen GCM( boolean show) {
+ assert _phase.ordinal() <= Phase.Select.ordinal();
+ _phase = Phase.Schedule;
+ long t0 = System.currentTimeMillis();
+
+ GlobalCodeMotion.buildCFG(this);
+ _times[Phase.Schedule.ordinal()] = System.currentTimeMillis() - t0;
+ if( show )
+ System.out.println(new GraphVisualizer().generateDotOutput(compunit(),null,null));
+ return this;
+ }
+
+ // ---------------------------
+ // Local (basic block) scheduler phase, a classic list scheduler
+ public CodeGen localSched() {
+ assert _phase == Phase.Schedule;
+ _phase = Phase.LocalSched;
+ long t0 = System.currentTimeMillis();
+ ListScheduler.sched(this);
+ _times[Phase.LocalSched.ordinal()] = System.currentTimeMillis() - t0;
+ return this;
+ }
+
+
+ // ---------------------------
+ // Register Allocation
+ public RegAlloc _regAlloc;
+ public CodeGen regAlloc() {
+ assert _phase == Phase.LocalSched;
+ _phase = Phase.RegAlloc;
+ long t0 = System.currentTimeMillis();
+ _regAlloc = new RegAlloc(this);
+ _regAlloc.regAlloc();
+ _times[Phase.RegAlloc.ordinal()] = System.currentTimeMillis() - t0;
+ return this;
+ }
+
+ // 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() {
+ assert _phase == Phase.RegAlloc;
+ _phase = Phase.Encoding;
+ long t0 = System.currentTimeMillis();
+
+ _encoding = new Encoding(this).encode();
+
+ _times[Phase.Encoding.ordinal()] = System.currentTimeMillis() - t0;
+ return this;
+ }
+
+ // ---------------------------
+ // Exporting to external formats
+ ElfWriter _elf;
+ public CodeGen exportELF( boolean inMemory, boolean emitEntrySymbol ) {
+ assert _phase == Phase.Encoding;
+ _phase = Phase.Export;
+ long t0 = System.currentTimeMillis();
+ if( _encoding!=null ) {
+ if( inMemory )
+ new LinkMem(this).link(_encoding); // In memory patching
+ else
+ _elf = new ElfWriter(this).export(emitEntrySymbol);
+ }
+ _times[Phase.Export.ordinal()] = System.currentTimeMillis() - t0;
+ return this;
+ }
+
+
+ // ---------------------------
+
+ // 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 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);
+ for( String str : elf._strs )
+ if( str.startsWith(Parser.CLZ) )
+ _externSymbols.putIfAbsent(str,elf);
+ 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") )
+ _files.add(dir);
+ }
+
+ // Map from object file names to ElfReaders
+ public final HashMap<String,ElfReader> _elfs = new HashMap<>();
+ 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( _phase!=null && _phase.ordinal() > Phase.Schedule.ordinal() )
+ return IRPrinter.prettyPrint( this );
+ if( _stop == null )
+ return "No StopNode";
+ return _stop.p(4999);
+ }
+
+ // 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,""+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/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java
new file mode 100644
index 00000000..2681066e
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java
@@ -0,0 +1,138 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.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
+ //public Encoding _encoding; // Encoding for output
+
+ public TypeStruct _clz; // The one TypeStruct being published
+ //BAOS _serial; // Serialized IR for this ELF file
+ Ary<CompUnit> _deps; // CompUnits that this CompUnit depends on
+
+ //// List of symbols exported by this compilation unit, and their Node
+ //// definitions.
+ //public HashMap<String,Node> _exported;
+ // 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
+ ( !_obj.exists() || // No object file
+ _smp.lastModified() > _obj.lastModified() || // Out-of-date with source?
+ checkDependentObjs() ); // Out-of-date with other objs?
+ _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;
+ }
+
+ // Return true if _obj is out-of-date relative to dependents.
+ private boolean checkDependentObjs() {
+ ElfReader elf = CODE.getElf(_obj);
+ elf.loadPublicSymbols();
+ for( String dep : elf._deps ) {
+ File obj = new File(CODE._buildDir+"/"+dep+".o");
+ if( obj.lastModified() > _obj.lastModified() ) // Out-of-date with deps?
+ return true;
+ }
+ return false;
+ }
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java
new file mode 100644
index 00000000..9d9b8a1c
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java
@@ -0,0 +1,87 @@
+package com.seaofnodes.simple.codegen;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class ElfExpr {
+ public static void main(String[] args) {
+ try (FileOutputStream fos = new FileOutputStream("hello.o")) {
+ ByteBuffer buffer = ByteBuffer.allocate(512);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ // === ELF HEADER (64 bytes) ===
+ buffer.put(new byte[]{
+ 0x7F, 'E', 'L', 'F', // ELF Magic
+ 2, 1, 1, 0, // 64-bit, little-endian, version
+ 0, 0, 0, 0, 0, 0, 0, // ABI padding
+ 2, 0, // Type: Executable (ET_EXEC)
+ 0x3E, 0x00, // Machine: x86-64
+ 1, 0, 0, 0, // ELF version
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Entry point
+ 0, 0, 0, 0, 0, 0, 0, 0, // Program header offset
+ 64, 0, 0, 0, 0, 0, 0, 0, // Section header offset
+ 0, 0, 0, 0, // Flags
+ 64, 0, // ELF header size
+ 0, 0, // Program header entry size
+ 0, 0, // Number of program header entries
+ 40, 0, // Section header entry size
+ 2, 0, // Number of section headers
+ 1, 0 // Section header string table index
+ });
+
+ // === .TEXT SECTION (Executable Code) ===
+ byte[] textSection = {
+ // sys_write (write(1, message, 13))
+ (byte) 0x48, (byte) 0xC7, (byte) 0xC0, 0x01, 0x00, 0x00, 0x00, // mov rax, 1
+ (byte) 0x48, (byte) 0xC7, (byte) 0xC7, 0x01, 0x00, 0x00, 0x00, // mov rdi, 1
+ (byte) 0x48, (byte) 0xBE, (byte)0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rsi, message
+ (byte) 0x48, (byte) 0xC7, (byte) 0xC2, 0x0D, 0x00, 0x00, 0x00, // mov rdx, 13
+ (byte) 0x0F, (byte) 0x05, // syscall
+
+ // sys_exit (exit(0))
+ (byte) 0x48, (byte) 0xC7, (byte) 0xC0, 0x3C, 0x00, 0x00, 0x00, // mov rax, 60
+ (byte) 0x48, (byte) 0x31, (byte) 0xFF, // xor rdi, rdi
+ (byte) 0x0F, (byte) 0x05 // syscall
+ };
+
+ // Append section data
+ buffer.position(256);
+ buffer.put(textSection);
+
+ // === STRING MESSAGE (.data Section) ===
+ byte[] message = "Hello, World!\n".getBytes();
+ buffer.position(400);
+ buffer.put(message);
+
+ // === SECTION HEADERS ===
+ buffer.position(64);
+
+ // Null Section (required as first entry)
+ buffer.put(new byte[40]);
+
+ // .text Section
+ buffer.putInt(1); // Offset in section string table (".text")
+ buffer.putInt(1); // Type: SHT_PROGBITS
+ buffer.putLong(6); // Flags: Executable | Allocatable
+ buffer.putLong(0); // Virtual address
+ buffer.putLong(256); // Offset to section data
+ buffer.putLong(textSection.length); // Section size
+ buffer.putInt(0); // Link
+ buffer.putInt(0); // Info
+ buffer.putLong(4); // Address alignment
+ buffer.putLong(0); // No extra entries
+
+ // === SECTION HEADER STRING TABLE ===
+ buffer.position(500);
+ buffer.put(".text".getBytes());
+ buffer.put((byte) 0x00); // Null terminator
+
+ fos.write(buffer.array());
+ System.out.println("ELF object file 'hello.o' created.");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java
new file mode 100644
index 00000000..534bb10f
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java
@@ -0,0 +1,234 @@
+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;
+ // 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;
+
+ // 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 = 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);
+ }
+
+ // Loads the entire IR
+ public TypeStruct loadSimple(CodeGen code) {
+ 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/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
new file mode 100644
index 00000000..508cdcca
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
@@ -0,0 +1,387 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+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 ) {
+ super(".rela.text", SHT_RELA, SHF_INFO_LINK, 24/*entsize*/);
+ _link = 2;
+ _info = info;
+ }
+
+ void writeReloOffPlus(int offset, int symidx, byte relo) {
+ assert symidx > 0;
+ write8(offset);
+ write8(((long)symidx << 32L) | relo);
+ write8(-4);
+ }
+ }
+
+
+ // ------------------------------------------------------------------------
+ // 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_GLOBAL ? _globals : _contents;
+ 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_GLOBAL ? gidx : -99;
+ }
+
+ 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) {
+ for( Node stopcu : stop._inputs ) {
+ for( Node ret : stopcu._inputs ) {
+ FunNode fun = ((ReturnNode)ret).fun();
+ if( !_code.owns(fun) )
+ continue;
+ int end = enc.opStart(fun.ret()) + enc.opLen(fun.ret());
+ long value = enc.opStart(fun);
+ long size = end - value;
+ if( fun._name != null ) // Anonymous functions have no name
+ symbol(fun._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.owns(fun) ) {
+ if( "main".equals(fun._name) )
+ return; // Top-level declared a main, take the existing one
+ 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("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_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); }
+ }
+
+
+ // ------------------------------------------------------------------------
+
+ public ElfWriter export(boolean emitEntrySymbol) {
+ // 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*/+7/*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;
+ DataSection text = new DataSection(".text" , Section.SHT_PROGBITS, enc._bits , 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);
+ // 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);
+
+ for( Node n : enc._externals.keySet()) {
+ String extern = enc._externals.get(n);
+ int symidx = symbols.symbol(extern, 0, SYM_BIND_GLOBAL, SYM_TYPE_NOTYPE);
+ int offset = enc.opStart(n) + enc.opLen(n) - 4;
+ relocations.writeReloOffPlus(offset, symidx, (byte)4/*PLT32*/);
+ }
+
+ // Write relocations for the constant pool
+ for( Encoding.Relo relo : enc._bigCons.values() ) {
+ boolean ro = !(relo._t instanceof TypeStruct ts) || ts.isConstant();
+ DataSection data = ro ? rodata : rwdata;
+ int symidx = symbols.symbol("CPOOL$"+symbols.gidx(), data._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC, relo._target, relo._t.size());
+ relocations.writeReloOffPlus(relo._opStart+relo._off, symidx, relo._elf );
+ }
+
+ // 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._srcName + ".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/chapter25/src/main/java/com/seaofnodes/simple/codegen/Encoding.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Encoding.java
new file mode 100644
index 00000000..4c7475fd
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Encoding.java
@@ -0,0 +1,606 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.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 {
+
+ // 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<>();
+
+ // Big Constant relocation info.
+ public static class Relo {
+ public final Node _op;
+ public final Type _t; // Constant type
+ 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;
+ }
+ }
+
+ Encoding( CodeGen code ) {
+ _code = code;
+ }
+
+ // 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.owns(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;
+ FunNode fun = _code.link(tfp);
+ if( fun != null && !_code.owns(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;
+ }
+
+ // 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();
+
+ // Write the constant pool
+ writeConstantPool(_cpool,true );
+ // Write the static memory
+ writeConstantPool(_sdata,false);
+
+ // 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.owns(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);
+ }
+ }
+
+ // --------------------------------------------------
+ // Write the constant pool into the BAOS and optionally patch locally
+ void writeConstantPool( BAOS bits, boolean ro ) {
+ padN(16,bits);
+
+ // radix sort the big constants by alignment
+ Ary<Relo>[] raligns = new Ary[5];
+ for( Node op : _bigCons.keySet() ) {
+ Relo relo = _bigCons.get(op);
+ // non-constant structs in the r/w data, everything else in r/o data
+ if( (relo._t instanceof TypeStruct ts && !ts.isConstant()) == ro )
+ continue;
+ int align = relo._t.alignment();
+ Ary<Relo> relos = raligns[align]==null ? (raligns[align]=new Ary<>(Relo.class)) : raligns[align];
+ relos.add(relo);
+ }
+
+
+ // Types can be used more than once; collapse the dups
+ HashMap<Type,Integer> targets = new HashMap<>();
+
+ // By alignment
+ for( int align = raligns.length-1; align >= 0; align-- ) {
+ Ary<Relo> relos = raligns[align];
+ if( relos == null ) continue;
+ for( Relo relo : relos ) {
+ // Map from relo to constant start and patch
+ Integer target = targets.get(relo._t);
+ if( target==null ) {
+ targets.put(relo._t,target = bits.size());
+ // Write constant into constant pool
+ switch( relo._t ) {
+ case TypeTuple tt -> throw Utils.TODO("no tuples here, use structs instead");
+ case TypeStruct ts -> addStruct(bits,ts);
+ // Simple primitive (e.g. larger int, float, function ptr)
+ default -> addN(align,relo._t,bits);
+ }
+ }
+ // Record target address and opcode start.
+ // Target is relative to the cpool/sdata start.
+ relo._target = target;
+ relo._opStart= opStart(relo._op);
+ }
+ }
+
+ }
+
+ // 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);
+ }
+
+ // Structs use internal field layout
+ private void addStruct( BAOS bits, TypeStruct ts ) {
+ // Field order by offset
+ int[] layout = ts.layout();
+ int off=0; // offset in the struct
+ for( int fn=0; fn<ts._fields.length; fn++ ) {
+ Field f = ts._fields[layout[fn]];
+ int foff = ts. offset(layout[fn]);
+ // Pad up to field
+ while( off < foff ) { bits.write(0); off++; };
+ // Constant array fields are special
+ if( f._fname=="[]" ) { // Must be a constant array
+ ((TypeConAry)f._t).write(bits);
+ off += ((TypeConAry)f._t).len();
+ } else {
+ int log = f._t.log_size();
+ addN(log,f._t.isConstant() ? f._t : f._t.makeZero(),bits);
+ off += 1<<log;
+ }
+ }
+ }
+
+
+ // 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;
+
+ void patchGlobalRelocations() {
+ for( Node src : _externals.keySet() ) {
+ int start = opStart(src);
+ String dst = _externals.get(src);
+ int target = switch( dst ) {
+ case "calloc" -> SENTINEL_CALLOC;
+ case "write" -> SENTINEL_WRITE ;
+ default -> throw Utils.TODO();
+ };
+ ((RIPRelSize)src).patch(this, start, opLen(src), target - start);
+ }
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java
new file mode 100644
index 00000000..b0060024
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java
@@ -0,0 +1,171 @@
+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);
+ }
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
new file mode 100644
index 00000000..5d8ed03c
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -0,0 +1,376 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.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 GlobalCodeMotion {
+
+ // 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 ) {
+ Ary<CFGNode> rpo = new Ary<>(CFGNode.class);
+ _rpo_cfg(null, code._start, code.visit(), rpo);
+ // Reverse in-place
+ for( int i=0; i< rpo.size()>>1; i++ )
+ rpo.swap(i,rpo.size()-1-i);
+ // Set global CFG
+ code._cfg = rpo;
+
+ schedEarly(code);
+
+ // Break up shared global constants by functions
+ breakUpGlobalConstants(code._start );
+
+ code._visit.clear();
+ schedLate(code);
+ }
+
+ // 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
+ if( def instanceof ReturnNode && use instanceof CallEndNode )
+ return;
+ 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);
+ }
+
+ // Break up shared global constants by functions
+ private static void breakUpGlobalConstants( Node start ) {
+
+ // For all global constants
+ for( int i=0; i< start.nOuts(); i++ ) {
+ Node con = start.out(i);
+ // Instruction selection can expand one shared TypeNode constant
+ // into a constant-valued machine expression. Split either form
+ // before scheduling, since separate functions have no CFG LCA.
+ if( !(con instanceof CFGNode) &&
+ ((con._type.isHighOrConst() && con.isConst()) ||
+ con instanceof MachNode mach && mach.isClone() && con._type.isConstant()) ) {
+ breakUpGlobalConstantSingle( con );
+ i--; // Removed a global constant, re-run same index
+ }
+ }
+ }
+
+
+ private static void breakUpGlobalConstantSingle( Node con ) {
+ // While constant has users in different functions
+ while( true ) {
+ // Find a function user, and another function
+ FunNode fun = null;
+ boolean done=true;
+ for( Node use : con.outs() ) {
+ if( use == null ) continue;
+ FunNode fun2 = useFun(use);
+ if( fun2==null || !CodeGen.CODE.owns(fun2) ) continue;
+ if( fun==null || fun==fun2 ) fun=fun2;
+ else { done=false; break; }
+ }
+ // Single function user, so this constant is not shared
+ if( done ) {
+ con.setDef(0,fun);
+ return;
+ }
+ // Move function users to a private constant
+ Node con2 = con.copy(); // Private constant clone
+ // Node copies made through Node(Node) carry unregistered inputs,
+ // while some machine copies construct fresh registered edges.
+ // Detach correctly in either case before pinning to the function.
+ Node old = con2.in(0);
+ if( old != null && old._outputs.find(con2) != -1 )
+ con2.setDef(0,null);
+ else
+ con2._inputs.set(0,null);
+ con2.setDef(0,fun);
+ // Move function users to this private constant
+ for( int j=0; j<con._outputs._len; j++ ) {
+ Node use = con.out(j);
+ if( use == null ) continue;
+ FunNode fun2 = useFun(use);
+ if( fun2==null || !CodeGen.CODE.owns(fun2) ) continue;
+ if( fun2==fun ) {
+ use.setDef(use._inputs.find(con),con2);
+ j--;
+ }
+ }
+ }
+ }
+
+ 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();
+ if( cfg==null ) return null;
+ FunNode fun = cfg.fun();
+ // Use itself is not in a function... which makes it a 2-part
+ // constant such as happens on some chips where large constants have
+ // to be built up in parts. PtrToInt followed by the array-body Add is
+ // another zero-code/global-value chain, so walk through any number of
+ // single-use pieces until reaching the function-owned use.
+ if( fun==null ) {
+ do {
+ assert use.nOuts()==1;
+ use = use.out(0);
+ cfg = use.cfg0();
+ fun = cfg==null ? null : cfg.fun();
+ } while( fun==null );
+ }
+ return fun;
+ }
+
+
+ // ------------------------------------------------------------------------
+ // 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) {
+ // Reverse Post-Order on CFG
+ for( CFGNode cfg : code._cfg ) {
+ cfg.loopDepth();
+ for( Node n : cfg._inputs )
+ _schedEarly(n,code );
+ if( cfg instanceof RegionNode )
+ for( Node phi : cfg._outputs )
+ if( phi instanceof PhiNode )
+ _schedEarly(phi,code );
+ }
+ }
+
+ private static void _schedEarly(Node n, CodeGen code) {
+ if( n==null || code._visit.get(n._nid) ) return; // Been there, done that
+ assert !(n instanceof CFGNode);
+ code._visit.set(n._nid);
+ // Schedule not-pinned not-CFG inputs before self. Since skipping
+ // Pinned, this never walks the backedge of Phis (and thus spins around
+ // a data-only loop), eventually attempting relying on some pre-visited-
+ // not-post-visited data op with no scheduled control.
+ for( Node def : n._inputs )
+ if( def!=null && !(def instanceof PhiNode) )
+ _schedEarly(def,code);
+
+ // If not-pinned (e.g. constants, projections, phi) and not-CFG
+ if( !n.isPinned() ) {
+ // Schedule at deepest input
+ CFGNode early = code._start; // Maximally early, lowest idepth
+ if( n.in(0) instanceof CFGNode cfg ) early = cfg;
+ 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);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ private static void schedLate( CodeGen code ) {
+ CFGNode[] late = new CFGNode[code.UID()];
+ Node[] ns = new Node[code.UID()];
+ // Breadth-first scheduling
+ breadth(code._stop,ns,late);
+
+ // Copy the best placement choice into the control slot
+ for( int i=0; i<late.length; i++ )
+ if( ns[i] != null && !(ns[i] instanceof ProjNode) )
+ ns[i].setDef(0,late[i]);
+ }
+
+ private static void breadth(Node stop, Node[] ns, CFGNode[] late) {
+ // Things on the worklist have some (but perhaps not all) uses done.
+ WorkList<Node> work = new WorkList<>();
+ 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 : ld.mem()._outputs )
+ if( late[memuse._nid]==null &&
+ // New makes new memory, never crushes load memory
+ !(memuse instanceof NewNode) &&
+ // Load-use directly defines memory
+ (memuse._type instanceof TypeMem ||
+ // Load-use directly defines memory
+ memuse instanceof CallNode ||
+ // Load-use indirectly defines memory
+ (memuse._type instanceof TypeTuple tt && tt._types[ld._alias] instanceof TypeMem)) )
+ continue outer;
+
+ // All uses done, schedule
+ _doSchedLate(n,ns,late);
+ }
+
+ // Walk all inputs and put on worklist, as their last-use might now be done
+ for( Node def : n._inputs ) {
+ if( def!=null && late[def._nid]==null )
+ work.push(def);
+ // if the def has a load use, maybe the load can fire
+ if( def!=null )
+ for( Node out : def._outputs )
+ if( out instanceof MemOpNode ld && ld._isLoad && late[ld._nid]==null )
+ work.push(ld);
+ }
+
+ 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, Node[] ns, CFGNode[] late) {
+ // 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)._idom(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);
+
+
+ // Nodes setting a single register and getting killed will stay close
+ // to the uses, since they will be forced to spill anyway. The kill
+ // check is very weak, and some may be hoisted only to spill in the RA.
+ if( n instanceof MachNode mach ) {
+ RegMask out = mach.outregmap();
+ if( out!=null && out.size1() ) {
+ int reg = mach.outregmap().firstReg();
+ // Look for kills
+ outer:
+ for( CFGNode lca2=lca; lca2 != early; lca2 = lca2.idom() ) {
+ if( lca2 instanceof MachNode mach2 ) {
+ for( int i=1; i<lca2.nIns(); i++ ) {
+ RegMask mask = mach2.regmap(i);
+ if( mask!=null && mask.test(reg) ) {
+ early = lca2 instanceof IfNode ? lca2.idom() : lca2;
+ break outer;
+ }
+ }
+ RegMask kill = mach2.killmap();
+ if( kill != null )
+ throw Utils.TODO();
+ }
+ }
+ }
+ }
+
+ // 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);
+ ns [n._nid] = 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 && n.cfg0().fun()==parm.fun() )
+ 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)._idom(found,null);
+
+ 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;
+ }
+
+ private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late) {
+ // We could skip final-field loads here.
+ // Walk LCA->early, flagging Load's block location choices
+ for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ cfg._anti = load._nid;
+ // Walk load->mem uses, looking for Stores causing an anti-dep
+ for( Node mem : load.mem()._outputs ) {
+ switch( mem ) {
+ case MemOpNode st:
+ if( !st._isLoad && load._alias == st._alias ) {
+ assert late[mem._nid] != null;
+ lca = anti_dep( load, late[mem._nid], mem.cfg0(), lca, st );
+ }
+ break; // Loads do not cause anti-deps on other loads
+ case EscapeNode esc:
+ if( load._alias == esc.fld()._alias ) {
+ assert late[mem._nid] != null;
+ lca = anti_dep( load, late[mem._nid], mem.cfg0(), lca, mem );
+ }
+ break;
+ case CallNode call:
+ assert late[call._nid]!=null;
+ lca = anti_dep(load,late[call._nid],call.cfg0(),lca,call);
+ break;
+ case PhiNode phi:
+ // Repeat anti-dep for matching Phi inputs.
+ // No anti-dep edges but may raise the LCA.
+ 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);
+ break;
+ case NewNode st: break;
+ case ReturnNode ret: break; // Load must already be ahead of Return
+ case MemMergeNode ret: break; // Mem uses now on ScopeMin
+ case NeverNode never: break;
+ default: throw Utils.TODO();
+ }
+ }
+ return lca;
+ }
+
+ //
+ private static CFGNode anti_dep( MemOpNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st ) {
+ // Store and Load overlap, need anti-dependence
+ if( stblk._anti==load._nid ) {
+ lca = stblk._idom(lca,null); // Raise Loads LCA
+ if( lca == stblk && st != null && st._inputs.find(load) == -1 ) // And if something moved,
+ st.addDef(load); // Add anti-dep as well
+ return lca; // Cap this stores' anti-dep to here
+ }
+ return lca;
+ }
+
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java
new file mode 100644
index 00000000..1d88597c
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java
@@ -0,0 +1,556 @@
+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;
+
+// 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);
+
+ // 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 );
+ }
+ }
+ }
+ }
+
+ // All inputs live - except in self conflicts, where we keep the prior def alive
+ // until it goes.
+ TMP.put(lrg1,def);
+ }
+ }
+
+ // 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) ) {
+ // Disallow clone-ables from killing registers. Just fail
+ // them and re-clone closer to target... so no kill.
+ // Special case for Intel XOR used to zero.
+ Node n = (Node)m;
+ CFGNode effUseBlk = n.out(0) instanceof PhiNode phi ? phi.region().cfg(phi._inputs.find(n)) : n.out(0).cfg0();
+ if( m.isClone() && // Must be clonable
+ (n.nOuts()>1 || // Has many users OR
+ // Only 1 user but effective use is remote block
+ effUseBlk != n.cfg0() ))
+ // Then fail the clonable; it should split or move
+ alloc.fail(alloc.lrg((Node)m));
+ // Else clonable cannot move
+ 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) {
+ // Casts never produce a *new* value, just lift our understanding of the value.
+ // They do not force self-conflicts.
+ if( prior!=null && prior != n && !(n instanceof CheckCastNode) ) {
+ 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 );
+ }
+ 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);
+
+ // 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) {
+ // Out of trivial colorable, pick an at-risk to pull
+ if( sptr==swork )
+ swap(color_stack,sptr,pickRisky(color_stack,sptr));
+ // 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 ) {
+ int best=sptr;
+ int bestScore = pickRiskyScore(color_stack[best]);
+ for( int i=sptr+1; i<color_stack.length; i++ ) {
+ if( bestScore == 1000000 ) return best; // Already max score
+ int iScore = pickRiskyScore(color_stack[i]);
+ 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 ) {
+ // Pick single-def clonables that are not right next to their single-use.
+ // Failing to color these will clone them closer to their uses.
+ if( !lrg._multiDef && lrg._machDef.isClone() && lrg._machUse != null ) {
+ Node def = ((Node)lrg._machDef);
+ Node use = ((Node)lrg._machUse);
+ CFGNode cfg = def.cfg0();
+ if( cfg != use.cfg0() || // Different blocks OR
+ // Same block, but not close
+ cfg._outputs.find(def) < cfg._outputs.find(use)+1 )
+ return 1000000;
+ }
+
+ // Always pick callee-save registers as being very large area recovered
+ // and very cheap to spill.
+ if( lrg._machDef instanceof CalleeSaveNode )
+ return 1000000-2-lrg._mask.firstReg();
+ if( lrg._splitDef != null && lrg._splitDef. in(1) instanceof CalleeSaveNode &&
+ lrg._splitUse != null && lrg._splitUse.out(0) instanceof ReturnNode )
+ return 1000000-1;
+
+ // TODO: cost/benefit model. Perhaps counting loop-depth (freq) of def/use for cost
+ // and "area" for benefit
+ return 1000 + (lrg._multiUse ? -100 : 0);
+ }
+
+ 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; // Yes biasable, advance is slot 1
+ 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(alrg._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/chapter25/src/main/java/com/seaofnodes/simple/codegen/LRG.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/LRG.java
new file mode 100644
index 00000000..3a4596fe
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/LRG.java
@@ -0,0 +1,238 @@
+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; }
+
+ 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/chapter25/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java
new file mode 100644
index 00000000..e0b59395
--- /dev/null
+++ b/chapter25/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);
+ boolean ro = !(relo._t instanceof TypeStruct ts) || ts.isConstant();
+ int target = relo._target+(ro ? cpool : sdata);
+ ((RIPRelSize)relo._op).patch(enc, relo._opStart, enc.opLen(relo._op), target - relo._opStart);
+ }
+
+ return _code;
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java
new file mode 100644
index 00000000..b6b3a22c
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java
@@ -0,0 +1,261 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.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 ) {
+ XS.clear();
+ for( CFGNode bb : code._cfg )
+ 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/chapter25/src/main/java/com/seaofnodes/simple/codegen/Machine.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Machine.java
new file mode 100644
index 00000000..91d07d57
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Machine.java
@@ -0,0 +1,115 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeFunPtr;
+
+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; }
+
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Module.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Module.java
new file mode 100644
index 00000000..a5d85dbc
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java
new file mode 100644
index 00000000..b88067c4
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java
@@ -0,0 +1,360 @@
+package com.seaofnodes.simple.codegen;
+
+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.Arrays;
+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); // Used for asserts only
+ resetToTop(code,oldTypes);
+ unlinkStart(code);
+
+ // OPTIMISTIC INTERPROCEDURAL SCCP
+ sccp(code, oldTypes);
+
+ // After SCCP add everything changed to the worklist and see if
+ // peepholes can make more progress.
+ moveChangesToWorklist(code, oldTypes);
+
+ // SCCP can leave a recursive numeric component at BOTTOM with no
+ // arithmetic mode selected. Resolve the whole component from all
+ // available int/float evidence before IterPeeps consumes the types.
+ resolveNumericModes(code);
+
+ // Progress with improved types. E.g. since we have a full Call Graph
+ // here, we might do better inlining.
+ code._iter.iterate(code);
+
+ // TODO:
+ // loop unroll, peel, RCE, etc
+
+ // Freeze field sizes; do struct layouts; convert field offsets into
+ // constants.
+ fieldSizes(code);
+ // Propagate field size constants
+ code._iter.iterate(code);
+
+
+ // Retain surviving functions in their CompUnits for later code
+ // emission. Start inputs are semantic: they mean unknown callers.
+ for( FunNode fun : code._linker )
+ if( fun != null && !fun.isDead() && !fun._type.isHigh() )
+ keepInCompUnit(code,fun);
+
+ for( CompUnit cu : code._compunits.values() ) {
+ ProjNode mem = cu._start.proj(1);
+ if( mem != null ) mem.unkeep();
+ }
+
+ // To help with testing, sort StopNode 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) ));
+ }
+
+ // Reset Types before running the worklist algorithm
+ private static void resetToTop(CodeGen code, Ary<Type> oldTypes ) {
+ code._start.walk( x -> {
+ oldTypes.setX(x._nid,x._type); // Record original types for asserts
+ 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;
+ } );
+ }
+
+
+ // If wholeWorld, remove the Start inputs to functions - they are not
+ // called unless they get linked (hence go dead and can be removed). If
+ // not wholeWorld, keep only public methods (and not the std lib).
+ private static void unlinkStart(CodeGen code) {
+ // Keep the Start & Start.ProjMem alive even as we remove all uses
+ for( CompUnit cu : code._compunits.values() ) {
+ ProjNode mem = cu._start.proj(1);
+ if( mem!=null ) mem.keep();
+ }
+
+ // Iterate the Stop-of-Stops
+ for( Node stop : code._stop._inputs ) {
+ for( int i=0; i<stop.nIns(); i++ ) {
+ ReturnNode ret = (ReturnNode)stop.in(i);
+ FunNode fun = ret.fun();
+ // Start is a semantic unknown-caller edge. Public functions
+ // keep it; closed-world functions must be reached by a
+ // concrete Call edge discovered during SCCP.
+ if( !(code.owns(fun) && fun.isClz()) &&
+ !code.publicFIDX(fun.sig().fidx()) &&
+ 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();
+ 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.lookupFun(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.
+ private static void linkStart( CodeGen code, FunNode fun ) {
+ assert !fun.isDead();
+ StartCUNode start = fun._compunit._start;
+ if( fun.nIns() < 2 || fun.in(1) != start ) {
+ assert !start.isDead();
+ keepInCompUnit(code,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);
+ }
+ }
+ }
+
+ // Keep a surviving function rooted in its compilation unit without
+ // claiming that it has unknown callers.
+ private static void keepInCompUnit(CodeGen code, FunNode fun) {
+ StopCUNode stop = fun._compunit._stop;
+ if( stop._inputs.find(fun.ret()) == -1 )
+ code.add(stop).addDef(fun.ret());
+ }
+
+
+ // The core SCCP algorithm
+ private static void sccp( CodeGen code, Ary<Type> oldTypes ) {
+ // 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( 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 && oval.isHigh() && !nval.isHigh() && 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 && n._type instanceof TypeTuple tt )
+ linkStart(code,tt);
+
+
+ // Since n._type changed, visit all output neighbors
+ code._iter.addAll(n._outputs);
+ n.moveDepsToWorklist(code._iter);
+ // Quadratic (expensive) small-step assert
+ assert 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;
+ }
+
+ // After SCCP add everything changed to the worklist and see if peepholes
+ // can make more progress.
+ private static void moveChangesToWorklist(CodeGen code, Ary<Type> oldTypes) {
+ code._start.walk( x -> {
+ assert x.compute() == x._type; // Hit the fixed point
+ assert x._nid >= oldTypes._len || x._type.isa(oldTypes.at(x._nid)); // Hit at least the bottom-up type
+ code.add(x);
+ return null;
+ });
+ }
+
+ // Resolve arithmetic modes which are ambiguous only because they belong to
+ // a recursive component. Float wins because integer operands can be
+ // converted to float.
+ private static void resolveNumericModes(CodeGen code) {
+ final BitSet visited = new BitSet();
+ code._stop.walk(n -> {
+ if( 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);
+ if( mode != 0 ) {
+ // If both evidence, FP wins
+ modeNode.setMode((byte)((mode&2)==2 ? 2 : 1)).init();
+ code.add(n);
+ }
+ }
+ return null;
+ });
+ }
+
+ // 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 || 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;
+ switch( n ) {
+ case ModeNode modeNode -> evidence = modeNode.mode();
+ case PhiNode phi -> {}
+ case ReturnNode ret -> {}
+ case ProjNode proj when proj._idx==2 && proj.in(0) instanceof CallEndNode cend -> {
+ for( int i=1; i<cend.nIns(); i++ )
+ evidence |= numericEvidence(cend.in(i),visited);
+ }
+ case ConvertNode convert -> {
+ Type dst = convert.dst();
+ evidence = dst instanceof TypeFloat ? 2 :
+ dst instanceof TypeInteger ? 1 : 0;
+ }
+ default -> { return 0; }
+ }
+ for( int i=1; i<n.nIns(); i++ )
+ evidence |= numericEvidence(n.in(i),visited);
+
+ return evidence;
+ }
+
+
+ // Freeze field sizes; do struct layouts; convert field offsets into
+ // constants.
+ private static void fieldSizes( CodeGen code ) {
+ for( int i=0; i<code._start.nOuts(); i++ ) {
+ Node use = code._start.out(i);
+ if( use instanceof ConFldOffNode off ) {
+ Node con = off.asOffset();
+ if( con != null ) { // Can be null for trying to reference missing field
+ off.subsume( con );
+ i--;
+ }
+ }
+ }
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
new file mode 100644
index 00000000..191016be
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
@@ -0,0 +1,379 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.IterPeeps;
+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 {
+
+ // 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 parseAll(CodeGen code, CompUnit cunit) {
+ WORK.clear(); // Cleanup from prior tests
+ NEEDS_LOAD.clear();
+ // Prime the pump with the first CompUnit
+ WORK.add(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 )
+ loadCompUnit(code,cu);
+ if( !NEEDS_LOAD.isEmpty() )
+ code._stop.walk(n -> {
+ if( n instanceof CallNode )
+ code.add(n);
+ return null;
+ });
+
+ // 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 ) {
+ TypeStruct clz = (TypeStruct)Parser.TYPES.get(fref._name);
+ if( clz == null )
+ return null;
+ // Class pointer:
+ TypeMemPtr clzptr = TypeMemPtr.make((byte)2,clz,true);
+ // Class pointer constant:
+ ConstantNode clzCon = code.con(clzptr);
+ // Replace and optimize
+ fref.addDef(clzCon);
+ code.add(fref);
+ }
+ // Adds sort by NIDs, which just got shuffled
+ if( n instanceof AddNode add )
+ code.add(add);
+ 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) {
+ LinkedHashSet<String> required = new LinkedHashSet<>();
+ code._stop.walk(n -> {
+ TypeStruct ts = CodeGen.requiredStruct(n);
+ if( ts!=null && ts._fref ) required.add(ts._name);
+ return null;
+ });
+ for( String typeName : required ) {
+ try {
+ CompUnit xcunit = findRequiredType(code,cunit,typeName);
+ if( xcunit != null )
+ cunit.addDep(xcunit);
+ } 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?
+ try {
+ NEEDS_LOAD.add(cunit);
+ // Load dependent classes
+ ElfReader elf = ElfReader.load(cunit._obj,cunit);
+ elf.loadPublicSymbols();
+ for( String fname : elf._deps ) {
+ // Check CompUnit for being up to date
+ CompUnit sub = makeCUnit(code,null,fname,null/*depObj(code,cunit,fname)*/);
+ // If not seen before or out of date, parse the new nested cunit
+ if( sub._clz == null && sub._par == null )
+ WORK.add(sub);
+ }
+ } catch( IOException ioe ) {
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ private static File depObj(CodeGen code, CompUnit cunit, String fname) {
+ if( cunit._smp != null )
+ return null;
+ ElfReader elf = code.findExternalSimple(Parser.addClzPrefix(fname.replace('/','.')));
+ if( elf == null )
+ throw new RuntimeException("Cannot find external Simple dependency '"+fname+"'");
+ return elf._file;
+ }
+
+ 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 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 = ElfReader.load(cunit._obj,cunit);
+ cunit._clz = elf.loadSimple(code);
+ 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();
+ code.link(fun);
+ }
+ }
+
+ // Hook the new code into the existing code graph
+ for( Node lstopcu : lstop._inputs )
+ code._stop.addDef(lstopcu);
+ lstart.subsume(code._start);
+
+ // 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 ) // Also the instance type, if a constructor exists
+ Parser.TYPES.put(cunit._cname, ((TypeMem)((TypeFunPtr)inst._t)._ret)._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 )
+ WORK.add(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 = ElfReader.load(cunit._obj,cunit);
+ TypeStruct clz = typedElf == null ? null : typedElf.loadPublicTypes(code);
+ if( clz != null )
+ Parser.TYPES.put(clz._name,clz);
+ else
+ 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 )
+ WORK.add(cunit);
+ return cunit;
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java
new file mode 100644
index 00000000..48319006
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
new file mode 100644
index 00000000..4c00391c
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
@@ -0,0 +1,646 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.node.*;
+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<>();
+ 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,"");
+ }
+ }
+ }
+
+
+ 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);
+ }
+
+ // 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 Utils.TODO("Allocator taking too long");
+ round++;
+ }
+ postColor(); // Remove no-op spills
+ _done = true;
+ }
+
+ 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() && (!lrg._multiDef || lrg._1regUseCnt==1) ) {
+ if( lrg._1regDefCnt <= 1 &&
+ lrg._1regUseCnt <= 1 &&
+ (lrg._1regDefCnt + 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;
+ }
+
+ // 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;
+ }
+
+ // Single-def live range with an empty mask. There are many single-reg
+ // uses. Theory is there's many repeats of the same reg amongst the uses.
+ // In of splitting once per use, start by splitting into groups based on
+ // required input register.
+ boolean splitEmptyMaskByUse( byte round, LRG lrg ) {
+ Node def = (Node)lrg._machDef;
+
+ // Look at each use, and break into non-overlapping register classes.
+ Ary<RegMask> rclass = new Ary<>(RegMask.class);
+ boolean done=false;
+ int ncalls=0;
+ while( !done ) {
+ done = true;
+ for( Node use : def._outputs )
+ if( use instanceof MachNode mach ) {
+ if( mach instanceof CallNode ) ncalls++;
+ for( int i=1; i<use.nIns(); i++ )
+ if( use.in(i)==def )
+ done = putIntoRegClass( rclass, mach.regmap(i) );
+ }
+ }
+
+ // See how many register classes we split into. Generally not
+ // productive to split like this across calls, which are going to kill
+ // all registers anyways.
+ if( rclass._len <= 1 || ncalls > 1 ) return false;
+
+ // Split by class
+ Ary<Node> ns = new Ary<>(Node.class);
+ for( RegMask rmask : rclass ) {
+ ns.addAll(def._outputs);
+ Node split = makeSplit(def,"popular",round,lrg,rmask);
+ split.insertAfter( def );
+ if( split.nIns()>1 ) split.setDef(1,def);
+ // all uses by class to split
+ for( int j=0; j < def._outputs._len; j++ ) {
+ Node use = def._outputs.at(j);
+ if( use instanceof MachNode mach && use!=split ) {
+ // Check all use inputs for n, in case there's several
+ for( int i = 1; i < use.nIns(); i++ )
+ // Find a def input, and check register class
+ if( use.in( i ) == def && mach.regmap( i ).overlap( rmask ) ) {
+ RegMask m = mach.regmap( i );
+ if( m!=null && mach.regmap( i ).overlap( rmask ) )
+ // Modify use to use the split version specialized to this rclass
+ { use.setDefOrdered( i, split ); j--; break; }
+ }
+ }
+ }
+ ns.clear();
+ }
+ return true;
+ }
+
+
+ // Put use into a register class, perhaps adding a class or perhaps
+ // narrowing a class (and causing a repeat)
+ private static boolean putIntoRegClass( Ary<RegMask> rclass, RegMask rmask ) {
+ for( int i=0; i<rclass._len; i++ ) {
+ RegMask omask = rclass.at(i);
+ if( omask.and(rmask) == omask ) return true; // Within the same register class
+ if( omask.overlap(rmask) ) {
+ rclass.set(i,new RegMask(omask.copy().and(rmask)));
+ return false; // Need go again
+ }
+ }
+ // Add a new class, no need to go again
+ rclass.push(rmask);
+ return true;
+ }
+
+ // 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 );
+
+ // 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 &&
+ !(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 which extends the LRG. Split also before
+ // Phi slot 1 (and not all inputs), because Phis extend the live range.
+ // TODO: split before all inputs (except the last; at least 1 split here must be extra)
+ if( def instanceof PhiNode phi && !(def instanceof ParmNode) ) {
+ SplitNode split = makeSplit("def/self",round,lrg);
+ insertAfterAndReplace(split,def,false);
+ if( split.nOuts()==0 )
+ split.killOrdered();
+ insertBefore(phi,1,"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;
+ }
+
+
+ // 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++ )
+ 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);
+
+
+ // If the minLoopDepth is less than the maxLoopDepth: for-all defs and
+ // uses, if at minLoopDepth or lower, split after def and before use.
+ for( Node n : _ns ) {
+ if( n instanceof SplitNode && min!=max ) continue; // Ignoring splits; since spilling need to split in a deeper loop
+ 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 || 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 || 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()) || 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 ) {
+ // Do not count splits
+ if( n instanceof SplitNode ) 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);
+ // 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;
+
+ // Split has same reg? Useless! Can remove it!
+ if( defreg == usereg ) {
+ n.removeSplit();
+ j--;
+ continue;
+ }
+ // Split is kept; count against split score
+ _spills++;
+ _spillScaled += (1<<bb.loopDepth()*3);
+ assert _spillScaled >= 0;
+ }
+ }
+ }
+
+ 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);
+ }
+ // Check no clobbers
+ for( int idx = j-1; bb.out(idx) != hi; idx++) {
+ Node n = bb.out(idx);
+ if( lrg(n)!=null && lrg(n)._reg == defreg )
+ return false; // Clobbered
+ }
+ lo.setDefOrdered(1,hi.in(1));
+ return true;
+ }
+
+
+ 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( lrg(n)!=null && lrg(n)._reg == defreg )
+ return false; // Clobbered
+ }
+ throw Utils.TODO();
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegMask.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegMask.java
new file mode 100644
index 00000000..81e47e8e
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegMask.java
@@ -0,0 +1,125 @@
+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<64 ) {
+ long bits0 = _bits0 >> (reg+1);
+ if( bits0!=0 )
+ return (short)(reg+1+Long.numberOfTrailingZeros(bits0));
+ reg=64;
+ }
+ long bits1 = _bits1 >> (reg-64+1);
+ if( bits1!=0 )
+ return (short)(reg+1+Long.numberOfTrailingZeros(bits1));
+ return -1;
+ }
+
+ 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 ((_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/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java
new file mode 100644
index 00000000..1da9a3b1
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
new file mode 100644
index 00000000..8014bb2c
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
@@ -0,0 +1,617 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.*;
+import java.util.*;
+
+abstract public class Serialize {
+
+ static void serialize( CodeGen code ) {
+
+ // Get all Nodes in a sane order
+ Ary<Node> nodes = nodeOrder(code);
+
+ // Array of dependent file names
+ HashSet<String> xdeps = new HashSet<>();
+ for( CompUnit cu : code._compunits.values() ) {
+ if( cu._deps != null )
+ for( CompUnit cudep : cu._deps )
+ xdeps.add(cudep._fname);
+ }
+ String[] deps = xdeps.toArray(new String[xdeps.size()]);
+
+ // Top-level clazz being reported
+ String topName = code._srcName == null ? "Test" : code._srcName;
+ CompUnit top = code._compunits.get(topName);
+ TypeStruct clz = top._clz;
+
+ // Preserve which same-named structs are the source-level declarations.
+ // Optimized graph types can carry sharper field states (e.g. len==0),
+ // and cannot be used to reconstruct this mapping during loading.
+ TreeMap<String,TypeStruct> named = new TreeMap<>();
+ for( Type type : Parser.TYPES.values() )
+ if( type instanceof TypeStruct ts )
+ named.put(ts._name,ts);
+ TypeStruct[] pubTypes = named.values().toArray(new TypeStruct[0]);
+
+ // Compress into bytes
+ BAOS baos = write(nodes, clz, pubTypes, deps, 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,elf._pubTypes,elf._deps, 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
+ code._serial = baos;
+ }
+
+ // --------------------------------------------------
+ static BAOS write(Ary<Node> nodes, TypeStruct clz, TypeStruct[] pubTypes, String[] depobjs, GlobalBits aliases, GlobalBits fidxs, GlobalBits rpcs, HashMap<Integer,String> externFunc) {
+ // Initialize the mapping from bits/tags to types
+ Type.TAGOFFS();
+
+ BAOS baos = new BAOS();
+ // A - Print a header
+ baos.write('C').write('0').write('D').write('E');
+
+ // Count unique Types
+ var types = new HashMap<Type,Integer>();
+ // 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);
+ if( n instanceof ParmNode parm ) parm._declaredType.gather(types);
+ if( n instanceof TypeNode con ) con._con .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( TypeStruct ts : pubTypes )
+ if( types.containsKey(ts) )
+ writtenPubs.add(ts);
+ // 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);
+ // 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);
+
+ // 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)));
+ }
+
+
+ // 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);
+
+ // Radix sort by Node ID#, new compressed NIDs
+ IdentityHashMap<Node,Integer> anodes = new IdentityHashMap<>();
+ for( Node n : nodes )
+ anodes.put(n,anodes.size()+1); // +1 bias, reserve 0 for null
+
+ // 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._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++ ) {
+ Node def = n.in(j);
+ Integer idx = def==null ? Integer.valueOf(0) : anodes.get(def);
+ 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;
+ }
+ 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);
+ }
+ }
+
+ // Returned array of *public* strings only.
+ 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("Missing magic word");
+
+ // 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];
+ }
+
+ // --------------------------------------------------
+ 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 ) {
+ 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( "Missing magic word" );
+
+ // 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];
+ }
+
+ // 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);
+ }
+ return 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( 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 = n instanceof MemMergeNode && i >= GlobalBits.RESERVED && def != null
+ ? aliases.map(fileAliases,i)
+ : 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);
+
+ assert check(code,nodes);
+ elf._nodes = nodes;
+ }
+
+ // Check loaded types and nodes are consistent
+ private static boolean check( CodeGen code, Ary<Node> nodes ) {
+ // Loaded nodes are complete, unlike nodes still being built by the
+ // parser. Recompute them with post-parse semantics. The compilation-
+ // unit Stop is the exception: its escape type depends on the complete
+ // program's public interface, which ParseAll freezes only after every
+ // compilation unit has been loaded.
+ boolean good = true;
+ CodeGen.Phase phase = code._phase;
+ code._phase = CodeGen.Phase.Opto;
+ try {
+ for( Node n : nodes ) {
+ if( n instanceof StopCUNode && !code.publicInterfaceFrozen() )
+ continue;
+ Type nval = n.compute();
+ if( nval != n._type ) {
+ System.err.println("Node "+n+" read in as "+n._type+" but computes as "+nval);
+ good = false;
+ }
+ }
+ } finally {
+ code._phase = phase;
+ }
+ return good;
+ }
+
+ // --------------------------------------------------
+ public static Ary<Node> nodeOrder( CodeGen code, CompUnit cu ) {
+ assert code._start._ltree != null; // Uses loop tree
+ BitSet visit = code.visit();
+ Ary<Node> nodes = new Ary<>(Node.class);
+ visit.set(code._stop._nid);
+ // Walk all listed functions, in this single compilation unit
+ for( FunNode fun : code._linker )
+ if( fun!=null && !fun.isDead() && fun._compunit == cu )
+ _funWalk(fun,nodes,visit);
+
+ // Just constants used by the listed functions
+ Ary<Node> cons = new Ary<>(Node.class);
+ cons.add(code._start);
+ for( Node n : code._start._outputs ) {
+ if( n instanceof ConstantNode con && !visit.get(con._nid) ) {
+ for( Node use : con.outs() ) {
+ // Special case for Cast:BOT which is basically a double-indirect constant
+ if( use instanceof CheckCastNode cast && cast.in(0)==null )
+ for( Node useuse : cast.outs() )
+ conUsed(cast,useuse,visit,cons);
+ // PtrToInt is another zero-code value rooted at a global
+ // constant. Its array-body Add is the value used by the
+ // function, so include that extra link in the ownership
+ // test as well.
+ if( use instanceof PtrToIntNode ptr )
+ for( Node add : ptr.outs() ) {
+ for( Node addUse : add.outs() )
+ conUsed(add,addUse,visit,cons);
+ conUsed(ptr,add,visit,cons);
+ }
+ // Constant used by somebody, so flag for output
+ conUsed(con,use,visit,cons);
+ }
+ }
+ }
+ // 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 conUsed( Node con, Node use, BitSet visit, Ary<Node> cons ) {
+ if( use == null || !visit.get(use._nid) || visit.get(con._nid) ) return;
+ cons.add(con);
+ visit.set(con._nid);
+ }
+
+ // --------------------------------------------------
+ public static Ary<Node> nodeOrder( CodeGen code ) {
+ assert code._start._ltree != null; // Uses loop tree
+ 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._start != null && !cu._start.isDead() ) {
+ nodes.add(cu._start);
+ // Memory proj
+ nodes.add(cu._start.proj(1));
+ // 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);
+ 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 ) {
+ 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 nodes walk outer loops before inner, so they hit the
+ // Return first, so Return is at the bottom of the RPO.
+ if( n instanceof IfNode iff ) {
+ CProjNode c0 = iff.cproj(0);
+ CProjNode c1 = iff.cproj(1);
+ if( c0._ltree.depth() > c1._ltree.depth() )
+ { c0 = c1; c1 = iff.cproj(0); }
+ _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/chapter25/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/AddNode.java
new file mode 100644
index 00000000..46607c23
--- /dev/null
+++ b/chapter25/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 )
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/AndNode.java
new file mode 100644
index 00000000..083a9a8c
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/AndNode.java
@@ -0,0 +1,50 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+public class AndNode extends ArithNode {
+ public AndNode(Parser.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 public String glabel() { 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 )
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java
new file mode 100644
index 00000000..8c21aef7
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java
@@ -0,0 +1,198 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.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.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+public abstract class ArithNode extends Node implements ModeNode {
+ // Source location for late reported errors
+ Parser.Lexer _loc;
+ // Mode is 0 for unknown, 1 for int, 2 for flt
+ byte _mode;
+
+ public ArithNode( Parser.Lexer loc, Node lhs, Node rhs) { super(null, lhs, rhs); _loc = loc; }
+ public ArithNode( Parser.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);
+ }
+ @Override public String glabel() { return op(); }
+ abstract String op();
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append(op()), visited);
+ return sb.append(")");
+ }
+
+ 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;
+ }
+
+ // 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, Parser.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.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 airthmetic 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;
+ if( t1.isHigh() || t2.isHigh() )
+ return _mode==0 ? Type.TOP :
+ _mode==1 ? TypeInteger.TOP :
+ TypeFloat.F64.dual();
+ byte mode = _mode==0 ? mode(t1,t2) : _mode;
+ if( mode==0 )
+ return Type.BOTTOM;
+ 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 ) {
+ if( f1.isConstant() && f2.isConstant() )
+ return TypeFloat.constant(doOp(f1.value(),f2.value()));
+ return t1.meet(t2);
+ }
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/BoolNode.java
new file mode 100644
index 00000000..c8722f67
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/BoolNode.java
@@ -0,0 +1,195 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.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.BitSet;
+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
+ public String glabel() { return op(); }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append(op()), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public TypeInteger compute() {
+ Type t1 = in(1)._type;
+ Type t2 = in(2)._type;
+ // 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( _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 ((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,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 "<" ; }
+ public String glabel() { return "&lt;"; }
+ 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 "<="; }
+ public String glabel() { return "&lt;="; }
+ 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<" ; }
+ public String glabel() { return "u&lt;"; }
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
new file mode 100644
index 00000000..c4a13375
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
@@ -0,0 +1,312 @@
+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.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); }
+
+ /**
+ * Record that an alias has been split out. The graph rewrite installing
+ * its parallel precise Phi must happen before this semantic change.
+ */
+ public void splitAlias(int alias) {
+ assert alias > 1;
+ if( _aliases.get(alias) ) return;
+ unlock();
+ if( _aliases==EMPTY )
+ _aliases = new BitSet();
+ _aliases.set(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( !(region() instanceof RegionNode r ) )
+ return in(1); // Input has collapse to e.g. starting control.
+ if( r.inProgress() || r.nIns()<=1 )
+ return null; // Input is in-progress
+ if( nOuts()==0 ) return null;
+ if( nIns() <= 2 ) return null;
+
+ // "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,false)==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 ScopeNode scope -> 0;
+ case ReturnNode ret -> 0;
+ case ProjNode proj -> 0;
+ case ConstantNode con -> {
+ assert !(con._con instanceof TypeMem tmem && tmem._alias != 1);
+ yield 0;
+ }
+ case MemMergeNode mmm -> missingAlias(mmm,false);
+ case MemOpNode mem -> unsplit(mem._alias);
+ case BulkMemPhiNode bulk -> missingAlias(bulk);
+ default -> throw Utils.TODO();
+ };
+ }
+
+ // Return an alias required by a user but still covered by this Phi, or 0.
+ private int outputAlias(Node use) {
+ use = addDep(use); // User alias sharpening changes this decision
+ return switch(use) {
+ case ScopeNode scope -> 0;
+ case ParmNode parm -> 0;
+ case MemMergeNode mmm -> mmm.in(1)==this ? missingAlias(mmm,true) : 0;
+ 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
+ : "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) {
+ return alias <= 1 || _aliases.get(alias) ? 0 : 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 precise MemMerge slice still covered by this Phi. When inspecting
+ // a user, ignore slots which point back to this Phi.
+ private int missingAlias(MemMergeNode mmm, boolean user) {
+ for( int alias=2; alias<mmm.nIns(); alias++ )
+ if( mmm.in(alias)!=null &&
+ mmm.alias(alias)!=mmm.in(1) &&
+ (!user || mmm.in(alias)!=this) &&
+ !_aliases.get(alias) )
+ return alias;
+ return 0;
+ }
+
+ // Slice out given alias
+ private MemMergeNode slice( int alias ) {
+ assert !_aliases.get(alias);
+ assert !hasDup(alias);
+ // Inputs are the same; MemPhi will sharpen his own inputs
+ MemPhiNode mphi = new MemPhiNode("$"+alias,alias);
+ for( int i=0; i<nIns(); i++ ) mphi.addDef(in(i));
+ Node mem = mphi.peephole();
+ assert ((TypeMem)mem._type)._alias==alias;
+
+ 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));
+ Node bulk = bphi.peephole();
+ CodeGen.CODE.add(bulk);
+ CodeGen.CODE.add(mphi);
+ return aggregate(bulk,alias,mem);
+ }
+
+ // 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(false,null,bulk);
+ for( int old = _aliases.nextSetBit(0); old >= 0; old = _aliases.nextSetBit(old+1) )
+ if( old != alias )
+ mmm.alias(old,precisePhi(old));
+ mmm.alias(alias,precise);
+ assert checkMerge(mmm);
+ mmm.init();
+ // Slicing changes representation, not the set of values in memory.
+ // Newly exposed precise inputs can carry conservative bulk escape
+ // summaries; do not let those invent escapes absent from this Phi.
+ if( _type instanceof TypeMem old && mmm._type instanceof TypeMem mem )
+ mmm._type = TypeMem.make(mem._alias,mem._t,mem._one,mem._clz,
+ mem._final,old._escFs,old._escAs);
+ return mmm;
+ }
+
+ // Find the precise Phi parallel to this bulk Phi at the same merge.
+ private Node precisePhi(int alias) {
+ Node found = null;
+ for( Node use : region().outs() )
+ if( use instanceof MemPhiNode mphi && mphi._alias==alias ) {
+ assert found==null;
+ found = mphi;
+ }
+ // The parallel Phi may already have collapsed to a common input. Do
+ // not recover it from downstream MemMerges: they are distinct program
+ // points and can legitimately carry different values for this alias.
+ // Reconstruct from this Phi's merge inputs instead. MemPhi selects
+ // the alias from each input MemMerge and collapses when, for example,
+ // the alias is loop-invariant here.
+ if( found==null ) {
+ MemPhiNode mphi = new MemPhiNode("$"+alias,alias);
+ mphi.addDef(region());
+ for( int i=1; i<nIns(); i++ )
+ mphi.addDef(preciseInput(in(i),alias));
+ found = mphi.peephole();
+ CodeGen.CODE.add(found);
+ }
+ return found;
+ }
+
+ // 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;
+ }
+
+ private boolean hasDup( int alias ) {
+ for( Node use : region().outs() )
+ if( use instanceof MemPhiNode mphi && mphi._alias==alias )
+ return true;
+ return false;
+ }
+
+ @Override public boolean eq(Node n) {
+ return _aliases.equals(((BulkMemPhiNode)n)._aliases) && super.eq(n);
+ }
+
+ @Override int hash() { return _aliases.hashCode(); }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java
new file mode 100644
index 00000000..2b668da8
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -0,0 +1,227 @@
+package com.seaofnodes.simple.node;
+
+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;
+ _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 int _idepth;
+ public int idepth() {
+ return CodeGen.CODE.validIDepth(_idepth) ? _idepth : (_idepth=CodeGen.CODE.iDepthFrom(idom().idepth()));
+ }
+
+ // 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 _idom(CFGNode rhs, Node dep) {
+ if( rhs==null ) return this;
+ CFGNode lhs = this;
+ while( lhs != rhs ) {
+ var 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();
+ if( lhs==null || rhs==null ) return null;
+ }
+ return lhs;
+ }
+
+ // Anti-dependence field support
+ public int _anti; // Per-CFG field to help find anti-deps
+
+ // 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 is a LoopNode which contains all at depth 1.
+ 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 = new BitSet();
+ int pre = 1;
+ _pre = pre++;
+ for( FunNode fun : funs ) {
+ if( fun!=null && !fun.isDead() && !fun._type.isHigh() ) {
+ work.push(fun);
+ if(fun.in(1) instanceof StartCUNode start && start._pre==0 ) {
+ start._pre = pre++;
+ start._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);
+ }
+
+ int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post, Ary<FunNode> work ) {
+ // Pre-walked?
+ if( _pre!=0 ) return pre;
+ _pre = pre++;
+ // Pre-walk
+ for( Node use : _outputs )
+ if( use instanceof CFGNode usecfg ) {
+ if( skip( usecfg ) ) {
+ if( usecfg._pre==0 && usecfg instanceof FunNode fun2 ) work.add( fun2 );
+ } else
+ pre = usecfg._bltWalk( pre, use instanceof FunNode fuse ? fuse : fun, stop, post, work );
+ }
+
+ if( this instanceof ReturnNode ret ) {
+ ret._ltree = stop._ltree;
+ } else {
+ // 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 walk from a Call to linked Fun's.
+ return usecfg instanceof XCtrlNode ||
+ (this instanceof CallNode && 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/chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java
new file mode 100644
index 00000000..b74619a3
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java
@@ -0,0 +1,84 @@
+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.type.TypeTuple;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.BitSet;
+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 public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(_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);
+ }
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndMach.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndMach.java
new file mode 100644
index 00000000..05707132
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
new file mode 100644
index 00000000..8e7f47f3
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -0,0 +1,275 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.BitSet;
+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;
+ 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(nIns());
+ // 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); }
+ 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
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("cend( ");
+ sb.append( in(0) instanceof CallNode ? "Call, " : "----, ");
+ for( int i=1; i<nIns()-1; i++ )
+ in(i)._print0(sb,visited).append(",");
+ sb.setLength(sb.length()-1);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if( !(in(0) instanceof CallNode call) || call._type != Type.CONTROL )
+ return TypeTuple.STATE.dual();
+ // Grab the TFP and use the functions declared return type.
+ // If the call.fptr() is a FRef, return will be BOTTOM.
+ Type ftype = addDep(call.fptr())._type;
+ if( !(ftype instanceof TypeFunPtr tfp) )
+ return ftype.isHigh() ? TypeTuple.STATE.dual() : TypeTuple.STATE;
+ // Mid-fold, just take the one single callers' return type
+ if( _folding ) {
+ TypeTuple tt = (TypeTuple)in(1)._type;
+ return tt.makeFrom(2,tt.ret().join(tfp._ret));
+ }
+
+ // Here, if I can figure out I've linked *all* callers, then I can meet
+ // across the linked returns and join with the function return type.
+
+ // External functions have an exact FIDX but deliberately no local
+ // FunNode/Return edge. They conservatively crush public memory and
+ // produce their declared return type. A missing non-external target
+ // is still an unresolved/error call.
+ int externs = 0;
+ if( !XInt.isHigh(tfp.fidxs()) )
+ for( int fidx = XInt.next(tfp.fidxs(),0); fidx >= 0; fidx = XInt.next(tfp.fidxs(),fidx) )
+ if( CodeGen.CODE.externFunc(fidx)!=null )
+ externs++;
+ boolean hasExtern = externs != 0;
+
+ // If we have an error-call, e.g. wrong args, then we never link.
+ // Pre-Opto this looks like a missing target fcn, and we assume it will
+ // appear later - meanwhile, we use a conservative approx of memory
+ // effects.
+ if( (nIns()-1)+externs < tfp.nfcns() ) {
+ // If before SCCP, we might call extras or also the unknown target.
+ StartNode start = CodeGen.CODE._start;
+ addDep(start);
+ Type tmem = start._type instanceof TypeTuple tt ? tt._types[1] : (start._type.isHigh() ? TypeMem.TOP : TypeMem.BOT);
+ Type ret = tfp.ret();
+ // If during Opto, assume call won't be called and thus won't return anything.
+ if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() )
+ ret = Type.TOP;
+ return TypeTuple.make(Type.CONTROL,tmem,ret);
+ }
+
+ // A linked function for every concrete function
+ TypeTuple state = hasExtern
+ ? TypeTuple.make(Type.CONTROL,TypeMem.BOT,tfp.ret())
+ : TypeTuple.STATE.dual();
+ for( int i=1; i<nIns(); i++ )
+ state = (TypeTuple)state.meet(in(i)._type);
+ // At least as good as the TFP
+ return state.makeFrom(2,state.ret().join(tfp.ret()));
+ }
+
+ @Override
+ public Node idealize() {
+
+ // Worklist-based inlining. Cannot inline if folding, or calling
+ // multiple targets or no CallNode (malformed because dying).
+ if( !_folding && nIns()==2 && in(0) instanceof CallNode call ) {
+ Node fptr = call.fptr();
+ if( fptr.isConst() && // We have an immediate call
+ // Function is being called, and its not-null
+ fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && !tfp.isHigh() &&
+ // Arguments are correct
+ call.nargs()==tfp.nargs() && goodArgs(call,tfp) ) {
+ ReturnNode ret = (ReturnNode)in(1);
+ FunNode fun = ret.fun();
+ int isTrivial = trivialInlining( fptr, fun );
+
+ // Encouraged inlining because small size and constructor.
+ int maxSize = fun._name!=null && fun.isInit() && !fun.isClz() ? 200 : 100;
+ if( isTrivial==1 && fun._approxUIDs < maxSize ) {
+ assert fun.sig().isa(tfp);
+ assert !CodeGen.CODE._midAssert; // Triggered inlining
+ CodeGen.CODE.add(fun);
+ // 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;
+ assert trivialInlining( fptr2, fun2 )==0;
+ isTrivial = 0;
+ }
+
+ // Trivial inlining: call site calls a single function; single function
+ // is only called by this call site.
+ if( isTrivial==0 )
+ return doTrivialInlining(fun);
+
+ } else { // Function not (yet) a constant function pointer
+ addDep(fptr);
+ }
+ }
+
+ return null;
+ }
+
+ 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) )
+ { addDep(arg); return false; }
+ }
+ return true;
+ }
+
+
+ // Check for trivial inlining: call only calls fun; fun only called by
+ // call. Returns 0 for trivial, +1 for not-trivial because fptr/fun, and
+ // -1 for not-trivial because idoms.
+ public int trivialInlining( Node fptr, FunNode fun ) {
+ // Heuristic forced inlining off via name
+ if( fun._name != null &&
+ fun._name.endsWith("_noInline") )
+ return -1;
+
+ if( !fun.sig().isa(fptr._type) )
+ return -1; // Stall until these align
+
+ // 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; }
+
+ CFGNode idom = call(), prior = this;
+ while( !(idom instanceof FunNode) ) {
+ if( idom==null ) {
+ addDep(prior);
+ if( prior instanceof RegionNode && !(prior instanceof StartNode) )
+ for( int i=1; i<prior.nIns(); i++ )
+ addDep(prior.in(i));
+ return -1;
+ }
+ prior = idom;
+ idom = idom.idom();
+ }
+
+
+ // If the *inlined* function is mid-collapse, also do not inline (yet)
+ idom = fun.ret();
+ while( idom != fun ) {
+ if( idom==null ) return -1; // Forced off, half-folded call
+ if( idom instanceof FunNode fun2 )
+ { addDep(fun2); return -1; }
+ if( idom instanceof CallEndNode cend && cend._folding )
+ { addDep(cend); return -1; }
+ idom = idom.idom();
+ }
+
+ // Only fun user is this call
+ if( fun.nIns() > 2 ) { addDep(fun); return 1; }
+ // Trivial inlining: call site calls a single function; single function
+ // is only called by this call site.
+ assert fun.in(1)==call();
+
+ return 0;
+ }
+
+
+ // Do trivial inlining. Inlining does not need to clone code, merely
+ // triggers folding the Call/Fun and Return/CallEnd away.
+ private Node doTrivialInlining( FunNode fun ) {
+ assert !CodeGen.CODE._midAssert; // Triggered inlining
+ // Add the size heuristic to grow caller, preventing self-recursive
+ // functions from endlessly inlining.
+ fun()._approxUIDs += fun._approxUIDs;
+ // 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();
+ return this;
+ }
+
+
+ @Override public Node pcopy(int idx) {
+ return _folding ? in(1).in(idx) : null;
+ }
+
+ @Override public Node copy() {
+ return _folding ? super.copy() : new CallEndNode(_rpc);
+ }
+
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java
new file mode 100644
index 00000000..f2825650
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java
@@ -0,0 +1,192 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFunPtr;
+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;
+
+/**
+ * Call
+ */
+public class CallNode extends CFGNode {
+
+ // Source location for late reported errors
+ public final Parser.Lexer _loc;
+
+ public CallNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; }
+ 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()); }
+ static Node make( BAOS bais ) { return new CallNode(null,new Node[bais.packed1()]); }
+
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ String fname = name();
+ if( fname == null ) fptr()._print0(sb,visited);
+ else sb.append(fname);
+ sb.append("( ");
+ for( int i=2; i<nIns()-1; i++ )
+ in(i)._print0(sb,visited).append(",");
+ sb.setLength(sb.length()-1);
+ return sb.append(")");
+ }
+ public String name() {
+ if( fptr()._type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
+ FunNode fun = CodeGen.CODE.lookupFun(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() { return false; }
+
+
+ // 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() {
+ 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);
+ fun.addDef(this);
+ for( Node use : fun._outputs )
+ if( use instanceof ParmNode parm )
+ parm.addDef(parm._idx==0 ? ConstantNode.seed(cend()._rpc).peephole() : arg(parm._idx));
+ // Call end points to function return
+ CodeGen.CODE.add(cend()).addDef(fun.ret());
+ 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().keep();
+ cend.delDef(cend._inputs.find(ret));
+ CodeGen.CODE.add(cend);
+ assert !linked(fun);
+ ret.unkeep();
+ }
+
+
+ // 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) )
+ throw Utils.TODO();
+ 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)) ) {
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java
new file mode 100644
index 00000000..0afc5827
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/CheckCastMach.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CheckCastMach.java
new file mode 100644
index 00000000..9943c7b4
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CheckCastMach.java
@@ -0,0 +1,20 @@
+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;
+
+/**
+ * Cast a pointer to read-only
+ */
+public class CheckCastMach extends CheckCastNode implements MachNode {
+ public CheckCastMach( CheckCastNode 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 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/chapter25/src/main/java/com/seaofnodes/simple/node/CheckCastNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CheckCastNode.java
new file mode 100644
index 00000000..900ee70e
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CheckCastNode.java
@@ -0,0 +1,82 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+// Upcast (join) the input to a t. Used after guard test to lift an input.
+// Can also be used to make a type-assertion if ctrl is null.
+public class CheckCastNode extends TypeNode {
+ public CheckCastNode(Type t, Node ctrl, Node in) { super(t,ctrl, in); }
+
+ public CheckCastNode(CheckCastNode c) {
+ super(c); // Call parent copy constructor
+ setType(compute()); // Ensure type is recomputed
+ }
+ @Override public Tag serialTag() { return Tag.CheckCast; }
+ @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 new CheckCastNode(types[bais.packed2()], null, null); }
+
+ @Override public String label() { return "("+_con.str()+")"; }
+
+ @Override
+ public String uniqueName() { return "CheckCast_" + _nid; }
+
+ @Override public boolean isPinned() { return in(0)!=null; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return in(1)._print0(sb.append(label()), visited);
+ }
+
+ @Override
+ public Type compute() {
+ // Cast array to int
+ Type t1 = in(1)._type;
+ if( _con == TypeInteger.BOT && t1 instanceof TypeMemPtr tmp && tmp._obj.isAry() )
+ return _con;
+
+ // If unconditional, it must collapse (or error)
+ if( in(0)==null )
+ return _con;
+ // If conditional, return the join. The guard test ensures
+ // the join is correct.
+ return t1.join(_con);
+ }
+
+ @Override
+ public Node idealize() {
+ return in(1)._type.isa(_con) ? in(1) : null;
+ }
+
+ @Override
+ public Parser.ParseException err() {
+ // Has a condition to test, so OK
+ if( in(0) != null ) return null;
+ if( in(1)._type != Type.BOTTOM )
+ // No condition to test, so this must optimize away
+ return Parser.error( "Type " + in(1)._type.str() + " is not of declared type " + _con.str(), null );
+ // Only happens for un-initialized refs that must init.
+ // Allow uses of initializing stores, but other uses represent a
+ // partially initialized field, and should error
+ boolean bad = false;
+ StoreNode init=null;
+ for( Node use : _outputs )
+ if( (use instanceof StoreNode st && st._init && !((TypeMemPtr)st.ptr()._type)._obj._name.startsWith("class") ) )
+ init = st;
+ else if( (use instanceof StoreNode st && st.val() instanceof CheckCastNode cast && cast.in(1)._type == Type.BOTTOM) )
+ /*bogus init store in constructor*/;
+ else bad = true;
+ if( !bad ) return null;
+ if( init==null ) return null; // Error somewhere else
+ // Expecting an init store
+ TypeMemPtr ptr = (TypeMemPtr)init.ptr()._type;
+ throw Parser.error("'"+ptr._obj._name+"' is not fully initialized, field '" + init._name + "' is only partially set in the constructor", init._loc);
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
new file mode 100644
index 00000000..11a16d28
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
@@ -0,0 +1,73 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.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.BitSet;
+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 public String glabel() { return label(); }
+ @Override public String uniqueName() { return "Off_" + _nid; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append(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;
+ 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(); }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
new file mode 100644
index 00000000..feed8c5c
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
@@ -0,0 +1,88 @@
+package com.seaofnodes.simple.node;
+
+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.BitSet;
+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 Opto. 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.Opto.ordinal() )
+ return raw(type);
+ if( type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
+ FunNode fun = CodeGen.CODE.lookupFun(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 public String label() { return "#"+_con; }
+ @Override public String glabel() { return "#"+_con.gprint(); }
+ @Override public String uniqueName() { return "Con_" + _nid; }
+ @Override public Node copy() { return raw(this); }
+
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ if( _con instanceof TypeFunPtr tfp && tfp.isConstant() ) {
+ FunNode fun = CodeGen.CODE.lookupFun(tfp);
+ if( fun!=null && fun._name != null )
+ return sb.append("{ ").append(fun._name).append("}");
+ }
+ return sb.append(_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/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
new file mode 100644
index 00000000..4e8e93ce
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
@@ -0,0 +1,96 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+
+import java.util.BitSet;
+import java.util.HashMap;
+
+// Convert a value to an authoritative destination type. The destination is
+// known structurally from a declaration; the source family might sharpen later.
+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 public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return val()._print0(sb.append(label()).append("("),visited).append(")");
+ }
+
+ // The declaration fixes the result family during pessimistic parsing.
+ // Optimistic SCCP must retain stronger facts already inside that family.
+ @Override public Type compute() {
+ Type src = val()._type;
+ if( CodeGen.CODE._phase == CodeGen.Phase.Opto ) {
+ if( src.isHigh() ) return Type.TOP;
+ if( 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) ) 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());
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/CtrlNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
new file mode 100644
index 00000000..39c95aa4
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
@@ -0,0 +1,16 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import java.util.BitSet;
+
+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 public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Cctrl"); }
+ @Override public boolean isConst() { return true; }
+ @Override public Type compute() { return Type.CONTROL; }
+ @Override public Node idealize() { return null; }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/DivNode.java
new file mode 100644
index 00000000..dcc9a27a
--- /dev/null
+++ b/chapter25/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 )
+ return in(1);
+
+ // Div of constant
+ if( _mode==2 ) {
+ if( in(2)._type == TypeFloat.constant(1.) )
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/EscapeNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
new file mode 100644
index 00000000..df654291
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
@@ -0,0 +1,114 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.*;
+import java.util.BitSet;
+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
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("Esc#").append(fld()._alias).append(" {");
+ if( self()==null ) sb.append("---");
+ else self()._print0(sb,visited).append(",");
+ priv()._print0(sb,visited).append("}, ");
+ pub ()._print0(sb,visited);
+ return sb;
+ }
+
+ @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 GLB of private
+ 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() )
+ return 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/chapter25/src/main/java/com/seaofnodes/simple/node/ExternNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ExternNode.java
new file mode 100644
index 00000000..9768f41f
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ExternNode.java
@@ -0,0 +1,40 @@
+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 String glabel() { return _con.print(new SB().p("#"), new BitSet(), true).p(":").p(_extern).toString(); }
+ @Override public String uniqueName() { return "Extern_" + _nid; }
+
+ @Override public boolean eq(Node n) { return this==n; }
+ @Override public void gather( HashMap<String,Integer> strs ) {
+ Serialize.gather(strs,_extern);
+ }
+
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/FRefNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/FRefNode.java
new file mode 100644
index 00000000..723e0007
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/FRefNode.java
@@ -0,0 +1,45 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.Var;
+import com.seaofnodes.simple.type.Type;
+import java.util.BitSet;
+
+/**
+ * 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 Parser.Lexer _loc; // Source location
+
+ public FRefNode( String name, Parser.Lexer loc ) { super(FREF_TYPE); _name = name; _loc = loc; }
+
+ @Override public String label() { return "FRef"+_name; }
+
+ @Override public String uniqueName() { return "FRef_" + _nid; }
+
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append("FRef_").append(_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/chapter25/src/main/java/com/seaofnodes/simple/node/FldOffNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/FldOffNode.java
new file mode 100644
index 00000000..3a44f588
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/FldOffNode.java
@@ -0,0 +1,50 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.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.BitSet;
+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 Parser.Lexer _loc; //
+ public FldOffNode( Node base, String fname, Parser.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 public String glabel() { return label(); }
+ @Override public String uniqueName() { return "Off_" + _nid; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append(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(); }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java
new file mode 100644
index 00000000..a9d03543
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java
@@ -0,0 +1,448 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.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.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;
+
+ 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( Parser.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( Parser.Lexer loc, TypeFunPtr sig, String name, CompUnit compunit, Node... nodes ) { this(loc,nodes,sig,name,compunit,false); }
+ public FunNode( TypeFunPtr sig, String name, boolean ext ) {
+ this(null,null,sig,name,null,ext);
+ assert ext; // No need for ext
+ throw Utils.TODO();
+ }
+ 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(nIns()); // Number of linked calls
+ baos.packed2(types.get(_sig)); // NPE if fails lookup
+ baos.packed2(_name==null ? 0 : strs.get(_name));
+ baos.packed2(_approxUIDs);
+ }
+ 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();
+ 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); }
+
+ // 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);
+ if( _sig != sig ) {
+ CODE.add(this);
+ // 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);
+ _sig = sig;
+ unlock();
+ }
+ }
+
+ @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 Parser, which invents new calls "from whole cloth".
+ // - 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( in(1) instanceof StartCUNode start ) {
+ boolean preOpto = CodeGen.CODE._phase == null ||
+ CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal();
+ if( !start.escapedFIDX(_sig.fidx()) && nIns()<=2 && _ret!=null &&
+ !(preOpto && hasFunPtr()) ) {
+ _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;
+ }
+
+ // Before SCCP has discovered all Call edges, a live function pointer is
+ // enough reason to retain the unknown-caller Start edge.
+ private boolean hasFunPtr() {
+ for( Node use : ret().outs() )
+ if( use instanceof FunPtrNode && !use.isDead() ) {
+ addDep(use);
+ return true;
+ }
+ return false;
+ }
+
+ // Bypass Region idom, always assume depth == 1, one more than Start,
+ // unless folding
+ @Override public int idepth() {
+ if( _folding ) return super.idepth();
+ return CodeGen.CODE.iDepthAt(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; }
+
+ 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>"); }
+
+ // 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() {
+ // Reverse up (stop to start) CFG only, collect bitmap.
+ BitSet cfgs = new BitSet();
+ cfgs.set(_nid);
+ walkUp(ret(),cfgs );
+
+ // 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();
+ 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 walkUp(CFGNode n, BitSet cfgs) {
+ if( cfgs.get(n._nid) ) return;
+ cfgs.set(n._nid);
+ if( n instanceof RegionNode r )
+ for( int i=1; i<n.nIns(); i++ )
+ walkUp(n.cfg(i),cfgs);
+ else walkUp(n.cfg0(),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) && unfolded( n ) )
+ 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) && unfolded( n.in( 0 ) ) )
+ 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;
+ }
+
+ // A CFG is folding, and so is basically Data
+ private static boolean unfolded( Node cfg) {
+ if( cfg instanceof CallNode call && call.cend().folding() ) return false;
+ if( cfg instanceof CallEndNode cend && cend.folding() ) return false;
+ if( cfg instanceof FunNode fun && fun._folding ) return false;
+ if( cfg instanceof ReturnNode ret && ret._fun._folding ) return false;
+ return true;
+ }
+
+ // Clone function body. Give function a new FIDX.
+ FunNode copyBody() {
+ // Build the function body BitSet
+ BitSet body = body();
+ assert body.cardinality() < 100;
+ // Walk the body, cloning
+ IdentityHashMap<Node,Node> map = new IdentityHashMap<>();
+ BitSet visit = CodeGen.CODE.visit();
+ bodyCopy( visit, body, map, this );
+ visit.clear();
+ bodyEdge( visit, body, map, this );
+ 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;
+
+ // Remove all callers - this is a private copy
+ while( fun2.nIns() > 1 )
+ if( fun2.in(1) instanceof CallNode call && body.get(call._nid) )
+ call.unlink(fun2,1); // Recursive calls unlink
+ else fun2.removeDeadPath(1); // Start and external calls can just be removed
+
+ // 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;
+ }
+
+ // 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);
+ // The cloned Call should not clone any linked edges.
+ if( n instanceof CallEndNode cend && !cend._folding ) {
+ m.addDef( map.get(cend.call()) );
+ } else {
+ for( Node e : n._inputs ) {
+ Node x = e;
+ if( e != null ) {
+ x = map.get(e);
+ if( x == null ) x = e;
+ }
+ m.addDef(x);
+ }
+ }
+ for( Node x : n._outputs )
+ bodyEdge(visit,body,map,x);
+ }
+
+
+ // FunNodes must match signature (equivalent: no 2 FunNodes are ever GVN'able)
+ @Override public boolean eq( Node n ) {
+ return _sig == ((FunNode)n)._sig;
+ }
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java
new file mode 100644
index 00000000..33757640
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java
@@ -0,0 +1,65 @@
+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.BitSet;
+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 String glabel() { return "#"+_con.gprint(); }
+ @Override public String uniqueName() { return "FunPtr_" + _nid; }
+ @Override public Node copy() {
+ FunPtrNode fptr = new FunPtrNode((TypeFunPtr)_con,null,null);
+ fptr._type = _type;
+ return fptr;
+ }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ FunNode fun = CodeGen.CODE.lookupFun(((TypeFunPtr)_con).fidx());
+ return fun!=null && fun._name!=null
+ ? sb.append("{ ").append(fun._name).append("}")
+ : sb.append(_con);
+ }
+
+ @Override public boolean isConst() { return true; }
+ @Override public boolean isPinned() { return true; }
+ @Override public Type compute() {
+ ReturnNode ret = ret();
+ return ret == null ? _con : ret.fun().sig();
+ }
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/GuardMach.java b/chapter25/src/main/java/com/seaofnodes/simple/node/GuardMach.java
new file mode 100644
index 00000000..202b1ab8
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/GuardNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/GuardNode.java
new file mode 100644
index 00000000..e3c6f0e2
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/GuardNode.java
@@ -0,0 +1,61 @@
+package com.seaofnodes.simple.node;
+
+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.BitSet;
+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 public String uniqueName() { return "Guard_"+_nid; }
+ @Override public boolean isPinned() { return true; }
+
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return in(1)._print0(sb.append(label()),visited);
+ }
+
+ @Override public Type compute() {
+ 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(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/chapter25/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/IfNode.java
new file mode 100644
index 00000000..d8d72411
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/IfNode.java
@@ -0,0 +1,99 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Utils;
+import java.util.BitSet;
+
+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
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("if( ");
+ if( in(1)==null ) sb.append("never");
+ else in(1)._print0(sb, visited);
+ return sb.append(" )");
+ }
+
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
new file mode 100644
index 00000000..8b8bcaac
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -0,0 +1,332 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.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(Parser.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; }
+
+ // GraphVis DOT code (must be valid Java identifiers) and debugger labels
+ @Override public String label() { return "ld_"+mlabel(); }
+ // GraphVis node-internal labels
+ @Override public String glabel() { return "." +_name; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); }
+
+ @Override
+ public Type compute() {
+ Type mem0 = aliasMem()._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( ptr0 == Type.NIL )
+ return TypeScalar.TOP;
+ if( !(ptr0 instanceof TypeMemPtr ptr) )
+ return mem0.isHigh() ? TypeScalar.TOP : scalar(mem._t);
+
+ // Load field from object
+ TypeMemPtr tmp = ptr;
+ 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;
+ // Load member of constant array
+ if( t instanceof TypeConAry ary )
+ t = ary.elem(); // TODO: if offset is known, can peek the constant
+
+ // Now, do the same for memory
+ if( mem._t instanceof TypeStruct ts ) {
+ assert ts._name==tmp._obj._name;
+ Field mfld = ts.field(_name);
+ // Lift from declared type and memory input
+ if( mfld != null )
+ t = t.join(mfld._t);
+ } else if( mem._alias==_alias ) {
+ t = t.join(mem._t);
+ } else if( mem._t == Type.TOP ) {
+ return TypeScalar.TOP;
+ }
+
+ // 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() )
+ 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 ptr = ptr();
+ Node mem = aliasMem();
+
+ // Loads into structs do not need a ctrl edge, as null-ptr checking is
+ // baked into the type system. Loads into arrays DO need the ctrl
+ // edge, at least until proper range-checking is in place.
+ if( in(0)!=null && ptr()._type instanceof TypeMemPtr tmp &&
+ // Never can be an array
+ (!tmp._obj._open || (tmp._obj._fields.length > 1 && tmp._obj._fields[0]._fname != "#")) ) {
+ 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;
+ unlock(); // Alias participates in GVN semantics
+ _alias = fld._alias;
+ // Bulk memory may already have inspected this user. Revisit it
+ // once the declaring shape is stable; an open forward reference
+ // can still change this Load's semantics.
+ if( !tmp._obj._open && !tmp._obj._fref )
+ CodeGen.CODE.add(mem());
+ return this;
+ }
+ // Must sharpen alias first
+ if( _alias == 1 )
+ return null;
+
+ // Simple Load-after-Store on same address.
+ if( mem instanceof StoreNode st &&
+ ptr == st.nnptr() &&
+ off() == st.off() ) { // Must check same object
+ assert _name.equals(st._name); // Equiv class aliasing is perfect
+ return extend(st.val());
+ }
+
+ // Expose the same effective memory input already observed by compute.
+ if( mem != mem() ) {
+ for( Node ld : mem._outputs )
+ if( ld instanceof LoadNode )
+ CodeGen.CODE.add(ld);
+ if( mem instanceof BulkMemPhiNode ) CodeGen.CODE.add(mem);
+ setDef(1,mem);
+ 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()._idom(ld.cfg0(),this) == ld.cfg0() ) // Higher control means load is legal earlier
+ return ld;
+
+ // Load-after-Store on same address, but bypassing provably unrelated
+ // stores. This is a more complex superset of the above two peeps.
+ // "Provably unrelated" is really weak.
+ if( ptr instanceof ReadOnlyNode ro )
+ ptr = ro.in(1);
+ outer:
+ while( true ) {
+ switch( mem ) {
+ case StoreNode st:
+ if( ptr == addDep(st.ptr()) && off() == st.off() )
+ return extend(castRO(st.val())); // Proved equal
+ // Can we prove unequal? Offsets do not overlap?
+ if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap
+ !neverAlias(ptr,st.ptr()) ) { // And might alias
+ addDep( off()); // Offsets can fold, proving unequal
+ addDep(st.off()); // Offsets can fold, proving unequal
+ break outer; // Cannot tell, stop trying
+ }
+ // Pointers cannot overlap
+ mem = st.mem(); // Proved never equal
+ break;
+ case PhiNode phi:
+ // Assume related
+ addDep(phi);
+ break outer;
+ case ConstantNode con:
+ // Load from constant memory
+ if( con._con instanceof TypeMem tmem )
+ return ConstantNode.make(tmem._t);
+ break outer; // Assume shortly dead
+ case ProjNode mproj: // Memory projection
+ switch( mproj.in(0) ) {
+ case NewNode nnn1:
+ // Direct load from e.g. new array elements
+ assert _name=="[]";
+ Type decl = declaredType();
+ return decl==Type.BOTTOM ? null : ConstantNode.make(decl.makeZero());
+ case StartNode start: break outer;
+ case CallEndNode cend: addDep(mproj); break outer; // TODO: Bypass no-alias call
+ default: throw Utils.TODO();
+ }
+ case CheckCastNode cast: mem = cast.in(1); break;
+ case MemMergeNode merge: mem = merge.alias(_alias); break;
+ case EscapeNode esc:
+ if( esc.self()==ptr ) // Proved equal
+ { mem = esc.priv(); break; }
+ // Two NewNodes are always unequal
+ if( esc.self().in(0) instanceof NewNode && ptr.in(0) instanceof NewNode )
+ { mem = esc.pub(); break; }
+ // TODO: Can we prove unequal?
+ break outer;
+
+ default:
+ throw Utils.TODO();
+ }
+ }
+
+ // 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 PhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 &&
+ // Offset can be hoisted
+ off() instanceof ConstantNode &&
+ // Not control dependent
+ in(0)==null &&
+ // Pointer can be hoisted
+ hoistPtr(ptr,memphi) ) {
+
+ // Profit on RHS/Loop backedge
+ if( profit(memphi,2) ||
+ // Else must not be a loop to count profit on LHS.
+ (!(memphi.region() instanceof LoopNode) && profit(memphi,1)) ) {
+ // profit() peepholes inputs and can restore a required control
+ // edge. Recheck before manufacturing control-free loads.
+ if( in(0)!=null )
+ return null;
+ Node ld1 = ld(1);
+ Node ld2 = ld(2);
+ PhiNode phi = new PhiNode(_name, memphi.region(),ld1,ld2);
+ phi.setType(phi.compute().join(_type));
+ return phi;
+ }
+ }
+
+ return null;
+ }
+
+ // Semantic memory input for this Load. Compute defines behavior from
+ // this view; ideal merely exposes the same edge in the graph.
+ private Node aliasMem() {
+ Node mem = mem();
+ return _alias != 1 && mem instanceof MemMergeNode merge
+ ? merge.alias(_alias)
+ : mem;
+ }
+
+ private Node ld( int idx ) {
+ Node mem = mem(), ptr = ptr();
+ assert in(0)==null;
+ return new LoadNode(_loc,_name,_alias,null,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).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;
+ }
+
+ // Profitable if we find a matching Store on this Phi arm.
+ private boolean profit(PhiNode phi, int idx) {
+ Node px = phi.in(idx);
+ if( px==null ) return false;
+ if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() )
+ // To avoid cyclic pushing a Load up then down, getting here means
+ // the load *must* replace with the high/constant.
+ return true;
+ if( px instanceof StoreNode st1 && ptr()==addDep(st1.nnptr() )&& off()==st1.off() )
+ // To avoid cyclic pushing a Load up then down, getting here means
+ // the load *must* match against the Store
+ return true;
+ addDep(px);
+ return false;
+ }
+
+ // Read-Only is a deep property, and cannot be cast-away
+ private Node castRO(Node rez) {
+ if( ptr()._type.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( shf._type==TypeInteger.ZERO ) {
+ // Val can be stale and on worklist. Preserve momnotoncity and bring it up to date
+ if( val._type.isa(_type) )
+ return val;
+ Node val2 = val.peephole();
+ assert val._type.isa(_type);
+ return val2;
+ }
+ Node shl = new ShlNode(null,val,shf.keep()).peephole();
+ return new SarNode(null,shl,shf.unkeep());
+ }
+
+ @Override public int log_size() { return declaredType().log_size(); }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/LoopNode.java
new file mode 100644
index 00000000..44ca35d8
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/LoopNode.java
@@ -0,0 +1,110 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.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( Parser.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 CodeGen.CODE.validIDepth(_idepth) ? _idepth : (_idepth=CodeGen.CODE.iDepthFrom(idom().idepth()));
+ }
+ // 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 StopNode 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 stop; // Found an exit, not an infinite loop
+ }
+ x = x.idom();
+ }
+ // Found a no-exit loop. Insert an exit
+ NeverNode iff = (NeverNode)new NeverNode(1.2f,back()).peephole(); // 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 = stop._ltree;
+
+ // Now fold control into the exit. Might have 1 valid exit, or an
+ // XCtrl or a bunch of prior NeverNode exits.
+ Node top = ConstantNode.make(Type.TOP).peephole();
+ Node memout = new MemMergeNode(false);
+ 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 = stop._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 stop;
+ }
+
+ private static boolean nested(LoopTree inner, LoopTree outer) {
+ for( ; inner!=null; inner = inner._par )
+ if( inner == outer )
+ return true;
+ return false;
+ }
+
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java
new file mode 100644
index 00000000..9440a7ea
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java
@@ -0,0 +1,27 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.Utils;
+import java.io.ByteArrayOutputStream;
+import java.util.BitSet;
+
+// 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 Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("(").append(op()).append(",");
+ for( int i=1; i<nIns(); i++ )
+ (in(i)==null ? sb.append(" ---") : in(i)._print0(sb, visited)).append(",");
+ sb.setLength(sb.length()-1);
+ return sb.append(")");
+ }
+
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MachNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MachNode.java
new file mode 100644
index 00000000..025b5dc4
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
new file mode 100644
index 00000000..a88748b5
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
@@ -0,0 +1,258 @@
+package com.seaofnodes.simple.node;
+
+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 {
+
+ /*
+ * In-Progress is the mutable construction form. No optimizations are
+ * allowed. If not "in progress" then normal peeps work.
+ */
+ public final boolean _inProgress;
+
+ public MemMergeNode( boolean inProgress) { _type = TypeMem.BOT; _inProgress = inProgress; }
+ public MemMergeNode( boolean inProgress, Node ...nodes) { super(nodes); _type = TypeMem.BOT; _inProgress = inProgress; }
+ public MemMergeNode(MemMergeNode mem) { super(mem); _inProgress = false; }
+ @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(false); mem.setDefX(bais.packed1()-1,null); return mem; }
+
+
+ // The mutable construction form is "in progress"; otherwise this is a
+ // graph-semantic memory merge.
+ boolean inProgress() { return _inProgress; }
+
+ @Override public String label() { return "ALLMEM"; }
+ @Override public boolean isMem() { return true; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append(_inProgress ? "Merge[" : "MEM[ ");
+ for( int j=2; j<nIns(); j++ ) {
+ sb.append(j);
+ sb.append(":");
+ Node n = in(j);
+ while( n instanceof ScopeNode loop ) {
+ sb.append("Lazy_");
+ n = loop.mem(j);
+ }
+ if( n==null ) sb.append("___ ");
+ else n._print0(sb, visited).append(" ");
+ }
+ sb.setLength(sb.length()-1);
+ return sb.append("]");
+ }
+
+ // Make a memory merge: no longer a Scope really, tracking memory state but
+ // not related to the parser in any way.
+ public Node merge() {
+ // Force default memory to not be lazy
+ MemMergeNode merge = new MemMergeNode(false);
+ for( Node n : _inputs )
+ merge.addDef(n);
+ for( int i=1; i<nIns(); i++ )
+ merge._mem(i,null);
+ return merge.peephole();
+ }
+
+
+ @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);
+ while( true ) {
+ if( in !=null && in._type.isHigh() )
+ return TypeMem.TOP;
+ if( in instanceof MemMergeNode mem ) {
+ in = mem.in(i);
+ } else break;
+ }
+ 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 && 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);
+
+ } else {
+ // Default mixed mem is just BOT
+ TypeMem mem = defmem;
+ boolean progress = true;
+ while( progress ) {
+ progress = false;
+ for( int i=2; i<nIns(); i++ ) {
+ // Has this alias escaped already?
+ if( XInt.bit(mem._escAs,i) ) {
+ Node n = in(i);
+ if( n != null && !n._type.isHigh() ) {
+ TypeMem mem2 = (TypeMem)mem.meet(n._type);
+ // More escape aliases
+ if( mem2 != mem )
+ { mem = mem2; progress = true; }
+ }
+ }
+ }
+ }
+ return mem;
+ }
+ }
+
+ @Override public Node idealize() {
+ if( inProgress() ) return null;
+ if( nIns()==0 ) return null;
+
+ // Fold defaults into the default
+ boolean progress=false, allDefault=true;
+ for( int i=2; i<nIns(); i++ ) {
+ if( in(i) instanceof CheckCastNode cast )
+ { setDef(i,cast.in(1)); progress=true; }
+ if( in(1) == in(i) && in(1) != null ) { 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 ) {
+ // 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 this;
+ }
+
+ 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); }
+
+ // Read or update from memory.
+ // A shared implementation allows us to create lazy phis both during
+ // lookups and updates; the lazy phi creation is part of chapter 8.
+ Node _mem( int alias, Node st ) {
+ // Memory projections are made lazily; if one does not exist
+ // then it must be START.proj(1)
+ Node old = alias(alias);
+ assert !(old instanceof ScopeNode); // Parser lazy memory is handled by ScopeNode
+ // Memory projections are made lazily; expand as needed
+ return st==null ? old : alias(alias,st); // Not lazy, so this is the answer
+ }
+
+ // Remove lazy Phis on all aliases
+ public void removeLazyAll() {
+ for( int i = 1; i < nIns(); i++)
+ _mem(i,null);
+ }
+
+ public void _merge( MemMergeNode that, RegionNode r) {
+ assert _inProgress;
+ int len = Math.max(nIns(),that.nIns());
+ for( int i = 1; i < len; i++)
+ if( alias(i) != that.alias(i) ) { // No need for redundant Phis
+ // If we are in lazy phi mode we need to a lookup
+ // by alias as it will trigger a phi creation
+ Node lhs = this._mem(i,null);
+ Node rhs = that._mem(i,null);
+ alias(i, new MemPhiNode(Parser.memName(i), i, r, lhs, rhs).peephole());
+ }
+ }
+
+ // Fill in the backedge of any inserted Phis. Keep precise aliases for
+ // what is known at the loop head. For aliases discovered in the loop
+ // body, gather them into a actual (not inProgress) MemMerge and merge into
+ // the default alias.
+ void _endLoopMem( ScopeNode scope, MemMergeNode back, MemMergeNode exit ) {
+ assert _inProgress && back._inProgress && exit._inProgress;
+ Node exit_def = exit.alias(1);
+ MemMergeNode more = null; // Dont often need the "more discovered" merge, make it lazy
+ int len = Math.max(nIns(),back.nIns());
+ for( int i=2; i<len; i++ ) {
+ if( !(i < this.nIns() && this.in(i)!=null) && // Alias is not known to loop head
+ (i < back.nIns() && back.in(i)!=null) ) { // Alias IS known to loop back
+ // Gather
+ if( more==null ) {
+ more = new MemMergeNode(false);
+ more.alias(1,back.in(1));
+ back.alias(1,more);
+ }
+ more.alias(i,back.in(i));
+ back.setDef(i,null);
+ }
+ }
+
+ for( int i=1; i<nIns(); i++ ) {
+ if( in(i) instanceof PhiNode phi && phi.region()==scope.ctrl() ) {
+ assert phi.in(2)==null;
+ phi.setDef(2,back.alias(i)==scope ? phi : back.alias(i)); // Fill backedge
+ }
+ if( exit_def == scope ) // Replace a lazy-phi on the exit path also
+ exit.alias(i,in(i));
+ }
+ }
+
+ // 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 || !_inProgress;
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
new file mode 100644
index 00000000..d824bc3a
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
@@ -0,0 +1,131 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.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.lang.StringBuilder;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+/**
+ * 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 Parser.Lexer _loc;
+
+ public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node ctrl, Node mem, Node ptr, Node off) {
+ super(glb, ctrl, mem, ptr, off);
+ _name = name;
+ _alias = alias;
+ _loc = loc;
+ _isLoad = isLoad;
+ }
+ public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node ctrl, Node mem, Node ptr, Node off, Node value) {
+ this(loc, name, alias, isLoad, glb, 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 public StringBuilder _print1( StringBuilder sb, BitSet visited ) { return _printMach(sb,visited); }
+ public StringBuilder _printMach( StringBuilder sb, BitSet visited ) { throw Utils.TODO(); }
+ public int log_size() { return _con.log_size(); }
+
+ @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.Opto.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.Opto.ordinal() )
+ return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc);
+ return null;
+
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
new file mode 100644
index 00000000..e8e2592c
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
@@ -0,0 +1,72 @@
+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;
+ }
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java
new file mode 100644
index 00000000..6101fba2
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -0,0 +1,78 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.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;
+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 String glabel() { return "-"; }
+ @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 StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("(-"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ Type t = in(1)._type;
+ if( t.isHigh() )
+ return _mode==0 ? Type.TOP :
+ _mode==1 ? TypeInteger.TOP :
+ t instanceof TypeFloat ? t : TypeFloat.F64.dual();
+ if( _mode==0 )
+ return Type.BOTTOM;
+ if( _mode==1 ) {
+ if( t instanceof TypeInteger i0 &&
+ 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 )
+ return f.isConstant() ? TypeFloat.constant(-f.value()) : f;
+ return TypeFloat.F64;
+ }
+
+ @Override
+ public Node idealize() {
+ // -(-x) is x
+ if( in(1) instanceof MinusNode minus )
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/ModeNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ModeNode.java
new file mode 100644
index 00000000..dc635447
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MulNode.java
new file mode 100644
index 00000000..e773ed57
--- /dev/null
+++ b/chapter25/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;
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java
new file mode 100644
index 00000000..b4d6d78e
--- /dev/null
+++ b/chapter25/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) { return null; }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/NeverNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/NeverNode.java
new file mode 100644
index 00000000..e2c279d2
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/NeverNode.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeTuple;
+
+import java.util.BitSet;
+
+// "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 public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Never"); }
+
+ @Override public Type compute() { return TypeTuple.IF_BOTH; }
+
+ @Override public Node idealize() { return null; }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/NewNode.java
new file mode 100644
index 00000000..26a9c0fd
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/NewNode.java
@@ -0,0 +1,104 @@
+package com.seaofnodes.simple.node;
+
+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.BitSet;
+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 glabel() {
+ return _ts.isAry() ? "ary_"+_ts._fields[1]._t.str() : _ts.str();
+ }
+ @Override public String label() { return "new_"+glabel(); }
+
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("new ");
+ return sb.append(_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(); }
+
+ // ------------
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java
new file mode 100644
index 00000000..986754f4
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -0,0 +1,828 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.IterPeeps;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.GlobalBits;
+import com.seaofnodes.simple.print.IRPrinter;
+import com.seaofnodes.simple.print.JSViewer;
+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 implements Cloneable {
+
+ /**
+ * Each node has a unique dense Node ID within a compilation context
+ * The ID is useful for debugging, for using as an offset in a bitvector,
+ * as well as for computing equality of nodes (to be implemented later).
+ */
+ public int _nid;
+
+ /**
+ * 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) {
+ _nid = 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 ) {
+ assert CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Select.ordinal();
+ _nid = CODE.getUID(); // allocate unique dense ID
+ _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,EQ,NE,LT,LE,ULT,
+ CallEnd,Call,CheckCast,ConFldOff,Con,CProj,
+ Div,Escape,Extern,Fun,FunPtr,If,Load,Loop,
+ MemMerge,Minus,Mul,
+ New,Never,Not,Or,Parm,Phi,Proj,
+ ReadOnly,Return,Region,RoundF32,
+ Sar,Shl,Shr,Start,Stop,Store,Struct,Sub,ToFloat,
+ XCtrl,Xor,StartCU,StopCU,Guard,MemPhi,BulkMemPhi,PtrToInt;
+ 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 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 XCtrl -> new XCtrlNode();
+ case Xor -> { int mode=bais.packed1(); assert mode==1; yield new XorNode(null,null,null); }
+ case Guard -> GuardNode.make(bais);
+
+ 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);
+ case CallEnd-> CallEndNode.make(bais ,types);
+ case CheckCast -> CheckCastNode.make(bais,types);
+ 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 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 Struct-> StructNode.make(bais, types);
+ case Region-> RegionNode.make(bais);
+
+ default -> throw Utils.TODO();
+ };
+ }
+ };
+ 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(); }
+
+ // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99"
+ public String uniqueName() {
+ // Get rid of $ as graphviz doesn't like it
+ String label = label().replaceAll("\\$", "");
+ return label + _nid;
+ }
+
+ // Graphical label, e.g. "+" or "Region" or "=="
+ public String glabel() { return label(); }
+
+ // Extra fun stuff, for assembly printing. Jump labels, parser locations,
+ // variable types, etc.
+ public String comment() { return null; }
+
+ // ------------------------------------------------------------------------
+
+ // Debugger Printing.
+
+ // {@code toString} is what you get in the debugger. It has to print 1
+ // line (because this is what a debugger typically displays by default) and
+ // has to be robust with broken graph/nodes.
+ @Override
+ public final String toString() { return print(); }
+
+ // This is a *deep* print. We print with a mutually recursive tik-tok
+ // style; the common _print0 calls the per-Node _print1, which calls back
+ // to _print0;
+ public final String print() {
+ return _print0(new StringBuilder(), new BitSet()).toString();
+ }
+
+ // This is the common print: check for repeats, check for DEAD and print
+ // "DEAD" else call the per-Node print1.
+ public final StringBuilder _print0(StringBuilder sb, BitSet visited) {
+ if( visited.get(_nid) && !(this instanceof ConstantNode) )
+ return sb.append(label());
+ visited.set(_nid);
+ return isDead()
+ ? sb.append(uniqueName()).append(":DEAD")
+ : _print1(sb, visited);
+ }
+ // Every Node implements this; a partial-line recursive print
+ abstract public StringBuilder _print1(StringBuilder sb, BitSet visited);
+
+ public String p(int depth) { return IRPrinter.prettyPrint(this,depth); }
+
+ public boolean isConst() { return false; }
+
+ // ------------------------------------------------------------------------
+ // Graph Node & Edge manipulation
+
+ /**
+ * 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));
+ 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 && CODE.add(old_def).delUse(this) )
+ old_def.kill(); // If we removed the last use, the old def is now dead
+ }
+ 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( ) {
+ if( _type==null ) // Brand-new node, never peeped before
+ JSViewer.show();
+ Node n = peepholeOpt();
+ if( n!=null ) // Made progress?
+ JSViewer.show(); // Show again
+ return n==null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); // 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 peeps are disabled, still allow high Phis to collapse;
+ // they typically come from dead Regions, and we want the Region to
+ // collapse, which requires the Phis to die first.
+ if( !isConst() && !(this instanceof ParmNode parm && parm.inProgress()) &&
+ // A high Load through a bad pointer still owns the source error;
+ // do not replace it with an unlocated constant before typeCheck.
+ !(this instanceof LoadNode && err()!=null) &&
+ _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;
+
+ private boolean addDepImpl( Node dep ) {
+ // Running peepholes during the big assert cannot have side effects
+ // like adding dependencies.
+ if( CODE._midAssert ) return false;
+ 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;
+
+ // 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; }
+
+ /** Pinned in the schedule; these are data nodes whose input#0 is not allowed to change */
+ public boolean isPinned() { 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() {
+ 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;
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/NotNode.java
new file mode 100644
index 00000000..22b5bffe
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/NotNode.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Utils;
+import java.util.BitSet;
+
+public class NotNode extends Node {
+ public NotNode(Node in) { super(null, in); }
+ @Override public Tag serialTag() { return Tag.Not; }
+
+ @Override public String glabel() { return "!"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("(!"), visited);
+ return sb.append(")");
+ }
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/OrNode.java
new file mode 100644
index 00000000..ba250c26
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/OrNode.java
@@ -0,0 +1,46 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+public class OrNode extends ArithNode {
+ public OrNode( Parser.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 public String glabel() { 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 )
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java
new file mode 100644
index 00000000..6488b827
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java
@@ -0,0 +1,87 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.BitSet;
+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(nIns()); // Number of 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 public String glabel() { return _label; }
+
+ @Override protected Type declaredType() { return _declaredType; }
+ @Override protected Type constrain(Type t) { return t.join(_declaredType); }
+
+ @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
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ if( fun()._name!=null && fun()._name.endsWith("<clinit>") && _label.equals("arg") )
+ return sb.append("arg");
+ sb.append("Parm_").append(_label).append("(");
+ for( Node in : _inputs ) {
+ if (in == null) sb.append("____");
+ else in._print0(sb, visited);
+ sb.append(",");
+ }
+ sb.setLength(sb.length()-1);
+ sb.append(")");
+ return sb;
+ }
+
+
+ // Always in-progress until we run out of unknown callers
+ @Override public boolean inProgress() { return in(0) instanceof FunNode fun && fun.inProgress(); }
+
+ @Override
+ public Node idealize() {
+ if( !(region() instanceof FunNode) )
+ return 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/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java
new file mode 100644
index 00000000..2c57eb8b
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java
@@ -0,0 +1,294 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.Serialize;
+import com.seaofnodes.simple.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;
+
+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 public String glabel() { return "&phi;_"+_label; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ if( !(region() instanceof RegionNode r) || r.inProgress() )
+ sb.append("Z");
+ sb.append("Phi(");
+ for( Node in : _inputs ) {
+ if (in == null) sb.append("____");
+ else in._print0(sb, visited);
+ sb.append(",");
+ }
+ sb.setLength(sb.length()-1);
+ sb.append(")");
+ return sb;
+ }
+
+ public CFGNode region() { return (CFGNode)in(0); }
+ @Override public boolean isMem() { return _type instanceof TypeMem; }
+ @Override public boolean isPinned() { return true; }
+ boolean isRPC() { return false; }
+
+ @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() )
+ 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);
+ }
+ }
+ Type declared = declaredType();
+ Type newt = constrain(t);
+
+ // phi loop widening part
+ if( region() instanceof LoopNode && // Only around loops
+ newt instanceof TypeInteger newi &&
+ // Types changed and are falling (the optimistic case, expected to fall forever)
+ newi != _type ) {
+ if( !newi.isConstant() && (!(_type instanceof TypeInteger oldi) || newi._widen <= oldi._widen) )
+ return newi.same_but_slightly_wider_than(
+ declared instanceof TypeInteger ? declared : TypeInteger.BOT);
+ }
+
+ return newt;
+ }
+
+ // Ordinary Phis have no parser-supplied type constraint.
+ protected Type declaredType() { return Type.BOTTOM; }
+ protected Type constrain(Type t) { return t; }
+
+ @Override
+ public Node idealize() {
+ if( !(region() instanceof RegionNode r ) )
+ return in(1); // Input has collapse to e.g. starting control.
+ if( r.inProgress() || r.nIns()<=1 || 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)) - we are losing the cast JOIN effects, so just remove.
+ if( nIns()==3 ) {
+ if( in(1) instanceof GuardNode cast && cast._nonZero && in(2)._type.makeZero()==in(2)._type && cast.in(1)!=this ) return cast.in(1);
+ if( in(2) instanceof GuardNode cast && cast._nonZero && in(1)._type.makeZero()==in(1)._type && cast.in(1)!=this ) return cast.in(1);
+ }
+
+ // If merging a null-checked null and the checked value, just use the value.
+ // if( val ) ..; phi(Region,False=0/null,True=val);
+ // then replace with plain val.
+ if( nIns()==3 ) {
+ int nullx = -1;
+ if( in(1)._type == in(1)._type.makeZero() ) nullx = 1;
+ if( in(2)._type == in(2)._type.makeZero() ) nullx = 2;
+ if( nullx != -1 ) {
+ Node val = in(3-nullx);
+ if( val instanceof GuardNode cast && cast._nonZero )
+ val = cast.in(1);
+ Node ridom = r.idom(this);
+ if( ridom instanceof IfNode iff && addDep(iff.pred())==val ) {
+ // Must walk the idom on the null side to make sure we hit False.
+ CFGNode idom = r.cfg(nullx);
+ while( idom != null && idom.nIns() > 0 && idom.in(0) != iff ) idom = idom.idom();
+ if( idom instanceof CProjNode proj && proj._idx==1 )
+ return val;
+ } else if( ridom != null ) addDep(ridom);
+ }
+ }
+
+ return 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 busy=null;
+ for( int i=1; i<nIns(); i++ ) {
+ Node op = in(i);
+ if( op.nOuts()==0 )
+ { op.kill(); return false; }
+ if( in(1).getClass() != op.getClass() || op.in(0)!=null || in(1).nIns() != op.nIns() )
+ return false; // Wrong class or CFG bound or mismatched inputs
+ if( in(1) instanceof MemOpNode mem ) {
+ // Mismatched aliases
+ if( mem._alias != ((MemOpNode)op)._alias ) return false;
+ // Load is clobbered somewhere, and can not be pulled forward past the Phi?
+ if( mem instanceof LoadNode )
+ for( Node use : op.outs() )
+ if( use instanceof StoreNode )
+ return false;
+ }
+ if( in(1) instanceof EscapeNode )
+ return false;
+ if( in(1) instanceof MemMergeNode )
+ return false; // Have to keep aliases straight
+ if( op.nOuts() > 1 ) {
+ if( busy==null ) busy = op;
+ else { // Too many users, but addDep in case lose users
+ for( Node out : op._outputs )
+ if( out!=null && out!=this )
+ addDep(out);
+ for( Node out : busy._outputs )
+ if( out!=null && out!=this )
+ addDep(out);
+ return false;
+ }
+ }
+ for( int j=1; j<in(1).nIns(); j++ )
+ if( op.in(j) instanceof ScopeNode || (op.in(j)==null ^ in(1).in(j)==null) )
+ return false; // Lazy Phi input
+ }
+ return true;
+ }
+
+ private Node drop_same_op() {
+ assert !(in(1) instanceof CFGNode);
+ Node op = in(1);
+ Node cp = op.copy();
+ cp._type = null; // Fresh type
+ cp.addDef(null); // No control
+
+ for( int j=1; j<op.nIns(); j++ ) {
+ boolean needsPhi = false;
+ Node x = op.in(j); // Jth input from sample #1
+ for( int i=2; i<nIns(); i++ )
+ if( in(i).in(j) != x )
+ { needsPhi=true; break; }
+ if( needsPhi ) {
+ x = make(_label,op.in(j)._type.glb(false));
+ x.addDef(region());
+ for( int i=1; i<nIns(); i++ )
+ x.addDef(in(i).in(j));
+ x = x.peephole();
+ }
+ cp.addDef(x);
+ }
+ // Test not running backwards, which can happen for e.g. And's
+ if( cp.compute().isa(compute()) )
+ return cp;
+ 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 && _type != TypeScalar.BOT ) return null;
+
+ // Scalar BOT means we mixed scalar families, e.g. int and ptr.
+ // 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 || in(i)._type == TypeScalar.BOT )
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/Proj.java b/chapter25/src/main/java/com/seaofnodes/simple/node/Proj.java
new file mode 100644
index 00000000..c8b9378f
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java
new file mode 100644
index 00000000..97b9ed45
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java
@@ -0,0 +1,71 @@
+package com.seaofnodes.simple.node;
+
+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.BitSet;
+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
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ if( _label != null ) return sb.append(_label);
+ if( in(0) instanceof CallEndNode cend && cend.call()!=null )
+ return cend.call()._print0(sb,visited);
+ return sb.append("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 boolean isPinned() { return true; }
+
+ @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); }
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java b/chapter25/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java
new file mode 100644
index 00000000..9a9b94fd
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java
new file mode 100644
index 00000000..d781620b
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java
@@ -0,0 +1,40 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.TypeMemPtr;
+
+import java.util.BitSet;
+
+/**
+ * Reinterpret a non-null managed pointer as its raw integer address.
+ *
+ * This node changes representation families but emits no code. In particular,
+ * it is not a {@link CheckCastNode}: 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 public String glabel() { return "(i64*)"; }
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return in(1)._print0(sb.append("(i64*)"),visited);
+ }
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java
new file mode 100644
index 00000000..a35f5a1d
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java
new file mode 100644
index 00000000..c36be85a
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java
@@ -0,0 +1,30 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMemPtr;
+import java.util.BitSet;
+
+/**
+ * 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
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return in(1)._print0(sb.append("(const)"),visited);
+ }
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/RegionNode.java
new file mode 100644
index 00000000..dc48f6f2
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/RegionNode.java
@@ -0,0 +1,175 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.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 Parser.Lexer _loc;
+
+ public RegionNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; }
+ public RegionNode(RegionNode r, Parser.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
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append(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 &&
+ addDep(p1.in(0))==addDep(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);
+ }
+ }
+ return isDead() ? CodeGen.CODE.XCTRL : delDef(path);
+ }
+
+ 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( CodeGen.CODE.validIDepth(_idepth) )
+ return _idepth;
+ int d=0;
+ for( Node n : _inputs )
+ if( n!=null )
+ d = Math.max(d,CodeGen.CODE.iDepthFrom(((CFGNode)n).idepth()));
+ return _idepth = d;
+ }
+
+ @Override public CFGNode idom(Node dep) {
+ CFGNode lca = null;
+ // Walk the LHS & RHS idom trees in parallel until they match, or either fails.
+ // Because this does not cache, it can be linear in the size of the program.
+ for( int i=1; i<nIns(); i++ )
+ if( !cfg(i)._type.isHigh() )
+ lca = cfg(i)._idom(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/chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
new file mode 100644
index 00000000..93cf1b91
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
@@ -0,0 +1,134 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.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;
+import java.util.BitSet;
+
+/**
+ * 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
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("return ");
+ if( expr()==null ) sb.append("----");
+ else expr()._print0(sb, visited);
+ return sb.append(";");
+ }
+
+ // No one unique control follows; can be many call end sites
+ @Override public CFGNode uctrl() { return null; }
+
+ @Override
+ public Type compute() {
+ return TypeTuple.make(ctrl()._type,mem()._type,expr()._type);
+ }
+
+ @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));
+
+ // If dead (cannot be reached; infinite loop), kill the exit values
+ if( ctrl()._type==Type.XCONTROL &&
+ !(mem() instanceof ConstantNode && expr() instanceof ConstantNode) ) {
+ Node top = ConstantNode.make(Type.TOP).peephole();
+ setDef(1,top);
+ setDef(2,top);
+ return this;
+ }
+
+ return null;
+ }
+
+ @Override public Parser.ParseException err() {
+ if( ctrl()._type == Type.CONTROL &&
+ expr()._type == Type.TOP )
+ return Parser.error("No defined return type",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/chapter25/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter25/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java
new file mode 100644
index 00000000..322a6bc0
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java
@@ -0,0 +1,37 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+
+import java.util.BitSet;
+
+public class RoundF32Node extends Node {
+ public RoundF32Node(Node lhs) { super(null, lhs); }
+ @Override public Tag serialTag() { return Tag.RoundF32; }
+
+ @Override public String glabel() { return "(f32)"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return in(1)._print0(sb.append("((f32)"), visited).append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if (in(1)._type instanceof TypeFloat i0 && i0.isConstant() )
+ return TypeFloat.constant((float)i0.value());
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java
new file mode 100644
index 00000000..8ad5988d
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java
@@ -0,0 +1,38 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+public class SarNode extends ArithNode {
+ public SarNode(Parser.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 public String glabel() { return "&gt;&gt;"; }
+
+ @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 )
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
new file mode 100644
index 00000000..58508a1c
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -0,0 +1,562 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.Var;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.ParseAll;
+import com.seaofnodes.simple.codegen.CompUnit;
+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 Func(String name) { _name=name;} public final String _name; }
+
+ 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(true);
+ _vars = new Ary<>(Var .class);
+ _kinds = new Ary<>(Kind.class);
+ _guards = new Ary<>(Node.class);
+ }
+
+ @Override public String label() { return "Scope"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("Scope[ ");
+ int j=1;
+ for( int i=0; i<nIns(); i++ ) {
+ if( j < depth() && i == _kinds.at(j)._lexSize ) { sb.append("| "); j++; }
+ Var v = var(i);
+ sb.append(v._type());
+ sb.append(" ");
+ if( v._final ) sb.append("!");
+ sb.append(v._name);
+ sb.append("=");
+ Node n = in(i);
+ while( n instanceof ScopeNode loop ) {
+ sb.append("Lazy_");
+ n = loop.in(i);
+ }
+ if( n==null ) sb.append("___");
+ else n._print0(sb, visited);
+ sb.append(", ");
+ }
+ sb.setLength(sb.length()-2);
+ return sb.append("]");
+ }
+
+
+ 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 Kind kind( Var v ) {
+ int kx = kindx(v);
+ return kx == -1 ? null : _kinds.at(kx);
+ }
+
+ 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);
+ }
+
+ // 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, Parser.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. Insert a forward ref outside any
+ // enclosing Kind.Func scope, skipping any nested Blocks or Allocs and
+ // return the Var. May insert at the outermost scope, which means this
+ // must be defined externally, or it's an error.
+
+ public Var defineFRef( String id, Type t, boolean xfinal, Parser.Lexer loc ) {
+ // Kind/Lexical scope index
+ int kidx = enclosingFunction();
+ int idx = _kinds.at(kidx)._lexSize;
+ Var var = new Var(idx,id,t,xfinal,loc,true);
+ FRefNode fref = new FRefNode(id,loc).init();
+ fref._type = fref._con = t;
+ // Insert in the lex scope just prior to kidx
+ insert(var,fref,kidx);
+ 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 && !var(i)._final ? 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, Parser.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);
+ }
+ }
+
+
+ // ------------------------------------------------
+ // 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( var(i)._final ) continue; // Final vars did not get modified in the loop
+ Node n = in(i);
+ if( var(i).type().isHighOrConst() ) { // Cannot lift higher than a constant, so no Phi
+ n.subsume(n=ConstantNode.make(var(i).type()).init());
+ } 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.
+ public void addGuards( Node ctrl, Node pred, boolean invert ) {
+ assert ctrl instanceof CFGNode;
+ _guards.add(ctrl); // Marker to distinguish 0,1,2 guards
+ // add pred & its cast to the normal input list, with special Vars
+ if( pred==null || pred.isDead() )
+ return; // Dead, do not add any guards
+ // 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 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);
+ }
+ }
+
+
+ // Remove matching pred/cast pairs from this guarded region.
+ public void removeGuards( Node ctrl ) {
+ assert ctrl instanceof CFGNode;
+ // 0,1 or 2 guards
+ 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 ) {
+ assert old!=null && old!=cast;
+ for( int i=0; i<nIns(); i++ )
+ if( in(i)==old )
+ setDef(i,cast);
+ }
+
+ // ------------------------------------------------------------------------
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java
new file mode 100644
index 00000000..459b26eb
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java
@@ -0,0 +1,51 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.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(Parser.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 public String glabel() { return "&lt;&lt;"; }
+
+ @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 )
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java
new file mode 100644
index 00000000..5e4870d1
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java
@@ -0,0 +1,34 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+public class ShrNode extends ArithNode {
+ public ShrNode(Parser.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 public String glabel() { return "&gt;&gt;&gt;"; }
+
+ @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 )
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/SplitNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/SplitNode.java
new file mode 100644
index 00000000..4b58187f
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/SplitNode.java
@@ -0,0 +1,29 @@
+package com.seaofnodes.simple.node;
+
+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;
+import java.util.BitSet;
+
+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; }
+ @Override public String op() { return "mov"; }
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("mov(");
+ if( in(1) == null ) sb.append("---");
+ else in(1)._print0(sb,visited);
+ return sb.append(")");
+ }
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/StartCUNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StartCUNode.java
new file mode 100644
index 00000000..d828c5f1
--- /dev/null
+++ b/chapter25/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 CodeGen.CODE.iDepthAt(1); }
+ @Override public CFGNode idom(Node dep) { return cfg0(); }
+ @Override public void gather( HashMap<String,Integer> strs ) {
+ Serialize.gather(strs,_fname);
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StartNode.java
new file mode 100644
index 00000000..7d2b2773
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StartNode.java
@@ -0,0 +1,87 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Utils;
+import java.util.Arrays;
+import java.util.BitSet;
+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
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append(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 && fun.isModInit() )
+ { assert C==null; C = fun; }
+ if( C==null )
+ for( FunNode fun : CodeGen.CODE._linker )
+ if( fun!=null && !fun.isDead() && fun.isModInit() )
+ { assert C==null; C = fun; }
+ return C;
+ }
+
+ boolean escapedFIDX(int fidx) {
+ // Public class fields are callable by the unknown external world
+ // regardless of the current SCCP approximation of Start memory.
+ if( CodeGen.CODE.publicFIDX(fidx) ) return true;
+ if( !(_type instanceof TypeTuple tt) )
+ return !_type.isHigh(); // TOP never escapes, BOTTOM always escapes
+ TypeMem tmem = (TypeMem)tt._types[1];
+ if( tmem == TypeMem.BOT ) return false;
+ if( tmem == TypeMem.TOP ) return false;
+ if( XInt.isHigh(tmem._escFs) )
+ 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 CodeGen.CODE.iDepthAt(0); }
+ @Override public CFGNode idom(Node dep) { return null; }
+
+ @Override public boolean eq( Node n ) { return true; }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
new file mode 100644
index 00000000..ab0f5da6
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
@@ -0,0 +1,122 @@
+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 = TypeMem.START;
+ // Escape summaries are monotone discovery facts. In Opto, a Return
+ // can widen from a precise set to FULL; the public-only normalization
+ // below must not forget private bits already discovered precisely from
+ // that Return on an earlier SCCP step.
+ if( CodeGen.CODE._phase == CodeGen.Phase.Opto &&
+ _type instanceof TypeTuple old && old.mem() instanceof TypeMem omem )
+ xmem = xmem.makeFrom(XInt.meet(xmem._escFs,omem._escFs),
+ XInt.meet(xmem._escAs,omem._escAs));
+
+ 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);
+ // Instance <init> is an allocation helper, never an externally
+ // callable constructor when a user constructor exists. Otherwise
+ // the same no-arg <init> is the public constructor.
+ boolean pub = fun.isPublic() && (fun.isClz() || noUserCtor);
+ 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()) ) {
+ 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 = xmem.escapes(tret.ret());
+ xmem = publicBottom(xmem);
+ xmem = (TypeMem)xmem.meet(publicBottom(tret.mem(),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.
+ private TypeMem publicBottom(TypeMem mem) { return publicBottom(mem,mem); }
+ private 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 != CodeGen.Phase.Opto ) 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);
+ }
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StopNode.java
new file mode 100644
index 00000000..45f5519e
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StopNode.java
@@ -0,0 +1,100 @@
+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.BitSet;
+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();
+ _type = compute();
+ }
+ 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(nIns()); }
+ static Node make( BAOS bais ) {
+ StopNode stop = new StopNode();
+ stop.setDefX(bais.packed1()-1,null);
+ return stop;
+ }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ // 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 ret1._print0(sb,visited);
+
+ sb.append("Stop[ ");
+ for( Node n : _inputs )
+ if( n instanceof ReturnNode ret ) {
+ String name = ret.fun()._name;
+ if( name== null || !name.startsWith("sys.") )
+ ret._print0(sb, visited).append(" ");
+ }
+ return sb.append("]");
+ }
+
+ @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( _idepth!=0 ) 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 _idepth=d;
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java
new file mode 100644
index 00000000..c0006511
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -0,0 +1,321 @@
+package com.seaofnodes.simple.node;
+
+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.BitSet;
+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(Parser.Lexer loc, String name, int alias, Type glb, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init) {
+ this(loc,name,alias,glb,ctrl,mem,ptr,off,value,init,(byte)0);
+ }
+ private StoreNode(Parser.Lexer loc, String name, int alias, Type glb, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init, byte size) {
+ super(loc, name, alias, false, glb, 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);
+ }
+
+ // GraphVis DOT code and debugger labels
+ @Override public String label() { return "st_"+mlabel(); }
+ // GraphVis node-internal labels
+ @Override public String glabel() { return "." +_name+"="; }
+ @Override public boolean isMem() { return true; }
+
+ public Node nnptr() { return ptr() instanceof GuardNode cast && cast._nonZero ? cast.in(1) : ptr(); }
+ public Node val() { return in(4); }
+ public int storeSize() { assert _size==1 || _size==2 || _size==4 || _size==8; return _size; }
+ @Override public int log_size() { return Integer.numberOfTrailingZeros(storeSize()); }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append(".").append(_name).append("=").append( val()).append(";");
+ }
+
+ @Override
+ public Type compute() {
+ Type val = val()._type;
+ Type mem0 = aliasMem()._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 && decl != Type.TOP )
+ val = val.join(decl);
+ //if( err()!=null )
+ // val = Type.BOTTOM;
+ // 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.
+ Field fld = ptr._obj.field(_name);
+ 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() {
+ assert !(mem() instanceof CheckCastNode);
+
+ // 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.
+ Type storage = storageType();
+ if( _size==0 && storage!=Type.BOTTOM && storage!=Type.TOP ) {
+ unlock(); // Width participates in GVN identity.
+ _size = storeSize(storage);
+ return init();
+ }
+
+ // A precise Store can confirm its alias from a precise memory input.
+ // Do not sharpen an alias-#1 (whole-memory) Store in place: users of
+ // that Store already treat it as the complete memory state. The
+ // pointer/field path below replaces it with a precise Store wrapped in
+ // a MemMerge, preserving any other precise aliases in its input.
+ if( mem()._type instanceof TypeMem mem ) {
+ assert mem._alias > 0;
+ if( _alias != 1 && mem._alias != 1 )
+ assert _alias == mem._alias;
+ }
+
+ // Forward-ref loads eventually sharpen to a declared type
+ Field fld;
+ if( _alias==1 && ptr()._type instanceof TypeMemPtr tmp && (fld=tmp._obj.field(_name)) != null ) {
+ // 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 stores 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.
+ Type decl = storageType();
+ byte size = _size==0 && decl!=Type.BOTTOM && decl!=Type.TOP ? storeSize(decl) : _size;
+ 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;
+ 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(false,null,mem());
+ if( !(mem() instanceof BulkMemPhiNode) )
+ mmm.alias(fld._alias,st);
+ return mmm.init();
+ }
+ // Expose the same effective memory input already observed by compute.
+ Node aliasMem = aliasMem();
+ if( aliasMem != mem() ) {
+ setDef(1,aliasMem);
+ CodeGen.CODE.add(aliasMem);
+ return this;
+ }
+
+ // 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;
+ }
+
+ // Simple store-after-MemMerge to a known alias can bypass. Happens when inlining.
+ if( _alias != 1 && mem() instanceof MemMergeNode mem ) {
+ setDef(1,mem.alias(_alias));
+ return this;
+ }
+
+ // Resolve the semantic Store variant exactly once, after structural
+ // memory/alias peeps. This avoids publishing a half-sharpened Store
+ // while BulkMemPhi is constructing precise slices.
+ if( storage != Type.BOTTOM && storage != Type.TOP )
+ assert _size==storeSize(storage) : "Store width changed for '"+_name+"': "+_size+" from "+storage;
+
+ // Value is automatically truncated by narrow store
+ if( _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( _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;
+ }
+
+ private static byte storeSize(Type decl) {
+ // `decl` is the target field declaration, not the stored value. In
+ // particular a constant zero stored into u32 is still a 4-byte Store;
+ // taking the constant's GLB here would incorrectly select i64.
+ byte size = (byte)(1 << decl.log_size());
+ assert size==1 || size==2 || size==4 || size==8;
+ return size;
+ }
+
+ // 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 Type storageType() {
+ // Freshly parsed field stores retain the declaring structure on their
+ // symbolic offset even after the pointer's flow type sharpens.
+ if( off() instanceof ConFldOffNode coff ) {
+ Field fld = coff._ts.field(_name);
+ if( fld != null && fld._t != null ) return fld._t;
+ }
+ if( !(ptr()._type instanceof TypeMemPtr tmp) ) return Type.BOTTOM;
+ Type base = Parser.TYPES.get(tmp._obj._name);
+ TypeStruct obj = base instanceof TypeStruct ts ? ts : tmp._obj;
+ Field fld = obj.field(_name);
+ // Imported optimized structs can expose the current constant field
+ // value instead of its declaration. A constant has no storage width;
+ // an imported Store already carries its serialized, frozen `_size`.
+ return fld == null || fld._t == null || fld._t.isConstant() ? Type.BOTTOM : fld._t;
+ }
+
+ // 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;
+ };
+ }
+
+ // Select a precise alias from a whole-memory partition. Alias #1 is the
+ // bulk remainder itself.
+ private static Node memSlice( Node mem, int alias ) {
+ return alias != 1 && mem instanceof MemMergeNode merge
+ ? merge.alias(alias)
+ : mem;
+ }
+
+ // Semantic memory input for this Store. Compute defines behavior from
+ // this view; ideal exposes the same edge in the graph.
+ private Node aliasMem() {
+ Node mem = mem();
+ if( _alias != 1 && mem instanceof MemMergeNode merge )
+ mem = merge.alias(_alias);
+ return mem;
+ }
+
+ // 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;
+ if( _size==0 && CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Opto.ordinal() )
+ throw Utils.TODO("Failed to decide Store size");
+ if( ptr()._type == Type.TOP )
+ return null; // This means we have an error input, report elsewhere
+ TypeMemPtr tmp = (TypeMemPtr)ptr()._type;
+ Field f = tmp._obj.field(_name);
+ if( f!=null && f._final && !_init )
+ return Parser.error("Cannot modify final field '"+_name+"'",_loc);
+ return null;
+ }
+
+ @Override public boolean eq(Node n) { return _size==((StoreNode)n)._size && super.eq(n); }
+ @Override int hash() { return super.hash() ^ _size; }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StructNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StructNode.java
new file mode 100644
index 00000000..4dcc695f
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StructNode.java
@@ -0,0 +1,60 @@
+package com.seaofnodes.simple.node;
+
+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;
+import java.util.IdentityHashMap;
+
+/**
+ * Build a compound object
+ */
+public class StructNode extends Node {
+
+ public final TypeStruct _ts;
+ public StructNode(TypeStruct ts) { _ts=ts; assert !ts._open; }
+ @Override public Tag serialTag() { return Tag.Struct; }
+ @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));
+ //baos.packed2(types.get(_type)); // Write _type not _minType, which can be higher
+ throw Utils.TODO();
+ }
+ static Node make( BAOS bais, Type[] types) {
+ //Node[] ins = new Node[bais.packed1()];
+ //return new StructNode(types[bais.packed2()], ins);
+ throw Utils.TODO();
+ }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ if( _ts==null ) return sb.append("STRUCT?");
+ sb.append(_ts._name).append(" { ");
+ for( int i=0; i<nIns(); i++ ) {
+ sb.append(_ts._fields[i]._fname).append(":");
+ sb.append(in(i)==null ? Type.BOTTOM : in(i)._type);
+ sb.append("; ");
+ }
+ sb.setLength(sb.length()-2);
+ return sb.append("}");
+ }
+
+ @Override
+ public TypeStruct compute() {
+ if( _ts==null ) return TypeStruct.BOT;
+ Field[] fs = new Field[_ts._fields.length];
+ for( int i=0; i<fs.length; i++ )
+ fs[i] = _ts._fields[i].makeFrom(in(i)==null ? Type.TOP : in(i)._type);
+ return TypeStruct.make(_ts._name,false,fs);
+ }
+
+ @Override
+ public Node idealize() { return null; }
+
+ @Override
+ public boolean eq(Node n) { return _ts == ((StructNode)n)._ts; }
+
+ @Override
+ int hash() { return _ts==null ? 0 : _ts.hashCode(); }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/SubNode.java
new file mode 100644
index 00000000..aba1df90
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -0,0 +1,45 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.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() {
+ // x - (-y) is x+y
+ if( in(2) instanceof MinusNode minus )
+ return new AddNode(in(1),minus.in(1));
+
+ // (-x) - y is -(x+y)
+ if( in(1) instanceof MinusNode minus )
+ return new MinusNode(new AddNode(minus.in(1),in(2)).peephole());
+
+ return super.idealize();
+ }
+
+ @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs,_mode); }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java
new file mode 100644
index 00000000..2d8cc788
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+import com.seaofnodes.simple.type.TypeInteger;
+
+import java.util.BitSet;
+
+public class ToFloatNode extends Node {
+ public ToFloatNode(Node lhs) { super(null, lhs); }
+ @Override public Tag serialTag() { return Tag.ToFloat; }
+
+ @Override public String glabel() { return "(flt)"; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return in(1)._print0(sb.append("(flt)"), visited);
+ }
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/TypeNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/TypeNode.java
new file mode 100644
index 00000000..3ab2d6f9
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/TypeNode.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.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/chapter25/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
new file mode 100644
index 00000000..a8c06b37
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
@@ -0,0 +1,15 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import java.util.BitSet;
+
+public class XCtrlNode extends CFGNode {
+ public XCtrlNode() { super(new Node[]{CodeGen.CODE._start}); }
+ @Override public Tag serialTag() { return Tag.XCtrl; }
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Xctrl"); }
+ @Override public boolean isConst() { return true; }
+ @Override public Type compute() { return Type.XCONTROL; }
+ @Override public Node idealize() { return null; }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java
new file mode 100644
index 00000000..95d4461e
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java
@@ -0,0 +1,44 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+public class XorNode extends ArithNode {
+ public XorNode(Parser.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 public String glabel() { 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 )
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java
new file mode 100644
index 00000000..1c865254
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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,arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java
new file mode 100644
index 00000000..eb75c1a0
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.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;
+
+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,arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java
new file mode 100644
index 00000000..93c9e8c3
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java
@@ -0,0 +1,27 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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), arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java
new file mode 100644
index 00000000..77115d54
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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,arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java
new file mode 100644
index 00000000..5b739cd3
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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), arm.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(arm.decodeImm12(_imm));
+ }
+} \ No newline at end of file
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java
new file mode 100644
index 00000000..b83fb3cb
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java
@@ -0,0 +1,18 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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,arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java
new file mode 100644
index 00000000..38373e67
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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(arm.imm_shift(arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java
new file mode 100644
index 00000000..5cf8a89c
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java
@@ -0,0 +1,79 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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( arm.b_cond(arm.OP_BRANCH, 0, arm.make_condition(_bop, _fp)) );
+ } else {
+ if( _bop=="!=" ) return; // Inverted, no code
+ enc.add4(arm.b(arm.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,arm.b_cond(arm.OP_BRANCH, delta, arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java
new file mode 100644
index 00000000..8f070525
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java
@@ -0,0 +1,49 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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(arm.b(arm.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,arm.b(arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java
new file mode 100644
index 00000000..c84aa9e5
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class CallRRARM extends CallNode implements MachNode {
+ CallRRARM(CallNode call) { super(call); }
+ @Override public String op() { return "callr"; }
+ @Override public String label() { return op(); }
+ @Override public RegMask regmap(int i) {
+ return i==_inputs._len
+ ? 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(this);
+ enc.add4(arm.blr(arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java
new file mode 100644
index 00000000..794ff0c9
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.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;
+
+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,arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java
new file mode 100644
index 00000000..67ffc27b
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java
new file mode 100644
index 00000000..f84a1649
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java
@@ -0,0 +1,40 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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), arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java
new file mode 100644
index 00000000..de254eeb
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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,arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java
new file mode 100644
index 00000000..813bebe4
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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,arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java
new file mode 100644
index 00000000..42553868
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java
@@ -0,0 +1,44 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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(arm.load_pc(arm.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, arm.load_pc(arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java
new file mode 100644
index 00000000..44a417f9
--- /dev/null
+++ b/chapter25/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.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);
+ 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(arm.imm_inst(arm.OPI_ADD, -sz&0xFFF, arm.RSP, arm.RSP));
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java
new file mode 100644
index 00000000..f1fa162f
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.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.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 = arm.float_cast(arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java
new file mode 100644
index 00000000..dbed9487
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java
@@ -0,0 +1,70 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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();
+ int nb0 = 0;
+ int nb1 = 0;
+ // Count number of 0000 and FFFF blocks
+ for (int i=0; i<64; i+=16) {
+ int block = (int)(x >> i) & 0xFFFF;
+ if (block == 0) nb0++;
+ if (block == 0xFFFF) nb1++;
+ }
+ int pattern;
+ int op;
+ if(nb0 >= nb1) {
+ // More 0 blocks then F blocks, use movz
+ pattern = 0;
+ op = arm.OP_MOVZ;
+ } else {
+ // More F blocks then 0 blocks, use movn
+ pattern = 0xFFFF;
+ op = arm.OP_MOVN;
+ }
+ int invert = pattern;
+ for (int i=0; i<4; i++) {
+ int block = (int)x & 0xFFFF;
+ x >>= 16;
+ if (block != pattern) {
+ enc.add4(arm.mov(op, i, block ^ invert, self));
+ op = arm.OP_MOVK;
+ invert = 0;
+ }
+ }
+ if (op != arm.OP_MOVK) {
+ // All blocks are the same, special case
+ enc.add4(arm.mov(op, 0, 0, self));
+ }
+ }
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
new file mode 100644
index 00000000..f8c9bfef
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
@@ -0,0 +1,39 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.LoadNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.type.TypeFloat;
+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 arm.MEM_MASK; }
+
+ private static final int[] OP_LOADS = new int[]{ arm.OP_LOAD_IMM_8, arm.OP_LOAD_IMM_16, arm.OP_LOAD_IMM_32, arm.OP_LOAD_IMM_64, };
+ private int imm_op() {
+ return _con == TypeFloat.F32 ? arm.OPF_LOAD_IMM_32
+ : _con == TypeFloat.F64 ? arm.OPF_LOAD_IMM_64
+ : OP_LOADS[_con.log_size()];
+ }
+
+ private static final int[] OP_LOAD_RS = new int[]{ arm.OP_LOAD_R_8 , arm.OP_LOAD_R_16 , arm.OP_LOAD_R_32 , arm.OP_LOAD_R_64, };
+
+ // ldr(immediate - unsigned offset) | ldr(register)
+ @Override public void encoding( Encoding enc ) {
+ ldst_encode(enc, imm_op(), 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java
new file mode 100644
index 00000000..80ce493f
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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,arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java
new file mode 100644
index 00000000..7fe16cbe
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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(arm.imm_shift(arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java
new file mode 100644
index 00000000..fc005e04
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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,arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java
new file mode 100644
index 00000000..78844b0a
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java
@@ -0,0 +1,28 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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(arm.imm_shift(arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java
new file mode 100644
index 00000000..08bdf974
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java
@@ -0,0 +1,71 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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;
+import java.lang.StringBuilder;
+import java.util.BitSet;
+
+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 public StringBuilder _printMach(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); }
+
+ @Override public Type compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+
+ 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 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);
+ int body = off() == null
+ ? arm.load_str_imm(opcode_imm, _off, ptr, val, size)
+ : arm.indr_adr(opcode_reg, off, arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java
new file mode 100644
index 00000000..a23ec75b
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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,arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java
new file mode 100644
index 00000000..73ebbf00
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java
@@ -0,0 +1,18 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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,arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java
new file mode 100644
index 00000000..881b41f9
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java
new file mode 100644
index 00000000..df2a6bc9
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java
@@ -0,0 +1,38 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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(arm.mov(arm.OP_MOVZ, 0, 1, 0)). // movz x0,#1
+ add4(arm.b(arm.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, arm.b_calloc(arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
new file mode 100644
index 00000000..d78421ca
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
@@ -0,0 +1,27 @@
+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.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 = arm.imm_inst(arm.OP_SUBS, 0, reg1, self);
+ enc.add4(subs);
+ int cset = arm.cond_set(arm.OP_CSET, 31, arm.COND.EQ, 31, reg1);
+ enc.add4(cset);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java
new file mode 100644
index 00000000..e8245749
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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,arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java
new file mode 100644
index 00000000..a48caa51
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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), arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java
new file mode 100644
index 00000000..b419fe0a
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java
new file mode 100644
index 00000000..4b7d6f13
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java
new file mode 100644
index 00000000..2136ebf5
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java
@@ -0,0 +1,15 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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(arm.imm_inst(arm.OPI_ADD, (frameAdjust*-8)&0xFFF, arm.RSP, arm.RSP));
+ enc.add4(arm.ret(arm.OP_RET));
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java
new file mode 100644
index 00000000..c597b9dd
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java
@@ -0,0 +1,35 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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 = arm.cset(arm.OP_CSET,0b11111, arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java
new file mode 100644
index 00000000..be975fb6
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java
@@ -0,0 +1,59 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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(arm.load_str_imm(arm.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(arm.load_str_imm(arm.OP_LOAD_IMM_64, off, arm.RSP, dst, 8));
+ return;
+ }
+
+ // pick opcode based on regs
+ if(!dstX && !srcX) {
+ // GPR->GPR
+ enc.add4(arm.mov_reg(arm.OP_MOV, src, dst));
+ } else if(dstX && srcX) {
+ // FPR->FPR
+ // fmov reg
+ enc.add4(arm.f_mov_reg(arm.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(arm.f_mov_general(arm.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(arm.f_mov_general(arm.OP_FMOV, 0b01, 0, 0b110, src - arm.D_OFFSET, dst));
+ }
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java
new file mode 100644
index 00000000..b11a2c89
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java
@@ -0,0 +1,43 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.node.StoreNode;
+import com.seaofnodes.simple.type.TypeFloat;
+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[]{ arm.OP_STORE_IMM_8, arm.OP_STORE_IMM_16, arm.OP_STORE_IMM_32, arm.OP_STORE_IMM_64, };
+ private int imm_op() {
+ return val()!=null && val()._type instanceof TypeFloat
+ ? (_bytes==4 ? arm.OPF_STORE_IMM_32 : arm.OPF_STORE_IMM_64)
+ : OP_STORES[Integer.numberOfTrailingZeros(_bytes)];
+ }
+
+ private static final int[] OP_STORE_RS = new int[]{ arm.OP_STORE_R_8, arm.OP_STORE_R_16, arm.OP_STORE_R_32, arm.OP_STORE_R_64, };
+
+ @Override public void encoding( Encoding enc ) {
+ ldst_encode(enc, imm_op(), 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java
new file mode 100644
index 00000000..e3b95e50
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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,arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java
new file mode 100644
index 00000000..a252e542
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java
@@ -0,0 +1,18 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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,arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java
new file mode 100644
index 00000000..b50107c9
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java
@@ -0,0 +1,28 @@
+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 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), arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java
new file mode 100644
index 00000000..9d490c8b
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java
@@ -0,0 +1,50 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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(arm.adrp(1,0, arm.OP_ADRP, 0,dst));
+ // add x0, x0, 0
+ arm.imm_inst(enc,arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java
new file mode 100644
index 00000000..f86d85dc
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java
@@ -0,0 +1,48 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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 ) throw Utils.TODO();
+ enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1);
+ short dst = enc.reg(this);
+ // adrp x0, 0
+ enc.add4(arm.adrp(1,0, arm.OP_ADRP, 0,dst));
+ // add x0, x0, 0
+ arm.imm_inst(enc,arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java
new file mode 100644
index 00000000..2dd5fd6b
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java
@@ -0,0 +1,49 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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;
+import java.util.BitSet;
+
+// unconditional jump
+public class UJmpARM extends CFGNode implements MachNode, RIPRelSize {
+ UJmpARM() { }
+ @Override public String op() { return "jmp"; }
+ @Override public String label() { return op(); }
+ @Override public StringBuilder _print1( StringBuilder sb, BitSet visited ) {
+ return sb.append("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 = arm.b(arm.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,arm.b(arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java
new file mode 100644
index 00000000..52bda021
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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,arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java
new file mode 100644
index 00000000..23b29644
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java
@@ -0,0 +1,26 @@
+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.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), arm.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
new file mode 100644
index 00000000..852fbdb1
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -0,0 +1,875 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+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);
+
+ // major opcode: OP
+ public static int OP_ADD = 0b10_001_011;
+ public static int OPF_ADD = 0b00_011_110;
+ public static int OPF_OP_ADD = 0b00_101_0;
+ public static int OPI_ADD = 0b10_010_00100;
+
+ public static int OPI_SUB = 0b11_010_00100;
+ public static int OP_UJMP = 0b000101;
+
+ public static int OP_ADRP = 0b10000;
+
+ public static int OP_ASR = 0b1010;
+ public static int OPI_ASR = 0b10_0100_1101;
+
+ public static int OP_LSL = 0b1000;
+ public static int OPI_LSL =0b1101001101;
+
+ public static int OP_LSR = 0b1001;
+ public static int OPI_LSR = 0b1101001101;
+
+ public static int OP_BRANCH = 0b01_0101_00;
+ public static int OP_CALL = 0b10_010_1;
+
+ public static int OP_CALLRARM = 0b1101011000111111000000;
+
+ public static int OP_SUBS = 0b1111000100;
+ public static int OP_CSET = 0b10011010100;
+ public static int OP_CMP = 0b11101011;
+ public static int OPI_CMP = 0b1111000100;
+
+ public static int OP_DIV = 0b10011010110;
+ public static int OPF_DIV = 0b000110;
+
+ public static int OP_MUL = 0b10011011000;
+ public static int OPF_MUL = 0b10;
+
+ public static int OPF_ARM = 0b01011100;
+
+ public static int OP_FLOAT_C = 0b10011110;
+
+ public static int OP_XOR = 0b11001010;
+ public static int OPI_XOR = 0b110100100;
+
+ public static int OP_AND = 0b10_0010_10;
+ public static int OPI_AND = 0b10_0100_100;
+
+ public static int OP_OR = 0b10101010;
+ public static int OPI_OR = 0b101100100;
+
+ public static int OP_SUB = 0b11001011;
+ public static int OPF_SUB = 0b1110;
+
+ public static int OP_MOVK = 0b111100101;
+ public static int OP_MOVN = 0b100100101;
+ public static int OP_MOVZ = 0b110100101;
+
+ public static int OP_RET = 0b1101011001011111000000;
+ // Store/load opcodes
+ public static int OP_LOAD_R_64 = 0b11111000011;
+ public static int OP_LOAD_R_32 = 0b10111000011;
+ public static int OP_LOAD_R_16 = 0b01111000010;
+ public static int OP_LOAD_R_8 = 0b00111000011;
+
+ public static int OP_LOAD_IMM_64 = 0b1111100101;
+ public static int OP_LOAD_IMM_32 = 0b1011100101;
+ public static int OP_LOAD_IMM_16 = 0b0111100101;
+ public static int OP_LOAD_IMM_8 = 0b0011100101;
+
+ // load
+ public static int OPF_LOAD_R_64 = 0b11111100011;
+ public static int OPF_LOAD_R_32 = 0b10111100011;
+
+ public static int OPF_LOAD_IMM_64 = 0b1111110101;
+ public static int OPF_LOAD_IMM_32 = 0b1011110101;
+
+ public static int OP_STORE_R_64 = 0b11111000001;
+ public static int OP_STORE_R_32 = 0b10111000001;
+ public static int OP_STORE_R_16 = 0b01111000001;
+ public static int OP_STORE_R_8 = 0b00111000001;
+
+ public static int OP_STORE_IMM_64 = 0b1111100100;
+ public static int OP_STORE_IMM_32 = 0b1011100100;
+ public static int OP_STORE_IMM_16 = 0b0111100100;
+ public static int OP_STORE_IMM_8 = 0b0011100100;
+
+ public static int OPF_STORE_R_64 = 0b11111100001;
+ public static int OPF_STORE_R_32 = 0b10111100001;
+
+ public static int OPF_STORE_IMM_32 = 0b1111110100;
+ public static int OPF_STORE_IMM_64 = 0b1111110100;
+
+
+ public static int OP_FMOV = 0b10011110;
+ public static int OP_FMOV_REG = 0b00011110;
+ // https://docsmirror.github.io/A64/2023-06/mov_orr_log_shift.html
+ public static int OP_MOV = 0b10101010000;
+ // 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
+ public enum OPTION {
+ UXTB,
+ UXTH,
+ UXTX,
+ SXTB,
+ SXTH,
+ SXTW,
+ SXTX,
+ }
+
+ static public int cset(int opcode, int rm, COND cond, int rn, int rd) {
+ assert 0 <= rm && rm < 32;
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ // Is one of the standard conditions, excluding AL and NV, encoded in the "cond" field with its least significant bit inverted.
+ int actualCond = cond.ordinal() ^ 1;
+ return (opcode << 21) | (rm << 16) | (actualCond << 12) | (0b01 << 10) | (rn << 5) | rd;
+ }
+
+ static public int cset(int opcode, COND cond, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ return (opcode << 16) | (cond.ordinal() << 12) | (rn << 5) | rd;
+ }
+
+ public enum STORE_LOAD_OPTION {
+ UXTW, // base+ u32 index [<< logsize]
+ UXTX, // base+ u64 index [<< logsize]
+ SXTW, // base+ s32 index [<< logsize]
+ SXTX, // base+ s64 index [<< logsize]
+ }
+
+ public enum COND {
+ EQ,
+ NE,
+ CS,
+ CC,
+ MI,
+ PL,
+ VS,
+ VC,
+ HI,
+ LS,
+ GE,
+ LT,
+ GT,
+ LE,
+ AL,
+ NV
+ }
+
+ // 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) {
+ if( !ti.isConstant() ) return -1;
+ if( !ti.isConstant() ) return -1;
+ long val = ti.value();
+ if (val == 0 || val == -1) return -1; // Special cases are not allowed
+ int immr = 0;
+ // Rotate until we have 0[...]1
+
+ // Rotate until:
+ // The number is negative (MSB is 1)
+ // Or the LSB is not 1
+ while (val < 0 || (val & 1)==0) {
+ // circular rotation
+ val = (val >>> 63) | (val << 1);
+ immr++;
+ }
+ // Start by assuming that val might be made of two 32-bit chunks that repeat.
+ int size = 32;
+ long pattern = val;
+ // Is upper half of pattern the same as the lower?
+ while ((pattern & ((1L<<size)-1)) == (pattern >> size)) {
+ // Then only take one half
+ pattern >>= size;
+ size >>= 1;
+ }
+ size <<= 1;
+ int imms = Long.bitCount(pattern);
+ // Pattern should now be zeros followed by ones 0000011111
+ if (pattern != (1L<<imms)-1) return -1;
+ imms--;
+ if (size == 64) return 0x1000 | immr << 6 | imms;
+ return (32-size)<<1 | immr << 6 | imms;
+ }
+
+ // sh is encoded in opcode
+ public static int imm_inst(int opcode, int imm12, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ assert opcode >=0 && imm12 >= 0; // Caller zeros high order bits
+ return (opcode << 22) | (imm12 << 10) | (rn << 5) | rd;
+ }
+
+ public static int imm_shift(int opcode, int imm, int imms, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ return (opcode << 22) | (1 << 22) | (imm << 16) | (imms << 10) | (rn << 5) | rd;
+ }
+
+ 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);
+
+ int body = 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 = 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 = 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 = imm_inst_n(opcode, imm13, reg1, self);
+ enc.add4(body);
+ }
+
+ // nth bit comes from immediate and not opcode
+ public static int imm_inst_n(int opcode, int imm13, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ assert 0 <= imm13 && imm13 <= 0x1FFF;
+ return (opcode << 23) | (imm13 << 10) | (rn << 5) | rd;
+ }
+
+ public static int imm_inst_l(int opcode, int imm12, int self) {
+ int body = imm_inst(opcode, imm12&0xFFF, self, self);
+ return 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 = imm_inst(opcode, imm12&0xFFF, reg1, self);
+ return body;
+ }
+
+ // for normal add, reg1, reg2 cases (reg-to-reg)
+ // using shifted-reg form
+ public static int r_reg(int opcode, int shift, int rm, int imm6, int rn, int rd) {
+ assert 0 <= rm && rm < 32;
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ return (opcode << 24) | (shift << 21) | (rm << 16) | (imm6 << 10) | (rn << 5) | rd;
+ }
+ 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 = 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 = r_reg(opcode, 0, reg2, 0, reg1, 31);
+ enc.add4(body);
+ }
+
+ public static int shift_reg(int opcode, int rm, int op2, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rm && rm < 32;
+ assert 0 <= rd && rd < 32;
+ return (opcode << 21) | (rm << 16) | (op2 << 10) | (rn << 5) | rd;
+ }
+ 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 = 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 int madd(int opcode, int rm, int ra, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ assert 0 <= rm && rm < 32;
+ return (opcode << 21) | (rm << 16) | (ra << 10) | (rn << 5) | rd;
+ }
+ 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 = madd(opcode, reg2, ra, reg1, self);
+ enc.add4(body);
+ }
+
+ // encodes movk, movn, and movz
+ public static int mov(int opcode, int shift, int imm16, int rd) {
+ assert 0 <= rd && rd < 32;
+ return (opcode << 23) | (shift << 21) | (imm16 << 5) | rd;
+ }
+
+ public static int mov_reg(int opcode, int src, int rd) {
+ assert 0 <= rd && rd < 32;
+ return (opcode << 21) | (src << 16) | 0b11111 << 5 | rd;
+ }
+
+ public static int ret(int opcode) {
+ return (opcode << 10);
+ }
+
+ // FMOV (scalar, immediate)
+ public static int f_scalar(int opcode, int ftype, int rm, int op, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ assert 0 <= rm && rm < 32;
+ return (opcode << 24) | (ftype << 22) | (1 << 21) | (rm << 16) | (op << 10) | (rn << 5) | rd;
+ }
+
+ public static int f_mov_reg(int opcode, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ return (opcode << 24) | (0b01100000010000 << 10) | (rn << 5) | rd;
+ }
+ public static int f_mov_general(int opcode, int ftype, int rmode, int opcode1, int rn, int rd) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rd && rd < 32;
+ return (opcode << 24) |(ftype << 22) | (1 << 21) | (rmode << 19) | (opcode1 << 16) | (rn << 5) | rd;
+ }
+ 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 = f_scalar(0b00011110, 1, reg2, op, reg1, self);
+ enc.add4(body);
+ }
+
+ // share same encoding with LDR (literal, SIMD&FP) flavour
+ public static int load_pc(int opcode, int offset, int rt) {
+ offset>>=2;
+ return (opcode << 24) | (offset << 5) | rt;
+ }
+ // int l
+ public static int adrp(int op, int imlo,int opcode, int imhi, int rd) {
+ assert 0 <= rd && rd < 32;
+ assert 0 <= imlo && imlo <= 0x3; // 2 bits
+ assert 0 <= imhi && imhi <= 0x7FFFF; // 19 bits
+ return (op << 31) | (imlo << 29) |(opcode << 24) | (imhi << 5) | rd;
+ }
+
+ 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 , adrp(1, imm21 & 3, OP_ADRP, imm21 >>> 2, rd));
+ enc.patch4(opStart+4, imm_inst_l(OPI_ADD, target & 0xFFF, rd));
+ }
+
+ // [Rptr+Roff]
+ public static int indr_adr(int opcode, int off, STORE_LOAD_OPTION option, int s, int ptr, int rt) {
+ assert 0 <= ptr && ptr < 32;
+ assert 0 <= rt && rt < 32;
+ return (opcode << 21) | (off << 16) | (option.ordinal() << 13) | (s << 12) | (2 << 10) | (ptr << 5) | rt;
+ }
+ // [Rptr+imm9]
+ public static int load_str_imm(int opcode, int imm12, int ptr, int rt, int size) {
+ assert 0 <= ptr && ptr < 32;
+ assert 0 <= rt && rt < 32;
+
+ if(size == 8) imm12 = imm12 >> 3;
+ if(size == 4) imm12 = imm12 >> 2;
+ if(size == 2) imm12 = imm12 >> 1;
+ // size == 1 imm12 = imm12
+ return (opcode << 22) | ((imm12) << 10) | (ptr << 5) | rt;
+ }
+
+ // encoding for vcvt, size is encoded in operand
+ // <Qd>, <Qm>
+ // F32.S32
+ //encoded as op = 0b00, size = 0b10.
+ // VCVT<c>.<Td>.<Tm> <Dd>, <Dm>
+ // opcode is broken down into 4 pieces
+ public static int f_convert(int opcode_1, int opcode_2, int opcode_3, int opcode_4, int vd, int vm) {
+ return (opcode_1 << 28) | (opcode_2 << 24) | (opcode_3 << 20) | (opcode_4 << 16) |
+ (vd << 12) | (0x01100010 << 4) | vm;
+ }
+ public static int float_cast(int opcode, int ftype, int rn, int rd) {
+ assert 0 <= rd && rd < 32;
+ assert 0 <= rn && rn < 32;
+ return (opcode << 24) | (ftype << 22) | (2176 << 10) | (rn << 5) | rd;
+ }
+
+
+ // ftype = 3
+ public static int f_cmp(int opcode, int ftype, int rm, int rn) {
+ assert 0 <= rn && rn < 32;
+ assert 0 <= rm && rm < 32;
+ // Todo: |8 is not needed
+ return (opcode << 24) | (ftype << 21) | (rm << 16) | (8 << 10) | (rn << 5);
+ }
+ 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 = f_cmp(0b00011110, 3, reg2, reg1);
+ enc.add4(body);
+ }
+
+ public static COND make_condition(String bop, boolean fp) {
+ // One of {N,Z,C} and !V
+ // OR { !N, !Z, C, V }
+ return fp
+ ? switch (bop) { // FP uses unsigned encodings
+ case "==" -> COND.EQ; // Z
+ case "!=" -> COND.NE; // !Z
+ case "<" -> COND.MI; // N
+ case "<=" -> COND.LS; // Z || !C
+ case ">=" -> COND.PL; // !N
+ case ">" -> COND.HI; // !Z && C
+ default -> throw Utils.TODO();
+ }
+ : switch (bop) {
+ case "==" -> COND.EQ; // Z
+ case "!=" -> COND.NE; // !Z
+ case "<" -> COND.LT; // N != V
+ case "<=" -> COND.LE; // Z || N != V
+ case ">=" -> COND.GE; // N == V
+ case ">" -> COND.GT; // !Z && N == V
+ default -> throw Utils.TODO();
+ };
+ }
+
+ public static int b_cond(int opcode, int delta, COND cond) {
+ // 24-5 == 19bits offset range
+ assert -(1<<19) <= delta && delta < (1<<19);
+ assert (delta&3)==0;
+ delta>>=2;
+ delta &= (1L<<19)-1; // Zero extend
+ return (opcode << 24) | ((delta)<< 5) | cond.ordinal();
+ }
+
+ public static int cond_set(int opcode, int rm, COND cond, int rn, int rd) {
+ assert 0 <= rd && rd < 32;
+ assert 0 <= rn && rn < 32;
+ return (opcode << 21) | (rm << 16) | (cond.ordinal() << 12) | (rn << 5) | rd;
+ }
+
+ // Branch with Link to Register calls a subroutine at an address in a register, setting register X30 to PC+4.
+ public static int blr(int opcode, int rd) {
+ assert 0 <= rd && rd < 32;
+ return opcode << 10 | rd << 5;
+ }
+ public static int b(int opcode, int delta) {
+ assert -(1<<26) <= delta && delta < (1<<26);
+ assert (delta&3)==0;
+ delta>>=2;
+ delta &= (1L<<26)-1; // Zero extend
+ return (opcode << 26) | delta;
+ }
+ // no aligned assert(assert (delta&3)==0;)
+ public static int b_calloc(int opcode, int delta) {
+ assert -(1<<26) <= delta && delta < (1<<26);
+ delta>>=2;
+ delta &= (1L<<26)-1; // Zero extend
+ return (opcode << 26) | delta;
+ }
+
+ public static long decodeImm12(int imm12) {
+ int immr = (imm12 >> 6) & 0x3F;
+ int imms = imm12 & 0x3F;
+ int size;
+ if ((imm12 & 0x1000) != 0) {
+ size = 64;
+ } else {
+ size = 31-(imms >> 1);
+ size |= size >> 1;
+ size |= size >> 2;
+ size |= size >> 4;
+ size++;
+ imms &= ~((32-size) << 1);
+ }
+ long val = (2L << imms)-1;
+ while (size < 64) {
+ val |= val << size;
+ size <<= 1;
+ }
+ val = (val >>> immr) | val << (64-immr);
+ return val;
+ }
+
+ @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) { 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 CheckCastNode cast -> new CheckCastMach(cast);
+ case PtrToIntNode ptr -> new PtrToIntMach(ptr);
+ case GuardNode guard -> new GuardMach(guard);
+ case CallEndNode cend-> new CallEndMach(cend);
+ 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 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) {
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java
new file mode 100644
index 00000000..1c157bc1
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java
@@ -0,0 +1,41 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java
new file mode 100644
index 00000000..8636df9f
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.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;
+
+// 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java
new file mode 100644
index 00000000..511b0583
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java
new file mode 100644
index 00000000..7010916f
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java
new file mode 100644
index 00000000..842c8bdc
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java
@@ -0,0 +1,12 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java
new file mode 100644
index 00000000..1845985e
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java
new file mode 100644
index 00000000..b7284265
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java
@@ -0,0 +1,93 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+import java.util.BitSet;
+
+// 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 public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("if( ");
+ if( in(1)==null ) sb.append("0");
+ else in(1)._print0(sb,visited);
+ sb.append(_bop);
+ if( in(2)==null ) sb.append("0");
+ else in(2)._print0(sb,visited);
+ return sb.append(" )");
+ }
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java
new file mode 100644
index 00000000..8e1b4a48
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java
@@ -0,0 +1,61 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java
new file mode 100644
index 00000000..7369f9f5
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java
@@ -0,0 +1,31 @@
+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 CallRRISC extends CallNode implements MachNode {
+ CallRRISC( CallNode call ) { super(call); }
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java
new file mode 100644
index 00000000..68ed6f6c
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java
new file mode 100644
index 00000000..515ffa47
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java
new file mode 100644
index 00000000..c1a9b018
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java
@@ -0,0 +1,47 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java
new file mode 100644
index 00000000..6fce6244
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java
new file mode 100644
index 00000000..db0547fe
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java
new file mode 100644
index 00000000..f2beb25c
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java
@@ -0,0 +1,53 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+import java.util.BitSet;
+
+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 public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ int imm12 = (_imm12<<20)>>20; // Sign extend 12 bits
+ in(1)._print0(sb.append("( "), visited);
+ return sb.append(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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java
new file mode 100644
index 00000000..e6b4c4ed
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java
@@ -0,0 +1,48 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java
new file mode 100644
index 00000000..b795ba96
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java
@@ -0,0 +1,30 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java
new file mode 100644
index 00000000..7333f2b7
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java
new file mode 100644
index 00000000..592cdb84
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java
@@ -0,0 +1,64 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java
new file mode 100644
index 00000000..79f22fb8
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java
@@ -0,0 +1,50 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+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.*;
+import java.util.BitSet;
+
+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 public StringBuilder _printMach(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); }
+
+ @Override public Type compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java
new file mode 100644
index 00000000..5cc40079
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.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;
+
+// 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java
new file mode 100644
index 00000000..9cb9ace7
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java
new file mode 100644
index 00000000..d8183d09
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.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;
+
+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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java
new file mode 100644
index 00000000..4a6583ce
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java
@@ -0,0 +1,48 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
new file mode 100644
index 00000000..1369ee76
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.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));
+ enc.add4(riscv.i_type(riscv.OP_IMM, dst, 2, src, 1));
+ }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java
new file mode 100644
index 00000000..963b9d50
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java
@@ -0,0 +1,12 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java
new file mode 100644
index 00000000..a310a1f4
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java
new file mode 100644
index 00000000..0f8db4e5
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java
new file mode 100644
index 00000000..2f9ac469
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java
new file mode 100644
index 00000000..7e4631d3
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java
new file mode 100644
index 00000000..3424cd01
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
new file mode 100644
index 00000000..690e2102
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
@@ -0,0 +1,17 @@
+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 ) {
+ super(src,imm12);
+ _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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java
new file mode 100644
index 00000000..848cc59f
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java
new file mode 100644
index 00000000..12c0ddec
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java
@@ -0,0 +1,12 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java
new file mode 100644
index 00000000..5a4d4cc6
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java
new file mode 100644
index 00000000..58fa8a4b
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java
@@ -0,0 +1,60 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java
new file mode 100644
index 00000000..fcfa0774
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java
@@ -0,0 +1,14 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java
new file mode 100644
index 00000000..e2e34415
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java
new file mode 100644
index 00000000..2f73e139
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java
@@ -0,0 +1,12 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java
new file mode 100644
index 00000000..1eccc1c8
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
new file mode 100644
index 00000000..ebd6a5ae
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
@@ -0,0 +1,53 @@
+package com.seaofnodes.simple.node.cpus.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 riscv.MEM_MASK; // Wide mask to store GPR and FPR
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java
new file mode 100644
index 00000000..da3e92f4
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.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;
+
+// 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java
new file mode 100644
index 00000000..2adfce23
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java
new file mode 100644
index 00000000..1f269152
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java
@@ -0,0 +1,55 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java
new file mode 100644
index 00000000..aa001e1e
--- /dev/null
+++ b/chapter25/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.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 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 ) throw Utils.TODO();
+ 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>>12));
+ // Load dst,[dst+#low12_constant_pool]
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java
new file mode 100644
index 00000000..c4d5d8af
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java
@@ -0,0 +1,47 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+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;
+import java.util.BitSet;
+
+// unconditional jump
+public class UJmpRISC extends CFGNode implements MachNode, RIPRelSize {
+ @Override public String op() { return "jmp"; }
+ @Override public String label() { return op(); }
+ @Override public StringBuilder _print1( StringBuilder sb, BitSet visited ) { return sb.append("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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java
new file mode 100644
index 00000000..19ea6bc7
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java
@@ -0,0 +1,13 @@
+package com.seaofnodes.simple.node.cpus.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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java
new file mode 100644
index 00000000..413a7c3b
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
new file mode 100644
index 00000000..078a7693
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -0,0 +1,566 @@
+package com.seaofnodes.simple.node.cpus.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
+ };
+
+ // major opcode: OP
+ public static int OP_LOAD = 0b00_000_11;
+ public static int OP_LOADFP = 0b00_001_11;
+ public static int OP_CUSTOM0 = 0b00_010_11;
+ public static int OP_IMM = 0b00_100_11;
+ public static int OP_AUIPC = 0b00_101_11;
+
+ public static int OP_STORE = 0b01_000_11;
+ public static int OP_STOREFP = 0b01_001_11;
+ public static int OP_CUSTOM1 = 0b01_010_11;
+ public static int OP = 0b01_100_11;
+ public static int OP_LUI = 0b01_101_11;
+
+ public static int OP_CUSTOM2 = 0b10_010_11;
+ public static int OP_FP = 0b10_100_11;
+
+ public static int OP_BRANCH = 0b11_000_11;
+ public static int OP_JALR = 0b11_001_11;
+ public static int OP_RESERVED= 0b11_010_11;
+ public static int OP_JAL = 0b11_011_11;
+
+
+ // Since riscv instructions are fixed we can just or them together
+ public static int r_type(int opcode, int rd, int func3, int rs1, int rs2, int func7) {
+ assert 0 <= rs1 && rs1 < 32;
+ assert 0 <= rs2 && rs2 < 32;
+ assert 0 <= rd && rd < 32;
+ return (func7 << 25) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (rd << 7) | opcode;
+ }
+ 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 = 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 = r_type(OP_FP,dst,func3.ordinal(),src1,src2,func7);
+ enc.add4(body);
+ }
+
+
+ public static int u_type(int opcode, int rd, int imm20) {
+ assert 0 <= rd && rd < 32;
+ return (imm20 << 12) | (rd << 7) | opcode;
+ }
+
+ public static int j_type(int opcode, int rd, int delta) {
+ assert -(1L<<20) <= delta && delta < (1L<<20);
+ assert 0 <= rd && rd < 32;
+ // Messy branch offset encoding
+ // 31 30-21 20 19-12 11-7 6-0
+ // 20 10- 1 11 19-12 rpc JAL
+ assert (delta&1)==0; // Low bit is always zero, not encoded
+ int imm10_01 = (delta>> 1) & 0x3FF;
+ int imm11 = (delta>>11) & 1;
+ int imm12_19 = (delta>>12) & 0xFF;
+ int imm20 = (delta>>19) & 1;
+ int bits = imm20<<19 | imm10_01 << 9 | imm11 << 8 | imm12_19;
+ return bits << 12 | rd << 7 | opcode;
+ }
+
+
+ public static int i_type(int opcode, int rd, int func3, int rs1, int imm12) {
+ assert 0 <= rd && rd < 32;
+ assert 0 <= rs1 && rs1 < 32;
+ assert opcode >= 0 && func3 >=0 && imm12 >= 0; // Zero-extend by caller
+ return (imm12 << 20) | (rs1 << 15) | (func3 << 12) | (rd << 7) | opcode;
+ }
+
+ // S-type instructions(store)
+ public static int s_type(int opcode, int func3, int rs1, int rs2, int imm12) {
+ assert 0 <= rs1 && rs1 < 32;
+ assert 0 <= rs2 && rs2 < 32;
+ assert 0 <= func3;
+
+ assert imm12 >= 0; // Masked to high zero bits by caller
+ int imm_lo = imm12 & 0x1F;
+ int imm_hi = imm12 >> 5;
+ return (imm_hi << 25) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (imm_lo << 7) | opcode;
+ }
+
+ // BRANCH
+ public static int b_type(int opcode, int func3, short rs1, short rs2, int delta) {
+ assert 0 <= rs1 && rs1 < 32;
+ assert 0 <= rs2 && rs2 < 32;
+ assert -4*1024 <= delta && delta < 4*1024;
+ assert (delta&1)==0; // Low bit is always zero, not encoded
+ // Messy branch offset encoding
+ // 31 30 29 28 27 26 25 24-20 19-15 14-12 11 10 9 8 7 6-0
+ // 12 10 9 8 7 6 5 SRC2 SRC1 FUNC3 4 3 2 1 11 OP
+ int imm4_1 = (delta>> 1) & 0xF;
+ int imm10_5= (delta>> 5) &0x3F;
+ int imm11 = (delta>>11) & 1;
+ int imm12 = (delta>>12) & 1;
+ int imm5 = imm4_1<<1 | imm11;
+ int imm7 = imm12<<6 | imm10_5;
+ return (imm7 << 25 ) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (imm5 << 7) | opcode;
+ }
+
+ public enum RM {
+ RNE, // Round to Nearest, ties to Even
+ RTZ, // Round towards Zero
+ RDN, // Round Down
+ RUP, // Round Up
+ DIRECT, // Round to Nearest, ties to Max Magnitude
+ RESERVED1, // Reserved for futue use
+ RESERVED2, // Reserved for future use
+ DYN, // In instruction’s rm field, selects dynamic rounding mode; In Rounding Mode register, reserved
+ }
+
+ // Since opcode is the same just return back func3
+ static public int jumpop(String op) {
+ return switch(op) {
+ case "==" -> 0x0;
+ case "!=" -> 0x1;
+ case "<" -> 0x4;
+ case ">=" -> 0x5;
+ case "u<" -> 0x6;
+ case "u<=" -> 0x7;
+ default -> throw Utils.TODO();
+ };
+ }
+
+ // Since opcode is the same just return back func3
+ static public int fsetop(String op) {
+ return switch(op) {
+ case "<" -> 1;
+ case "<=" -> 0;
+ case "==" -> 2;
+ default -> throw Utils.TODO();
+ };
+ }
+
+ @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 CheckCastNode cast -> new CheckCastMach(cast);
+ case PtrToIntNode ptr -> new PtrToIntMach(ptr);
+ case GuardNode guard -> new GuardMach(guard);
+ case CallEndNode cend -> new CallEndMach(cend);
+ 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 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);
+ 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) {
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java
new file mode 100644
index 00000000..093e2bd2
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java
@@ -0,0 +1,44 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ // addsd xmm0, DWORD PTR [rdi+0xc]
+ short dst = enc.reg(this );
+ short ptr = enc.reg(ptr());
+ short idx = enc.reg(idx());
+ // F opcode
+ enc.add1(0xF2);
+ // rex prefix must come next (REX.W is not set)
+ x86_64_v2.rexF(dst, ptr, idx, false, enc);
+
+ // FP ADD
+ enc.add1(0x0F).add1(0x58);
+
+ x86_64_v2.indirectAdr(_scale, idx, ptr, _off, dst, enc);
+ }
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java
new file mode 100644
index 00000000..02e0c7ea
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ // F2 0F 58 /r ADDSD xmm1, xmm2/m64
+ short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET);
+ short src = (short)(enc.reg(in(2)) - x86_64_v2.XMM_OFFSET);
+ // Fopcode
+ enc.add1(0xF2);
+ // rex prefix must come next (REX.W is not set)
+ x86_64_v2.rexF(dst, src, 0, false, enc);
+
+ enc.add1(0x0F).add1(0x58);
+
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java
new file mode 100644
index 00000000..0ba390c6
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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
+ enc.add1( x86_64_v2.rex(0,dst,0));
+ enc.add1(0xFF);
+ enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, _imm==1 ? 0 : 1, dst) );
+ } else super.encoding(enc);
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java
new file mode 100644
index 00000000..3ee721e3
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java
@@ -0,0 +1,34 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ // add something to register from memory
+ // add eax,DWORD PTR [rdi+0xc]
+ // REX.W + 03 /r ADD r64, r/m64
+ short dst = enc.reg(this );
+ short ptr = enc.reg(ptr());
+ short idx = enc.reg(idx());
+
+ enc.add1(x86_64_v2.rex(dst, ptr, idx));
+ // opcode
+ enc.add1(0x03);
+
+ x86_64_v2.indirectAdr(_scale, idx, ptr, _off, dst, enc);
+ }
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java
new file mode 100644
index 00000000..84543e8f
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java
new file mode 100644
index 00000000..3c1b567c
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java
new file mode 100644
index 00000000..dedbce45
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java
new file mode 100644
index 00000000..14b8e4da
--- /dev/null
+++ b/chapter25/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.simple.codegen.*;
+import com.seaofnodes.simple.node.CallNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.util.SB;
+
+public class CallRX86 extends CallNode implements MachNode {
+ CallRX86( CallNode call ) { super(call); }
+ @Override public String op() { return "callr"; }
+ @Override public String label() { return op(); }
+ @Override public RegMask regmap(int i) {
+ return i==_inputs._len
+ ? 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 ) {
+ // FF /2 CALL r/m64
+ // calls the function in the register
+ short src = enc.reg(fptr());
+ enc.add1(0xFF);
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT,2,src));
+ }
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java
new file mode 100644
index 00000000..989895b2
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java
@@ -0,0 +1,54 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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);
+ enc.add1(0xe8).add4(0);
+ }
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java
new file mode 100644
index 00000000..5b97ef63
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ // UCOMISD
+ short src1 = (short)(enc.reg(in(1)) - x86_64_v2.XMM_OFFSET);
+ short src2 = (short)(enc.reg(in(2)) - x86_64_v2.XMM_OFFSET);
+ // float opcode
+ enc.add1(0x66);
+ // rex prefix must come next (REX.W is not set)
+ x86_64_v2.rexF(src1, src2, 0, false, enc);
+
+ enc.add1(0x0F).add1(0x2E);
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, src1, src2));
+ }
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java
new file mode 100644
index 00000000..8b8318eb
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java
@@ -0,0 +1,44 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ short dst = enc.reg(in(1)); // Also src1
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ // opcode; 0x81 or 0x83; 0x69 or 0x6B
+ enc.add1( 0x81 + (x86_64_v2.imm8(_imm) ? 2 : 0) );
+ enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 7, dst) );
+ // immediate(4 bytes) 32 bits or (1 byte)8 bits
+ if( x86_64_v2.imm8(_imm) ) enc.add1(_imm);
+ else enc.add4(_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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java
new file mode 100644
index 00000000..24812fea
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java
@@ -0,0 +1,78 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ // REX.W + 81 /7 id CMP r/m64, imm32 | REX.W + 39 /r CMP r/m64,r64
+ // CMP [mem], imm32
+
+ short ptr = enc.reg(ptr());
+ short idx = enc.reg(idx());
+ short src = enc.reg(val());
+
+ if(src == -1) {
+ assert !_swap;
+ int log = _con.log_size();
+ x86_64_v2.rexF(-1, ptr, idx, log==3, enc);
+ switch(log) {
+ case 0: enc.add1(0x80); break;
+ case 1: enc.add1(0x66).add1(0x81); break;
+ case 2: enc.add1(0x81); break;
+ case 3: enc.add1(0x81); break;
+ }
+ x86_64_v2.indirectAdr(_scale, idx, ptr, _off, 7, enc);
+ switch(log) {
+ case 0: enc.add1(_imm); break;
+ case 1: enc.add2(_imm); break;
+ case 2: enc.add4(_imm); break;
+ case 3: enc.add8(_imm); break;
+ }
+ } 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) {
+ if(decl instanceof TypeFloat) {
+ src -= (short)x86_64_v2.XMM_OFFSET;
+ enc.add1(decl==TypeFloat.F32 ? 0xF3 : 0xF2).add1(0x0F).add1(0xC2);
+ } else {
+ int log = decl.log_size();
+ x86_64_v2.rexF(src, ptr, idx, log == 3, enc);
+ int swap = _swap ? 2 : 0;
+ switch(log) {
+ case 0: enc.add1(0x38+swap); break;
+ case 1: enc.add1(0x66).add1(0x39+swap); break;
+ case 2: enc.add1(0x39+swap); break;
+ case 3: enc.add1(0x39+swap); break;
+ }
+ }
+ x86_64_v2.indirectAdr(scale, idx, ptr, off, src, enc);
+ }
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java
new file mode 100644
index 00000000..e7305cdf
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ short dst = enc.reg(in(1));
+ short src = enc.reg(in(2));
+
+ enc.add1(x86_64_v2.rex(dst, src, 0));
+ enc.add1(0x3B);
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java
new file mode 100644
index 00000000..3427aea5
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java
@@ -0,0 +1,34 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ // F2 0F 5E /r DIVSD xmm1, xmm2/m64
+ short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET);
+ short src = (short)(enc.reg(in(2)) - x86_64_v2.XMM_OFFSET);
+
+ // Fopcode
+ enc.add1(0xF2);
+ // rex prefix must come next (REX.W is not set)
+ x86_64_v2.rexF(dst, src, 0, false, enc);
+
+ enc.add1(0x0F).add1(0x5E);
+
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java
new file mode 100644
index 00000000..b5904a7f
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ // REX.W + F7 /7 IDIV r/m64
+ short src = enc.reg(in(2));
+ // sign extend rax before 128/64 bits division
+ // sign extend rax to rdx:rax
+ enc.add1(x86_64_v2.REX_W);
+ enc.add1(0x99);
+ enc.add1(x86_64_v2.rex(0, src, 0));
+ // divide
+ enc.add1(0xF7); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0x07, src));
+ }
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java
new file mode 100644
index 00000000..15e95374
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java
@@ -0,0 +1,50 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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();
+ // Load the constant into a FPR
+ // movsd xmm, [rip + 0]
+ // F2 0F 10 /r MOVSD xmm1, m64
+ _encSize = 1/*Fop*/+0/*REX*/+2/*op*/+1/*MODRM*/+4/*offset*/;
+ short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET);
+ // Fopcode
+ enc.add1(0xF2);
+ // rex prefix must come next (REX.W is not set)
+ _encSize += x86_64_v2.rexF(dst, 0, 0, false, enc);
+ enc.add1(0x0F).add1(0x10);
+ // hard-code rip here
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT, dst, 0x05));
+ enc.add4(0);
+ enc.largeConstant(this,_con,_encSize-4,2/*ELF encoding 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java
new file mode 100644
index 00000000..1724109a
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java
@@ -0,0 +1,35 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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
+ // opcode: 0x83, addi rsp with immediate 8
+ enc.add1( x86_64_v2.REX_W ).add1( x86_64_v2.imm8(sz) ? 0x83 : 0x81 );
+ enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0b101, x86_64_v2.RSP) );
+ if( x86_64_v2.imm8(sz) ) enc.add1(sz);
+ else enc.add4(sz);
+ }
+
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java
new file mode 100644
index 00000000..9f3d7905
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ // F2 0F 2A /r CVTSI2SD xmm1, r32/m32
+ short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET);
+ short src = enc.reg(in(1));
+
+ // Fopcode
+ enc.add1(0xF2);
+ // rex prefix must come next (REX.W is not set)
+ x86_64_v2.rexF(dst, src, 0, true, enc);
+
+ enc.add1(0x0F).add1(0x2A);
+
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java
new file mode 100644
index 00000000..5e406911
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java
@@ -0,0 +1,38 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ short dst = enc.reg(this); // Also src1
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ // opcode; 0x81 or 0x83; 0x69 or 0x6B
+ enc.add1( opcode() + (x86_64_v2.imm8(_imm) ? 2 : 0) );
+ enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, mod(), dst) );
+ // immediate(4 bytes) 32 bits or (1 byte)8 bits
+ if( x86_64_v2.imm8(_imm) ) enc.add1(_imm);
+ else enc.add4(_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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java
new file mode 100644
index 00000000..1973ea4e
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java
@@ -0,0 +1,79 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ short dst = enc.reg(this);
+ // Short form for zero
+ if( _con==Type.NIL || _con==TypeInteger.ZERO ) {
+ // XOR dst,dst. Can skip REX is dst is low 8, makes this a 32b
+ // xor, which will also zero the high bits.
+ if( dst >= 8 ) enc.add1(x86_64_v2.rex(dst, dst, 0));
+ enc.add1(0x33); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst));
+ return;
+ }
+
+ if( _ext!=null ) throw Utils.TODO();
+ // Simply move the constant into a GPR
+ // Conditional encoding based on 64 or 32 bits
+ //REX.W + C7 /0 id MOV r/m64, imm32
+
+ long imm = ((TypeInteger)_con).value();
+ if (Integer.MIN_VALUE <= imm && imm < 0) {
+ // We need sign extension, so use imm32 into 64 bit register
+ // REX.W + C7 /0 id MOV r/m64, imm32
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1(0xC7); // 32 bits encoding
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0x00, dst));
+ enc.add4((int)imm);
+ } else if (0 <= imm && imm <= 0xFFFFFFFFL) {
+ // We want zero extension into 64 bit register
+ // so move 32 bit into 32 bit register which zeros
+ // the upper bits in the 64 bit register.
+ // B8+ rd id MOV r32, imm32
+ if (dst >= 8) enc.add1(0x41);
+ enc.add1(0xB8 + (dst & 0x07));
+ enc.add4((int)imm);
+ } else {
+ // Just write the full 64 bit constant
+ // REX.W + B8+ rd io MOV r64, imm64
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1(0xB8 + (dst & 0x07));
+ enc.add8(imm); // 64 bits encoding
+ }
+ }
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java
new file mode 100644
index 00000000..de27aa5c
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java
@@ -0,0 +1,81 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ int op = x86_64_v2.jumpop(_bop);
+ enc.add1(op-16).add1(0); // Short form jump
+ } else {
+ if( _bop=="!=" ) return; // Inverted, no code
+ enc.add1(0xEB).add1(0); // Never-node
+ }
+ 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; // Inverted never-node, no code
+ return (byte)(x86_64_v2.imm8(delta-2) ? 2 : 6);
+ }
+
+ // 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
+ byte[] bits = enc.bits();
+ if( opLen==2 ) {
+ assert in(1)==null || bits[opStart] == x86_64_v2.jumpop(_bop)-16;
+ delta -= 2; // Offset from opcode END
+ assert (byte)delta==delta;
+ bits[opStart+1] = (byte)delta;
+ } else {
+ assert in(1)==null || bits[opStart] == x86_64_v2.jumpop(_bop)-16;
+ delta -= 6; // Offset from opcode END
+ bits[opStart] = 0x0F;
+ bits[opStart+1] = (byte)(in(1)==null ? 0xE9 : x86_64_v2.jumpop(_bop));
+ enc.patch4(opStart+2,delta);
+ }
+ }
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java
new file mode 100644
index 00000000..2cd6018f
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java
@@ -0,0 +1,55 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ // REX.W + 8D /r LEA r64,m
+ short dst = enc.reg(this);
+ short ptr = enc.reg(in(1));
+ short idx = enc.reg(in(2));
+ // ptr is null
+ // just do: [(index * s) + disp32]
+ if( ptr == -1 ) {
+ enc.add1(x86_64_v2.rex(dst, 0, idx));
+ enc.add1(0x8D); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT, dst, 0x04));
+ enc.add1(x86_64_v2.sib(_scale, idx, x86_64_v2.RBP));
+ enc.add4(_offset);
+ return;
+ }
+
+ enc.add1(x86_64_v2.rex(dst, ptr, idx));
+ enc.add1(0x8D); // opcode
+ // rsp is hard-coded here(0x04)
+ x86_64_v2.indirectAdr(_scale, idx, ptr, _offset, dst, enc);
+ }
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java
new file mode 100644
index 00000000..c55002c1
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java
@@ -0,0 +1,57 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ if( decl == TypeFloat.F32) enc.add1(0xF3);
+ if( decl == TypeFloat.F64) enc.add1(0xF2);
+
+ if( decl.isa(TypeFloat.F64) )
+ dst -= (short)x86_64_v2.XMM_OFFSET;
+
+ x86_64_v2.rexF(dst, ptr, idx, decl != TypeInteger.U32 && decl != TypeFloat.F32 && decl != TypeFloat.F64, enc);
+
+ if( false ) ;
+ else if( decl == TypeFloat.F32 ) enc.add1(0x0F).add1(0x10); // F3 0F 10 /r MOVSS xmm1, m32
+ else if( decl == TypeFloat.F64 ) enc.add1(0x0F).add1(0x10); // F2 0F 10 /r MOVSD xmm1, m64
+ else if( decl.isa(TypeInteger.I8)) enc.add1(0x0F).add1(0xBE); // sign extend: REX.W + 0F BE /r MOVSX r64, r/m8
+ else if( decl == TypeInteger.I16 ) enc.add1(0x0F).add1(0xBF); // sign extend: REX.W + 0F BF /r MOVSX r64, r/m16
+ else if( decl == TypeInteger.I32 ) enc.add1(0x63); // sign extend: REX.W + 63 /r MOVSXD r64, r/m32
+ else if( decl == TypeInteger.U8 ) enc.add1(0x0F).add1(0xB6); // zero extend: REX.W + 0F B6 /r MOVZX r64, r/m8
+ else if( decl == TypeInteger.U16 ) enc.add1(0x0F).add1(0xB7); // zero extend: REX.W + 0F B7 /r MOVZX r64, r/m16
+ // Covers U32, I64/BOT, TMP
+ else if( decl.log_size()>=2 ) enc.add1(0x8B); // zero extend: 8B /r MOV r32, r/m32
+ else throw Utils.TODO();
+
+ // includes modrm internally
+ x86_64_v2.indirectAdr(scale, idx, ptr, off, dst, enc);
+ }
+
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java
new file mode 100644
index 00000000..bd56d022
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java
@@ -0,0 +1,40 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ // add something to memory
+ // REX.W + 01 /r | REX.W + 81 /0 id
+ // ADD [mem], imm32/reg
+ short ptr = enc.reg(ptr());
+ short idx = enc.reg(idx());
+ short src = enc.reg(val());
+
+ enc.add1(x86_64_v2.rex(src, ptr, idx));
+ // opcode
+ enc.add1( src == -1 ? (_imm==1 ? 0xFF : 0x81): 0x01);
+
+ // includes modrm
+ x86_64_v2.indirectAdr(_scale, idx, ptr, _off, src == -1 ? 0 : src, enc);
+ if( src == -1 && _imm!=1 ) enc.add4(_imm);
+ }
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
new file mode 100644
index 00000000..6069c07b
--- /dev/null
+++ b/chapter25/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.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+import java.lang.StringBuilder;
+import java.util.BitSet;
+
+// 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 public StringBuilder _printMach(StringBuilder sb, BitSet visited) {
+ return sb.append(".").append(_name);
+ }
+
+ @Override public String label() { return op(); }
+ @Override public Type compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+
+ // 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 >= 2
+ ? 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java
new file mode 100644
index 00000000..7932c893
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ // F2 0F 59 /r MULSD xmm1,xmm2/m64
+ short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET);
+ short src = (short)(enc.reg(in(2)) - x86_64_v2.XMM_OFFSET);
+
+ // Fopcode
+ enc.add1(0xF2);
+ // rex prefix must come next (REX.W is not set)
+ x86_64_v2.rexF(dst, src, 0, false, enc);
+
+ enc.add1(0x0F).add1(0x59).add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java
new file mode 100644
index 00000000..1e9e708f
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java
@@ -0,0 +1,34 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ short dst = enc.reg(this ); // Also src1
+ short src = enc.reg(in(1));
+ enc.add1(x86_64_v2.rex(dst, src, 0));
+ // opcode; 0x69 or 0x6B
+ enc.add1( 0x69 + (x86_64_v2.imm8(_imm) ? 2 : 0) );
+ enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src) );
+ // immediate(4 bytes) 32 bits or (1 byte)8 bits
+ if( x86_64_v2.imm8(_imm) ) enc.add1(_imm);
+ else enc.add4(_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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java
new file mode 100644
index 00000000..4d2a11ed
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java
@@ -0,0 +1,29 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ // REX.W + 0F AF /r IMUL r64, r/m64
+ short dst = enc.reg(this ); // src1
+ short src = enc.reg(in(2)); // src2
+ enc.add1(x86_64_v2.rex(dst, src, 0));
+ enc.add1(0x0F); // opcode
+ enc.add1(0xAF); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java
new file mode 100644
index 00000000..0c2baced
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ short dst = enc.reg(this );
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1(0xF7); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 3, dst));
+ }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java
new file mode 100644
index 00000000..40a38809
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java
@@ -0,0 +1,26 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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");
+ // ldi rcx,#1 // number of elements to calloc
+ enc.add1(0xB8 + _arg2Reg).add4(1);
+ // E8 cd CALL rel32;
+ enc.add1(0xE8);
+ enc.add4(0); // offset
+ }
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java
new file mode 100644
index 00000000..d9056be9
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java
@@ -0,0 +1,39 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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); // Cleared out by peeps
+ short dst = enc.reg(this );
+ short src = enc.reg(in(1));
+
+ // Pre-zero using XOR dst,dst; since zero'd will not have a
+ // byte-dependency from the setz. Can skip REX is dst is low 8, makes
+ // this a 32b xor, which will also zero the high bits.
+ if( dst >= 8 ) enc.add1(x86_64_v2.rex(dst, dst, 0));
+ enc.add1(0x33); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst));
+
+ // test rdi,rdi
+ enc.add1(x86_64_v2.rex(src, src, 0));
+ enc.add1(0x85);
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, src, src));
+
+ // setz (sete dil)
+ enc.add1(x86_64_v2.rex(dst, 0, 0));
+ enc.add1(0x0F);
+ enc.add1(0x94);
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, 0));
+ }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java
new file mode 100644
index 00000000..15f4824d
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java
new file mode 100644
index 00000000..bc8f0ea8
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java
new file mode 100644
index 00000000..5cfdd804
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java
new file mode 100644
index 00000000..046c7b4c
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java
new file mode 100644
index 00000000..5a475efe
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java
@@ -0,0 +1,27 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ // REX.W + 01 /r
+ short dst = enc.reg(in(1)); // src1
+ short src = enc.reg(in(2)); // src2
+
+ enc.add1(x86_64_v2.rex(dst, src, 0));
+ enc.add1(opcode()); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java
new file mode 100644
index 00000000..a35d43a2
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ enc.add1( x86_64_v2.REX_W ).add1( x86_64_v2.imm8(sz) ? 0x83 : 0x81 );
+ enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0b000, x86_64_v2.RSP) );
+ if( x86_64_v2.imm8(sz) ) enc.add1(sz);
+ else enc.add4(sz);
+ }
+ enc.add1(0xC3);
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java
new file mode 100644
index 00000000..95e0c336
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java
@@ -0,0 +1,39 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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) {
+ short dst = enc.reg(this); // Also src1
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1( opcode());
+
+ enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, mod(), dst) );
+
+ // immediate(4 bytes) 32 bits or (1 byte)8 bits
+ if( x86_64_v2.imm8(_imm) ) enc.add1(_imm);
+ else enc.add4(_imm);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm);
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java
new file mode 100644
index 00000000..4cc8b558
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ short dst = enc.reg(this ); // src1
+ short src = enc.reg(in(2)); // src2
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1(opcode()); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 7, dst));
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java
new file mode 100644
index 00000000..177e35ab
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java
@@ -0,0 +1,55 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ // REX + 0F 94
+ short dst = enc.reg(this );
+
+ // Optional rex, for dst
+ if( dst >= 4 ) enc.add1(x86_64_v2.rex(0, dst, 0, false));
+ enc.add1(0x0F); // opcode
+ enc.add1(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();
+ });
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0, dst));
+
+ // low 8 bites are set, now zero extend for next instruction
+ if( dst >= 4 ) enc.add1(x86_64_v2.rex(dst, dst, 0, false));
+ enc.add1(0x0F); // opcode
+ enc.add1(0xB6); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst));
+ }
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java
new file mode 100644
index 00000000..f7b2f28a
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java
@@ -0,0 +1,39 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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) {
+ short dst = enc.reg(this); // Also src1
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1( opcode());
+
+ enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, mod(), dst) );
+
+ // immediate(4 bytes) 32 bits or (1 byte)8 bits
+ if( x86_64_v2.imm8(_imm) ) enc.add1(_imm);
+ else enc.add4(_imm);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm);
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java
new file mode 100644
index 00000000..5fee72d9
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ short dst = enc.reg(this ); // src1
+ short src = enc.reg(in(2)); // src2
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1(opcode()); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 4, dst));
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java
new file mode 100644
index 00000000..7e0378b1
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java
@@ -0,0 +1,34 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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) {
+ short dst = enc.reg(this); // Also src1
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1( opcode());
+
+ enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, mod(), dst) );
+
+ // immediate(4 bytes) 32 bits or (1 byte)8 bits
+ if( x86_64_v2.imm8(_imm) ) enc.add1(_imm);
+ else enc.add4(_imm);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm);
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java
new file mode 100644
index 00000000..67dcf055
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ short dst = enc.reg(this ); // src1
+ short src = enc.reg(in(2)); // src2
+ enc.add1(x86_64_v2.rex(0, dst, 0));
+ enc.add1(opcode()); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 5, dst));
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
new file mode 100644
index 00000000..bc3d421d
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
@@ -0,0 +1,100 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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; }
+
+ // Need to handle 8 cases: . reg->reg, reg->xmm, reg->flags, xmm->reg, xmm->xmm, xmm->flags, flags->reg, flags->xmm,
+ // flags->flags.
+ @Override public void encoding( Encoding enc ) {
+ // REX.W + 8B /r MOV r64, r/m64
+ short dst = enc.reg(this );
+ short src = enc.reg(in(1));
+ if( dst == x86_64_v2.FLAGS ) {
+ // mov reg, flags
+ // push rcx
+ // popf (Pop the top of the stack into the FLAGS register)
+ // 50+rd PUSH r64
+ x86_64_v2.rexF(0,src,0,false,enc);
+ enc.add1(0x50 + src);
+ // popf
+ enc.add1(0x9D);
+ return;
+ }
+ if( src == x86_64_v2.FLAGS ) {
+ // mov flags, reg
+ // pushf; pop reg
+ enc.add1(0x9C);
+ // 58+ rd POP r64
+ x86_64_v2.rexF(0,dst,0,false, enc);
+ enc.add1(0x58 + dst);
+ return;
+ }
+
+ // Flag for being either XMM or stack
+ boolean dstX = dst >= x86_64_v2.XMM_OFFSET;
+ boolean 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);
+ enc.add1(0xFF);
+ x86_64_v2.indirectAdr(0, (short)-1/*index*/, (short)x86_64_v2.RSP, soff, 6, enc);
+ enc.add1(0x8F);
+ x86_64_v2.indirectAdr(0, (short)-1/*index*/, (short)x86_64_v2.RSP, off, 0, enc);
+ 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;
+ }
+
+ // reg-reg move. Adjust numbering for GPR vs FPR reg set
+ if( dstX ) dst -= (short) x86_64_v2.XMM_OFFSET;
+ if( srcX ) src -= (short) x86_64_v2.XMM_OFFSET;
+
+ // 0x66 if moving between register classes
+ if( dstX ^ srcX ) enc.add1(0x66);
+ if( !dstX && srcX )
+ { short tmp=src; src=dst; dst=tmp; }
+ enc.add1(x86_64_v2.rex(dst, src, 0));
+
+ // pick opcode based on regs
+ if( !dstX && !srcX ) {
+ // reg->reg (MOV r64, r/m64)
+ enc.add1(0x8B);
+ } else if( dstX && srcX ) {
+ // xmm->xmm (NP 0F 28 /r MOVAPS xmm1, xmm2/m128)
+ enc.add1(0x0F);
+ enc.add1(0x28);
+ } else if( dstX && !srcX ) {
+ // reg->xmm (66 REX.W 0F 6E /r MOVQ xmm, r/m64)
+ enc.add1(0x0F);
+ enc.add1(0x6E);
+ } else if( !dstX && srcX ) {
+ // xmm->reg(66 0F 7E /r MOVQ r/m64, xmm)
+ enc.add1(0x0F);
+ enc.add1(0x7E);
+ }
+
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java
new file mode 100644
index 00000000..faedeb8b
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java
@@ -0,0 +1,85 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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;
+import java.util.BitSet;
+
+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 public StringBuilder _printMach(StringBuilder sb, BitSet visited) {
+ Node val = val();
+ sb.append(".").append(_name).append("=");
+ if( val==null ) sb.append(_imm);
+ else val._print0(sb,visited);
+ return sb.append(";");
+ }
+ // Register mask allowed as a result. 0 for no register.
+ @Override public RegMask outregmap() { return null; }
+ @Override public void encoding( Encoding enc ) {
+ // REX.W + C7 /0 id MOV r/m64, imm32 |
+ // REX.W + 89 /r MOV r/m64, r64
+ short ptr = enc.reg(ptr());
+ short idx = enc.reg(idx());
+ short src = enc.reg(val());
+
+ if( src == -1 ) {
+ int log = Integer.numberOfTrailingZeros(_sz-'0');
+ x86_64_v2.rexF(-1, ptr, idx, log==3, enc);
+ switch( log ) {
+ case 0: enc .add1(0xC6); break;
+ case 1: enc.add1(0x66).add1(0xC7); break;
+ case 2: enc .add1(0xC7); break;
+ case 3: enc .add1(0xC7); break;
+ }
+ x86_64_v2.indirectAdr(_scale, idx, ptr, _off, src, enc);
+ switch( log ) {
+ case 0: enc.add1(_imm); break;
+ case 1: enc.add2(_imm); break;
+ case 2: enc.add4(_imm); break;
+ case 3: enc.add4(_imm); break; // Limit of a 4 byte immediate
+ }
+ } 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 ) {
+ int log = decl.log_size();
+ // Float reg being stored
+ if( src >= x86_64_v2.XMM_OFFSET ) {
+ src -= (short)x86_64_v2.XMM_OFFSET;
+ assert log == 2 || log == 3;
+ enc.add1( log==2 ? 0xF3 : 0xF2 );
+ x86_64_v2.rexF(src,ptr,idx,false,enc);
+ enc.add1(0x0F).add1(0x11);
+ } else {
+ // byte stores from sil, dil, bpl, spl need a rex
+ if( log == 0 && src >= x86_64_v2.RSP ) enc.add1(x86_64_v2.rex(src,ptr,idx,false));
+ else x86_64_v2.rexF(src,ptr,idx,log==3,enc);
+ switch( log ) {
+ case 0: enc .add1(0x88); break;
+ case 1: enc.add1(0x66).add1(0x89); break;
+ case 2: enc .add1(0x89); break;
+ case 3: enc .add1(0x89); break;
+ }
+ }
+ x86_64_v2.indirectAdr(scale, idx, ptr, off, src, enc);
+ }
+
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java
new file mode 100644
index 00000000..b6530a87
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) {
+ // F2 0F 5C /r SUBSD xmm1, xmm2/m64
+ short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET);
+ short src = (short)(enc.reg(in(2)) - x86_64_v2.XMM_OFFSET);
+ // Fopcode
+ enc.add1(0xF2);
+ // rex prefix must come next (REX.W is not set)
+ x86_64_v2.rexF(dst, src, 0, false, enc);
+
+ enc.add1(0x0F).add1(0x5C);
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
+ }
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java
new file mode 100644
index 00000000..8da34bf5
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java
new file mode 100644
index 00000000..cbed5996
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java
@@ -0,0 +1,55 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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 ) { super(fptr); _ext = null; }
+ // 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((TypeFunPtr)_con,_ext); }
+ @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); // Internal patch
+ else enc.external(this,_ext); // ELF-file external patch
+ // lea to load function pointer address
+ // lea rax, [rip+disp32]
+ short dst = enc.reg(this);
+ // 0 or 1 for REX depending on the dst.
+ // Zero/sign extend should be fine, so not wide.
+ _opLen = (byte)(6+ x86_64_v2.rexF(dst, 0, 0, true, enc));
+ enc.add1(0x8D); // opcode
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT, dst, 0b101));
+ enc.add4(0);
+ }
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java
new file mode 100644
index 00000000..cc8741b3
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java
@@ -0,0 +1,40 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeMemPtr;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+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 ) {
+ if( _ext!=null ) throw Utils.TODO();
+ short dst = enc.reg(this);
+ // 0 or 1 for REX depending on the dst.
+ _opLen = (byte)(6+ x86_64_v2.rexF(dst, 0, 0, true, enc));
+ enc.add1(0x8D).add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT, dst, 0b101)).add4(0);
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java
new file mode 100644
index 00000000..92d2a87d
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java
@@ -0,0 +1,54 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+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;
+import java.util.BitSet;
+
+// unconditional jump
+public class UJmpX86 extends CFGNode implements MachNode, RIPRelSize {
+ UJmpX86() { }
+ @Override public String op() { return "jmp"; }
+ @Override public String label() { return op(); }
+ @Override public StringBuilder _print1( StringBuilder sb, BitSet visited ) {
+ return sb.append("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());
+ // Short-form jump
+ enc.add1(0xEB).add1(0);
+ //// E9 cd JMP rel32
+ //enc.add1(0xE9).add4(0);
+ }
+
+ // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding
+ @Override public byte encSize(int delta) { return (byte)(x86_64_v2.imm8(delta-2) ? 2 : 5); }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ byte[] bits = enc.bits();
+ if( opLen==2 ) {
+ assert bits[opStart] == (byte)0xEB;
+ delta -= 2; // Offset from opcode END
+ assert (byte)delta==delta;
+ bits[opStart+1] = (byte)delta;
+ } else {
+ assert bits[opStart] == (byte)0xEB;
+ bits[opStart] = (byte)0xE9; // Long form
+ delta -= 5; // Offset from opcode END
+ enc.patch4(opStart+1,delta);
+ }
+ }
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java
new file mode 100644
index 00000000..2d989a50
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java
new file mode 100644
index 00000000..86703903
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
new file mode 100644
index 00000000..a1ad17dc
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
@@ -0,0 +1,680 @@
+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 /* | (1L<<FLAGS)*/ | 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;
+
+ public enum MOD {
+ INDIRECT, // [mem]
+ INDIRECT_disp8, // [mem + 0x12]
+ INDIRECT_disp32, // [mem + 0x12345678]
+ DIRECT, // mem
+ };
+
+ // 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");
+ };
+ }
+
+ public static int modrm(MOD mod, int reg, int m_r) {
+ if( reg == -1 ) reg=0; // Missing reg in this flavor
+ // combine all the bits
+ assert 0 <= reg && reg < 16;
+ assert 0 <= m_r && m_r < 16;
+ return (mod.ordinal() << 6) | ((reg & 0x07) << 3) | m_r & 0x07;
+ }
+
+ // 00 000 000
+ // same bit-layout as modrm
+ public static int sib(int scale, int index, int base) {
+ assert 0 <= base && base < 16;
+ assert 0 <= index && index < 16;
+ return (scale << 6) | ((index & 0x07) << 3) | base & 0x07;
+ }
+
+ // reg1 is reg(R)
+ // reg2 is r/mem(B)
+ // reg3 is X(index)
+ // reg4 is X(base)
+
+ // 0 denotes no direct register
+ public static int rex(int reg, int ptr, int idx, boolean wide) {
+ // assuming 64 bit by default so: 0100 1000
+ assert -1 <= reg && reg < 16;
+ assert -1 <= ptr && ptr < 16;
+ assert -1 <= idx && idx < 16;
+
+ int rex = wide ? REX_W : REX;
+ if( 8 <= reg ) rex |= 0b00000100; // REX.R
+ if( 8 <= ptr ) rex |= 0b00000001; // REX.B
+ if( 8 <= idx ) rex |= 0b00000010; // REX.X
+ return rex;
+ }
+
+ public static int rex(int reg, int ptr, int idx) {
+ return rex(reg, ptr, idx, true);
+ }
+
+ // rex for floats. Return size (0 or 1)
+ // don't need to use REX if 0x40.
+ public static byte rexF(int reg, int ptr, int idx, boolean wide, Encoding enc) {
+ int rex = rex(reg, ptr, idx, wide);
+ if (rex == REX) return 0;
+ enc.add1(rex);
+ return 1;
+ }
+ // Function used for encoding indirect memory addresses
+ // Does not always generate SIB byte e.g index == -1.
+ // -1 denotes empty value, not set - note 0 is different from -1 as it can represent rax.
+ // Looks for best mod locally
+ public static void indirectAdr( int scale, short index, short base, int offset, int reg, Encoding enc ) {
+ // Assume indirect
+ assert 0 <= base && base < 16;
+ assert -1 <= index && index < 16 && index != RSP;
+
+ MOD mod = MOD.INDIRECT;
+ // is 1 byte enough or need more?
+ if( offset != 0 )
+ mod = imm8(offset)
+ ? MOD.INDIRECT_disp8
+ : MOD.INDIRECT_disp32;
+
+ if( mod == MOD.INDIRECT && (base == RBP || base == R13) ) {
+ mod = MOD.INDIRECT_disp8;
+ } else if( index == -1 && (base == RSP || base == R12) ) {
+ index = RSP;
+ }
+
+ if( index == -1 ) {
+ enc.add1(modrm(mod, reg, base));
+ } else {
+ enc.add1(modrm(mod, reg, x86_64_v2.RSP));
+ enc.add1(sib(scale, index, base));
+ }
+
+ if( mod == MOD.INDIRECT_disp8 ) {
+ enc.add1(offset);
+ } else if( mod == MOD.INDIRECT_disp32 ) {
+ enc.add4(offset);
+ }
+ }
+
+
+ // 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 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 CProjNode c -> new CProjNode(c);
+ case CallEndNode cend -> new CallEndMach(cend);
+ case CallNode call -> call(call);
+ case CheckCastNode cast -> new CheckCastMach(cast);
+ 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 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()) &&
+ (val!=null || 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 ) {
+ 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) {
+ val = null;
+ imm = (int) ti.value();
+ assert imm == ti.value(); // In 32-bit range
+ } else {
+ val = xval;
+ }
+ return imm;
+ }
+
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java b/chapter25/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
new file mode 100644
index 00000000..206983a0
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
@@ -0,0 +1,302 @@
+package com.seaofnodes.simple.print;
+
+import com.seaofnodes.simple.codegen.CompUnit;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding.Relo;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.*;
+import java.util.HashMap;
+import java.util.HashSet;
+
+public abstract class ASMPrinter {
+
+ public static SB print(SB sb, CodeGen code ) {
+ Encoding enc = code._encoding;
+ if( enc == null ) return sb.p("No encoding for ").p(code._srcName);
+
+ // instruction address
+ int iadr = 0;
+ // Print all functions in order
+ for( int i=0; i<enc._cfg._len; i++ )
+ if( enc._cfg.at(i) instanceof FunNode fun )
+ iadr = print(iadr,sb,code,enc,fun,i);
+
+ // constant pools
+ iadr = (iadr+15)&-16; // pad to 16
+ if( enc._cpool.size()>0 )
+ iadr = printConstantPool(iadr, sb, enc._cpool,enc._bigCons,true ,"Constant Pool");
+
+ iadr = (iadr+15)&-16; // pad to 16
+ if( enc._sdata.size()>0 )
+ iadr = printConstantPool(iadr, sb,enc._sdata,enc._bigCons,false, "Static Data" );
+
+ return sb;
+ }
+
+
+ static int printConstantPool( int iadr, SB sb, BAOS bits, HashMap<Node,Relo> bigCons, boolean ro, String msg ) {
+ sb.p("--- ").p(msg).p(" ------").nl();
+ // radix sort the big constants by alignment
+ Ary<Relo>[] raligns = new Ary[5];
+ for( Node op : bigCons.keySet() ) {
+ Relo relo = bigCons.get(op);
+ // non-constant structs in the r/w data, everything else in r/o data
+ if( (relo._t instanceof TypeStruct ts && !ts.isConstant()) == ro )
+ continue;
+ int align = relo._t.alignment();
+ Ary<Relo> relos = raligns[align]==null ? (raligns[align]=new Ary<>( Relo.class)) : raligns[align];
+ relos.add(relo);
+ }
+
+ HashSet<Type> targets = new HashSet<>();
+
+ // By alignment
+ int dadr=0;
+ for( int align = 4; align >= 0; align-- ) {
+ Ary<Relo> relos = raligns[align];
+ if( relos == null ) continue;
+ for( Relo relo : relos ) {
+ if( targets.contains(relo._t) ) continue;
+ targets.add(relo._t);
+ sb.hex2(iadr+dadr).p(" ");
+ if( relo._t instanceof TypeStruct ts ) {
+ int sz = ts.size();
+ int log = 1<<align;
+ sz = (sz + (log -1)) & -log; // Round up final padding
+ for( int i=0; i<sz; i++ )
+ sb.hex1(bits.read1(dadr++));
+ } else {
+ // Simple primitive (e.g. larger int, float)
+ pN(bits,sb,dadr,align).fix(9-(1<<align),"");
+ dadr += (1<<align);
+ }
+ relo._t.print(sb.p('\t')).nl();
+ }
+ }
+ return iadr+dadr;
+ }
+
+
+ private static SB pN( BAOS bits, SB sb, int dadr, int log ) {
+ switch( log ) {
+ case 0: sb.hex1(bits.read1(dadr)); break;
+ case 1: sb.hex2(bits.read2(dadr)); break;
+ case 2: sb.hex4(bits.read4(dadr)); break;
+ case 3: sb.hex8(bits.read8(dadr)); break;
+ }
+ return sb;
+ }
+
+
+ private static int print(int iadr, SB sb, CodeGen code, Encoding enc, FunNode fun, int cfgidx) {
+ // Function header
+ sb.nl().p("---");
+ if( fun._name != null ) sb.p(fun._name).p(" ");
+ fun.sig().print(sb);
+ sb.p("---------------------------").nl();
+ iadr = (iadr + 15)&-16; // All function entries padded to 16 align
+
+ if( fun._frameAdjust != 0 )
+ iadr = doInst(iadr,sb,code,enc, cfgidx,fun,true,enc!=null);
+ while( !(enc._cfg.at(cfgidx) instanceof ReturnNode) )
+ iadr = doBlock(iadr,sb,code,enc,fun,cfgidx++);
+
+ // Function separator
+ sb.p("---");
+ fun.sig().print(sb);
+ sb.p("---------------------------").nl();
+ return iadr;
+ }
+
+ static private final int opWidth = 5;
+ static private final int argWidth = 30;
+ static int doBlock(int iadr, SB sb, CodeGen code, Encoding enc, FunNode fun, int cfgidx) {
+ final int encWidth = code._mach==null ? 2 : code._mach.defaultOpSize()*2;
+ CFGNode bb = enc._cfg.at(cfgidx);
+ if( bb != fun && !(bb instanceof IfNode) && !(bb instanceof CallEndNode) && !(bb instanceof CallNode) && !(bb instanceof CProjNode && bb.in(0) instanceof CallEndNode ))
+ sb.p(label(bb)).p(":").nl();
+ if( bb instanceof CallNode ) return iadr;
+ final boolean postEncode= code._phase.ordinal() >=CodeGen.Phase.Encoding.ordinal();
+ final boolean postAlloc = code._phase.ordinal() > CodeGen.Phase.RegAlloc.ordinal() ||
+ (code._phase.ordinal() == CodeGen.Phase.RegAlloc.ordinal() && code._regAlloc.done());
+
+ boolean once=false;
+ for( Node n : bb.outs() ) {
+ if( !(n instanceof PhiNode phi) ) continue;
+ if( phi._type instanceof TypeMem || phi._type instanceof TypeRPC ) continue; // Nothing for the hidden ones
+ // Post-RegAlloc phi prints all on one line
+ if( postAlloc ) {
+ if( !once ) { once=true; sb.fix(4," ").p(" ").fix(encWidth,"").p(" "); }
+ sb.p(phi._label).p(':').p(code.reg(phi)).p(',');
+ } else {
+ // Pre-RegAlloc phi prints one line per
+ sb.fix(4," ").p(" ").fix(encWidth,"").p(" ").fix(opWidth,phi._label).p(" ").p(code.reg(phi));
+ if( phi.getClass() == PhiNode.class ) {
+ sb.p(" = phi( ");
+ for( int i=1; i<phi.nIns(); i++ )
+ sb.p("N").p(phi.in(i)._nid).p(",");
+ sb.unchar().p(" )");
+ }
+ sb.nl();
+ }
+ }
+ if( once ) sb.unchar().nl();
+
+ // All the non-phis
+ for( int i=0; i<bb.nOuts(); i++ )
+ if( !(bb.out(i) instanceof PhiNode) )
+ iadr = doInst(iadr, sb,code, enc, cfgidx, bb.out(i),postAlloc, postEncode );
+
+ return iadr;
+ }
+
+ static int doInst( int iadr, SB sb, CodeGen code, Encoding enc, int cfgidx, Node n, boolean postAlloc, boolean postEncode ) {
+ if( n==null || n instanceof CProjNode ) return iadr;
+ if( postAlloc && n instanceof CalleeSaveNode ) return iadr;
+ if( postEncode && n instanceof ProjNode ) return iadr;
+ if( n instanceof MemMergeNode ) return iadr;
+ if( n instanceof EscapeNode ) return iadr;
+ if( n.getClass() == ConstantNode.class ) return iadr; // Default placeholders
+ final int dopz = code._mach==null ? 2 : code._mach.defaultOpSize();
+ final int encWidth = dopz*2;
+
+ // All blocks ending in a Region will need to either fall into or jump
+ // to this block. Until the post-reg-alloc block layout cleanup, we
+ // need to assume a jump. There's no real hardware op here, yet.
+ if( n instanceof RegionNode cfg && !(n instanceof FunNode) ) {
+ if( postEncode ) return iadr; // All jumps inserted already
+ while( cfgidx < enc._cfg._len-1 ) {
+ CFGNode next = enc._cfg.at(++cfgidx);
+ if( next == n ) return iadr; // Fall-through, no branch
+ if( next.nOuts()>1 )
+ break; // Has code in the block, need to jump around
+ // No code in the block, can fall through it
+ }
+ sb.hex2(iadr++).p(" ").fix(encWidth,"??").p(" ").fix(opWidth,"JMP").p(" ").fix(argWidth,label(cfg)).nl();
+ return iadr;
+ }
+
+ // ProjNodes following a multi (e.g. Call or New results),
+ // get indent slightly and just print their index & node#
+ if( n instanceof ProjNode proj ) {
+ if( proj._type instanceof TypeMem ) return iadr; // Nothing for the hidden ones
+ sb.fix(4," ").p(" ").fix(encWidth,"").p(" ").fix(opWidth,code.reg(n)).p(" // ");
+ if( proj._label != null ) sb.p(proj._label);
+ sb.nl();
+ return iadr;
+ }
+
+ // ADDR ENCODING Op--- dst = src op src // Comment
+ // 1234 abcdefgh ld4 RAX = [Rbase + off] // Comment
+ sb.hex2(iadr).p(" ");
+
+ // Encoding
+ int fatEncoding = 0;
+ if( enc != null ) {
+ int size = enc.opLen(n);
+ if( code._asmLittle )
+ for( int i=0; i<Math.min(size,dopz); i++ )
+ sb.hex1(enc._bits.buf()[iadr++]);
+ else {
+ iadr += Math.min(size,dopz);
+ for( int i=0; i<Math.min(size,dopz); i++ )
+ sb.hex1(enc._bits.buf()[iadr-i-1]);
+ }
+ for( int i=size*2; i<encWidth; i++ )
+ sb.p(" ");
+ fatEncoding = size - (encWidth>>1); // Not-printed parts of encoding
+ } else
+ sb.fix(encWidth,"");
+ sb.p(" ");
+
+ // Op; generally "ld4" or "call"
+ sb.fix(opWidth, n instanceof MachNode mach ? mach.op() : n.label()).p(" ");
+
+ // General asm args
+ String isMultiOp = null;
+ if( n instanceof MachNode mach ) {
+ int old = sb.len();
+ mach.asm(code,sb);
+ int len = sb.len();
+ isMultiOp = isMultiOp(sb,old,len);
+ sb.fix(argWidth-(len-old),""); // Pad out
+
+ } else if( !(n._type instanceof TypeMem) ) {
+ // Room for some inputs
+ sb.fix(5, n._nid+":" );
+ int off = 0;
+ for( int i=0; i<n.nIns(); i++ ) {
+ sb.fix(5, n.in(i) == null ? "___" : String.valueOf(n.in(i)._nid));
+ off += 5;
+ if( off >= argWidth ) break;
+ }
+ if( off < argWidth ) sb.fix(argWidth-off,"");
+ }
+ sb.p(" ");
+
+ // Comment
+ String comment = n.comment();
+ if( comment!=null ) sb.p("// ").p(comment);
+
+ sb.nl();
+
+ // Printing more op bits than fit
+ if( isMultiOp != null ) {
+ // Multiple ops, template style, no RA, no scheduling. Print out
+ // one-line-per-newline, with encoding bits up front.
+ int size = enc != null ? enc.opLen(n) : 0;
+ int off = Math.min(size,dopz);
+ while( isMultiOp!=null ) {
+ sb.hex2(iadr).p(" ");
+ int len = Math.min(size-off,dopz);
+ for( int i=0; i<len; i++ )
+ sb.hex1(enc._bits.buf()[iadr++]);
+ off += len;
+ for( int i=len; i<dopz; i++ ) sb.p(" ");
+ sb.p(" ");
+ int x = isMultiOp.indexOf('\n');
+ if( x== -1 ) { // Last line
+ sb.p(isMultiOp).nl();
+ isMultiOp = null;
+ } else {
+ sb.p(isMultiOp.substring(0,x+1)); // Includes the newline
+ isMultiOp = isMultiOp.substring(x+1);
+ }
+
+ }
+
+ } else if( fatEncoding > 0 ) {
+ // Extra bytes past the default encoding width, all put on a line by
+ // themselves. X86 special for super long encodings
+ sb.hex2(iadr).p(" ");
+ for( int i=0; i<fatEncoding; i++ )
+ sb.hex1(enc._bits.buf()[iadr++]);
+ sb.nl();
+ }
+
+
+ // MultiNodes are immediately followed by projection(s)
+ if( !(n instanceof CFGNode) && n instanceof MultiNode ) {
+ for( Node proj : n._outputs ) {
+ if( proj instanceof ProjNode ) // it could also be an ante-dep
+ doInst(iadr,sb,code,enc, cfgidx, proj,postAlloc,postEncode);
+ }
+ }
+
+ return iadr;
+ }
+
+ private static String label( CFGNode bb ) {
+ return (bb instanceof LoopNode ? "LOOP" : "L")+bb._nid;
+ }
+
+ private static String isMultiOp(SB sb, int old, int len) {
+ if( old==len || sb.at(old)!='\n' ) return null;
+ String rest = sb.subString(old+1,len);
+ sb.setLen(old);
+ return rest;
+ }
+
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java b/chapter25/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java
new file mode 100644
index 00000000..ad2df8b6
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java
@@ -0,0 +1,288 @@
+package com.seaofnodes.simple.print;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.CompUnit;
+import com.seaofnodes.simple.node.*;
+import java.util.*;
+import static com.seaofnodes.simple.util.Utils.TODO;
+
+/**
+ * Simple visualizer that outputs GraphViz dot format.
+ * The dot output must be saved to a file and run manually via dot to generate the SVG output.
+ * Currently, this is done manually.
+ */
+public class GraphVisualizer {
+
+ /**
+ * If set to true we put the control nodes in a separate cluster from
+ * data nodes.
+ */
+ boolean _separateControlCluster = false;
+
+ public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; }
+ public GraphVisualizer() { this(false); }
+
+ public String generateDotOutput(Parser parse) { return generateDotOutput(parse._ref,parse._scope,parse._xScopes); }
+ public String generateDotOutput(CompUnit cu, Node scope, Stack<ScopeNode> xScopes) {
+
+ // Since the graph has cycles, we need to create a flat list of all the
+ // nodes in the graph.
+ Collection<Node> all = findAll(xScopes, cu._stop, scope);
+ StringBuilder sb = new StringBuilder();
+ sb.append("digraph chapter18 {\n");
+ sb.append("/*\n");
+ sb.append(cu._src);
+ sb.append("\n*/\n");
+
+ // To keep the Scopes below the graph and pointing up into the graph we
+ // need to group the Nodes in a subgraph cluster, and the scopes into a
+ // different subgraph cluster. THEN we can draw edges between the
+ // scopes and nodes. If we try to cross subgraph cluster borders while
+ // still making the subgraphs DOT gets confused.
+ sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes
+
+ // CNC - turned off Apr/8/2024, gives more flex in the layout and
+ // removes some of the more ludicrous layout choices.
+ // Preserve node input order
+ //sb.append("\tordering=\"in\";\n");
+
+ // Merge multiple edges hitting the same node. Makes common shared
+ // nodes much prettier to look at.
+ sb.append("\tconcentrate=\"true\";\n");
+
+ // Force nested scopes to order
+ sb.append("\tcompound=\"true\";\n");
+
+ // Just the Nodes first, in a cluster no edges
+ nodes(sb, all);
+
+ // Now the scopes, in a cluster no edges
+ if( xScopes != null )
+ for( ScopeNode xscope : xScopes )
+ if( !xscope.isDead() )
+ scope( sb, xscope );
+
+ // Walk the Node edges
+ nodeEdges(sb, all);
+
+ // Walk the active Scope edges
+ if( xScopes != null )
+ for( ScopeNode xscope : xScopes )
+ if( !xscope.isDead() )
+ scopeEdges( sb, xscope );
+
+ sb.append("}\n");
+ return sb.toString();
+ }
+
+ private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection<Node> all) {
+ if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster
+ return;
+ // Just the Nodes first, in a cluster no edges
+ sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name
+ for (Node n : all) {
+ if( n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode || n instanceof XCtrlNode )
+ continue; // Do not emit, rolled into MultiNode or Scope cluster already
+ if( _separateControlCluster && doCtrl && !(n instanceof CFGNode) ) continue;
+ if( _separateControlCluster && !doCtrl && (n instanceof CFGNode) ) continue;
+ sb.append("\t\t").append(n.uniqueName()).append(" [ ");
+ String lab = n.glabel();
+ if (n instanceof MultiNode) {
+ // Make a box with the MultiNode on top, and all the projections on the bottom
+ sb.append("shape=plaintext label=<\n");
+ sb.append("\t\t\t<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\">\n");
+ sb.append("\t\t\t<TR><TD BGCOLOR=\"yellow\">").append(lab).append("</TD></TR>\n");
+ sb.append("\t\t\t<TR>");
+ boolean doProjTable = false;
+ n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : (x._nid - y._nid));
+ for (Node use : n._outputs) {
+ if (use instanceof ProjNode proj) {
+ if (!doProjTable) {
+ doProjTable = true;
+ sb.append("<TD>").append("\n");
+ sb.append("\t\t\t\t<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">").append("\n");
+ sb.append("\t\t\t\t<TR>");
+ }
+ sb.append("<TD PORT=\"p").append(proj._idx).append("\">").append(proj.glabel()).append("</TD>");
+ }
+ if (use instanceof CProjNode proj) {
+ if (!doProjTable) {
+ doProjTable = true;
+ sb.append("<TD>").append("\n");
+ sb.append("\t\t\t\t<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">").append("\n");
+ sb.append("\t\t\t\t<TR>");
+ }
+ sb.append("<TD PORT=\"p").append(proj._idx).append("\" BGCOLOR=\"yellow\">").append(proj.glabel()).append("</TD>");
+ }
+ }
+ if (doProjTable) {
+ sb.append("</TR>").append("\n");
+ sb.append("\t\t\t\t</TABLE>").append("\n");
+ sb.append("\t\t\t</TD>");
+ }
+ sb.append("</TR>\n");
+ sb.append("\t\t\t</TABLE>>\n\t\t");
+
+ } else {
+ // control nodes have box shape
+ // other nodes are ellipses, i.e. default shape
+ if( n instanceof CFGNode ) sb.append("shape=box style=filled fillcolor=yellow ");
+ else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow ");
+ sb.append("label=\"").append(lab).append("\" ");
+ }
+ sb.append("];\n");
+ }
+ if (!_separateControlCluster) {
+ // Force Region & Phis to line up
+ for (Node n : all) {
+ if (n instanceof RegionNode region) {
+ sb.append("\t\t{ rank=same; ");
+ sb.append(region.uniqueName()).append(";");
+ for (Node phi : region._outputs)
+ if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";");
+ sb.append("}\n");
+ }
+ }
+ }
+ sb.append("\t}\n"); // End Node cluster
+ }
+
+ private void nodes(StringBuilder sb, Collection<Node> all) {
+ nodesByCluster(sb, true, all);
+ nodesByCluster(sb, false, all);
+ }
+
+ // Build a nested scope display, walking the _prev edge
+ private void scope( StringBuilder sb, ScopeNode scope ) {
+ sb.append("\tnode [shape=plaintext];\n");
+ int last = scope.nIns();
+ int max = scope.depth();
+ for( int i = 0; i < max; i++ ) {
+ int level = max-i-1;
+ String scopeName = makeScopeName(scope, level);
+ sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name
+ // Special for memory ScopeMinNode
+ if( level==0 ) {
+ Node n = scope.mem();
+ sb.append("\t\t").append(n.uniqueName()).append(" [label=\"").append(n.glabel()).append("\"];\n");
+ }
+ sb.append("\t\t").append(scopeName).append(" [label=<\n");
+ sb.append("\t\t\t<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n");
+ // Add the scope level
+ sb.append("\t\t\t<TR><TD BGCOLOR=\"cyan\">").append(level).append("</TD>");
+ int lexStart = scope._kinds.at(level)._lexSize;
+ for( int j=lexStart; j<last; j++ ) {
+ var v = scope._vars.at(j);
+ sb.append("<TD PORT=\"").append(makePortName(scopeName, v._name)).append("\">").append(v._name).append("</TD>");
+ }
+ last = lexStart;
+ sb.append("</TR>\n");
+ sb.append("\t\t\t</TABLE>>];\n");
+ }
+ // Scope clusters nest, so the graphics shows the nested scopes, so
+ // they are not closed as they are printed; so they just keep nesting.
+ // We close them all at once here.
+ sb.append( "\t}\n".repeat( max ) ); // End all Scope clusters
+ }
+
+ private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; }
+ private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; }
+
+ // Walk the node edges
+ private void nodeEdges(StringBuilder sb, Collection<Node> all) {
+ // All them edge labels
+ sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n");
+ for( Node n : all ) {
+ // Do not display the Constant->Start edge;
+ // ProjNodes handled by Multi;
+ // ScopeNodes are done separately
+ if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n instanceof XCtrlNode )
+ continue;
+ for( int i=0; i<n.nIns(); i++ ) {
+ Node def = n.in(i);
+ if( n instanceof PhiNode && def instanceof RegionNode ) {
+ // Draw a dotted use->def edge from Phi to Region.
+ sb.append('\t').append(n.uniqueName());
+ sb.append(" -> ");
+ sb.append(def.uniqueName());
+ sb.append(" [style=dotted taillabel=").append(i).append("];\n");
+ } else if( def != null ) {
+ // Most edges land here use->def
+ sb.append('\t').append(n.uniqueName()).append(" -> ");
+ if( def instanceof CProjNode proj ) {
+ String mname = proj.ctrl().uniqueName();
+ sb.append(mname).append(":p").append(proj._idx);
+ } else if( def instanceof ProjNode proj ) {
+ String mname = proj.in(0).uniqueName();
+ sb.append(mname).append(":p").append(proj._idx);
+ } else sb.append(def.uniqueName());
+ // Number edges, so we can see how they track
+ sb.append("[taillabel=").append(i);
+ // The edge from New to ctrl is just for anchoring the New
+ if ( n instanceof NewNode )
+ sb.append(" color=green");
+ // control edges are colored red
+ else if( def instanceof CFGNode )
+ sb.append(" color=red");
+ else if( def.isMem() )
+ sb.append(" color=blue");
+ // Backedges do not add a ranking constraint
+ if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) )
+ sb.append(" constraint=false");
+ sb.append("];\n");
+ }
+ }
+ }
+ }
+
+ // Walk the scope edges
+ private void scopeEdges( StringBuilder sb, ScopeNode scope ) {
+ sb.append("\tedge [style=dashed color=cornflowerblue];\n");
+ int level=0;
+ for( var v : scope._vars ) {
+ Node def = scope.in(v._idx);
+ while( def instanceof ScopeNode lazy )
+ def = lazy.in(v._idx);
+ if( def==null ) continue;
+ while( level < scope.depth() && v._idx >= scope._kinds.at(level)._lexSize )
+ level++;
+ String scopeName = makeScopeName(scope, level-1);
+ sb.append("\t")
+ .append(scopeName).append(":")
+ .append('"').append(makePortName(scopeName, v._name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted
+ .append(" -> ");
+ if( def instanceof CProjNode proj ) {
+ sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx);
+ } else if( def instanceof ProjNode proj ) {
+ sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx);
+ } else sb.append(def.uniqueName());
+ sb.append(";\n");
+ }
+ }
+
+ /**
+ * Finds all nodes in the graph.
+ */
+ public static Collection<Node> findAll(Stack<ScopeNode> ss, Node... ns) {
+ final HashMap<Integer, Node> all = new HashMap<>();
+ for( Node n : ns )
+ walk(all, n);
+ if( ss != null )
+ for( Node n : ss )
+ walk(all, n);
+ return all.values();
+ }
+
+ /**
+ * Walk a subgraph and populate distinct nodes in the all list.
+ */
+ private static void walk(HashMap<Integer,Node> all, Node n) {
+ if(n == null ) return;
+ if (all.get(n._nid) != null) return; // Been there, done that
+ all.put(n._nid, n);
+ for (Node c : n._inputs)
+ walk(all, c);
+ for( Node c : n._outputs )
+ walk(all, c);
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/IRPrinter.java b/chapter25/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
new file mode 100644
index 00000000..7c0f84f3
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
@@ -0,0 +1,172 @@
+package com.seaofnodes.simple.print;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Serialize;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.SB;
+
+import java.util.*;
+
+public abstract class IRPrinter {
+
+ // Print a node on 1 line, columnar aligned, as:
+ // NNID NNAME DDEF DDEF [[ UUSE UUSE ]] TYPE
+ // 1234 sssss 1234 1234 1234 1234 1234 1234 tttttt
+ public static SB printLine( Node n, SB sb ) {
+ if( n==null ) return sb;
+ sb.p("%4d %-7.7s ".formatted(n._nid,n.label()));
+ if( n._inputs==null )
+ return sb.p("DEAD\n");
+ for( Node def : n._inputs )
+ sb.p(def==null ? "____" : "%4d".formatted(def._nid))
+ // Lazy Phi indicator
+ .p(n instanceof MemMergeNode && def instanceof MemMergeNode ? "^" : " ");
+ for( int i = n._inputs.size(); i<4; i++ )
+ sb.p(" ");
+ sb.p(" [[ ");
+ for( Node use : n._outputs )
+ sb.p(use==null ? "____ " : "%4d ".formatted(use._nid));
+ int lim = 6 - Math.max(n._inputs.size(),4);
+ for( int i = n._outputs.size(); i<lim; i++ )
+ sb.p(" ");
+ sb.p(" ]] ");
+ if( n._type!= null ) sb.p(n._type.str());
+ return sb.p("\n");
+ }
+
+ // Bulk whole program pretty print
+ public static String prettyPrint( CodeGen code ) {
+ // Loop-tree for nodeOrder
+ CFGNode.LoopTree old = code._start._ltree;
+ if( old==null )
+ code._start.buildLoopTree( code._linker, code._stop);
+ Ary<Node> nodes = Serialize.nodeOrder(code);
+ if( old == null )
+ code._start._ltree = null;
+ SB sb = new SB();
+ Node prior=null;
+ for( Node n : nodes ) {
+ if( n instanceof FunNode fun ) {
+ sb.nl().p("--- ");
+ fun.sig().print(sb.p(fun._name==null ? "" : fun._name).p(" "));
+ sb.p("----------------------\n");
+ }
+ if( n instanceof MultiNode || n instanceof RegionNode || n instanceof CallNode ||
+ (isMultiChild(prior) && !isMultiChild(n)) )
+ sb.nl();
+ printLine(n,sb);
+ if( n instanceof ReturnNode ret )
+ sb.p("--- ").p(ret._fun._name==null ? "" : ret._fun._name).p(" ----------------------\n");
+ prior = n;
+ }
+ return sb.toString();
+ }
+
+ private static boolean isMultiHead(Node n) {
+ return n instanceof RegionNode || n instanceof MultiNode;
+ }
+
+ private static boolean isMultiChild(Node n) {
+ return n!=null && n.nIns() > 0 && n.in(0) != null && isMultiHead(n.in(0));
+ }
+
+
+ // ----------------------------------------
+ // Another bulk pretty-printer. Makes more effort at basic-block grouping.
+ public static String prettyPrint(Node node, int depth) {
+ // Convert just that set to a post-order
+ Ary<Node> post = new Ary<>(Node.class);
+ Ary<Node> rets = new Ary<>(Node.class);
+ var visit = new IdentityHashMap<Node,Integer>();
+ assert depth < 5000;
+ postOrd( node, 0, depth, visit, post, rets);
+ // Function calls all print independent
+ while( rets.size()>0 )
+ postOrd( rets.pop(), 0, depth, visit, post, rets );
+
+
+ // Reverse the post-order walk
+ SB sb = new SB();
+ for( int i=0; i<post._len; i++ ) {
+ Node n = post.at(i);
+ // Split ahead of a Region/MultiNode
+ if( n instanceof RegionNode || n instanceof MultiNode ||
+ (i>0 && isMultiChild(post.at(i-1)) && !isMultiChild(n)) )
+ sb.nl();
+ if( n instanceof FunNode fun )
+ fun.sig().print(sb.p("--- ").p(fun._name==null ? "" : fun._name).p(" ")).p("----------------------\n");
+ printLine( n, sb ); // Print head
+ if( n instanceof ReturnNode ret ) {
+ FunNode fun = ret.fun();
+ sb.p("--- ").p(fun==null ? "" : fun._name).p("----------------------\n");
+ }
+ }
+ return sb.toString();
+ }
+
+
+ // keep the largest PO depth?; probably want *shortest*
+ private static void postOrd( Node n, int d, int cutoff, IdentityHashMap<Node,Integer> visit, Ary<Node> post, Ary<Node> rets ) {
+ if( n==null ) return;
+ if( d >= cutoff ) return; // Too deep
+ Integer depth = visit.get(n);
+ if( depth!=null ) return; // Been there, done that
+
+ // Not a multi-child (Phi of a Region or Proj of a MultiNode)
+ if( !isMultiHead(n) && !isMultiChild(n) ) {
+ visit.put(n,d); // Visit node N and depth D
+ for( Node def : n._inputs ) // Children before visit
+ postOrd(def, d+1, cutoff, visit, post, rets);
+ post.add(n); // Post-order add
+ return;
+ }
+
+ // Multi-child; print the parent and all children together
+ Node multi = isMultiHead(n) ? n : n.in(0);
+ // Multi + children are visited all at once
+ visit.put(multi,d+1);
+ for( Node out : multi.outs() )
+ visit.put(out,d);
+
+ // Order to visit all the children
+ Node[] outs = multi.outs().asAry();
+ Arrays.sort(outs, Comparator.comparingInt( IRPrinter::sortOrder ) );
+
+ // Do not walk out of Functions, this makes functions stand-alone
+ // but otherwise unordered
+ if( !(multi instanceof FunNode fun && fun._folding) ) {
+ // Visit all children inputs all at once
+ for( Node out : outs ) {
+ // A FunNode is both a multi-head AND a multi-child from Start
+ if( !isMultiHead(out) && out != null ) {
+ for( Node def : out._inputs ) {
+ postOrd(def, d+1, cutoff, visit, post, rets);
+ }
+ }
+ }
+ // Visit multi inputs
+ for( Node def : multi._inputs ) {
+ if( multi instanceof CallEndNode && def instanceof ReturnNode ret && !ret._fun._folding ) {
+ rets.add(ret); // But save the Return for a separate function print
+ } else {
+ postOrd(def, d+2, cutoff, visit, post, rets);
+ }
+ }
+ }
+
+ // Post visit the multi and all children
+ post.add(multi);
+ for( Node out : outs )
+ if( !isMultiHead(out) )
+ post.add(out);
+ }
+
+ static int sortOrder( Node n ) {
+ if( n instanceof ProjNode proj ) return proj._idx;
+ if( n instanceof PhiNode phi ) return phi._nid;
+ if( n == null ) return 0;
+ return n._nid+1000000;
+ }
+
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java b/chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java
new file mode 100644
index 00000000..0f34af2e
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java
@@ -0,0 +1,404 @@
+package com.seaofnodes.simple.print;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.Stack;
+import static com.seaofnodes.simple.codegen.CodeGen.CODE;
+
+public class JSViewer implements AutoCloseable {
+ // Display programs in an endless loop
+ public static void main( String[] args ) throws Exception {
+ try(var js = new JSViewer()) {
+ js.run();
+ }
+ }
+
+ static boolean SHOW;
+
+ // WebSocket to the browser for display
+ static SimpleWebSocket SERVER;
+
+ static int N; // Dot frames
+
+ JSViewer() throws Exception {
+ // Launch server; handshake
+ SERVER = new SimpleWebSocket(Paths.get("docs/index.html").toUri(),12345) ;
+ }
+
+ void run( ) throws Exception {
+ SERVER.put("!");
+ while( true ) {
+ String src = SERVER.get();
+ switch( src ) {
+ case null: return;
+ case "null": return;
+ case "+": break; // Client requests more frames, but we send them all anyway
+ default:
+ System.out.println(src);
+ try {
+ N=0;
+ // Use parser scope, xscope when building views
+ CodeGen code = new CodeGen(src);
+ SHOW = true;
+ show();
+ // Parse program, generating views at every parse point
+ code.parse();
+ // No longer user parse internal state when building views
+ code.opto();
+
+ // Catch and ignore Parser errors
+ } catch(RuntimeException re) {
+ System.err.println(re);
+ } finally {
+ SHOW = false;
+ SERVER.put("#"); // Final frame
+ }
+ break;
+ }
+ }
+ }
+
+ @Override public void close() throws IOException {
+ SERVER.close();
+ SERVER=null;
+ }
+
+ public static void show() { if( SERVER!=null && SHOW ) _show(); }
+ private static void _show() {
+ // Skip util we at least get the Parse object made
+ if( CODE._phase==null || CODE._phase.ordinal() < CodeGen.Phase.Parse.ordinal() )
+ return;
+ boolean midParse = CODE._phase == CodeGen.Phase.Parse;
+ Stack<ScopeNode> xScopes = midParse ? CODE.P._xScopes : null;
+
+ Collection<Node> all = GraphVisualizer.findAll(xScopes, CODE._stop, midParse ? CODE.P._scope: null);
+ SB sb = new SB();
+ sb.p("digraph view_").p(N++).p(" {\n").ii();
+ if( midParse )
+ sb.i().p("// POS: ").p(CODE.P.pos()).nl();
+
+ // To keep the Scopes below the graph and pointing up into the graph we
+ // need to group the Nodes in a subgraph cluster, and the scopes into a
+ // different subgraph cluster. THEN we can draw edges between the
+ // scopes and nodes. If we try to cross subgraph cluster borders while
+ // still making the subgraphs DOT gets confused.
+ sb.i().p("rankdir=BT;\n"); // Force Nodes before Scopes
+
+ // CNC - turned off Apr/8/2024, gives more flex in the layout and
+ // removes some of the more ludicrous layout choices.
+ // Preserve node input order
+ //sb.append("ordering=\"in\";\n");
+
+ // Merge multiple edges hitting the same node. Makes common shared
+ // nodes much prettier to look at.
+ sb.i().p("concentrate=\"true\";\n");
+
+ // Force nested scopes to order
+ sb.i().p("compound=\"true\";\n");
+
+ // Just the Nodes first, in a cluster no edges
+ nodes(sb, all);
+
+ // Now the scopes, in a cluster no edges
+ if( midParse )
+ for( ScopeNode xscope : xScopes )
+ if( !xscope.isDead() )
+ scope( sb, xscope );
+
+ // Walk the Node edges
+ nodeEdges(sb, midParse && !xScopes.empty() ? xScopes.peek() : null, all);
+
+ // Walk the active Scope edges
+ if( midParse )
+ for( ScopeNode xscope : xScopes )
+ if( !xscope.isDead() )
+ scopeEdges( sb, xscope );
+
+
+ sb.p("}\n").di();
+ // Tell client another DOT frame
+ String dot = sb.toString();
+ System.out.println(dot);
+ try {
+ SERVER.put(dot);
+ } catch( IOException ioe ) {
+ try { SERVER.close();} catch( IOException ignored ){}
+ }
+ }
+
+
+ private static void nodes(SB sb, Collection<Node> all) {
+ // Just the Nodes first, in a cluster no edges
+ sb.i().p("subgraph cluster_Nodes {\n").ii(); // Magic "cluster_" in the subgraph name
+ for( Node n : all ) {
+ if( n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode )
+ continue; // Do not emit, rolled into MultiNode or Scope cluster already
+ sb.i().p(n.uniqueName()).p(" [ ");
+ if( n instanceof MultiNode && !(n instanceof StartNode) ) {
+ // Make a box with the MultiNode on top, and all the projections on the bottom
+ sb. p("shape=plaintext label=<\n").ii();
+ sb.i().p("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\">\n");
+ // Row over single cell for node label
+ cell(sb.i().p("<TR>"), n.glabel(), n, null).p("</TR>\n");
+ // Row over single cell, for nested table
+ sb.i().p("<TR><TD>\n").ii();
+ sb.i().p("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">").p("\n");
+ sb.i().p("<TR>");
+ n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : ((x==null ? 99999 : x._nid) - (y==null ? 99999 : y._nid)));
+ boolean empty_row=true;
+ for( Node use : n._outputs ) {
+ int idx = idx(n);
+ if( idx != -1 ) {
+ cell(sb,use.glabel(),use,"p"+idx);
+ empty_row=false;
+ }
+ }
+ // At least one cell on row
+ if( empty_row ) sb.p("<TD></TD>");
+ sb. p("</TR>").p("\n");
+ sb.i().p("</TABLE>").p("\n").di();
+ sb.i().p("</TD></TR>\n");
+ sb.i().p("</TABLE>>\n").di();
+ sb.i().p("];\n");
+
+ } else {
+ // control nodes have box shape
+ // other nodes are ellipses, i.e. default shape
+ //else if (n instanceof PhiNode) sb.p("style=filled fillcolor=lightyellow ");
+ node(sb,n.glabel(),n);
+ sb.p("];\n");
+ }
+ }
+ // Force Region & Phis to line up
+ for (Node n : all) {
+ if (n instanceof RegionNode region) {
+ sb.i().p("{ rank=same; ");
+ sb.p(region.uniqueName()).p(";");
+ for (Node phi : region._outputs)
+ if (phi instanceof PhiNode) sb.p(phi.uniqueName()).p(";");
+ sb.p("}\n");
+ }
+ }
+ sb.di().i().p("}").nl(); // End Node cluster
+ }
+
+ // Build a nested scope display, walking the _prev edge
+ private static void scope( SB sb, ScopeNode scope ) {
+ sb.i().p("node [shape=plaintext];\n");
+ int last = scope.nIns();
+ int max = scope.depth()+1; // One more than lexsize
+ for( int i = 0; i < max; i++ ) {
+ int level = max-i-1;
+ String scopeName = makeScopeName(scope, level);
+ sb.i().p("subgraph cluster_").p(scopeName).p(" {\n").ii(); // Magic "cluster_" in the subgraph name
+ sb.i().p(scopeName).p(" [label=<\n").ii();
+ sb.i().p("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n");
+ int lexStart = level==0 ? 0 : scope._kinds.at(level-1)._lexSize;
+
+ // Special for memory ScopeMinNode
+ Node n = scope.nIns()>1 ? scope.mem() : null;
+ if( level==0 && n instanceof MemMergeNode merge && n.nIns()>2 ) {
+ sb.i().p("<TR>");
+ for( int m=2; m<n.nIns(); m++ )
+ cell(sb,"#"+m,merge.alias(m),"m"+m);
+ sb.p("</TR>\n"); // End scope level
+ }
+
+ // Scope variables, empty if none
+ if( lexStart<last ) {
+ sb.i().p("<TR>\n");
+ for( int j=lexStart; j<last; j++ ) {
+ if( scope.in(j)==null ) continue;
+ var v = scope._vars.at(j);
+ cell(sb.i(),v._name,scope.in(j),makePortName(scopeName, v._name)).nl();
+ }
+ last = lexStart;
+ sb.i().p("</TR>\n");
+ }
+ sb.i().p("</TABLE>>];\n").di();
+ // Scope label
+ sb.i().p(scopeName).p("A [label=\"").p(level).p("\" shape=doublecircle style=filled fillcolor=aqua fontsize=10 margin=0 width=0.2 ];\n");
+ }
+ // Scope clusters nest, so the graphics shows the nested scopes, so
+ // they are not closed as they are printed; so they just keep nesting.
+ // We close them all at once here.
+ for( int i=0; i<max; i++ )
+ sb.di().i().p("}\n");
+ }
+
+ // Append a cell, with color
+ private static SB cell(SB sb, String text, Node n, String port) {
+ sb.p("<TD");
+ String color = color(n);
+ sb.p(" BGCOLOR=\"").p(color).p("\"");
+ if( port!=null ) sb.p(" PORT=\"").p(port).p("\"");
+ return colorcell(sb.p(">"),text,n,dark(color)).p("</TD>");
+ }
+ private static SB node(SB sb, String text, Node n) {
+ String color = color(n);
+ sb.p("style=filled fillcolor=").p(color).p(" ");
+ String shape = switch( n ) {
+ case FunNode fun -> "shape=box peripheries=2";
+ case CFGNode cfg -> "shape=box";
+ default -> "";
+ };
+ sb.p(shape).p(" ");
+ return colorcell(sb.p("label=<"),text,n,dark(color)).p(">");
+ }
+
+ private static String color(Node n) {
+ return switch( n ) {
+ case CFGNode cfg -> "yellow";
+ case NewNode nnn -> "lightgreen";
+ case StructNode s -> "lightgreen";
+ default -> typeColor(n._type);
+ };
+ }
+ private static String typeColor(Type t) {
+ return t==null ? "white" : switch( t ) {
+ case TypeMem mem -> "blue";
+ case TypeMemPtr tmp -> "green";
+ case TypeFunPtr tfp -> "orange";
+ case TypeRPC rpc -> "antiquewhite";
+ case TypeInteger ti -> "lightblue";
+ case TypeFloat tf -> "aqua";
+ case Type tn -> tn==Type.NIL ? "lightgray" : "red";
+ };
+ }
+ private static final String[] DARKS = new String[]{"blue","green","black"};
+ private static boolean dark(String color) { return Utils.find(DARKS,color) != -1; }
+
+
+
+ // Called with an open div
+ private static SB colorcell(SB sb, String text, Node n, boolean dark) {
+ if( dark ) sb.p("<font color=\"white\">");
+ sb.p(text);
+ if( n._type!=null ) {
+ sb.p("<br /><font point-size=\"10\">");
+ n._type.gprint(sb);
+ sb.p("</font>");
+ }
+ if( dark ) sb.p("</font>");
+ return sb;
+ }
+
+
+ private static String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; }
+ private static String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; }
+
+ // Walk the node edges
+ private static void nodeEdges(SB sb, ScopeNode scope, Collection<Node> all) {
+ // All them edge labels
+ String scopeName = scope!=null ? makeScopeName(scope, 0) : null;
+ sb.i().p("edge [ fontname=Helvetica, fontsize=8 ];\n");
+ for( Node n : all ) {
+ // Do not display the Constant->Start edge;
+ if( n instanceof ConstantNode ||
+ n instanceof XCtrlNode ||
+ // ProjNodes handled by Multi;
+ n instanceof ProjNode ||
+ n instanceof CProjNode ||
+ // ScopeNodes are done separately
+ n instanceof ScopeNode ||
+ n instanceof MemMergeNode
+ )
+ continue;
+ if( n.isDead() )
+ throw Utils.TODO();
+ // Normal edges
+ for( int i=0; i<n.nIns(); i++ ) {
+ Node def = n.in(i);
+ if( def==null ) continue;
+ sb.i().p(n.uniqueName()).p(" -> ");
+ if( n instanceof PhiNode && def instanceof RegionNode ) {
+ // Draw a dotted use->def edge from Phi to Region.
+ sb.p(def.uniqueName());
+ sb.p(" [style=dotted taillabel=").p(i).p("];\n");
+ } else {
+ // Most edges land here use->def
+ if( def instanceof CProjNode proj ) {
+ String mname = proj.ctrl().uniqueName();
+ sb.p(mname).p(":p").p(proj._idx);
+ } else if( def instanceof ProjNode proj ) {
+ String mname = proj.in(0).uniqueName();
+ sb.p(mname).p(":p").p(proj._idx);
+ } else sb.p(def.uniqueName());
+ // Number edges, so we can see how they track
+ sb.p("[taillabel=").p(i);
+ // Color the edge
+ nodeEdgeColor(sb,def._type);
+ // Backedges do not add a ranking constraint
+ if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) )
+ sb.p(" constraint=false");
+ sb.p("];\n");
+ }
+ }
+
+ // Bonus edge if hooked by parser
+ if( (n.iskeep() || n.isUnused()) && scopeName != null ) {
+ sb.i().p(scopeName).p(" -> ").p(n.uniqueName()).p(" [ style=dashed color=grey];\n");
+ }
+ // Force functions above return
+ if( n instanceof ReturnNode ret && !ret.fun().isDead() )
+ sb.i().p(ret.ctrl().uniqueName()).p(" -> ").p(ret.fun().uniqueName()).p("[style=invis]\n");
+ }
+ }
+
+ private static void nodeEdgeColor(SB sb, Type t) {
+ sb.p(" color=").p(typeColor(t));
+ }
+
+ // Walk the scope edges
+ private static void scopeEdges( SB sb, ScopeNode scope ) {
+ sb.i().p("edge [style=dashed color=cornflowerblue];\n");
+ int level=0;
+ for( var v : scope._vars ) {
+ if( v._idx==1 ) continue; // Memory done separately
+ Node def = scope.in(v._idx);
+ while( def instanceof ScopeNode lazy )
+ def = lazy.in(v._idx);
+ if( def==null ) continue;
+ while( level < scope.depth() && v._idx >= scope._kinds.at(level)._lexSize )
+ level++;
+ String scopeName = makeScopeName(scope, level);
+ sb.i()
+ .p(scopeName).p(":")
+ .p('"').p(makePortName(scopeName, v._name)).p('"') // wrap port name with quotes because $ctrl is not valid unquoted
+ .p(" -> ");
+ defPort(sb,def).p(";\n");
+ }
+
+ // Memory
+ if( scope.nIns()>1 && scope.mem() instanceof MemMergeNode n ) {
+ for( int i=2; i<n.nIns(); i++ ) {
+ Node def = n.in(i);
+ while( def instanceof ScopeNode lazy )
+ def = lazy.in(i);
+ if( def==null ) continue;
+ String scopeName = makeScopeName(scope, 0);
+ sb.i().p(scopeName).p(":m").p(i).p(" -> ");
+ defPort(sb,def).p(";\n");
+ }
+ }
+
+ }
+
+ // (C)Projection index or -1
+ private static int idx(Node n) {
+ return n instanceof ProjNode p ? p._idx : (n instanceof CProjNode cp ? cp._idx : -1);
+ }
+
+ private static SB defPort(SB sb, Node def) {
+ int idx = idx(def);
+ return idx != -1
+ ? sb.p(def.in(0).uniqueName()).p(":p").p(idx)
+ : sb.p(def.uniqueName());
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java
new file mode 100644
index 00000000..4077533d
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleWebSocket.java
@@ -0,0 +1,149 @@
+package com.seaofnodes.simple.print;
+
+import com.seaofnodes.simple.util.Utils;
+import java.io.*;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+
+// Why-Oh-Why is this needed?
+
+// Pass in a URI and a port.
+// Opens the client; client has to open a websocket at the given port.
+// Connects, handshakes.
+// Thereafter you can call get/put to swap frames with client.
+
+class SimpleWebSocket extends ServerSocket {
+ final InputStream _in;
+ final OutputStream _out;
+
+ SimpleWebSocket(URI uri, int port) throws IOException, NoSuchAlgorithmException {
+ super(port);
+ // Launch client
+ java.awt.Desktop.getDesktop().browse(uri);
+ // Look for client; get in/out streams
+ Socket sock = accept();
+ _in = sock. getInputStream();
+ _out = sock.getOutputStream();
+
+
+ // Establish web socket with handshake; easier with a buffered reader
+ BufferedReader br = new BufferedReader(new InputStreamReader(_in));
+ String key = null;
+ while( br.ready() ) {
+ String line = br.readLine();
+ if( line.startsWith("Sec-WebSocket-Key: ") )
+ key = line.substring(19);
+ }
+ assert key!=null;
+ // Magic handshake
+ byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n"
+ + "Connection: Upgrade\r\n"
+ + "Upgrade: websocket\r\n"
+ + "Sec-WebSocket-Accept: "
+ + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-1").digest((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes( StandardCharsets.UTF_8 )))
+ + "\r\n\r\n").getBytes( StandardCharsets.UTF_8 );
+ _out.write(response,0,response.length);
+ }
+
+ final byte[] _ins = new byte[1024];
+ final byte[] _str = new byte[1024];
+ final byte[] _key = new byte[4];
+ int _x, _len;
+ private int rawget() { assert _x<_len; return _ins[_x++]&0xFF; }
+
+ // TODO: Flip to Direct Byte Buffer, this is just a poor-mans version
+ private void flipRead() throws IOException {
+ // Move _x to _len to offset 0
+ System.arraycopy(_ins,_x,_ins,0,(_len-_x));
+ _len = _len-_x;
+ _x=0;
+ // Read more
+ int len = _in.read(_ins,_len,_ins.length-_len);
+ if( len == -1 )
+ throw Utils.TODO(); // EOF
+ _len += len;
+ }
+ // Block until you read at least len
+ private void atLeast(int len) throws IOException {
+ if( _x+len <= _len ) return;
+ flipRead();
+ }
+
+
+ // Classic read from browser client. Encoded, so decoded here
+ public String get() throws IOException {
+ atLeast(1); // Need 1 byte for opcode
+ int op = rawget();
+ if( (op&0x80)!=0x80 ) // FIN bit not set?
+ throw Utils.TODO( "Multi-part from client: " + op ); // Multipart message
+ op &= 0x7F; // Strip FIN bit
+ switch( op&15) {
+ case 1: {
+ atLeast(5); // Min message length
+ // Message length
+ int len = rawget()-128;
+ if( len > 125 ) {
+ if( len > 126 )
+ throw Utils.TODO(); // Multi-byte length
+ len = (rawget()<<8) | rawget();
+ }
+ atLeast(len+4); // Rest of message
+ // Encoding key
+ _key[0] = (byte)rawget();
+ _key[1] = (byte)rawget();
+ _key[2] = (byte)rawget();
+ _key[3] = (byte)rawget();
+
+ // Decode bytes
+ for( int i=0; i<len; i++ )
+ _str[i] = (byte)(rawget() ^ _key[i & 3]);
+
+ return new String(_str,0,len);
+ }
+ // Client closes
+ case 8: return null;
+ // Ping
+ case 9:
+ // Send a PONG back. TODO: echo PING data in the PONG
+ _out.write(0x8A); // FIN + PONG
+ _out.write(0x00); // LENGTH==0
+ return get();
+ // Not (yet) implemented: continuation frames, ping/pong
+ default:
+ throw Utils.TODO("Unexpected op from client: "+op); // Multi-part message
+ }
+ }
+
+ // Classic put to browser client. No encoding.
+ public void put(String msg) throws IOException {
+ int len = msg.length();
+ if( len <= 125 ) {
+ _out.write(129); // FIN, opcode 1 - whole text message
+ _out.write(len);
+ for( int i=0; i<msg.length(); i++ )
+ _out.write(msg.charAt(i));
+ return;
+ }
+ if( len > 65535 )
+ throw Utils.TODO("MOAR LENGTH");
+ _out.write(129); // FIN, opcode 1 - whole text message
+ _out.write(126); // Length is 16b
+ _out.write(len>>>8);
+ _out.write(len&255);
+ for( int i=0; i<msg.length(); i++ )
+ _out.write(msg.charAt(i));
+ }
+
+ @Override public void close() throws IOException {
+ if( _out != null ) {
+ _out.write(0b10001000);
+ _out.flush();
+ }
+ super.close();
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/sys.java b/chapter25/src/main/java/com/seaofnodes/simple/sys.java
new file mode 100644
index 00000000..45106770
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter25/src/main/java/com/seaofnodes/simple/type/Field.java
new file mode 100644
index 00000000..b6ea5843
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/Field.java
@@ -0,0 +1,148 @@
+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.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;
+
+ private static final Ary<Field> FREE = new Ary<>(Field.class);
+ private Field(String fname, Type type, int alias, boolean xfinal ) { super(TFLD); init(fname,type,alias,xfinal); }
+ private Field init(String fname, Type type, int alias, boolean xfinal ) {
+ _fname = fname;
+ _t = type;
+ _alias = alias;
+ _final = xfinal;
+ 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 ) {
+ return FREE.isEmpty() ? new Field(fname,type,alias,xfinal) : FREE.pop().init(fname,type,alias,xfinal);
+ }
+ // Malloc-from
+ public Field malloc( ) { return malloc(_fname,null,_alias,_final); }
+ @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 ) {
+ Field f = malloc(fname,type,alias,xfinal);
+ 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);
+ }
+ public Field makeFrom( boolean xfinal ) {
+ return xfinal == _final ? this : make(_fname,_t,_alias,xfinal);
+ }
+
+ // 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 void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(TEST2); ts.add(FLT); }
+
+ @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;
+ return make(_fname, _t.meet(fld._t ), _alias, _final | fld._final);
+ }
+
+ @Override
+ Field xdual() { return malloc(_fname, _t.dual(),_alias,!_final); }
+
+ @Override Field rdual() {
+ if( _dual!=null ) return dual();
+ assert !_terned;
+ Field d = malloc(_fname,null,_alias,!_final);
+ (_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); }
+ @Override boolean _isGLB(boolean mem) { return _t._isGLB(true); }
+ @Override public Type _glb(boolean mem) { return make(_fname,_t._glb(true),_alias,true); }
+ @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); }
+
+ private boolean static_eq( Field f ) {
+ return _fname.equals(f._fname) && _alias==f._alias && _final==f._final && _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
+ @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));
+ }
+ 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);
+ }
+
+ @Override
+ public SB _print( SB sb, BitSet visit, boolean html ) {
+ sb.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/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java
new file mode 100644
index 00000000..fd3a7c59
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java
@@ -0,0 +1,682 @@
+package com.seaofnodes.simple.type;
+
+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(); }
+ public void set( int idx, Type t ) { throw Utils.TODO(); }
+ 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 && tcon.elemT() == t._type ) return tcon.ymeet(t);
+ if( t instanceof TypeConAry tcon && tcon.elemT() == _type ) return tcon.ymeet(this);
+ // 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
+ });
+ }
+
+
+ // ----------------------------------------------------------
+ // 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; }
+
+ //// Is forward-reference
+ //public boolean isFRef() { return false; }
+
+ // 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?
+ 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; }
+
+ // Compute greatest lower bound in the lattice. If values are in memory,
+ // ints and floats cannot widen.
+ public final boolean isGLB(boolean mem) { return recurClose(recurOpen()._isGLB(mem)); };
+ boolean _isGLB(boolean mem) {
+ return switch(_type) {
+ case TBOT -> false;
+ case TSCALAR -> this==TypeScalar.BOT;
+ case TNIL -> false;
+ case TCTRL -> true;
+ case TXCTRL -> false;
+ case TXNIL -> false;
+ case TTOP -> false;
+ default -> throw Utils.TODO();
+ };
+ }
+ public final Type glb(boolean mem) {
+ if( isGLB(mem) ) return this;
+ Type glb = recurOpen()._glb(mem).recurClose();
+ assert this.isa(glb);
+ return glb;
+ }
+ Type _glb(boolean mem) { assert is_simple(); return Type.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();
+ };
+ }
+
+ // 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();
+ };
+ }
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAry.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
new file mode 100644
index 00000000..26fa4a67
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
@@ -0,0 +1,95 @@
+package com.seaofnodes.simple.type;
+
+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;
+
+/**
+ * Represents a constant array of primitives
+ */
+public abstract class TypeConAry<A> extends TypeScalar {
+ boolean _any;
+ // One of byte,short,int,long,float,double array
+ public final A _ary;
+
+ TypeConAry( boolean any, A ary ) { super(TCONARY); _any = any; _ary = ary; }
+ public static void gather(ArrayList<Type> ts) {
+ TypeConAryB.gather(ts);
+ TypeConAryI.gather(ts);
+ }
+
+ @Override public String str() { return (_any?"~":"") + "[]"; }
+ @Override TypeConAry xdual() { return this; }
+
+ @Override Type xmeet(Type t) {
+ TypeConAry ary = (TypeConAry)t; // Invariant
+ assert _ary!=ary._ary; // Already interned and this!=t
+ // Unrelated constant arrays
+ return elem().meet(ary.elem());
+ }
+
+ Type ymeet( Type t ) {
+ if( t instanceof TypeInteger ti ) {
+ // if i can isa *each* element, then maybe can keep.
+ // no good to i.isa(elem()) because fails the dual
+ boolean fail = false;
+ for( int j=0; j<len(); j++ )
+ if( !ti.isa(TypeInteger.constant(at8(j))) )
+ { fail=true; break; }
+ Type e = elem();
+ if( fail )
+ return ti.meet(e);
+ return this;
+ }
+ throw Utils.TODO();
+ }
+
+ @Override public boolean isHigh() { return this==TOP; }
+ @Override boolean _isConstant() { return true; }
+ @Override Type _glb(boolean mem) { return this; }
+ @Override boolean _isGLB(boolean mem) { return true; }
+
+ // 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);
+ }
+ // Generic element _type
+ public int elemT() { return TBOT; }
+ public long at8(int idx) { throw Utils.TODO(); }
+ public int len() { throw Utils.TODO(); }
+ @Override public int log_size() { throw Utils.TODO(); }
+ public void write( BAOS baos ) { throw Utils.TODO(); }
+
+ // 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()]));
+ }
+
+ @Override boolean eq(Type t) {
+ return t instanceof TypeConAry ary && _any==ary._any && ary._ary==null;
+ }
+
+ @Override int hash() {
+ assert _ary==null;
+ return _any ? 1024 : 0;
+ }
+
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java
new file mode 100644
index 00000000..bd2827ed
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.util.BAOS;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class TypeConAryB extends TypeConAry<byte[]> {
+ private TypeConAryB(byte[] bs) { super(false,bs); }
+ public static TypeConAryB make( byte[] bs ) { return new TypeConAryB(bs).intern(); }
+ public static TypeConAryB make( String s ) { return make(s.getBytes()); }
+ static final TypeConAryB ABC = make("abc");
+ static final TypeConAryB ABCD = make("abcd");
+ public static void gather(ArrayList<Type> ts) { ts.add(ABC); ts.add(ABCD); }
+ @Override public int elemT() { return TINT; }
+ @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 && Arrays.equals(_ary,ary._ary); }
+ @Override int hash() { return Arrays.hashCode(_ary); }
+ @Override public void write( BAOS baos ) { baos.write(_ary); }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java
new file mode 100644
index 00000000..bb2ab83c
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java
@@ -0,0 +1,18 @@
+package com.seaofnodes.simple.type;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class TypeConAryI extends TypeConAry<int[]> {
+ private TypeConAryI(int[] bs) { super(false,bs); }
+ public static TypeConAryI make( int[] bs ) { return new TypeConAryI(bs).intern(); }
+ private static final TypeConAryI I123 = make(new int[]{1,2,3});
+ public static void gather(ArrayList<Type> ts) { ts.add(I123); }
+ @Override public int elemT() { return TINT; }
+ @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 && Arrays.equals(_ary,ary._ary); }
+ @Override int hash() { return Arrays.hashCode(_ary); }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFloat.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
new file mode 100644
index 00000000..f4968f28
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
@@ -0,0 +1,134 @@
+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();
+ };
+ }
+ 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 _isGLB(boolean mem) { return _glb(mem)==this; }
+ @Override Type _glb(boolean mem) {
+ if( !mem ) return F64;
+ if( _isConstant() ) return isF32() ? F32 : F64;
+ return isHigh() ? dual() : this;
+ }
+
+ @Override boolean _isConstant() { return _sz==0; }
+
+ @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();
+ };
+ }
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
new file mode 100644
index 00000000..e2ec6263
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
@@ -0,0 +1,304 @@
+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);
+ }
+ public static TypeFunPtr make( boolean nil, Type[] sig, Type ret ) { return make((byte)(nil ? 3 : 2),true,sig,ret, XInt.FULL); }
+ public static TypeFunPtr make1( byte nil, boolean open, Type[] sig, Type ret, int fidx ) {
+ return make(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((byte)2,_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); }
+
+ 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 boolean _isConstant() { return (_nil==2 && XInt.isConstant(_fidxs)) || (_nil==3 && _fidxs== XInt.EMPTY); }
+
+ @Override boolean _isFinal() { return true; }
+ @Override boolean _isGLB(boolean mem) { return true; }
+ @Override TypeFunPtr _glb(boolean mem) { 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 ) {
+ 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() { return "{"+printFIDX()+"}"; }
+
+ @Override
+ SB _print(SB sb, BitSet visit, boolean html ) {
+ 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/chapter25/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
new file mode 100644
index 00000000..4245626a
--- /dev/null
+++ b/chapter25/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) {
+ // Constants must always have a widen value of zero. Widening is only
+ // meaningful for ranges of values - which otherwide can increase in
+ // range size indefinitely.
+ assert min!=max || widen==0; // Constants always zero 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,lo==hi ? 0 : 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( Type minType ) {
+ return _widen < 3 ? make(_min,_max, (byte)(_widen+1)) : minType;
+ }
+ // 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); }
+
+ @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 _min==_max ? this : malloc(_max,_min,(byte)(-_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 boolean _isGLB(boolean mem) { return _glb(mem)==this; }
+ @Override Type _glb(boolean mem) {
+ if( !mem ) return BOT;
+ // GLB is not well-defined in memory; depends on the size of the memory field
+ 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);
+ baos.packed1(_widen);
+ }
+ }
+
+ 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());
+ default -> throw Utils.TODO();
+ };
+ }
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMem.java
new file mode 100644
index 00000000..3bbdf1df
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMem.java
@@ -0,0 +1,252 @@
+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;
+ // Public bulk memory is an aggregate of otherwise-unsplit aliases;
+ // a single final bit cannot describe it. Keep the lattice-dual high
+ // form distinct, and retain finality for private singleton memory and
+ // precise alias slices.
+ _final = t!=null && alias==1 && !one && !t.isHigh() ? false : 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(Type t) { return make(_alias,t,_one,_clz,_final,_escFs,_escAs); }
+ 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));
+ }
+ // Existing escapes plus `t` escapes
+ public TypeMem escapesFrom(int alias, 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); }
+
+ @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 & class facts are kept only when both sides agree.
+
+ // Final is kept if either side is final, merging a final & non-final
+ // memory can be argued either way here:
+
+ // - Keep it: merging a final & non-final memory means a later Store
+ // update will be flagged as an error.
+ // - Lose it: merging a final & non-final memory means a later Load
+ // cannot be promised the memory is unchanging.
+ return make(alias, mt, _one & that._one, _clz & that._clz, _final | that._final, 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();
+ if( d._alias==1 && !d._one && !d._t.isHigh() ) d._final = false;
+ 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(); }
+ @Override boolean _isFinal() { return _t._isFinal(); }
+ @Override boolean _isGLB(boolean mem) { return _t._isGLB(true); }
+ @Override public Type _glb(boolean mem) { return make(_alias,_t._glb(true),_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) {
+ 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) {
+ 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() {
+ 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
+ ? 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);
+ case Type tt -> tt == BOTTOM ? 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
+ ? XInt.FULL // Open structs can have any fields
+ : XInt.meet(xs,ts.aliases());
+ case TypeMem mem -> XInt.meet(xs,mem._escAs);
+ case Type tt -> tt == BOTTOM ? XInt.FULL : xs;
+ };
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
new file mode 100644
index 00000000..67d80591
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
@@ -0,0 +1,162 @@
+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;
+ 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; _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) {
+ TypeMemPtr tmp = malloc(nil, obj, one, pub);
+ 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); }
+ public TypeMemPtr makeNullable() { return makeFrom((byte)3); }
+ @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one, _pub); }
+ public TypeMemPtr makeHigh(byte nil) { return make(nil,_obj.makeHigh(),false); }
+ @Override public Type nonZero() { return makeFrom((byte)(_nil <= 1 ? 1 : 2)); }
+
+ // 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); }
+
+ @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);
+ }
+
+ @Override
+ TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), !_one, !_pub); }
+
+ @Override TypeMemPtr rdual() {
+ if( _dual!=null ) return dual();
+ assert !_terned;
+ TypeMemPtr d = malloc(dual0(), null, !_one, !_pub);
+ (_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); }
+
+ @Override boolean _isConstant() { return _one; }
+ @Override boolean _isFinal() { return _obj._isFinal(); }
+ @Override TypeMemPtr _makeRO() { return makeFrom(_obj._makeRO()); }
+ @Override boolean _isGLB(boolean mem) { return _obj._isGLB(true); }
+ @Override TypeMemPtr _glb(boolean mem) { return make((byte)3,_obj.glb2()); }
+ @Override TypeMemPtr _close( String name, HashMap<String, Type> TYPES ) { return malloc(_nil,_obj._close(name, TYPES ),_one,_pub); }
+
+ @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 _obj.hashCode() ^ super.hash() ^ (_one ? 2048 : 0) ^ (_pub ? 4096 : 0); }
+
+ @Override boolean eq(Type t) {
+ TypeMemPtr ptr = (TypeMemPtr)t; // Invariant
+ return 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 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 and public/private
+ @Override int TAGOFF() { return 8; }
+ @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) );
+ }
+ static TypeMemPtr packed( int tag, BAOS bais ) {
+ return malloc((byte)((tag&1)+2),null,(tag&2)==2,(tag&4)==4);
+ }
+
+ @Override public String str() {
+ if( this== NOTBOT) return "*void";
+ if( this== BOT) return "*void?";
+ return x()+"*"+(_obj==null?"---":_obj.str())+q();
+ }
+
+ @Override SB _print(SB sb, BitSet visit, boolean html ) {
+ if( this== NOTBOT) return sb.p("*void");
+ if( this== BOT) return sb.p("*void?");
+ return _obj.print(sb.p(x()).p("*"),visit,html).p(q());
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeNil.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeNil.java
new file mode 100644
index 00000000..32505aa2
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeNil.java
@@ -0,0 +1,62 @@
+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; }
+ @Override Type _glb(boolean mem) { return Type.NIL; }
+
+ 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/chapter25/src/main/java/com/seaofnodes/simple/type/TypePtr.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypePtr.java
new file mode 100644
index 00000000..3ff9765b
--- /dev/null
+++ b/chapter25/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(); }
+
+ @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); }
+
+ boolean _isGLB(boolean mem) { return this==PTR; }
+ @Override TypePtr _glb(boolean mem) { 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/chapter25/src/main/java/com/seaofnodes/simple/type/TypeRPC.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeRPC.java
new file mode 100644
index 00000000..a39ebc04
--- /dev/null
+++ b/chapter25/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 boolean _isGLB(boolean mem) { return true; }
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/type/TypeScalar.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeScalar.java
new file mode 100644
index 00000000..4fec3c33
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeScalar.java
@@ -0,0 +1,56 @@
+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 boolean _isGLB(boolean mem) { return this == BOT; }
+ @Override Type _glb(boolean mem) { return BOT; }
+
+ @Override int TAGOFF() { return 1; }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs) {
+ assert this == BOT;
+ baos.write(TAGOFFS[_type]);
+ }
+ static TypeScalar packed(int tag) {
+ assert tag == 0;
+ return BOT;
+ }
+
+ @Override public String str() { return isHigh() ? "~scalar" : "scalar"; }
+ @Override SB _print(SB sb, BitSet visit, boolean html) { return sb.p(str()); }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
new file mode 100644
index 00000000..f51ab859
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
@@ -0,0 +1,660 @@
+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. On the low side an
+ * open struct has infinitely many implicit BOT fields; its dual has an
+ * implicit TOP tail. A real declaration is therefore 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:
+ *
+ * ```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 makeHigh() {
+ Field[] fs = new Field[_fields.length];
+ for( int i=0; i<_fields.length; i++ )
+ fs[i] = _fields[i].makeFrom(Type.TOP);
+ return make(_name,_open,_fref,fs);
+ }
+
+ public TypeStruct makeInit() {
+ Field[] fs = new Field[_fields.length];
+ for( int i=0; i<_fields.length; i++ ) {
+ Type zero = _fields[i]._t.makeZero();
+ fs[i] = _fields[i].makeFrom(_fields[i]._t != Type.BOTTOM && _fields[i]._t != Type.TOP && 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;
+ if( base == null ) base = this;
+ 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 )
+ 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() {
+ if( VISIT.containsKey(_uid) ) return true; // Cycles assume constant
+ VISIT.put(_uid,this);
+ if( _open ) return false; // Infinite BOT fields
+ // Special case for constant arrays
+
+ // 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);
+ }
+
+ // Keeps the same struct, but lower-bounds all fields.
+ public TypeStruct glb2() {
+ return recurTransform(_uid,_name,_open,false,false,t -> t._glb(true));
+ }
+
+ @Override boolean _isGLB(boolean mem) {
+ if( VISIT.containsKey(_uid) ) return true; // Cycles assume GLB
+ VISIT.put(_uid,this);
+ for( Field f : _fields )
+ if( !f._t._isGLB(mem) )
+ return false;
+ return true;
+ }
+
+ // 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(); }
+ @Override public int size() { return offset(_fields.length); }
+ @Override public int alignment() {
+ int align = 0;
+ for( Field f : _fields )
+ 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( isFree() ) return sb.p("FREE:").p(_name);
+ if( isAry() && field("[]")._t instanceof TypeConAry con )
+ return sb.p(con.str());
+ sb.p(_name);
+ if( html || isAry() )
+ 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() { 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.Opto.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++ )
+ 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++ ) {
+ 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/chapter25/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
new file mode 100644
index 00000000..86f8aef1
--- /dev/null
+++ b/chapter25/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(); }
+
+ @Override TypeMemPtr _makeRO() { throw Utils.TODO(); }
+
+ @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(); }
+ @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/chapter25/src/main/java/com/seaofnodes/simple/type/XInt.java b/chapter25/src/main/java/com/seaofnodes/simple/type/XInt.java
new file mode 100644
index 00000000..e3054a73
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/XInt.java
@@ -0,0 +1,266 @@
+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);
+ for( int i=1; i<min; i++ ) {
+ if( (xs[i] | ys[i])!=xs[i] ) isX=false;
+ if( (xs[i] | ys[i])!=ys[i] ) isY=false;
+ }
+ if( !isHigh(xs) && min < ys.length )
+ isX = false; // YS has *some* more bits set, and XS does not
+ if( !isHigh(ys) && min < xs.length )
+ isY = false; // XS has *some* more bits set, and YS does not
+ if( isX ) return xs;
+ if( isY ) return ys;
+ // Get a free one
+ int[] ms = free(Math.max(xs.length,ys.length));
+ for( int i=1; i<min; i++ )
+ ms[i] = xs[i] | ys[i];
+ if( min < xs.length ) System.arraycopy(xs,min,ms,min,xs.length-min);
+ if( min < ys.length ) System.arraycopy(ys,min,ms,min,ys.length-min);
+ 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
+ x++; // Skip current
+ for( ; !bit(xs,x); x++ )
+ if( idx(x)>=xs.length )
+ return -1;
+ return x;
+ }
+
+ // 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/chapter25/src/main/java/com/seaofnodes/simple/util/Ary.java b/chapter25/src/main/java/com/seaofnodes/simple/util/Ary.java
new file mode 100644
index 00000000..d67801c2
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/util/AryInt.java b/chapter25/src/main/java/com/seaofnodes/simple/util/AryInt.java
new file mode 100644
index 00000000..b498fd78
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/util/BAOS.java b/chapter25/src/main/java/com/seaofnodes/simple/util/BAOS.java
new file mode 100644
index 00000000..61ce7387
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/util/IntHashMap.java b/chapter25/src/main/java/com/seaofnodes/simple/util/IntHashMap.java
new file mode 100644
index 00000000..55aaa9f9
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/util/IntHashMap.java
@@ -0,0 +1,65 @@
+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 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
+ 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];
+ }
+
+ 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 )
+ put(old[i],old[i+1]);
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/util/SB.java b/chapter25/src/main/java/com/seaofnodes/simple/util/SB.java
new file mode 100644
index 00000000..cc59f2a6
--- /dev/null
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/util/Utils.java b/chapter25/src/main/java/com/seaofnodes/simple/util/Utils.java
new file mode 100644
index 00000000..577a60af
--- /dev/null
+++ b/chapter25/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); }
+}