summaryrefslogtreecommitdiff
path: root/chapter-modules/src/main/java/com/seaofnodes
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-10-24 15:25:58 -0700
committerCliff Click <cliffc@acm.org>2026-10-24 15:25:58 -0700
commit4ae6a06d94b228a5eb67c8473131a791e310c233 (patch)
tree839dfa84f1bfb982122ce62183b6ae4145a4c615 /chapter-modules/src/main/java/com/seaofnodes
parentc4364363dbd969c9aa27aa7c43fbe6d40544501a (diff)
downloadSimple-4ae6a06d94b228a5eb67c8473131a791e310c233.tar.gz
Simple-4ae6a06d94b228a5eb67c8473131a791e310c233.zip
Rename modules chapter to 28a
Diffstat (limited to 'chapter-modules/src/main/java/com/seaofnodes')
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/IterPeeps.java349
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/Parser.java3157
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/Var.java54
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java101
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java214
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java105
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java625
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java98
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java460
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Encoding.java570
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java44
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/FinishCalls.java57
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java304
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/IFG.java580
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java126
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Iter.java38
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/LRG.java240
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java33
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java261
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/LocalIds.java33
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Machine.java127
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Opto.java317
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java285
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java14
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java780
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RegMask.java121
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java36
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/SourceError.java49
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/StaticData.java177
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java57
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/AddNode.java179
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/AndNode.java49
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/ArithNode.java208
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/BoolNode.java188
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java265
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/CFGNode.java303
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/CLInitNode.java165
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/CProjNode.java73
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/CallEndMach.java38
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/CallEndNode.java321
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/CallNode.java220
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java23
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java78
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/ConstantNode.java86
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/ConvertNode.java101
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/CtrlNode.java17
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/DivNode.java40
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/EscapeNode.java109
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/ExternNode.java27
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java16
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/FRefNode.java50
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/FldOffNode.java54
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/FunNode.java463
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java62
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/GuardMach.java17
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/GuardNode.java62
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/IfNode.java100
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/LoadNode.java331
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/LoopNode.java106
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java26
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/MachNode.java60
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java154
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/MemOpNode.java140
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java62
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/MinusNode.java77
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/ModeNode.java12
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/MulNode.java65
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/MultiNode.java29
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/NeverNode.java24
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/NewNode.java98
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/Node.java696
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/NotNode.java25
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/OrNode.java44
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/ParmNode.java95
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/PhiNode.java275
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/Proj.java2
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/ProjNode.java59
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java17
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java36
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java19
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java27
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/RegionNode.java172
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/ReturnNode.java129
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java33
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/SarNode.java37
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/ScopeNode.java719
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/ShlNode.java49
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/ShrNode.java32
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/SplitNode.java32
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/StartCUNode.java22
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/StartNode.java87
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/StopCUNode.java126
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/StopNode.java94
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/StoreNode.java326
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/SubNode.java56
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java26
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java24
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/TypeNode.java32
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java16
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/XorNode.java43
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java21
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java23
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java28
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java22
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java25
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java19
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java32
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java80
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java50
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java33
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java23
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java22
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java41
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java20
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java20
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java31
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java21
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java45
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java22
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java26
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java39
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java34
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java20
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java34
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java20
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java29
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java81
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java20
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java19
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java25
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java39
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java28
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java21
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java25
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java17
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java15
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java16
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java36
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java60
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java21
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java11
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java38
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java20
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java19
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java29
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java51
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java49
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java48
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java20
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java27
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java503
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java42
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java18
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java22
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java21
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java13
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java16
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java95
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java62
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java32
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java20
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java17
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java32
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java19
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java48
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java23
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java24
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java55
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java49
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java31
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java33
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java65
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java51
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java18
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java17
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java21
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java49
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java22
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java13
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java16
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java17
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java15
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java22
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java34
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java21
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java20
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java13
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java17
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java61
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java15
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java17
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java13
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java17
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java56
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java18
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java16
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java56
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java44
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java49
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java14
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java16
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java450
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java33
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java24
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java23
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java24
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java11
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java11
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java11
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java30
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java55
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java24
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java38
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java44
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java28
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java24
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java25
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java29
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java22
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java39
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java32
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java21
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java32
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java43
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java65
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java38
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java44
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java28
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java89
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java24
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java27
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java24
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java20
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java24
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java21
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java11
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java11
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java17
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java15
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java22
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java15
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java32
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java21
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java44
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java32
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java21
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java27
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java21
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java25
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java45
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java48
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java23
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java10
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java51
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java39
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java41
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java11
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java11
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java596
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java101
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/print/IRPrinter.java60
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java80
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java35
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/sys.java25
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/type/Field.java142
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/type/Type.java548
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeConAry.java80
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java21
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java16
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeFloat.java111
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java313
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeInteger.java185
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeMem.java229
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java179
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeNil.java61
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/type/TypePtr.java57
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeRPC.java86
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeScalar.java45
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeStruct.java640
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeTuple.java158
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/type/XInt.java244
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/util/Ary.java229
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/util/AryInt.java192
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/util/BAOS.java162
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/util/IntHashMap.java87
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/util/SB.java99
-rw-r--r--chapter-modules/src/main/java/com/seaofnodes/simple/util/Utils.java53
287 files changed, 0 insertions, 27677 deletions
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter-modules/src/main/java/com/seaofnodes/simple/IterPeeps.java
deleted file mode 100644
index e65ef6d9..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ /dev/null
@@ -1,349 +0,0 @@
-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 java.util.Arrays;
-import java.util.BitSet;
-import java.util.IdentityHashMap;
-import java.util.Random;
-
-/**
- * The IterPeeps runs after parsing. It iterates the peepholes to a fixed point
- * so that no more peepholes apply. This should be linear because peepholes rarely
- * (never?) increase code size. The graph should monotonically reduce in some
- * dimension, which is usually size. It might also reduce in e.g. number of
- * MulNodes or Load/Store nodes, swapping out more "expensive" Nodes for cheaper
- * ones.
- * <br>
- * The theoretical overall worklist is mindless just grabbing the next thing and
- * doing it. If the graph changes, put the neighbors on the worklist.
- * <br>
- * Lather, Rinse, Repeat until the worklist runs dry.
- * <p>
- * The main issues we have to deal with:
- *
- * <ul>
- * <li>Nodes have uses; replacing some set of Nodes with another requires more graph
- * reworking. Not rocket science, but it can be fiddly. Its helpful to have a
- * small set of graph munging utilities, and the strong invariant that the graph
- * is stable and correct between peepholes. In our case `Node.subsume` does
- * most of the munging, building on our prior stable Node utilities.</li>
- *
- * <li>Changing a Node also changes the graph "neighborhood". The neighbors need to
- * be checked to see if THEY can also peephole, and so on. After any peephole
- * or graph update we put a Nodes uses and defs on the worklist.</li>
- *
- * <li>Our strong invariant is that for all Nodes, either they are on the worklist
- * OR no peephole applies. This invariant is easy to check, although expensive.
- * Basically the normal "iterate peepholes to a fixed point" is linear, and this
- * check is linear at each peephole step... so quadratic overall. It's a useful
- * assert, but one we can disable once the overall algorithm is stable - and
- * then turn it back on again when some new set of peepholes is misbehaving.
- * The code for this is turned on in `IterPeeps.iterate` as `assert
- * progressOnList(stop);`</li>
- * </ul>
- */
-public class IterPeeps {
-
- public final WorkList<Node> _work;
- final WorkList<CallEndNode> _workInline;
- private int _cnt;
-
- public IterPeeps( long seed ) {
- _work = new WorkList<>(seed);
- _workInline = new WorkList<>(seed);
- }
-
- @SuppressWarnings("unchecked")
- public <N extends Node> N add( N n ) { return (N)_work.push(n); }
-
- public void addAll( Ary<Node> ary ) { _work.addAll(ary); }
-
- /**
- * Iterate peepholes and inlining to a fixed point
- */
- public void iterate( CodeGen code ) {
- boolean didInline = false;
- Ary<Node> defer = new Ary<>(Node.class);
- while( true ) {
- // Clean up everything that does not grow the code
- iteratePeeps(code);
-
- // Pick an inline candidate, no real heuristic, first come, first served
- boolean inlined = false;
- CallEndNode cend;
- while( (cend=_workInline.pop()) != null) {
- if( cend.isDead() ) continue;
- byte inline = cend.maybeInline();
- if( inline == -1 ) defer.add(cend);
- if( inline > 0 ) {
- inlined = true;
- break;
- }
- }
- // Inlined, run peeps until clean again
- if( inlined ) { didInline = true; continue; }
- // No more candidates, check the defer list
- if( !didInline ) break; // No more progress, so all the "maybe inline after cleanup" do not progress
- // Some inlining happened, retry all the "try again after cleanup" calls
- didInline = false;
- code.addAll(defer);
- defer.clear();
- }
- }
-
- // Run all the code-reduction and type-lifting peeps as possible
- private void iteratePeeps( CodeGen code ) {
- assert !CodeGen.expensiveAssert(0) || (progressOnList(code, _work) && schedulableUses(code));
-
- Node n;
- while( (n=_work.pop()) != null ) {
- if( n.isDead() ) continue;
- _cnt++; // Useful for debugging, searching which peephole broke things
- var obs = code._obs; // Collect peephole neighbors for graph display
- if( obs != null ) obs.before(n);
- Node x = n.peepholeOpt();
- if( n instanceof CallEndNode cend )
- _workInline.push(cend);
- if( x != null ) {
- assert !x.isDead(); // Peepholes return alive answers
- // peepholeOpt can return brand-new nodes, needing an initial type set
- if( x._type==null ) x.setType(x.compute());
- // Changes require neighbors onto the worklist
- if( x != n || !(x instanceof ConstantNode) ) {
- // All outputs of n (changing node) not x (prior existing node).
- for( Node z : n._outputs ) _work.push(z);
- // Everybody gets a free "go again" in case they didn't get
- // made in their final form.
- _work.push(x);
- // A self-returning peephole can have rewritten its input
- // edges. The new defs gained a user and may have
- // backwards, user-sensitive peepholes of their own.
- for( Node z : x._inputs ) _work.push(z);
- // If the result is not self, revisit all inputs (because
- // there's a new user), and replace in the graph.
- if( x != n ) {
- //for( Node z : n. _inputs ) _work.push(z);
- for( Node z : x._outputs ) _work.push(z);
- n.subsume(x);
- }
- }
- // If there are distant neighbors, move to worklist
- n.moveDepsToWorklist();
- // Very expensive assert.
- assert !CodeGen.expensiveAssert(_cnt) || (progressOnList(code, _work) && schedulableUses(code));
- }
- boolean unused = n.isUnused();
- if( unused ) {
- assert !(n instanceof StopNode); // StopNodes can die if all code in the compunit dies
- n.kill(); // Just plain dead
- }
- if( obs != null ) obs.after(n, x!=null ? x : unused ? n : null, true);
- }
- assert !CodeGen.expensiveAssert(0) || schedulableUses(code);
-
- }
-
- // Visit ALL nodes and confirm the invariant:
- // Either you are on the _work worklist OR running `iter()` makes no progress.
-
- // This invariant ensures that no progress is missed, i.e., when the
- // worklist is empty we have indeed done all that can be done. To help
- // with debugging, the {@code assert} is broken out in a place where it is easy to
- // stop if a change is found.
-
- // Also, the normal usage of `iter()` may attempt peepholes with distance
- // neighbors and these should fail, but will then try to add dependencies
- // {@link #Node.addDep} which is a side effect in an assert. The {@link
- // #midAssert} is used to stop this side effect.
- // Pessimistic solver assert
- public static boolean progressOnList(CodeGen code, WorkList<Node> list ) {
- code._midAssert = true;
- Node changed = code._stop.walk( n -> {
- // Ignore most in-progress things
- if( n.iskeep() ) return null;
- // Types must be forwards, even if on the worklist.
- Type nval = n.compute();
- assert nval.isa(n._type) : "Non-monotonic peep: "+n+"#"+n._nid+" old="+n._type+" new="+nval+" inputs="+inputTypes(n)+" peep="+n;
- if( list.on(n) )
- return null; // On worklist is ok!
- if( n instanceof CallEndNode cend ) {
- if( code._iter._workInline.on(cend) )
- return null; // On inline worklist is ok!
- assert cend.maybeInline() <= 0 : "Inline fired and not on worklist, CallEndNode#"+cend._nid;
- }
- // Off list: must be live, in GVN table, and no peeps.
- assert n.nOuts() > 0 : "Unused live node: "+n;
- assert code._gvn.get(n)==n : "Not in GVN table: "+n;
- Node m = n.peepholeOpt();
- assert m==null : "Peep fired and not on worklist, "+n.getClass().getSimpleName()+"#"+n._nid+" -> "+m;
- return m;
- });
- code._midAssert = false;
- return changed==null;
- }
-
- private static String inputTypes(Node n) {
- StringBuilder sb = new StringBuilder("[");
- for( Node in : n._inputs )
- sb.append(in==null ? "null" : in.getClass().getSimpleName()+"#"+in._nid+"="+in+":"+in._type+":keep="+in.iskeep()).append(',');
- return sb.append(']').toString();
- }
-
- // GCM assumes every movable node can be placed no earlier than its inputs
- // and no later than the LCA of its uses. Catch graphs where a use escapes
- // above an input-defined branch before the later scheduling pass walks off
- // the top of the idom tree.
- private static final IdentityHashMap<Node,CFGNode> earlyCache = new IdentityHashMap<>();
- private static final BitSet VISIT = new BitSet();
- public static boolean schedulableUses(CodeGen code) {
- earlyCache.clear();
- Node bad = code._stop.walk( n -> {
- if( n.iskeep() || n.isDead() || n.isConst() ||
- n instanceof CFGNode || n instanceof PhiNode || n instanceof ProjNode )
- return null;
- assert VISIT.isEmpty();
- CFGNode early = CFGNode.earlyCFG(n,earlyCache,VISIT);
- VISIT.clear();
- if( early == null ) return null;
- CFGNode lca = null;
- for( Node use : n._outputs ) {
- CFGNode ublk = useBlock(n,use);
- if( ublk != null )
- lca = ublk.domLCA(lca,null);
- }
- if( lca == null ) return null;
- if( !early.sameFun(lca) ) return null;
- assert early.dominates(lca,null) : badSchedule(n,early,lca);
- return null;
- });
- return bad == null;
- }
-
- private static CFGNode useBlock(Node n, Node use) {
- if( use == null )
- return null;
- if( use instanceof PhiNode phi ) {
- CFGNode found = null;
- for( int i=1; i<phi.nIns(); i++ )
- if( phi.in(i)==n ) {
- if( i >= phi.region().nIns() )
- return null;
- found = phi.region().cfg(i).domLCA(found,null);
- }
- return found;
- }
- return CFGNode.safeCFG(use);
- }
-
- private static String badSchedule(Node n, CFGNode early, CFGNode lca) {
- StringBuilder sb = new StringBuilder("Unschedulable data node ")
- .append(n.getClass().getSimpleName()).append('#').append(n._nid)
- .append(" early=").append(cfg(early))
- .append(" use-lca=").append(cfg(lca))
- .append(" node=").append(n).append("\ninputs:");
- for( int i=0; i<n.nIns(); i++ ) {
- Node in = n.in(i);
- sb.append("\n in").append(i).append(": ").append(node(in))
- .append(" cfg=").append(cfg(CFGNode.safeCFG(in)));
- }
- sb.append("\noutputs:");
- for( Node use : n._outputs )
- if( use != null )
- sb.append("\n use ").append(node(use))
- .append(" block=").append(cfg(useBlock(n,use)));
- return sb.toString();
- }
-
- private static String node(Node n) {
- return n==null ? "null" : n.getClass().getSimpleName()+"#"+n._nid+" "+n;
- }
-
- private static String cfg(CFGNode cfg) {
- return cfg==null ? "null" : cfg.getClass().getSimpleName()+"#"+cfg._nid+" "+cfg;
- }
-
- /**
- * Classic WorkList, with a fast add/remove, dup removal, random pull.
- * The Node's nid is used to check membership in the worklist.
- */
- @SuppressWarnings("unchecked")
- public static class WorkList<E extends Node> {
-
- private Node[] _es;
- private int _len;
- private final BitSet _on; // Bit set if Node._nid is on WorkList
- private final Random _R; // For randomizing pull from the WorkList
- private final long _seed;
-
- /* Useful stat - how many nodes are processed in the post parse iterative opt */
- private long _totalWork = 0;
-
- public WorkList() { this(123); }
- WorkList(long seed) {
- _es = new Node[1];
- _len=0;
- _on = new BitSet();
- _seed = seed;
- _R = new Random();
- _R.setSeed(_seed);
- }
-
- /**
- * Pushes a Node on the WorkList, ensuring no duplicates
- * If Node is null it will not be added.
- */
- public E push( E x ) {
- if( x==null ) return null;
- int idx = x._nid;
- if( !_on.get(idx) ) {
- _on.set(idx);
- if( _len==_es.length )
- _es = Arrays.copyOf(_es,_len<<1);
- _es[_len++] = x;
- _totalWork++;
- }
- return x;
- }
-
- public void addAll( Ary<E> ary ) {
- for( E n : ary )
- push(n);
- }
- public void addAll( E[] es ) {
- for( E n : es )
- push(n);
- }
-
-
- /**
- * True if Node is on the WorkList
- */
- public boolean on( E x ) { return _on.get(x._nid); }
-
- /**
- * Removes a random Node from the WorkList; null if WorkList is empty
- */
- public E pop() {
- if( _len == 0 ) return null;
- int idx = _R.nextInt(_len);
- E x = (E)_es[idx];
- _es[idx] = _es[--_len]; // Compress array
- _on.clear(x._nid);
- return x;
- }
-
- public boolean isEmpty() { return _len==0; }
- //Node[] asAry() { return Arrays.copyOf(_es,_len); }
- //public void clear() {
- // _len = 0;
- // _on.clear();
- // _R.setSeed(_seed);
- // _totalWork = 0;
- //}
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/Parser.java b/chapter-modules/src/main/java/com/seaofnodes/simple/Parser.java
deleted file mode 100644
index 031d36c6..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/Parser.java
+++ /dev/null
@@ -1,3157 +0,0 @@
-package com.seaofnodes.simple;
-
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-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
- Ary<FRefNode> _frefs; // Forward refs created during this parse
- TypeStruct _ctorOpenStruct; // Open struct being initialized by an allocation constructor block.
- // Constructor exit types survive scope teardown until all fields are known.
- // A null entry denotes a constructor with no returning path.
- private ArrayList<IdentityHashMap<Var,Type>> _ctorExits;
-
- // 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(null,bulk);
- merge.alias(alias,precise);
- return peep(merge);
- }
-
-
- public Ary<FRefNode> parse( CompUnit ref ) {
- assert _scope == null && _breakScope == null && _continueScope == null && _returnScope == null;
- assert _frefs == null;
- _lexer = new Lexer(ref);
- _frefs = new Ary<>(FRefNode.class);
- // 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");
-
- // All still-unresolved forward refs
- // should immediately resolve to a file-based name which is a Simple
- // class type.
- Ary<FRefNode> frefs = new Ary<>(FRefNode.class);
- for( FRefNode fref : _frefs ) {
- if( fref.nIns() != 1 && fref._con == FRefNode.FREF_TYPE ) continue;
- CompUnit cu = ParseAll.findCompUnitOrThrow(_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;
- _frefs = null;
-
- return frefs;
- }
-
-
- /**
- * Parses a function body, assuming the header is parsed.
- */
- private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, String[] ids) {
- return parseFunctionBody(sig, loc, null, ids);
- }
-
- private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, java.util.BitSet finals, String[] ids) {
- // Record & restore the existing scope vars. The function parse might
- // update fields, so when the parse is done reset back to the pre-parse
- // state.
- Node[] preParse = _scope.save();
-
- // Parse function body normally
- ReturnNode ret = _parseFunctionBody(null, sig,loc,finals,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) {
- return _parseFunctionBody(funName, sig, loc, null, ids);
- }
-
- private ReturnNode _parseFunctionBody( String funName, TypeFunPtr sig, Lexer loc, java.util.BitSet finals, String[] ids) {
- Constructor outerCtor=_pendingCtor;
- _pendingCtor=null;
- 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,isCtor && !funName.endsWith(".<clinit>")));
- 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") || (finals!=null && finals.get(i)), new ParmNode(ids[i],i+2,t,fun,ConstantNode.seed(t).peephole()).peephole(), loc);
- }
-
- if( isCtor && !isInit ) {
- int lex=_scope.klast()._lexSize;
- TypeStruct shape=(TypeStruct)TYPES.get(typeName);
- for( Field fld : shape._fields ) {
- int idx=fieldVarIdxBefore(lex,fld._fname);
- if( idx<0 ) continue;
- Var v=_scope.var(idx);
- if( v._fref || v._extern!=0 || v._uninit || _scope.in(v)._type.isConstant() ) continue;
- Node value=convertExpr(
- new LoadNode(loc,fld._fname,fld._alias,ctrl(),fun.parm(3),fun.parm(2),off(shape,fld._fname)).peephole(),v.type());
- _scope.update(v,value);
- }
- }
-
- // The initializer owns its run-once flag store. Parent initialization
- // is a structural requirement, executed before this body by CLInit lowering.
- if( fun.isClz() ) {
- Node self = fun.parm(2);
- Field flag = ((TypeMemPtr)self._type)._obj.field("$init");
- Node prior = mem().keep();
- Node st = peep(new StoreNode(loc,"$init",flag._alias,flag._t,ctrl(),prior,self,
- fldoff(self,"$init"),con(1),false));
- storeMem(st,flag._alias,prior);
- prior.unkeep();
- }
-
- // 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 );
- }
-
- if( isInit ) finishConstructor();
- _pendingCtor=outerCtor;
-
- ParmNode self = fun.parm(2);
- // Last expression is the return
- if( ctrl()._type==Type.CONTROL ) {
- // Constructors get special return types
- if( fun.isClz() ) {
- assert last!=null;
- } else if( isCtor ) {
- last = fun.parm(3);
- }
- addReturn(last);
- // exit path is now dead (nothing live after a "return")
- }
- if( isCtor )
- 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 = v._extern!=0
- ? Field.make(v._name,v.type(),v._extern,false,true)
- : old == null
- ? Field.make(v._name,v.type(),_code.alias(_ref._cname),v._final)
- : old.makeFrom(v.type());
- 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);
- }
-
- if( !upgrade ) {
- IdentityHashMap<Var,Type> exit = null;
- if( _returnScope!=null && !_returnScope.ctrl()._type.isHigh() ) {
- exit = new IdentityHashMap<>();
- int first = _scope._kinds.at(_scope.depth()-2)._lexSize+2;
- for( int i=first; i<lex; i++ )
- exit.put(_scope.var(i),_returnScope.in(i)._type);
- }
- _ctorExits.add(exit);
- } else if( !isClz ) {
- checkInitFields(tself,base);
- }
-
- // When can _returnScope be null here? A never-exit constructor will
- // not have any returns, and thus no need to gather values and store
- // them into the (never) constructed object
- if( _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 || v._extern!=0 ) continue; // C storage has no Simple initializer
- // NewNode's TOP field is the zero-code poison for a required
- // field. Leave it alone unless the declaration body supplied a
- // value; otherwise a user constructor must initialize it.
- Node val = _returnScope.in(vidx);
- if( upgrade && v._uninit &&
- !initialized(val._type) ) continue;
- // A numeric merge can still be untyped while parsing. Retain its
- // declared conversion at the helper boundary, as assignments do.
- if( fld._t instanceof TypeInteger || fld._t instanceof TypeFloat )
- val=convertExpr(val,fld._t);
- // 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);
- }
-
- // Validate each user constructor, or an implicit empty one, before the
- // declaration scope goes away. Later fields use their declaration defaults.
- private static boolean initialized(Type t) {
- return t!=Type.BOTTOM && !t.isHigh() && (!(t instanceof TypeNil tn) || tn.notNull());
- }
-
- private void checkInitFields(TypeStruct ts, int base) {
- if( _returnScope==null || _returnScope.ctrl()._type.isHigh() ) return;
- boolean implicit = _ctorExits.isEmpty();
- if( implicit ) _ctorExits.add(new IdentityHashMap<>());
- for( IdentityHashMap<Var,Type> exit : _ctorExits ) {
- if( exit==null ) continue;
- for( Field fld : ts._fields ) {
- int idx = fieldVarIdx(base,fld._fname);
- if( idx == -1 ) continue;
- Var v = _scope.var(idx);
- if( !v._uninit || v._fref ) continue;
- Type t = exit.getOrDefault(v,_returnScope.in(idx)._type);
- if( !initialized(t) )
- throw error("'"+ts._name+"' is not fully initialized, field '"+fld._fname+
- (implicit ? "' needs to be set in a constructor" :
- "' is only partially set in the constructor"),v._loc);
- }
- }
- }
-
- 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() {
- Kind.Func func = (Kind.Func)_scope._kinds.at(_scope.enclosingFuncOrDecl());
- 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.makeFrom(fld._t.makeStorage());
- }
- 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(";") ) 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 record Constructor(String name, Lexer body) {}
- private Constructor _pendingCtor;
-
- 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("=");
- if( _pendingCtor!=null ) throw error("Redefining constructor '"+name+"'");
- _pendingCtor=new Constructor(name,loc());
- // Parse the body after all field declarations are known, so self.x
- // and x share a local slot even when x is declared below this body.
- require("{");
- int depth=1;
- while( depth>0 ) {
- _lexer.skipWhiteSpace();
- if( _lexer.isEOF() ) throw error("Unclosed constructor body");
- char ch=_lexer.nextChar();
- if( ch=='{' ) depth++;
- else if( ch=='}' ) depth--;
- else if( ch=='"' || ch=='\'' ) {
- char quote=ch;
- while( !_lexer.isEOF() ) {
- ch=_lexer.nextChar();
- if( ch=='\\' ) _lexer.nextChar();
- else if( ch==quote ) break;
- }
- }
- }
- require(";");
- return _code.ZERO;
- }
-
- private void finishConstructor() {
- if( _pendingCtor==null ) return;
- Constructor ctor=_pendingCtor;
- _pendingCtor=null;
- Lexer after=_lexer;
- _lexer=new Lexer(ctor.body());
- defineConstructor(ctor.name());
- _lexer=after;
- }
-
- private void defineConstructor(String name) {
- TypeStruct self=_scope.constructorSelf();
- 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));
- 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
- CFGNode ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
- CFGNode ifF = (CFGNode)new CProjNode(ifNode.unkeep(), 1, "False").peephole();
-
- // for( ;;next ) body
- int nextPos = -1, nextEnd = -1;
- if( doFor ) {
- // Skip the next expression and parse it later
- nextPos = 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.balanceLoopFRefs(_scope);
- exit.balanceLoopFRefs(_scope);
- 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((CFGNode)_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
- CFGNode ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
- CFGNode ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole().keep();
- // In if true branch, the ifT proj node becomes the ctrl
- // But first clone the scope and set it as current
- ScopeNode fScope = _scope.dup(); // Duplicate current scope
- fScope.ctrl(ifF); // The pending arm already starts at False
- _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
- ifF.unkeep(); // fScope already owns the false control
- // Up-cast predicate, even if not else clause, because predicate can
- // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;`
- _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 "&&" -> con(lhs._type.makeZero()); // pred && expr_is_ignored
- case "||" -> parseLogical(); // pred || expr
- default -> throw TODO();
- };
- rhs.keep();
- _scope.removeGuards(ifF);
- // The RHS of `||` is parsed on the false side, so retain its updated
- // control, memory and locals just as an explicit else arm does.
- if( doRHS || fside.equals("||") )
- fScope = _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;
- ret.keep();
- r.peephole();
- return ret.unkeep();
- }
-
- /**
- * Parses a return statement; "return" already parsed.
- * The $ctrl edge is killed.
- *
- * <pre>
- * 'return' expr ;
- * </pre>
- * @return an expression {@link Node}, never {@code null}
- */
- private Node parseReturn() {
- var expr = 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);
- }
- } else if( isExplicitConstructorName(k._name) ) {
- // Field bindings live in the enclosing declaration, not among the
- // constructor's arguments/locals. Force lazy Phis on every exit.
- int first = _scope._kinds.at(lexN-1)._lexSize+2;
- for( int i=first; i<k._lexSize; i++ )
- _scope.update(_scope.var(i),null);
- }
-
- // 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", inferredReturnType(expr._type), true, expr, null);
-
- } else {
- // For <init> and <clinit> - ALL FIELDS IN LAST SCOPE are live
- // and need to have lazy-phi's inserted.
-
- // And fields might not match.... will need matching Var/define with default values
- int rlen = _returnScope.nIns()-1;
- RegionNode r = 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();
- }
-
- /** 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 + "'");
-
- if( def._extern!=0 ) // Parse the C binding as a memory lvalue
- { pos(old); return parseExpression(); }
-
- // 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.canInit(def) )
- throw error("Cannot reassign final '"+name+"'");
- checkSharedFunction(def);
-
- // 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 ) {
- if( !expr._type.accessISA(t) )
- throw error("Type "+expr._type.str()+" is not of declared type "+t.str());
- if( t instanceof TypeMemPtr tmp && tmp.isFinal() )
- expr = peep(new ReadOnlyNode(expr));
- // A FunPtr carries the semantic edge to its function's Return. Do not
- // hide that edge behind a Convert which can fold to a plain constant.
- if( expr instanceof FunPtrNode && t instanceof TypeFunPtr )
- return expr;
- // Auto-widen array to i64 (cast ptr to raw int bits)
- if( t == TypeInteger.BOT && expr._type instanceof TypeMemPtr tmp && tmp._obj.isAry() )
- return peep(new AddNode(peep(new PtrToIntNode(expr)),off(tmp._obj,"[]")));
- // Widen ints to floats, narrow wide types
- return peep(new ConvertNode(t,expr));
- }
-
- // Inferred `var` declarations describe writable slots, not the exact
- // value used to initialize them. Explicitly typed locals retain the
- // user's nullability and numeric-width guarantees; `val` retains the
- // initializer's precise type.
- private static Type mutableInferredType(Type t) {
- return switch( t ) {
- case TypeInteger ti -> TypeInteger.BOT;
- case TypeFloat tf -> TypeFloat.F64;
- case TypeMemPtr tmp -> tmp.makeVar();
- case TypeFunPtr tfp -> tfp;
- case TypeConAry<?> a -> a;
- case TypePtr ptr -> TypePtr.PTR;
- case TypeScalar ts -> TypeScalar.BOT;
- default -> t;
- };
- }
-
- private Type constructorFieldType(Type t) {
- if( t instanceof TypeMemPtr ptr && ptr._obj.isConAry() ) {
- TypeStruct declared=(TypeStruct)TYPES.get(ptr._obj._name);
- TypeStruct shape=typeAry(declared._fields[1]._t,ptr._obj._fields[1]._final);
- return TypeMemPtr.make((byte)2,shape,false).withAccess(ptr._ro);
- }
- return mutableInferredType(t);
- }
-
- // Seed an inferred return merge with the declared numeric family instead
- // of a transient constant/range. Pointer nullability remains part of the
- // inferred function result and is combined by the later Phi.
- private static Type inferredReturnType(Type t) {
- return switch( t ) {
- case TypeInteger ti -> TypeInteger.BOT;
- case TypeFloat tf -> TypeFloat.F64;
- default -> t;
- };
- }
-
- 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,";");
- }
-
- // The binding modifier sits immediately before the name. It never
- // changes permissions on the referenced object.
- private boolean bindingFinal(Type t) {
- if( match("!") ) return false;
- if( match("~") ) return true;
- return t==Type.TOP || t instanceof TypeNil;
- }
-
- /** Parse final: [!|~]var['=' asgn]
- */
- private Node parseDeclaration(Type t) {
- assert t!=null;
- // Has var/val instead of a user-declared type
- boolean inferType = t==Type.TOP || t==Type.BOTTOM;
- boolean xfinal = bindingFinal(t);
- Lexer loc = loc();
- String name = requireId();
- // Optional initializing expression follows
- boolean fld_final = false; // Field is final, but not deeply final
- boolean uninit = false; // Required non-null constructor field
- Node expr;
- if( match("=") ) {
- if( isExternDecl() ) {
- if( !(t instanceof TypeFunPtr) ) {
- Node ptr = externData(name,t);
- // The lexical binding is a fixed address; its contents are mutable.
- if( !_scope.define(name,t,true,ptr,loc) )
- throw error("Redefining name '"+name+"'",loc);
- _scope.lookup(name)._extern = _code.externAlias(name);
- return _code.ZERO;
- }
- 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();
- }
- // 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 = mutableInferredType(t);
- // Instance fields may change until the constructor finishes.
- // Keep the declared family/access, not the default's constant
- // value or string contents. Class constants remain static.
- else if( _scope.klast() instanceof Kind.Func fun && FunNode.isInstance(fun._name) && !(t instanceof TypeFunPtr) )
- t = constructorFieldType(t);
- }
-
- // expr is a constant function
- if( t instanceof TypeFunPtr decl && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
- t = decl.makeFrom(tfp.fidx());
- FunNode fun = _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 -> xfinal ? con(Type.BOTTOM) : _code.ZERO;
- case TypeFloat tf -> con(xfinal ? Type.BOTTOM : TypeFloat.FZERO);
- // Bottom signals type inference: they must be initialized in
- // the constructor and that's when we'll discover the type.
- case Type tt -> { assert tt==Type.BOTTOM; yield con(tt); }
- };
- // Every non-null pointer without a default is constructor-required.
- // Without `!` it is also shallow-final; mutability and definite
- // initialization are independent properties.
- if( (xfinal && !(t instanceof TypeNil)) || (t instanceof TypeNil tn && !tn.nullable()) ) {
- uninit = true;
- if( xfinal ) fld_final = true;
- }
- }
-
- // Lift expression, based on type
- // Do not let an authoritative Convert hide the nullable
- // not-yet-initialized state from constructor-local type flow.
- Node lift = uninit ? expr : 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>");
- // A same-named struct shares its file's class object and flag.
- Field flag = ts==null ? null : ts.field("$init");
- ts = ctorFld==null
- ? TypeStruct.make(clzName,false,fld)
- : TypeStruct.make(clzName,false,fld,ctorFld);
- if( flag!=null ) ts=ts.add(flag);
- TypeFunPtr alloc = makeAllocator((TypeStruct)TYPES.get(fullName),ret,ctorFld);
- ts = ts.add(Field.make("<new>",alloc,_code.alias(_ref._cname),true));
- 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,";");
- }
-
- // Build the declaration-side allocation helper. Allocation sites only
- // call this function; all layout, initialization and publication choices
- // are made here, where the complete struct and constructor are known.
- private TypeFunPtr makeAllocator(TypeStruct ts, ReturnNode initRet, Field ctorFld) {
- TypeFunPtr ctor = ctorFld==null ? null : (TypeFunPtr)ctorFld._t;
- int nargs = ctor==null ? 0 : ctor.nargs()-2; // Hide self and selfMem
- Type[] atypes = new Type[nargs];
- for( int i=0; i<nargs; i++ ) atypes[i] = ctor.arg(i+2);
- TypeMemPtr ptr = TypeMemPtr.make(ts);
- TypeFunPtr sig = TypeFunPtr.make1((byte)2,true,atypes,ptr,_code.fidx(_ref._cname));
- String name = (ts._name+".<new>").intern();
-
- int oldUID = _code.UID();
- FunNode fun = (FunNode)new FunNode(loc(),sig,name,_ref,null,_ref._start).peephole();
- _code.link(fun);
- Node rpc = new ParmNode("$rpc",0,TypeRPC.BOT,fun,ConstantNode.seed(TypeRPC.BOT).peephole()).peephole();
- Node pub = new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,
- new ProjNode(_ref._start,1,ScopeNode.MEM0).peephole()).peephole();
- Node[] parms = new Node[nargs];
- for( int i=0; i<nargs; i++ )
- parms[i] = new ParmNode("arg"+i,i+2,atypes[i],fun,ConstantNode.seed(atypes[i]).peephole()).peephole();
-
- NewNode nnn = new NewNode(ts,fun,off(ts," len")).init();
- Node self = new ProjNode(nnn,0,ts.str()).init();
- Node priv = new ProjNode(nnn,1,"#selfMem").init();
- Node[] initArgs = new Node[]{fun,pub,self,priv,
- new FunPtrNode(initRet.fun().sig(),_code._start,initRet).peephole()};
- CallEndNode cend = rawCall(initArgs,initRet.fun());
- Node ctl = new CProjNode(cend,0,ScopeNode.CTRL).peephole();
- pub = new ProjNode(cend,1,ScopeNode.MEM0).peephole();
- priv = new ProjNode(cend,2,"#selfMem").peephole();
-
- if( ctor != null ) {
- Node[] cargs = new Node[nargs+5];
- cargs[0]=ctl; cargs[1]=pub; cargs[2]=self; cargs[3]=priv;
- for( int i=0; i<nargs; i++ ) cargs[i+4]=parms[i];
- FunNode cfun = _code.link(ctor);
- cargs[cargs.length-1] = new FunPtrNode(ctor,_code._start,cfun.ret()).peephole();
- cend = rawCall(cargs,cfun);
- ctl = new CProjNode(cend,0,ScopeNode.CTRL).peephole();
- pub = new ProjNode(cend,1,ScopeNode.MEM0).peephole();
- priv = new ProjNode(cend,2,"#selfMem").peephole();
- }
-
- priv.keep();
- for( Field field : ts._fields ) {
- Type storage = field._t.makeStorage();
- Field escaped = field._final ? field : field.makeFrom(storage).makeFrom(true);
- pub.keep();
- Node esc = new EscapeNode(escaped,self,priv,pub).peephole();
- MemMergeNode merge = new MemMergeNode(null,pub);
- merge.alias(field._alias,esc);
- Node next = merge.peephole();
- pub.unkeep();
- pub = next;
- }
- priv.unkill();
- ReturnNode ret = (ReturnNode)new ReturnNode(ctl,pub,self,rpc,fun).peephole();
- fun.setRet(ret);
- fun._approxUIDs = _code.UID() - oldUID;
- _ref.addFun(_code,fun);
- return sig;
- }
-
- private CallEndNode rawCall(Node[] args, FunNode target) {
- // Establish this known declaration-side edge immediately, but do not
- // peephole or inline it while the surrounding factory is being built.
- // IterPeeps therefore starts with the complete initializer caller set.
- CallNode call = new CallNode(loc(),args).init();
- CallEndNode cend = new CallEndNode(call,TypeRPC.constant(_code.rpc(_ref._cname))).init();
- call.link(target);
- return cend;
- }
-
- // 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 ) {
- var oldExits = _ctorExits;
- _ctorExits = new ArrayList<>();
- // 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());
- }
-
- // Class object keeps a runtime flag to signal the <clinit> has run.
- if( isClz )
- TYPES.put(typeName,tself=tself.add(Field.make("$init",TypeInteger.BOOL,_code.alias(_ref._cname),false)));
-
- // 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);
- _ctorExits = oldExits;
- // 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();
- boolean writable = match("!");
- boolean readonly = !writable && match("~");
- String tname = _lexer.matchId();
- if( tname==null ) return posT(old1);
-
- // 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);
-
- // A qualifier before the name applies to the struct reference, not
- // to an enclosing array. Arrays have their own [] / [~] qualifier.
- if( t1 instanceof TypeMemPtr tmp ) {
- if( readonly || !writable ) t1 = tmp.makeRO();
- } else if( writable || readonly )
- return posT(old1);
-
- // 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)).makeRO();
- } 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();
- if( !match("!") ) 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 ) {
- // Non-null reference arrays are valid types, even though an ordinary
- // zero-filled `new T[len]` cannot construct one. Allocation retains
- // that check in alloc(); trusted runtimes (notably argv construction)
- // may fill the private array completely before publishing it.
- 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);
- }
- // Already have the aliases, just efinal is wrong
- return TypeStruct.makeAry(tname,TypeInteger.U32,ta._fields[0]._alias,t,ta._fields[1]._alias,efinal );
- }
-
- 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;
- lhs.keep();
-
- // Compare
- Node rhs = parseShift().keep();
- Node cmp = makeCompBool(dir,lhs.unkeep(),rhs).keep(); // Convert to a bool
-
- // Stacked compares?
- int dir0 = parseCompDir();
- if( dir0 == 0 ) {
- rhs.unkill();
- return cmp.unkeep();
- }
-
- // rhs is keeped() and becomes lhs
- // cmp is NOT keeped() and is the last test
- Node ifNode = new IfNode(ctrl(), cmp.unkeep()).peephole();
- Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
- Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole();
- // False side does nothing but capture memory & side-effects
- ScopeNode fail = _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.unkeep());
- 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().keep();
- 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.unkeep());
- 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._extern!=0 )
- return parsePostfixName(_scope.in(n),name,delta);
- if( n._final && !_scope.canInit(n) )
- throw error("Cannot reassign final '"+n._name+"'");
- checkSharedFunction(n);
- Node expr = convertExpr(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 parsePostfix(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,true,loc());
- _frefs.push((FRefNode)_scope.in(var));
- }
-
- // Load local value
- Node rvalue = _scope.in(var);
- if( var._extern!=0 )
- return parsePostfixName(rvalue,id,0);
- if( var._fref )
- return parsePostfix(rvalue);
- // A constructor may read the unfinished object, but each field it
- // reads must already be initialized on every path. Read the current
- // local value; other fields may still be waiting for their first write.
- if( inExplicitConstructor() && var._uninit && missingInit(rvalue,new BitSet()) )
- 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 && !inExplicitConstructor() )
- return parsePostfixName(_scope.in(method._lexSize),id,0);
-
- // 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 && !inExplicitConstructor() && // Out of scope
- !(var._final && rvalue._type.isConstant()) ) { // Not a final constant
- if( kx > 0 ) // Global scope is OK, only ever one of these (not one per function invoke)
- throw 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,0 );
- //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(_scope.update(_scope.var(selfx),null)));
- }
- // Assign-update direct into Scope
- if( var._final && !_scope.canInit(var) )
- throw error("Cannot reassign final '"+var._name+"'");
- checkSharedFunction(var);
- 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);
- case '|' -> new OrNode(null,lhs,rhs);
- case '&' -> new AndNode(null,lhs,rhs);
- case '^' -> new XorNode(loc(),lhs,rhs);
- case Lexer.SHL -> new ShlNode(loc(),lhs,rhs);
- case Lexer.SAR -> new SarNode(loc(),lhs,rhs);
- case Lexer.SHR -> new ShrNode(loc(),lhs,rhs);
- default -> throw TODO();
- };
- // Convert to float ops, or narrow int types; error if not declared type.
- // Also, if postfix LHS is still keep()
- return convertExpr(peep(op),t);
- }
-
-
- /**
- 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();
- // A field declaration can leave a short forward type name in TYPES.
- // Allocation happens in lexical context, so resolve that short name
- // to the completed nested type before selecting its class helper.
- String typeName = ts._name.indexOf('.') == -1 ? fullTypeName(ts._name) : ts._name;
- Type resolved = TYPES.get(typeName);
- if( resolved instanceof TypeStruct rts ) ts = rts;
- TypeStruct clz = (TypeStruct)TYPES.get(addClzPrefix(typeName));
- if( clz==null )
- throw error("Unknown struct type '" + ts._name + "'");
- Field afld = clz.field("<new>");
- Node alloc;
- if( afld != null ) {
- TypeFunPtr tfp = (TypeFunPtr)afld._t;
- FunNode fun = _code.link(tfp);
- alloc = fun==null
- ? new FRefNode((addClzPrefix(typeName)+".<new>").intern(),loc())
- : new FunPtrNode(tfp,_code._start,fun.ret()).peephole();
- } else {
- if( !ts._fref )
- throw error("Constructor arguments for '" + ts._name + "' but no constructor is defined");
- Type[] sig = new Type[ctorArgs._len];
- for( int i=0; i<ctorArgs._len; i++ )
- sig[i] = ctorArgs.at(i)._type;
- alloc = new FRefNode((addClzPrefix(typeName)+".<new>").intern(),loc(),
- TypeFunPtr.make(false,sig,TypeMemPtr.make(ts)));
- }
- Ary<Node> args = new Ary<>(Node.class);
- args.add(ctrl()); args.add(mem());
- for( Node arg : ctorArgs ) args.add(arg.unkeep());
- args.add(alloc);
- return functionCall(args);
- }
-
- 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;
- }
-
- 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.makeRO());
- }
-
- /**
- * 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 if( expr != self )
- self.isKill(); // Not a method call, no need for self
- return parsePostfix(expr);
- }
- private Node parsePostfix(Node expr) {
- _lexer.skipWhiteSpace();
- Lexer site = loc();
- 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,0,site);
- }
-
- private boolean privateSelf(Node ptr) {
- while( ptr instanceof ConvertNode || ptr instanceof ReadOnlyNode || ptr instanceof GuardNode ) ptr=ptr.in(1);
- Node self=defaultSelf();
- while( self instanceof ConvertNode || self instanceof ReadOnlyNode || self instanceof GuardNode ) self=self.in(1);
- return ptr==self;
- }
-
- // An explicit self.x read sees the same current value as bare x. Loading
- // the allocation here would miss updates still held in local SSA values.
- private Node privateField(String name, int pre) {
- int idx=fieldVarIdxBefore(_scope._kinds.at(_scope.enclosingFuncOrDecl())._lexSize,name);
- if( idx<0 ) throw error("Cannot resolve field '"+name+"'");
- Var v=_scope.var(idx);
- if( pre==0 && matchOpx('=','=') ) {
- checkSharedFunction(v);
- Node value=parseAsgn().keep();
- _scope.update(v,convertExpr(value,v.type()));
- return value.unkeep();
- }
- Node value=_scope.in(_scope.update(v,null));
- if( v._uninit && missingInit(value,new BitSet()) )
- throw error("Field '"+name+"' might not be initialized",v._loc);
- char ch=pre==0 ? _lexer.matchOperAssign() : (char)pre;
- if( ch!=0 ) {
- checkSharedFunction(v);
- Node op=opAssign(ch,value,v.type());
- _scope.update(v,op);
- if( pre!=0 && postfix(ch) ) value.unkeep();
- return pre==0 && postfix(ch) ? value.unkeep() : op;
- }
- return parsePostfixMethod(value,defaultSelf());
- }
-
- private void checkSharedFunction(Var v) {
- if( _scope.canInit(v) && v._final && v.type() instanceof TypeFunPtr t && t.isConstant() )
- throw error("Cannot reassign shared function '"+v._name+"'");
- }
-
- // A loop Phi is temporarily bottom before its backedge is parsed. That is
- // not an unwritten field: follow its live inputs to the actual defaults.
- private boolean missingInit(Node value, BitSet seen) {
- if( value==null || seen.get(value._nid) ) return false;
- seen.set(value._nid);
- if( value instanceof PhiNode phi && !(value instanceof ParmNode) ) {
- for( int i=1; i<phi.nIns(); i++ )
- if( phi.region().in(i)!=null && phi.region().in(i)._type!=Type.XCONTROL && missingInit(phi.in(i),seen) )
- return true;
- return false;
- }
- return !initialized(value._type);
- }
-
- private Node defaultSelf() {
- int selfx = _scope._kinds.at(_scope.enclosingFuncOrDecl())._lexSize;
- return _scope.in(_scope.update(_scope.var(selfx),null));
- }
-
- /**
- * Parse postfix expression; this can be a field expression, an array
- * lookup or a postfix operator like '#'
- *
- * <pre>
- * expr ['++' | '--' ] // Postfix unary write operator
- * expr [ [op]= expr ] // Field reference
- * expr ('[' expr ']')* [ [op]= expr ] // Array reference
- * </pre>
- */
-
- // pre is +/-1 for a prefix update, otherwise parse a postfix operation.
- private Node parsePostfixName(Node expr, String name, int pre) {
- return parsePostfixName(expr,name,pre,loc());
- }
-
- private Node parsePostfixName(Node expr, String name, int pre, Lexer site) {
- if( inExplicitConstructor() && privateSelf(expr) ) return privateField(name,pre);
- // Keep expr across possible updates
- expr.keep();
-
- // Get the best known TypeStruct from a pointer type.
- TypeStruct ts = null, ts2;
- if( expr._type instanceof TypeMemPtr tmp && (ts2=((TypeStruct)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 && !startsClzPrefix(ts._name) ) {
- Type clz0 = TYPES.get(addClzPrefix(ts._name));
- if( clz0 instanceof TypeStruct clz && (fld = latestStruct(clz).field(name)) != null ) {
- expr.unkeep();
- ts = latestStruct(clz);
- expr = con(TypeMemPtr.make((byte)2,ts,true)).keep();
- }
- }
- if( fld==null && ts!=null )
- fld = fieldOrOpen(ts,name,false);
- if( fld==null && ts!=null && !latestStruct(ts)._open )
- throw error("Accessing unknown field '"+name+"' from '*"+ts._name+"'");
-
- // We have to record "active touches" of a new class, to track when we
- // need to force class initialization. Basically, we insert an CLInit,
- // and if it does not fold away it'll turn into a runtime check.
- Kind.Func func = (Kind.Func)_scope._kinds.at(_scope.enclosingFuncOrDecl());
- if( (ts==null || ts==TypeStruct.BOT || ts.isHigh() || startsClzPrefix(ts._name)) &&
- !(ts!=null && (ts._name+".<clinit>").equals(func._name)) )
- classInit(site,expr);
-
- // With no field, we will update the bulk memory
- int alias = fld==null ? 1 : fld._alias;
- Type decl = fld==null ? Type.BOTTOM : fld._t;
-
- // Field offset; fixed for structs, computed for arrays
- Node off = (name=="[]"
- // Array element math, with unknown base offset and shift amount.
- ? 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( pre==0 && 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();
- if( expr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL )
- throw error("Cannot modify final field '"+name+"'");
- 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 = pre==0 ? _lexer.matchOperAssign() : (char)pre;
- 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 ).keep();
- mem.keep();
- if( expr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL )
- throw error("Cannot modify final field '"+name+"'");
- Node st = new StoreNode(loc(), name, alias, decl, ctrl(), mem, expr.unkeep(), off.unkeep(), op, false).peephole().keep();
- storeMem(st,alias,mem);
- st.unkeep();
- mem.unkeep();
-
- op.unkeep(); // A narrow Store may have folded away the expression's conversion.
- if( postfix(ch) ) load.unkeep();
- return pre==0 && postfix(ch) ? load : op;
- }
- 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 TypeFloat && t instanceof TypeInteger )
- val = new ToIntegerNode(val).peephole();
- 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);
- java.util.BitSet finals = new java.util.BitSet();
- 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;
- finals.set(ids.size(),bindingFinal(t));
- 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,finals,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);
- java.util.BitSet finals = new java.util.BitSet();
- 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;
- finals.set(ids.size(),bindingFinal(t));
- 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,finals,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) {
- Lexer site = loc();
- site._position--; // Opening '('; arguments can contain nested calls.
- 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,site);
- }
-
- private Node functionCall( Ary<Node> args ) {
- return functionCall(args,loc());
- }
-
- private Node functionCall( Ary<Node> args, Lexer site ) {
- // 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(site, 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());
- }
-
- // A lexical C binding has a singleton namespace with one external field.
- // The namespace occupies no storage; its field offset supplies the address.
- private Node externData(String name, Type t) {
- if( !(t instanceof TypeInteger) && !(t instanceof TypeFloat) )
- throw error("C data binding requires an integer or float type");
- String cname = ("C:"+name).intern();
- TypeStruct ts = TypeStruct.make(cname,false,Field.make(name,t,_code.externAlias(name),false,true));
- Type old = TYPES.putIfAbsent(cname,ts);
- if( old!=null && old!=ts )
- throw error("Conflicting types for C data '"+name+"'");
- return con(TypeMemPtr.make((byte)2,ts,true));
- }
-
- /**
- * 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 ); }
- private void classInit(Lexer site, Node ptr) {
- // Ordinary load machinery resolves the flag alias and offset when
- // the receiver is still a forward reference.
- Field flag = ptr._type instanceof TypeMemPtr tmp ? tmp._obj.field("$init") : null;
- int alias = flag==null ? 1 : flag._alias;
- Node ready = new LoadNode(site,"$init",alias,null,mem(),ptr,fldoff(ptr,"$init")).peephole();
- CLInitNode init = new CLInitNode(site,ctrl(),mem(),ptr,ready).peephole().keep();
- ctrl(new CProjNode(init,0,ScopeNode.CTRL).peephole());
- Node bulk = new ProjNode(init,1,ScopeNode.MEM0).peephole().keep();
- flag = init.flag();
- mem(flag==null ? bulk : mergeAlias(bulk,flag._alias,
- new ProjNode(init,2,memName(flag._alias)).peephole()));
- bulk.unkeep();
- init.unkeep();
- }
-
- // Field offset for a known type (but unknown offset)
- public static ConFldOffNode off( TypeStruct ts, String fname ) {
- return (ConFldOffNode)(new ConFldOffNode(ts,fname).peephole());
- }
- // Field offset for an unknown type
- public Node fldoff( Node n, String name ) {
- 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();
- StringBuilder sb = new StringBuilder();
- while( !_lexer.isEOF() ) {
- char c = _lexer.nextChar();
- if( c=='"' ) return sb.toString();
- if( c!='\\' ) {
- sb.append(c);
- continue;
- }
- if( _lexer.isEOF() )
- throw error("Unclosed string");
- sb.append(escapedChar("string"));
- }
- throw error("Unclosed string");
- }
-
- private char escapedChar(String kind) {
- char esc = _lexer.nextChar();
- return switch( esc ) {
- case '0' -> '\u0000';
- case 'n' -> '\n';
- case 't' -> '\t';
- case 'r' -> '\r';
- case '\\' -> '\\';
- case '"' -> '"';
- case '\'' -> '\'';
- default -> throw error("Unknown "+kind+" escape \\\\"+esc);
- };
- }
-
- // Already parsed "'"
- private Node parseChar() {
- if( _lexer.isEOF() ) throw error("Unclosed character");
- char c = _lexer.nextChar();
- if( c=='\\' ) {
- if( _lexer.isEOF() ) throw error("Unclosed character");
- c = escapedChar("character");
- }
- return require(con(TypeInteger.constant(c)),"'");
- }
-
- 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;
- public final CompUnit _cu; // Original source, retained across inlining.
- // 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 && (_cu!=null ? _cu==that._cu : _input==that._input) && _position < that._position;
- }
-
- public String position() { return _line_number+":"+(_position-_line_start+1); }
- public int pos() { return _position; }
-
- /**
- * Record the source text for lexing
- */
- public Lexer(String source) {
- this(source.getBytes());
- }
-
- /**
- * Direct from disk file source
- */
- public Lexer(byte[] buf) {
- _input = buf;
- _cu = null;
- }
-
- private Lexer(CompUnit cu) {
- _input = cu._src.getBytes();
- _cu = cu;
- }
-
- // Imported location: source text is only needed when reporting an error.
- private Lexer(CompUnit cu, int pos) {
- _input = null;
- _cu = cu;
- _position = pos;
- }
-
-
- /**
- * Copy a lexer from a lexer
- */
- private Lexer(Lexer l) {
- _input = l._input;
- _cu = l._cu;
- _position = l._position;
- _line_number = l._line_number;
- _line_start = l._line_start;
- }
-
- // Very handy in the debugger, shows the unparsed program
- @Override
- public String toString() {
- return _input==null ? _cu._cname+"@"+_position : new String(_input, _position, _input.length - _position);
- }
-
- // True if at EOF
- 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)
- // Distinct tags for the multi-character compound operators.
- static final char SHL = 2, SAR = 3, SHR = 4;
-
- public char matchOperAssign() {
- skipWhiteSpace();
- if( match("<<=" ) ) return SHL;
- if( match(">>>=") ) return SHR;
- if( match(">>=" ) ) return SAR;
- if( _position+2 >= _input.length ) return 0;
- char ch0 = (char)_input[_position];
- if( "+-/*&|^".indexOf(ch0) == -1 ) return 0;
- char ch1 = (char)_input[_position+1];
- if( ch1 == '=' ) { _position += 2; return ch0; }
- if( isIdLetter((char)_input[_position+2]) ) return 0;
- if( ch0 == '+' && ch1 == '+' ) { _position += 2; return (char) 1; }
- if( ch0 == '-' && ch1 == '-' ) { _position += 2; return (char)-1; }
- return 0;
- }
- }
-
- 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/chapter-modules/src/main/java/com/seaofnodes/simple/Var.java b/chapter-modules/src/main/java/com/seaofnodes/simple/Var.java
deleted file mode 100644
index af371d21..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/Var.java
+++ /dev/null
@@ -1,54 +0,0 @@
-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 int _extern; // C storage alias; zero for ordinary variables
- 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/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java
deleted file mode 100644
index 0b314978..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java
+++ /dev/null
@@ -1,101 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeMem;
-
-abstract public class BuildLRG {
- // Compute live ranges in a single forwards pass. Every def is a new live
- // range except live ranges are joined at Phi. Also, gather the
- // intersection of register masks in each live range. Example: "free
- // always zero" register forces register zero, and calling conventions
- // typically force registers.
-
- // Sets FAILED to the set of hard-conflicts (means no need for an IFG,
- // since it will not color, just split the conflicted ranges now). Returns
- // true if no hard-conflicts, although we still might not color.
- public static boolean run(int round, RegAlloc alloc) {
- FunNode fun = null;
- for( Node bb : alloc._code._cfg ) {
- if( bb instanceof FunNode fun2 )
- fun = fun2;
- for( Node n : bb.outs() ) {
- // A synthetic never-exit value has no runtime definition.
- if( n._type==Type.TOP ) continue;
- if( n instanceof PhiNode phi && !(phi._type instanceof TypeMem) ) {
- if( phi instanceof MachNode mach && mach.outregmap()==null )
- continue;
- // All real Phi inputs end up with the same LRG.
- // Pass 1: find any pre-existing LRG, to avoid make-then-Union a LRG
- LRG lrg = alloc.lrg(phi);
- if( lrg == null )
- for( int i=1; i<phi.nIns(); i++ )
- if( (lrg = alloc.lrg(phi.in(i))) != null )
- break;
- // If none, make one.
- if( lrg==null ) lrg = alloc.newLRG(n,fun);
- if( phi instanceof MachNode ) defLRG(alloc,n,fun);
- // Pass 2: everybody uses the same LRG
- lrg=alloc.union(lrg,phi);
- for( int i=phi instanceof ParmNode ? 2 : 1; i<n.nIns(); i++ )
- // TOP keeps the type merge neutral, but needs no register.
- if( n.in(i)._type!=Type.TOP )
- lrg=alloc.union(lrg,n.in(i));
- if( lrg._mask!=null && lrg._mask.isEmpty() )
- alloc.fail(lrg);
-
- } else if( n instanceof MachNode mach ) {
- // Attempt to commute ops to keep live ranges compatible.
- if( mach.commutes() && n.nOuts()==1 ) {
- int uidx = n.out(0)._inputs.find(n);
- RegMask mask1 = n.in (1) instanceof MachNode machx ? machx.outregmap() : alloc.lrg(n.in(1))._mask;
- RegMask mask2 = n.in (2) instanceof MachNode machx ? machx.outregmap() : alloc.lrg(n.in(2))._mask;
- RegMask masko = n.out(0) instanceof MachNode machx ? machx.regmap(uidx): mach.outregmap();
- if( masko!=null && !mask1.overlap(masko) && mask2.overlap(masko) )
- n.swap12();
- }
-
- // Define live range
- defLRG(alloc,n,fun);
-
- // Now, look in the opposite direction. How are incoming
- // LRGs affected by this node: For all uses, make live lrgs
- for( int i=1; i<n.nIns(); i++ )
- if( n.in(i)!=null ) {
- LRG lrg2 = alloc.lrg(n.in(i));
- if( lrg2 != null ) { // Anti-dep or other, no LRG
- RegMask use_mask = mach.regmap(i); // use_mask is also null for anti-dep
- if( use_mask!=null && !lrg2.machUse(mach,(short)i,use_mask.size1()).and(use_mask) )
- alloc.fail(lrg2); // Empty register mask, must split
- }
- }
-
- }
-
- // MultiNodes have projections which set registers
- if( n instanceof MultiNode && !(n instanceof CFGNode) )
- for( Node proj : n.outs() )
- if( proj instanceof MachNode )
- defLRG(alloc,proj,fun);
- }
- }
-
- // Collect live ranges
- alloc.unify();
-
- return alloc.success();
- }
-
- private static void defLRG( RegAlloc alloc, Node n, FunNode fun ) {
- MachNode mach = (MachNode)n;
- RegMask def_mask = mach.outregmap();
- if( def_mask == null ) return;
- LRG lrg = mach.twoAddress() == 0
- ? alloc.newLRG(n,fun) // Define a new LRG for N
- : alloc.lrg2(n,mach.twoAddress()); // Use the matching 2-adr input
- // Record mask and mach
- if( !lrg.machDef(mach,def_mask.size1()).and(def_mask) )
- alloc.fail(lrg); // Empty register mask, must split
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java
deleted file mode 100644
index 6c70b71d..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java
+++ /dev/null
@@ -1,214 +0,0 @@
-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.*;
-
-// Search for Class Initialization Cycles.
-
-// Done after Opto, so dead code does not make a cycle.
-
-// Build the inits-graph, a call-graph plus CLInits plus class parents.
-
-// (1) Scan all functions f, building _edges[f][]: the set of <cinit> fcns from
-// CompUnits from CLInitNodes, and the set of fcn call targets in f and the
-// class parent. These are all reported as fidxs (ints).
-//
-// (2) Walk the _edges graph using Tarjans SCC. Reject cycles containing more
-// than one <clinit>.
-//
-// (3) Report on <clinit> cycles
-//
-public class ClassInitDependencies {
- private final CodeGen _code;
- // Function FIDX -> callees, touched initializers, and parent initializer.
- public final int[][] _edgess;
- private final int[] _pres;
- private final int[] _lows;
- private final int[] _stk ;
- private int _sp;
-
-
- // Build once; tests can inspect the graph independently of cycle checking.
- public ClassInitDependencies( CodeGen code ) {
- _code = code;
- assert code._phase.ordinal() >= CodeGen.Phase.IterPostOpto.ordinal();
- _pres = new int[code._linker._len];
- _lows = new int[code._linker._len];
- _stk = new int[code._linker._len];
-
- //
- BitSet visit = code.visit(); // Visit set per-fcn
- Ary<CFGNode> work = new Ary<>(CFGNode.class); // Worklist per-fcn
- IntHashMap iset = new IntHashMap(0); // dedup hash set per-fcn
- _edgess = new int[code._linker._len][];
-
- // For all functions, build the _edges array
- for( FunNode fun : code._linker )
- if( fun!=null && !fun.isDead() )
- // Visit function and collect direct class touches and callees
- _edgess[fun.sig().fidx()] = walk(code,fun,work,iset,visit,null);
- visit.clear();
- }
-
- // Return the outgoing init edge set for this function
- private static int[] walk( CodeGen code, FunNode fun, Ary<CFGNode> work, IntHashMap iset, BitSet visit,
- Ary<Parser.Lexer> sites) {
- // Walk function body, looking for outgoing init edges
- assert iset .isEmpty(); // int hash set for de-dup
- assert visit.isEmpty(); // needs a visit set, because loops
- assert work .isEmpty(); // needs a worklist, because DAG, parallel explore paths
-
- // Children classes always dependent on parents
- if( fun.isClz() && fun._compunit._par != null )
- iset.put(code.link(fun._compunit._par).sig().fidx(),1);
-
- work.push(fun);
- while( !work.isEmpty() ) {
- CFGNode cfg = work.pop();
- if( visit.get(cfg._nid) ) continue; // Been there, done that
- visit.set(cfg._nid);
- assert !cfg._type.isHigh();
- // Do not walk off end of function
- if( cfg instanceof ReturnNode ) continue;
-
- // CLInit adds a class init requirement to this function
- // (plus all parent classes)
- if( cfg instanceof CLInitNode init ) {
- FunNode clz = code.link(init.cu()); // <clinit> referred to
- iset.put(clz.sig().fidx(),1); // Add to edge set
- if( sites!=null && sites.atX(clz.sig().fidx())==null ) sites.setX(clz.sig().fidx(),init._loc);
- }
-
- // Calls union all their fptr fidxs into the callee set
- if( cfg instanceof CallNode call && call.fptr()._type instanceof TypeFunPtr fp ) {
- assert !XInt.isHigh(fp.fidxs()) && fp.nfcns()>0; // Call is alive with valid targets
- // Union call targets into the callee set
- for( int dstx = XInt.next(fp.fidxs(),0); dstx>=0; dstx=XInt.next(fp.fidxs(),dstx) ) {
- iset.put(dstx,1);
- if( sites!=null && sites.atX(dstx)==null ) sites.setX(dstx,call._loc);
- }
- }
-
- // Forward recursion to walk the whole function
- for( Node use : cfg._outputs ) {
- // Do not walk into another function
- if( !(use instanceof CFGNode cfg1) || use instanceof FunNode || use instanceof StopNode ) continue;
- // Expect control flow edge is sensible
- assert (use instanceof RegionNode r && r._inputs.find(cfg)>0) || use.in(0)==cfg;
- if( !visit.get(cfg1._nid) ) // Small speed opt
- work.add(cfg1);
- }
- }
-
- visit.clear();
- int[] edges = iset.isEmpty() ? null : iset.keySet();
- iset.clear();
- return edges;
- }
-
- // Return an error string, or null if no cycle contains multiple clinits.
- public String check() {
- Arrays.fill(_pres,0);
- _sp = 0;
- _pres[0] = 1; // Preorders stored in pres[0] and start at 1
-
- int idx = -1;
- for( int fidx = 1; fidx < _edgess.length; fidx++ )
- if( _pres[fidx] == 0 && // Already visited
- (idx = scc(fidx)) != -1 ) // And found a cycle
- return report(idx); // Report cycle
- return null;
- }
-
- // Every internal edge of an SCC belongs to a cycle. Print all of them:
- // stack order is not a path, and enumerating all cycle paths can explode.
- // This bookkeeping and formatting are only needed on an error.
- private String report(int idx) {
- BitSet members = new BitSet();
- for( int i=idx; i<_sp; i++ ) members.set(_stk[i]);
- SB sb = new SB().p("Cyclic class initialization:");
- // Recover one surviving source site per edge only after finding an
- // error. A representative touch is enough; runtime first-touch order
- // need not be statically known. Lexer snapshots retain the source CU
- // even when the node has been inlined into a different function.
- Ary<CFGNode> work = new Ary<>(CFGNode.class);
- IntHashMap iset = new IntHashMap(0);
- BitSet visit = new BitSet();
- Ary<Parser.Lexer> sites = new Ary<>(Parser.Lexer.class);
- for( int i=idx; i<_sp; i++ ) {
- int src = _stk[i];
- FunNode fun = _code.link(src);
- Arrays.fill(sites._es,null);
- sites.clear();
- walk(_code,fun,work,iset,visit,sites);
- if( _edgess[src]!=null )
- for( int dst : _edgess[src] )
- if( members.get(dst) ) {
- sb.p("\n ").p(name(src)).p(" -> ").p(name(dst));
- Parser.Lexer loc = sites.atX(dst);
- if( loc!=null && loc._cu!=null )
- sb.p('\n').p(SourceError.format(loc._cu,loc.pos(),"cyclic class initialization"));
- else if( fun.isClz() && fun._compunit._par==_code.link(dst)._compunit )
- sb.p("\n parent initialization requirement");
- }
- }
- return sb.toString();
- }
-
- private String name(int fidx) {
- FunNode fun = _code.link(fidx);
- String owner=fun._compunit._cname;
- if( fun.isClz() ) return owner;
- String label=fun.label();
- return (label.startsWith(owner+".") ? label : owner+"."+label)+" [fidx "+fidx+"]";
- }
-
- // Tarjan SCC
- int scc( int fidx ) {
- // Assign pre-order number
- _lows[fidx] = _pres[fidx] = _pres[0]++;
- _stk[_sp++] = fidx;
- // walk outbound edges
- if( _edgess[fidx]!=null ) {
- for( int edge : _edgess[fidx] ) {
- if( edge >= _pres.length ) continue; // Out of bounds happens when calling C fcns
- if( _pres[edge] == 0 ) { // Not visited
- int idx = scc(edge);
- if( idx != -1 ) return idx; // Found a cycle
- _lows[fidx] = Math.min(_lows[fidx],_lows[edge]);
- } else if( _edgess[edge]!=null && onStk(edge) ) {
- _lows[fidx] = Math.min(_lows[fidx],_pres[edge]);
- }
- }
- }
- // Found a cycle
- if( _lows[fidx] == _pres[fidx] ) {
- int idx = find(fidx);
- if( hasClzCycle(idx) )
- return idx; // Found a class cycle
- // Pop stack, including fidx
- _sp = idx;
- }
- return -1; // No cycle
- }
-
- private int find(int e) {
- for( int i=0; i<_sp; i++ ) if( _stk[i] == e ) return i;
- return -1;
- }
- private boolean onStk(int e) { return find(e) != -1; }
-
- private boolean hasClzCycle(int idx) {
- int clinits = 0;
- for( int i=idx; i<_sp; i++ ) {
- FunNode fun = _code.link(_stk[i]);
- if( fun!=null && fun.isClz() && ++clinits > 1 )
- return true;
- }
- return false;
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java
deleted file mode 100644
index 689ceceb..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Coalesce.java
+++ /dev/null
@@ -1,105 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
deleted file mode 100644
index e6bace8c..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
+++ /dev/null
@@ -1,625 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import com.seaofnodes.graph.GraphObserver;
-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.util.*;
-
-
-// Compile Driver for a Compilation Unit
-@SuppressWarnings("unchecked")
-public class CodeGen {
-
- // Last created CodeGen as a global; used all over to avoid passing about a "context".
- public static CodeGen CODE;
-
- // Location of built-in CPU ports
- public static final String PORTS = "com.seaofnodes.simple.node.cpus";
-
- // Compile phases
- public enum Phase {
- Parse, // Parse ASCII text into Sea-of-Nodes IR
- Iter, // Pessimistic peepholes after parse
- Opto, // SCCP, numeric modes, seed the peephole worklist
- IterPostOpto, // Peepholes and inlining using SCCP results
- FinalizeIR, // Field layout, offset cleanup, function/CU roots
- TypeCheck, // Last check for bad programs
- Lower, // Expand checked class initialization to ordinary IR
- LoopTree, // Build a loop tree; break infinite loops
- Select, // Convert to target hardware nodes
- Schedule, // Global schedule (code motion) nodes
- LocalSched, // Local schedule
- RegAlloc, // Register allocation
- Encoding, // Encoding
- Export, // Export
- LastPhase // After last phase
- }
- public Phase _phase;
- // Null for normal compilation; owned by this compilation, not a global viewer.
- public GraphObserver<Node> _obs;
-
- /** True when tests deliberately randomize Iter worklist order. */
- public static boolean iterSeedOverridden() { return System.getProperty("simple.iter.seed") != null; }
-
- // Expensive assert sampling:
- // -Dsimple.assert.expensive=0 disables these checks
- // -Dsimple.assert.expensive=1 checks every step
- // -Dsimple.assert.expensive=N checks once every 1<<N steps
- private static final int EXPENSIVE_ASSERT_LOG = Integer.getInteger("simple.assert.expensive",8);
- public static boolean expensiveAssert() { return EXPENSIVE_ASSERT_LOG > 0; }
- public static boolean expensiveAssert( int trip ) {
- if( EXPENSIVE_ASSERT_LOG <= 0 ) return false;
- if( EXPENSIVE_ASSERT_LOG == 1 ) return true;
- int shift = Math.min(EXPENSIVE_ASSERT_LOG,30);
- return (trip & ((1<<shift)-1)) == 0;
- }
-
- // ---------------------------
- // Module Source Root
- public final String _modDir;
- // Module Build Root
- public final String _buildDir;
- // Slashed path from source root to the entry file, without ".smp".
- 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;
-
- // Source files discovered in this invocation. All live initializers
- // participate in cycle checking, including fully inlined bodies.
- public HashMap<String,CompUnit> _compunits;
-
- // Test shortcut for only one compilation unit
- public CompUnit compunit() {
- assert _compunits.size()==1;
- for( CompUnit cu : _compunits.values() )
- return cu;
- assert false : "no compilation unit";
- return null;
- }
-
- // True when fidx is published through a public class or instance field.
- public boolean publicFIDX(int fidx) {
- if( fidx < LocalIds.RESERVED ) return false;
- // 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;
- // Include instance methods even while their constant function fields
- // are stored in each instance rather than on the class object.
- for( Type t : Parser.TYPES.values() )
- if( t instanceof TypeStruct clz &&
- Parser.startsClzPrefix(clz._name) &&
- Parser.isPublicClassName(clz._name) &&
- resolvedClass(clz) ) {
- if( publicFieldFIDX(clz,fidx) ) return true;
- Type self = Parser.TYPES.get(clz._name.substring(Parser.CLZ.length()));
- if( self instanceof TypeStruct ts && publicFieldFIDX(ts,fidx) ) return true;
- }
- return false;
- }
-
- private static boolean publicFieldFIDX(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=LocalIds.RESERVED; 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.Lower.ordinal();
- return ConstantNode.make(t).peephole();
- }
- public ConstantNode con( Type t ) { return (ConstantNode)constant(t); }
- public ConstantNode con( long con ) { return con==0 ? ZERO : con(TypeInteger.constant(con)); }
-
-
- // ---------------------------
- // Test setup; no module nor file with specific argument
- public CodeGen( String src ) { this(src, 126L, true ); }
-
- // Test setup; no module nor file; can alter seed & argument; can re-run same CodeGen
- public CodeGen( String src, Type arg ) {
- this(null,null,null, src, 126L, true, arg);
- }
- // Test setup; no module nor file; can alter seed & argument; can re-run same CodeGen
- public CodeGen( String src, long workListSeed, boolean resetTypes ) {
- this(null,null,null, src, workListSeed, resetTypes, TypeInteger.BOT);
- }
-
- // Generic CodeGen, including full module setup
- public CodeGen( String modDir, String buildDir,
- String srcName, String src, long workListSeed, boolean resetTypes, Type arg ) {
- // Public singleton to avoid passing about this state to a huge count
- // of places. Probably becomes a TLS at some point.
- CODE = this;
- if( resetTypes ) Type.reset(); // Reset to recover from failed test
-
- _cwd = System.getProperty("user.dir")+"/";
- _modDir = modDir == null ? _cwd : modDir;
- _buildDir = buildDir == null ? _cwd : buildDir;
- _srcName = srcName;
- _callingConv = null; // Calling convention
- // Source code from test strings, not files
- _src = src;
- // All the compilation units
- _compunits = new HashMap<>();
- _phase = null;
- // Start GVN table
- _gvn = new HashMap<>();
- // Allow whole-suite worklist-order sweeps without rewriting the many
- // explicit seeds used by tests and command-line compilation.
- String iterSeed = System.getProperty("simple.iter.seed");
- _iter = new IterPeeps(iterSeed == null ? workListSeed : Long.parseLong(iterSeed));
- // End points of graph
- _stop = new StopNode().init();
- _start = new StartNode(null,_stop,arg).init();
- ZERO = con(TypeInteger.ZERO).keep();
- XCTRL = new XCtrlNode().peephole().keep();
- P = new Parser(this);
- }
-
-
- // Run the requested phases. ELF output contains machine code and data.
-
- // No code emission, just IR generation
- public CodeGen driver( Phase phase ) { return driver(phase,null,null,false,false,0); }
- // No object file writing, but code generation for a specific cpu/os pair (allows emulation)
- public CodeGen driver( Phase phase, String cpu, String callingConv ) {
- if( phase.ordinal() < Phase.Export.ordinal() )
- // No code outputted
- return driver(phase,cpu,callingConv,false,false,0);
- if( _srcName == null )
- throw new RuntimeException("No source filename provided, so do not know how to name the obj file");
- boolean emitEntrySymbol = !_srcName.contains(".");
- return driver( phase, cpu, callingConv, false, emitEntrySymbol, 0 );
- }
- // Write an object file for a specific cpu/os pair
-
- public CodeGen driver( String cpu, String callingConv, boolean inMemory, boolean emitEntrySymbol ) { return driver(Phase.Export,cpu,callingConv,inMemory,emitEntrySymbol,0); }
-
- // The Generic driver
- public CodeGen driver( Phase target, String cpu, String callingConv, boolean inMemory, boolean emitEntrySymbol, int dump ) {
- // Parse the entry source and discover all referenced source files.
- step(target, Phase.Parse, this::parse, dump);
- // Run peepholes to a fixed point
- step(target, Phase.Iter, this::iter, dump);
- // Run SCCP (and resolve int/flt expressions)
- step(target, Phase.Opto, this::opto, dump);
- // Run peepholes to a fixed point again, on the better types.
- step(target, Phase.IterPostOpto, this::iterPostOpto, dump);
- // Field layouts; field symbols become constant offsets
- step(target, Phase.FinalizeIR, this::finalizeIR, dump);
- // Final error check - bad programs fail here
- step(target, Phase.TypeCheck, this::typeCheck, dump);
- // Replace "fat" nodes with their exploded explicit IR; this can
- // introduce CFG diamonds
- step(target, Phase.Lower, this::lower, dump);
- // Compute the loop-tree, used for approximating exec frequencies for
- // scheduling and register allocation
- step(target, Phase.LoopTree, this::loopTree, dump);
- // Instruction selection
- step(target, Phase.Select, () -> instSelect(cpu,callingConv), dump);
- // Global Code Motion
- step(target, Phase.Schedule, this::GCM, dump);
- // Local (basic block only) scheduling
- step(target, Phase.LocalSched, this::localSched, dump);
- // Register allocation
- step(target, Phase.RegAlloc, this::regAlloc, dump);
- // Encode machine instructions into bits
- step(target, Phase.Encoding, this::encode, dump);
- // Export machine code and data into one ELF file
- step(target, Phase.Export, () -> exportELF(inMemory,emitEntrySymbol), dump);
- return this;
- }
-
- // The driver names the order; optional phases decide locally whether to run.
- private void step(Phase target, Phase next, Runnable work, int dump) {
- if( next.ordinal() > target.ordinal() || (_phase!=null && _phase.ordinal()>=next.ordinal()) ) return;
- Phase prior = _phase;
- work.run();
- if( _phase!=prior ) dump(dump);
- }
-
- // All phase entry points share ordering checks, timing, and viewer notification.
- // Record elapsed time even on failure, but report completion only on success.
- CodeGen phase(Phase next, boolean ordered, Runnable work) {
- assert ordered : "Cannot run "+next+" after "+_phase;
- _phase = next;
- long t0 = System.currentTimeMillis();
- work.run(); // Do The Phase
- _times[next.ordinal()] = System.currentTimeMillis() - t0;
- if( _obs!=null ) _obs.phase(next.name()); // Record for viewer
- return this;
- }
-
- public String entryClinitName() {
- return Parser.addClzPrefix(_srcName==null ? "Test" : _srcName.replace('/','.'))+".<clinit>";
- }
-
- // Verbose printing during compilation
- private void dump(int dump) {
- int p2 = _phase.ordinal();
- if( (dump & (1<<p2)) == 0 ) return;
- if( (dump & (1<<30)) != 0 )
- System.err.println("After "+_phase+":");
- System.err.println(_phase.ordinal() >= Phase.LocalSched.ordinal()
- ? asm(new SB()).toString()
- : IRPrinter.prettyPrint(this));
- }
-
-
- // Record times by phase
- public final long[] _times = new long[Phase.LastPhase.ordinal()];
-
- // ---------------------------
- /**
- * A counter, for unique node id generation. Starting with value 1, to
- * avoid bugs confusing node ID 0 with uninitialized values.
- */
- public int _uid = 1;
- public int UID() { return _uid; }
- public int getUID() {
- return _uid++;
- }
-
-
- // Allocate dense alias/function/call IDs within this invocation.
- // Class slots let a forward reference reserve an initializer's identity.
- public final LocalIds _aliases = new LocalIds();
- public int alias(String clz) { return _aliases.next(clz); }
-
- // C storage has one global alias per symbol, including across object files.
- public int externAlias(String name) { return _aliases.next("C:"+name,LocalIds.RESERVED); }
-
- // External data addresses supplied by an in-memory linker (image offsets).
- public final HashMap<String,Integer> _externDataAddresses = new HashMap<>();
-
- // Compute local function index (FIDX) from global function info. This is
- // called *in order* during parsing, and that order is part of the global
- // unique mapping
- public final LocalIds _fidxs = new LocalIds();
- // 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 LocalIds _rpcs = new LocalIds();
- public int rpc( String clz ) { return _rpcs.next(clz); }
-
-
- // Inlining can insert CFG edges and invalidate cached depth ordering.
- private char _iDepthVersion;
- public char iDepthVersion() { return _iDepthVersion; }
- public void invalidateIDepthCaches() {
- assert _iDepthVersion < Character.MAX_VALUE : "Dominator cache version exceeds 65535";
- ++_iDepthVersion;
- }
-
- // Popular visit bitset, declared here, so it gets reused all over
- public final BitSet _visit = new BitSet();
- public BitSet visit() { assert _visit.isEmpty(); return _visit; }
-
- // Start and Stop; end points of the generated IR
- public StartNode _start;
- public StopNode _stop;
-
- // Global Value Numbering. Hash over opcode and inputs; hits in this table
- // are structurally equal.
- public final HashMap<Node,Node> _gvn;
-
- // Reverse from a constant function pointer to the IR function being called.
- // Error to call with a non-constant TFP
- public FunNode link( TypeFunPtr tfp ) { return link(tfp.fidx()); }
- // Read-only linker lookup, suitable for printers and debugger display.
- // In particular, do not lazily remove a partially dead function.
- public FunNode _link(int fidx) { return _linker.atX(fidx); }
- public FunNode _link(TypeFunPtr tfp) { return _link(tfp.fidx()); }
- // Return the FunNode from a fidx
- public FunNode link( int fidx ) {
- FunNode fun =_linker.atX(fidx);
- if( fun!=null && fun.isDead() ) {
- _linker.setX(fidx,null); fun = null; }
- return fun;
- }
- // Return the <clinit> for a CompUnit
- public FunNode link( CompUnit cu ) {
- for( FunNode fun : _linker )
- if( fun!=null && fun._compunit == cu && fun.isClz() )
- return fun;
- return null;
- }
-
- // Insert linker mapping from constant function signature to the function
- // being called.
- public void link(FunNode fun) {
- int fidx = fun.sig().fidx();
- _linker.setX(fidx,fun);
- }
-
- // "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);
- public final Ary<CallNode> _calls = new Ary<>(CallNode.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() { return phase(Phase.Parse, _phase==null, () -> ParseAll.parse(this,_src)); }
-
-
- // ---------------------------
- // Iterator peepholes.
- public final IterPeeps _iter;
- // Stop tracking deps while assertin
- public boolean _midAssert;
- // Statistics on peepholes
- public int _iter_cnt, _iter_nop_cnt;
- // Stat gathering
- public void iterCnt() { if( !_midAssert ) _iter_cnt++; }
- public void iterNop() { if( !_midAssert ) _iter_nop_cnt++; }
-
- // Pessimistic peepholes after parsing. This lifts loaded and parsed
- // types before the optimistic Opto pass resets types and lets them fall.
- public CodeGen iter() { return phase(Phase.Iter, _phase==Phase.Parse, () -> Iter.run(this)); }
-
- // SCCP and numeric-mode resolution; leave cleanup work queued for Iter.
- public CodeGen opto() { return phase(Phase.Opto, _phase==Phase.Iter, () -> Opto.opto(this)); }
-
- public CodeGen iterPostOpto() { return phase(Phase.IterPostOpto, _phase==Phase.Opto, () -> _iter.iterate(this)); }
-
- public CodeGen finalizeIR() { return phase(Phase.FinalizeIR, _phase==Phase.IterPostOpto, () -> FinalizeIR.run(this)); }
-
- // Root a surviving function without claiming that it has unknown callers.
- void keepInCompUnit(FunNode fun) {
- StopCUNode stop = fun._compunit._stop;
- if( stop._inputs.find(fun.ret()) == -1 )
- add(stop).addDef(fun.ret());
- }
- public <N extends Node> N add( N n ) { return _iter.add(n); }
- public void addAll( Ary<Node> ary ) { _iter.addAll(ary); }
- public void addAll( Node n ) { _iter.add(n); _iter.addAll(n._inputs); }
-
- // ---------------------------
- // Last check for bad programs
- public CodeGen typeCheck() { return phase(Phase.TypeCheck, _phase==Phase.FinalizeIR, () -> TypeCheck.run(this)); }
-
- public static TypeStruct requiredStruct(Node n) {
- // CNC: remove? cleanup?
- 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;
- }
-
- // ---------------------------
- // TypeCheck queued surviving CLInits during its existing error walk.
- public CodeGen lower() {
- return phase(Phase.Lower, _phase==Phase.TypeCheck, () -> _iter.iterate(this));
- }
-
- // ---------------------------
- // Build the loop tree; break never-exit loops
- public CodeGen loopTree() {
- return phase(Phase.LoopTree, _phase==Phase.Lower, () -> { _start.buildLoopTree(_linker,_stop); FinishCalls.run(this); });
- }
-
- // ---------------------------
-
-
- // ---------------------------
- // Code generation CPU target
- public Machine _mach;
- // Chosen calling convention (usually either Win64 or SystemV)
- public String _callingConv;
- public String _cCallingConv;
- // Callee save registers
- public RegMask _callerSave;
- public RegMask _cCallerSave;
-
- // All returns have the following inputs:
- // 0 - ctrl
- // 1 - memory
- // 2 - varies with returning GPR,FPR
- // 3 - RPC
- // 4+- Caller save registers
- public RegMask[] _retMasks;
- // Return Program Counter
- public RegMask _rpcMask;
-
- // Convert to target hardware nodes
- public CodeGen instSelect( String cpu, String callingConv ) { return instSelect(cpu,callingConv,PORTS); }
- public CodeGen instSelect( String cpu, String callingConv, String base ) {
- return InstSelect.select(this,cpu,callingConv,base);
- }
-
- // ---------------------------
-
-
- // ---------------------------
- // Control Flow Graph in Reverse Post Order.
- public Ary<CFGNode> _cfg = new Ary<>(CFGNode.class);
-
- // Global schedule (code motion) nodes
- public CodeGen GCM() {
- return phase(Phase.Schedule, (_phase==Phase.LoopTree || _phase==Phase.Select), () -> GlobalCodeMotion.buildCFG(this));
- }
-
- // ---------------------------
- // Local (basic block) scheduler phase, a classic list scheduler
- public CodeGen localSched() { return phase(Phase.LocalSched, _phase==Phase.Schedule, () -> ListScheduler.sched(this)); }
-
-
- // ---------------------------
- // Register Allocation
- public RegAlloc _regAlloc;
- public CodeGen regAlloc() {
- return phase(Phase.RegAlloc, _phase==Phase.LocalSched, () -> {
- _regAlloc = new RegAlloc(this);
- _regAlloc.regAlloc();
- });
- }
-
- // Human-readable register name
- public String reg(Node n) {
- if( _phase.ordinal() >= Phase.RegAlloc.ordinal() ) {
- String s = _regAlloc.reg(n);
- if( s!=null ) return s;
- }
- return "N"+ n._nid;
- }
-
-
- // ---------------------------
- // Encoding
- public Encoding _encoding;
- public CodeGen encode() { return phase(Phase.Encoding, _phase==Phase.RegAlloc, () -> _encoding = new Encoding(this).encode()); }
-
- // ---------------------------
- // Exporting to external formats
- ElfWriter _elf;
- public CodeGen exportELF( boolean inMemory, boolean emitEntrySymbol ) {
- return phase(Phase.Export, _phase==Phase.Encoding, () -> {
- if( inMemory ) new LinkMem(this).link(_encoding);
- else _elf = new ElfWriter(this).export(emitEntrySymbol);
- });
- }
-
-
- // ---------------------------
-
- public boolean _asmLittle=true;
- public String asm() { return asm(new SB()).toString(); }
- SB asm(SB sb) {
- ASMPrinter.print(sb,this);
- return sb;
- }
-
-
- // Testing shortcuts
- public Node ctrl() { return compunit()._stop.ret().ctrl(); }
- public Node expr() { return compunit()._stop.ret().expr(); }
- public String print() { return compunit()._stop.print(); }
-
- // Debugging helper
- @Override public String toString() {
- if( _stop == null )
- return "No StopNode";
- return IRPrinter.prettyPrint(this);
- }
-
- // Debugging helper
- public Node f(int idx) {
- for( CompUnit cu : _compunits.values() ) {
- Node n = (cu._stop == null ? _stop : cu._stop).find(idx);
- if( n != null ) return n;
- }
- return null;
- }
-
-
- String printCFG() {
- if( _cfg==null ) return "no CFG";
- SB sb = new SB();
- for( CFGNode cfg : _cfg ) {
- sb.fix(8,""+(int)cfg._idepth);
- IRPrinter.printLine( cfg, sb );
- }
- return sb.toString();
- }
-
- public static void print_as_hex(Encoding enc) {
- for (byte b : enc._bits.toByteArray()) {
- System.out.print(String.format("%02X", b));
- }
- System.out.println();
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java
deleted file mode 100644
index c8b0a82c..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java
+++ /dev/null
@@ -1,98 +0,0 @@
-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 source file and its class. Parent links follow the source directory 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 File _smp; // Simple source file, e.g. module_root/A/B/C.smp
- final public String _src; // Source code, from file or test case
-
- public String sourceFile() {
- return _smp!=null ? _smp.getAbsolutePath() : null;
- }
-
- public TypeStruct _clz; // The one TypeStruct being published
-
- // _deps records units required for types, code, or class references,
- // without requiring their initializers to run. These source
- // dependencies may form a cycle.
- Ary<CompUnit> _deps;
-
- // Per-Compilation-Unit Start/StopNodes, keeping alive all exported Nodes.
- // Null means no code loaded (yet).
- public StartCUNode _start;
- public StopCUNode _stop ;
-
-
- // Discovered source file. Source is loaded if required to parse, and null
- // otherwise.
- CompUnit( CompUnit par, String fname, String cname, String name ) throws IOException {
- _par = par;
- _fname= fname;
- _cname= cname;
- _name = name;
- _smp = source(fname);
-
- _src = _smp.exists() ? Files.readString(_smp.toPath()) : null;
- }
-
- // The bundled library is ordinary source. A local sys tree can replace it;
- // this chapter never loads Simple code from an object file.
- static File source(String fname) {
- File file=new File(CODE._modDir,fname+".smp");
- if( file.exists() || !(fname.equals("sys") || fname.startsWith("sys/")) ) return file;
- return new File("src/main/smp",fname+".smp");
- }
-
- // 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;
- _smp = null;
- _src = src;
- }
-
- // Record source discovery without adding an initialization edge.
- 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( _src!=null ) x += ",hasSrc";
- x += "}";
- return x;
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
deleted file mode 100644
index c52209dd..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
+++ /dev/null
@@ -1,460 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.TypeFunPtr;
-import com.seaofnodes.simple.type.TypeStruct;
-import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.BAOS;
-import java.io.*;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.HashMap;
-
-public class ElfWriter {
-
- final CodeGen _code;
- Ary<Section> _sections;
- StringSection _strtab;
-
- ElfWriter( CodeGen code ) {
- _code = code;
- }
-
- // ------------------------------------------------------------------------
- abstract public class Section {
- static final int SHT_NULL = 0;
- static final int SHT_PROGBITS= 1;
- static final int SHT_SYMTAB = 2;
- static final int SHT_STRTAB = 3;
- static final int SHT_RELA = 4;
-
- final String _name;
- final int _type, _entsize, _index, _name_pos, _flags;
- int _link, _info;
- int _file_offset;
-
- Section(String name, int type, int entsize, int flags) {
- _name = name;
- _type = type;
- _entsize = entsize;
- _flags = flags;
- _index = _sections._len;
- _sections.push(this);
- _name_pos = name.isEmpty()
- ? 0 // Null section
- : _strtab == null
- ? 1 // StringSection
- // All the other sections
- : _strtab.writeCString(name);
- }
-
- void writeHeader(ByteBuffer out) {
- out.putInt(_name_pos); // name
- out.putInt(_type); // type
- out.putLong(_flags); // flags
- out.putLong(0); // addr
- out.putLong(_file_offset); // offset
- out.putLong(size()); // size
- out.putInt(_link); // link
- out.putInt(_info); // info
- out.putLong(16); // addralign
- out.putLong(_entsize); // Fixed-size element size
- }
-
- abstract void write(ByteBuffer out);
- abstract int size();
- }
-
- // ------------------------------------------------------------------------
- // Generic data sections; carries about a BAOS for bits
- public class DataSection extends Section {
- static final int SHF_WRITE = 1;
- static final int SHF_ALLOC = 2;
- static final int SHF_EXECINSTR = 4;
- static final int SHF_INFO_LINK = 64;
-
- final BAOS _contents;
-
- DataSection( String name, int type, int flags, int entsize ) { this(name, type, flags, entsize, new BAOS() ); }
- DataSection( String name, int type, BAOS contents, int flags ) { this(name, type, flags, 0, contents ); }
- private DataSection( String name, int type, int flags, int entsize, BAOS contents ) {
- super(name, type, entsize, flags);
- _contents = contents;
- }
-
- @Override void write(ByteBuffer out) { out.put(_contents.toByteArray()); }
- @Override int size() { return _contents.size(); }
- public void write2( int op ) {
- _contents.write(op );
- _contents.write(op>> 8);
- }
- public void write4( int op ) {
- _contents.write(op );
- _contents.write(op>> 8);
- _contents.write(op>>16);
- _contents.write(op>>24);
- }
- public void write8( long i64 ) {
- write4( (int) i64 );
- write4( (int)(i64>>32));
- }
-
- }
-
- // ------------------------------------------------------------------------
- // Section to collect and de-dup strings
- public class StringSection extends DataSection {
- final HashMap<String,Integer> _dedup = new HashMap<>();
-
- StringSection() {
- super(".strtab",SHT_STRTAB, 0, 0 );
- int emptyoff = writeCString(""); // Always empty string at offset 0
- assert emptyoff == 0;
- int namepos = writeCString(".strtab");
- assert namepos == _name_pos;
- }
-
- public int writeCString(String name) {
- if( name==null ) return 0; // Anonymous functions have no name
- Integer ii = _dedup.get(name);
- if( ii != null ) return ii;
- // place name in the string table
- int pos = _contents.size();
- _dedup.put(name,pos);
- byte[] name_bytes = name.getBytes();
- _contents.write(name_bytes);
- // the strings are null terminated
- _contents.write(0);
- return pos;
- }
- }
-
- // ------------------------------------------------------------------------
- // The relocations; 3 words per relocation
- public class ReloSection extends DataSection {
- ReloSection( int info ) {
- this(".rela.text",info);
- }
- ReloSection(String name,int info) {
- super(name, SHT_RELA, SHF_INFO_LINK, 24/*entsize*/);
- _link = 2;
- _info = info;
- }
-
- void writeReloOffPlus(int offset, int symidx, byte relo) {
- writeRelo(offset,symidx,relo,-4);
- }
- void writeRelo(int offset,int symidx,byte relo,long addend) {
- assert symidx > 0;
- write8(offset);
- write8(((long)symidx << 32L) | relo);
- write8(addend);
- }
- }
-
-
- // ------------------------------------------------------------------------
- // Symbols section; emit the symbols into the data section directly
- public class SymbolSection extends DataSection {
- public static final int SYMBOL_SIZE = 4+1+1+2+8+8;
- final BAOS _globals;
- final int _nlocals;
-
- SymbolSection(int nlocals) {
- super(".symtab", SHT_SYMTAB, 0/*flags*/, SYMBOL_SIZE);
- _globals = new BAOS();
- _nlocals = nlocals;
- }
-
- // Global symbol index
- int gidx() { return _nlocals+(_globals.size()/SYMBOL_SIZE); }
-
- public int symbol( String name, int shndx, int bind, int type, long value, long size ) {
- int gidx = gidx(); // Global index before I write to _globals
- BAOS baos = bind==SYM_BIND_LOCAL ? _contents : _globals;
- write4(baos,_strtab.writeCString(name));
- int info = (bind << 4) | (type & 0xF);
- baos.write(info);
- baos.write(0); // other/visibility
- write2(baos,shndx); // section index
- write8(baos,value);
- write8(baos,size);
- return bind==SYM_BIND_LOCAL ? -99 : gidx;
- }
-
- public int symbol( String name, int shndx, int bind, int type ) {
- return symbol(name,shndx,bind,type,0,0);
- }
-
- // creates function and stores where it starts.
- // Filters to just this class.obj file.
- void encodeFunctions(StopNode stop, Encoding enc, int text_idx, boolean emitEntrySymbol) {
- for( Node stopcu : stop._inputs ) {
- for( Node ret : stopcu._inputs ) {
- FunNode fun = ((ReturnNode)ret).fun();
- if( !_code.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
- // The C runtime owns `main` and adapts argc/argv into
- // a normal Simple array. Keep the Simple function on
- // its native ABI under a private entry symbol.
- String name = emitEntrySymbol && "main".equals(fun._name)
- ? "simple_main"
- : fun._name;
- symbol(name, text_idx, SYM_BIND_GLOBAL, SYM_TYPE_FUNC, value, size);
- }
- }
- }
- }
-
- // Insert a default-main for top-level compilations, if the top-level
- // does not declare a main.
- void symbolEntryClinit(int text_idx, Encoding enc) {
- FunNode clinit = null;
- String entryName = _code.entryClinitName();
- for( FunNode fun : _code._linker )
- if( fun != null && _code.owns(fun) ) {
- if( "main".equals(fun._name) )
- return; // encodeFunctions exported this as simple_main
- if( entryName.equals(fun._name) )
- clinit = fun; // Keep top-level entry, if we don't find an explicit main
- }
- // No top-level main, so the top-level clinit becomes a default main
- if( clinit != null ) {
- assert enc.opStart(clinit) == 0;
- symbol("simple_main",text_idx, SYM_BIND_GLOBAL, SYM_TYPE_FUNC, enc.opStart(clinit), 0);
- return;
- }
- throw new IllegalStateException("Missing entry <clinit> "+entryName);
- }
-
- static void write2( BAOS bits, int op ) {
- bits.write(op );
- bits.write(op>> 8);
- }
- static void write4( BAOS bits, int op ) {
- bits.write(op );
- bits.write(op>> 8);
- bits.write(op>>16);
- bits.write(op>>24);
- }
- static void write8( BAOS bits, long i64 ) {
- write4(bits, (int) i64 );
- write4(bits, (int)(i64>>32));
- }
-
- @Override
- void writeHeader(ByteBuffer out) {
- // points to string table section
- _link = _strtab._index;
- _info = _nlocals;
- super.writeHeader(out);
- }
-
- @Override void write(ByteBuffer out) {
- out.put(_contents.toByteArray());
- out.put(_globals .toByteArray());
- }
- @Override int size() {
- return _contents.size() + _globals.size();
- }
- }
-
- public static final int SYM_BIND_LOCAL = 0;
- public static final int SYM_BIND_GLOBAL = 1;
- public static final int SYM_BIND_WEAK = 2;
-
- public static final int SYM_TYPE_NOTYPE = 0;
- public static final int SYM_TYPE_OBJECT = 1;
- public static final int SYM_TYPE_FUNC = 2;
- public static final int SYM_TYPE_SECTION = 3;
-
- public static final int SECTION_HDR_SIZE = 64;
-
- // ------------------------------------------------------------------------
-
-
- // ------------------------------------------------------------------------
-
- // Native code can live above 4G, outside our four-byte function pointers.
- // Give address-taken natives a low entry which tail-jumps through the PLT.
- // A weak, stable name makes references from separate objects agree on one
- // entry. Do not patch references straight to the local copy: that would
- // bypass the linker's choice and break pointer equality across objects.
- private int nativeEntry(String name, HashMap<String,Integer> entries,
- DataSection text, SymbolSection symbols, ReloSection relos) {
- Integer old = entries.get(name);
- if( old!=null ) return old;
- int offset = text.size();
- text._contents.write(0xE9); // jmp rel32; preserve arguments and return address
- text.write4(0);
- int target = symbols.symbol(name,0,SYM_BIND_GLOBAL,SYM_TYPE_FUNC);
- relos.writeReloOffPlus(offset+1,target,(byte)4/*PLT32*/);
- int entry = symbols.symbol("__simple_native$"+name,text._index,
- SYM_BIND_WEAK,SYM_TYPE_FUNC,offset,5);
- entries.put(name,entry);
- return entry;
- }
-
- public ElfWriter export(boolean emitEntrySymbol) {
- // Sections are created in the order they are emitted.
- _sections = new Ary<>(Section.class);
-
- // Null section
- Section nullSection = new DataSection("",Section.SHT_NULL,0,0);
-
- // String section next; written out early and available to record/dedup
- // strings for all remaining sections
- _strtab = new StringSection();
-
- // place all the symbols
- int nlocals = 1/*null*/+9/*number of sections*/;
- SymbolSection symbols = new SymbolSection(nlocals);
-
- // we've already constructed these entire sections in the encoding phase
- Encoding enc = _code._encoding;
- // Append ELF-only thunks to a copy. Export must not change instruction
- // offsets or the image used by the in-memory linker and emulators.
- BAOS textBits = new BAOS(enc._bits.toByteArray(),enc._bits.size());
- DataSection text = new DataSection(".text" , Section.SHT_PROGBITS, textBits, DataSection.SHF_ALLOC | DataSection.SHF_WRITE | DataSection.SHF_EXECINSTR );
- // Constant pool
- DataSection rodata = new DataSection(".rodata", Section.SHT_PROGBITS, enc._cpool, DataSection.SHF_ALLOC );
- // Static/global pool
- DataSection rwdata = new DataSection(".data" , Section.SHT_PROGBITS, enc._sdata, DataSection.SHF_ALLOC | DataSection.SHF_WRITE );
-
-
- // populate function symbols
- symbols.encodeFunctions(_code._stop, _code._encoding, text._index, emitEntrySymbol);
- // Export the source compilation unit <clinit> as the C runtime "main".
- // Encoding keeps this function at text offset zero.
- if( emitEntrySymbol )
- symbols.symbolEntryClinit(text._index,enc);
-
- // create external .text relocations
- ReloSection relocations = new ReloSection(text._index);
- HashMap<String,Integer> nativeEntries = new HashMap<>();
-
- for( Node n : enc._externals.keySet()) {
- String extern = enc._externals.get(n);
- boolean nativeAddress = n instanceof FunPtrNode &&
- _code.externFunc(((TypeFunPtr)n._type).fidx())!=null;
- int symidx = nativeAddress
- ? nativeEntry(extern,nativeEntries,text,symbols,relocations)
- : symbols.symbol(extern,0,SYM_BIND_GLOBAL,SYM_TYPE_FUNC);
- int offset = enc.opStart(n) + enc.opLen(n) - 4;
- // Address materialization and static fields name the same thunk;
- // direct calls still go straight to the native symbol's PLT entry.
- relocations.writeReloOffPlus(offset,symidx,(byte)(nativeAddress ? 2/*PC32*/ : 4/*PLT32*/));
- }
-
- // Native ELF output is AMD64. Data addresses use PC32, not PLT calls.
- for( Node n : enc._externData.keySet() ) {
- String extern = enc._externData.get(n);
- int symidx = symbols.symbol(extern, 0, SYM_BIND_GLOBAL, SYM_TYPE_OBJECT);
- int offset = enc.opStart(n) + enc.opLen(n) - 4;
- relocations.writeReloOffPlus(offset, symidx, (byte)2/*PC32*/);
- }
-
- // Stable function identities are also addressable from data, including
- // anonymous functions which have no source-level exported name.
- HashMap<String,Integer> dataSymbols=new HashMap<>();
- for( FunNode fun : _code._linker )
- if( fun!=null && !fun.isDead() && _code.owns(fun) && enc.opStart(fun)>=0 ) {
- String name=_code._fidxs.symbol(fun.sig().fidx());
- dataSymbols.put(name,symbols.symbol(name,text._index,SYM_BIND_GLOBAL,SYM_TYPE_FUNC,enc.opStart(fun),0));
- }
- for( StaticData.ObjectData obj : enc._data.objects.values() ) {
- int section=obj.external ? 0 : obj.readOnly ? rodata._index : rwdata._index;
- dataSymbols.put(obj.symbol,symbols.symbol(obj.symbol,section,SYM_BIND_GLOBAL,SYM_TYPE_OBJECT,
- obj.external ? 0 : obj.offset,obj.external ? 0 : obj.size));
- }
- for( Encoding.Relo relo : enc._bigCons.values() ) {
- StaticData.ObjectData obj=enc._data.object(relo._t);
- relocations.writeReloOffPlus(relo._opStart+relo._off,dataSymbols.get(obj.symbol),relo._elf);
- }
- ReloSection roRelos=new ReloSection(".rela.rodata",rodata._index);
- ReloSection rwRelos=new ReloSection(".rela.data",rwdata._index);
- for( StaticData.Relocation relo : enc._data.relocations ) {
- String name=enc._data.targetSymbol(relo.value());
- String nativeName=relo.value() instanceof TypeFunPtr fp ? _code.externFunc(fp.fidx()) : null;
- Integer sym=nativeName==null ? dataSymbols.get(name)
- : nativeEntry(nativeName,nativeEntries,text,symbols,relocations);
- if( sym==null ) {
- sym=symbols.symbol(name,0,SYM_BIND_GLOBAL,SYM_TYPE_NOTYPE);
- dataSymbols.put(name,sym);
- }
- // AMD64 absolute 32/64-bit addresses; linker rejects overflow.
- byte kind=(byte)(relo.logSize()==2 ? 10 : 1);
- (relo.object().readOnly ? roRelos : rwRelos).writeRelo(relo.object().offset+relo.offset(),sym,kind,0);
- }
-
- // Create a "Simple" section for Simple types and ir
-
- // 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/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Encoding.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Encoding.java
deleted file mode 100644
index 06bf14f5..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Encoding.java
+++ /dev/null
@@ -1,570 +0,0 @@
-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 implements com.seaofnodes.isa.CodeSink {
-
- // Top-level program graph structure
- public final CodeGen _code;
-
- // CFG Nodes in RPO order, per-encoding.
- public Ary<CFGNode> _cfg;
-
- // Instruction bytes. The registers are encoded already. Relocatable data
- // is in a fixed format depending on the kind of relocation.
-
- // - RIP-relative offsets into the same chunk are just encoded with their
- // chunk relative offsets; no relocation info is required.
-
- // - Fixed address targets in the same encoding will be correct for a zero
- // base; moving the entire encoding requires adding the new base address.
-
- // - RIP-relative to external chunks have a zero offset; the matching
- // relocation info will be used to patch the correct value.
-
- public final BAOS _bits = new BAOS(); // Instructions / encodings
- public final BAOS _cpool = new BAOS(); // Constant r/o pool
- public final BAOS _sdata = new BAOS(); // Static r/w pool
- public final HashMap<Node,Relo> _bigCons = new HashMap<>();
- public final StaticData _data;
- public final HashMap<String,Integer> _externalAddresses = new HashMap<>();
-
- // Big Constant relocation info.
- public static class Relo {
- public final Node _op;
- public final Type _t; // Constant type
- public final int _align; // Log alignment, captured for diagnostic display
- public final int _structSize; // Capture layout during encoding, not printing
- public final byte _off; // Offset from start of opcode
- public final byte _elf; // ELF relocation type, e.g. 2/PC32
- public int _target; // Where constant is finally placed
- public int _opStart; // Opcode start
- Relo( Node op, Type t, byte off, byte elf ) {
- _op=op; _t=t; _off=off; _elf=elf;
- _align = t.alignment();
- _structSize = t instanceof TypeStruct ts ? ts.size() : 0;
- _readOnly = readOnly(t);
- }
- public boolean readOnly() { return _readOnly; }
- private final boolean _readOnly;
- private static boolean readOnly(Type t) {
- // Class initializers still write their fields, even when every
- // final value is constant. Their storage must remain writable.
- return !(t instanceof TypeStruct ts) ||
- (!Parser.startsClzPrefix(ts._name) && ts.isConstant());
- }
- }
-
- Encoding( CodeGen code ) {
- _code = code;
- _data = new StaticData(this);
- }
-
- // Shortcut to the defining register
- public short reg(Node n) {
- return _code._regAlloc.regnum(n);
- }
-
- public int read1(int idx) {
- byte[] buf = _bits.buf();
- return (buf[idx]&0xFF);
- }
- public int read2(int idx) {
- byte[] buf = _bits.buf();
- return (buf[idx]&0xFF) | (buf[idx+1]&0xFF) <<8;
- }
- public int read4(int idx) {
- byte[] buf = _bits.buf();
- return (buf[idx]&0xFF) | (buf[idx+1]&0xFF) <<8 | (buf[idx+2]&0xFF)<<16 | (buf[idx+3]&0xFF)<<24;
- }
- public long read8(int idx) {
- return (read4(idx) & 0xFFFFFFFFL) | read4(idx+4);
- }
-
- // This buffer is invalid/moving until after all encodings are written
- public byte[] bits() { return _bits.buf(); }
-
- // 4 byte little-endian write
- public void patch4( int idx, int val ) {
- byte[] buf = _bits.buf();
- buf[idx ] = (byte)(val );
- buf[idx+1] = (byte)(val>> 8);
- buf[idx+2] = (byte)(val>>16);
- buf[idx+3] = (byte)(val>>24);
- }
-
- static void padN(int n, BAOS bits) {
- while( (bits.size()+n-1 & -n) > bits.size() )
- bits.write(0);
- }
-
- // Convenience for writing log-N
- static void addN( int log, long x, BAOS bits ) {
- for( int i=0; i < 1<<log; i++ ) {
- bits.write((int)x);
- x >>= 8;
- }
- }
-
- public Encoding add1( int op ) { addN(0,op,_bits); return this; }
- public Encoding add2( int op ) { addN(1,op,_bits); return this; }
- public Encoding add4( int op ) { addN(2,op,_bits); return this; }
- public Encoding add8(long op ) { addN(3,op,_bits); return this; }
-
- private final static int SENTINEL = -1;
- private final IntHashMap _opStart = new IntHashMap(SENTINEL);
- private final IntHashMap _opLen = new IntHashMap(SENTINEL);
-
-
- // Read op start for n
- public int opStart(Node n) { return _opStart.get(n._nid); }
- // Read op length for n
- public byte opLen (Node n) { return (byte)_opLen.get(n._nid); }
-
- // Set op start for n
- public void opStart(Node n, int off) {
- assert off != SENTINEL;
- _opStart.put(n._nid,off);
- }
-
- // Set op start for n
- public void opStartAdd(Node n, int off) {
- if( off==0 ) return;
- int old = _opStart.get(n._nid);
- assert old != SENTINEL && old+off != SENTINEL;
- _opStart.put(n._nid,old+off);
- }
-
- // Set op length for n
- public void opLen(Node n, byte len) {
- assert len != SENTINEL;
- _opLen.put(n._nid,len);
- }
-
- // Nodes need "relocation" patching; things done after code is placed.
- // Record src and dst Nodes.
- private final HashMap<Node,CFGNode> _internals = new HashMap<>();
- // Source is a Call, destination in the Fun.
- public Encoding relo( CallNode call ) {
- FunNode fun = _code.link(call.tfp());
- if( fun != null && !_code.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;
- // Native functions have no local FunNode, but are still callable.
- // Do not treat their addresses as identities of deleted functions.
- String nativeName = _code.externFunc(tfp.fidx());
- if( nativeName!=null ) return external(fptr,nativeName);
- FunNode fun = _code.link(tfp);
- if( fun != null && !_code.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;
- }
-
- public final HashMap<Node,String> _externData = new HashMap<>();
- public void externalData(Node n, String name) { _externData.put(n,name); }
-
- // Store t as a 32/64 bit constant in the code space; generate RIP-relative
- // addressing to load it. Type is stored in either the .rodata or .data.
- public void largeConstant( Node relo, Type t, int off, int elf ) {
- // TODO: Any-old struct in the cpool
- assert t.isConstant() || (t instanceof TypeStruct ts && Parser.startsClzPrefix(ts._name));
- assert (byte)off == off;
- assert (byte)elf == elf;
- _bigCons.put(relo,new Relo(relo,t,(byte)off,(byte)elf));
- }
-
- // --------------------------------------------------
- Encoding encode( ) {
- // Basic block layout: negate branches to keep blocks in-order; insert
- // unconditional jumps. Attempt to keep backwards branches taken,
- // forwards not-taken (this is the default prediction on most
- // hardware). Layout is still Reverse Post Order but with more
- // restrictions.
- basicBlockLayout( );
-
- // Write encoding bits in order into a big byte array.
- // Record opcode start and length.
- writeEncodings();
-
- // Short-form RIP-relative support: replace long encodings with short
- // encodings and compact the code, changing all the offsets.
- compactShortForm();
-
- // Place pool objects and record relocations within their contents.
- _data.write();
-
- // Patch RIP-relative and local encodings now.
- patchLocalRelocations();
-
- return this;
- }
-
- // --------------------------------------------------
- // Basic block layout: negate branches to keep blocks in-order; insert
- // unconditional jumps. Attempt to keep backwards branches taken, forwards
- // not-taken (this is the default prediction on most hardware). Layout is
- // still Reverse Post Order but with more restrictions.
- private void basicBlockLayout( ) {
- IdentityHashMap<LoopNode,Ary<CFGNode>> rpos = new IdentityHashMap<>();
- _cfg = new Ary<>(CFGNode.class);
- rpos.put(_code._start.loop(),_cfg);
- BitSet visit = _code.visit();
-
- // Do them all except the <clinit>s
- Ary<FunNode> clinits = new Ary<>(FunNode.class);
- FunNode entry = null;
- String entryName = _code.entryClinitName();
- for( FunNode fun : _code._linker ) {
- if( fun != null && !fun.isDead() && _code.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);
- }
- }
-
- // Emit a single scalar as bits
- static void addN( int log, Type t, BAOS bits ) {
- long x = switch( t ) {
- case TypeInteger ti -> ti.value();
- case TypeFloat tf -> log==3
- ? Double.doubleToRawLongBits( tf.value())
- : Float.floatToRawIntBits((float)tf.value());
- case TypeFunPtr tfp -> 0; // These need to be relocated
- case TypeMemPtr tmp -> 0; // These need to be relocated
- default -> { if( t==Type.NIL ) yield 0; else throw Utils.TODO(); }
- };
- addN(log,x,bits);
- }
-
- // A series of libc/external calls that Simple can link against in a JIT.
- // Since no runtime in the JVM process, using magic numbers for the CPU
- // emulators to pick up on.
- public static int SENTINEL_CALLOC = -4;
- public static int SENTINEL_WRITE = -8;
-
- int externalAddress(String name) {
- Integer address=_externalAddresses.get(name);
- if( address!=null ) return address;
- return switch(name) {
- case "calloc" -> SENTINEL_CALLOC;
- case "write" -> SENTINEL_WRITE;
- default -> throw new IllegalArgumentException("Unresolved external function '"+name+"'");
- };
- }
-
- void patchGlobalRelocations() {
- for( Node src : _externData.keySet() ) {
- String name = _externData.get(src);
- Integer target = _code._externDataAddresses.get(name);
- if( target==null ) throw new IllegalArgumentException("Unresolved C data symbol '"+name+"'");
- int start = opStart(src);
- ((RIPRelSize)src).patch(this,start,opLen(src),target-start);
- }
- for( Node src : _externals.keySet() ) {
- int start = opStart(src);
- String dst = _externals.get(src);
- int target = externalAddress(dst);
- ((RIPRelSize)src).patch(this, start, opLen(src), target - start);
- }
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java
deleted file mode 100644
index 923486c6..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import com.seaofnodes.simple.node.*;
-import java.util.Arrays;
-
-// Comnpute field layouts only after SCCP and its peepholes converge.
-// Keep surviving functions rooted without introducing unknown callers.
-abstract class FinalizeIR {
- static void run(CodeGen code) {
- // Compute field layouts and replace symbolic field offsets by constants.
- for( int i=0; i<code._start.nOuts(); i++ ) {
- Node use = code._start.out(i);
- if( use instanceof ConFldOffNode off ) {
- Node con = off.asOffset();
- assert con != null : off._ts._name+"."+off._fname;
- off.subsume(con);
- i--;
- }
- }
- code._iter.iterate(code); // Propagate field offsets and clean up.
-
- // Retain surviving functions for emission without adding unknown callers.
- for( FunNode fun : code._linker )
- if( fun != null ) {
- // A folded static function field can leave a detached body
- // with internal cycles. It has no callers; later phases must
- // not revive it merely because its nodes are still allocated.
- if( fun.isDead() || fun._type.isHigh() ) {
- code._linker.set(fun.sig().fidx(),null);
- continue;
- }
- code.keepInCompUnit(fun);
- }
- for( CompUnit cu : code._compunits.values() ) {
- ProjNode mem = cu._start.proj(1);
- if( mem != null ) code.add(mem.unkeep());
- }
- // Stable print order: initializers first, then other Returns by NID.
- for( Node stop : code._stop._inputs )
- Arrays.sort(stop._inputs._es,0,stop.nIns(),
- (x,y) -> ((((ReturnNode)x).fun().isClz() ? -1 : x._nid) -
- (((ReturnNode)y).fun().isClz() ? -1 : y._nid) ));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/FinishCalls.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/FinishCalls.java
deleted file mode 100644
index 7ba696d3..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/FinishCalls.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import com.seaofnodes.simple.node.*;
-
-// Detach analysis call edges after building the loop tree. Every live body
-// belongs to this invocation; preserve it for scheduling and emission.
-abstract class FinishCalls {
-
-
- static void run(CodeGen code) {
-
- // The remaining passes assume all calls are unlinked; i.e. we are throwing
- // away the Call Graph here. Functions only reachable from internal calls
- // need to be re-hooked to stop/start less they go dead.
- for( FunNode fun : code._linker ) {
- // Already linked to start, not going dead
- if( fun==null || fun.isDead() )
- continue;
-
- // Insert the post-Opto code-generation hook. Unlike an Opto-time
- // Start input, this no longer carries unknown-caller semantics.
- ReturnNode ret = fun.ret();
- StartNode start = fun._compunit._start;
- assert start!=null;
- if( fun.nIns() <= 1 || fun.in(1) != start ) {
- fun.insertDef(1,start);
- for( Node use : fun._outputs )
- if( use instanceof ParmNode parm )
- parm.insertDef(1,ConstantNode.raw(parm._type));
- if( fun._compunit._stop._inputs.find(ret) == -1 )
- fun._compunit._stop.addDef(ret);
- }
- // Unlink from Call
- for( int i=2; i<fun.nIns(); i++ )
- ((CallNode)fun.in(i)).unlink(fun,i--);
- 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() );
- }
- }
-
- code._stop.walk(n -> {
- if( n instanceof CallEndNode cend )
- for( int i=1; i<cend.nIns(); i++ )
- if( cend.in(i) instanceof ReturnNode )
- cend.delDef(i--);
- if( n instanceof CallNode call )
- call.unlink_all();
- return null;
- });
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
deleted file mode 100644
index 4477025b..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
+++ /dev/null
@@ -1,304 +0,0 @@
-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
- assert !( def instanceof ReturnNode && use instanceof CallEndNode ); // All calls unwired now
- assert !( def instanceof CallNode && use instanceof FunNode ); // All calls unwired now
- visit.set(cfg._nid);
- for( Node useuse : cfg._outputs )
- _rpo_cfg(cfg,useuse,visit,rpo);
- rpo.add(cfg);
- }
-
- // After early scheduling, every global chain member has Start in slot 0.
- // Give each function one private copy of the entire dependency graph.
- private static void breakUpGlobalConstants( StartNode start ) {
- var globals = new IdentityHashMap<Node,Boolean>();
- var cons = new ArrayList<Node>();
- for( Node con : start._outputs )
- if( con!=null && !(con instanceof CFGNode) &&
- (con.isConst() || con instanceof MachNode mach && mach.outregmap()!=null) ) {
- globals.put(con,true);
- cons.add(con.keep()); // Preserve original inputs while rewiring users.
- }
-
- var copies = new IdentityHashMap<FunNode,IdentityHashMap<Node,Node>>();
- for( Node con : cons )
- for( Node use : con._outputs.asAry() ) {
- if( use==null || globals.containsKey(use) ) continue;
- FunNode fun = useFun(use);
- if( fun==null ) continue; // Global metadata has no function owner.
- assert CodeGen.CODE._mach==null || CodeGen.CODE.owns(fun);
- var local = copies.computeIfAbsent(fun,f -> new IdentityHashMap<>());
- Node copy = cloneGlobal(con,fun,globals,local);
- for( int i=0; i<use.nIns(); i++ )
- if( use.in(i)==con )
- use.setDef(i,copy);
- }
- for( Node con : cons ) con.unkill();
- }
-
- private static Node cloneGlobal(Node con, FunNode fun, IdentityHashMap<Node,Boolean> globals,
- IdentityHashMap<Node,Node> copies) {
- Node copy = copies.get(con);
- if( copy!=null ) return copy;
- copies.put(con,copy=con.copyEmpty());
- copy.addDef(fun);
- for( int i=1; i<con.nIns(); i++ ) {
- Node def = con.in(i);
- copy.addDef(globals.containsKey(def) ? cloneGlobal(def,fun,globals,copies) : def);
- }
- return copy;
- }
-
- private static FunNode useFun(Node use) {
- if( use instanceof ReturnNode ret ) return ret.fun();
- if( use instanceof ParmNode parm ) return parm.fun();
- CFGNode cfg = use.cfg0();
- while( cfg!=null && !(cfg instanceof FunNode) )
- cfg = cfg.idom();
- return (FunNode)cfg;
- }
-
-
- // ------------------------------------------------------------------------
- // Visit all nodes in CFG Reverse Post-Order, essentially defs before uses
- // (except at loops). Since defs are visited first - and hoisted as early
- // as possible, when we come to a use we place it just after its deepest
- // input.
- private static void schedEarly(CodeGen code) {
- // Reverse Post-Order on CFG
- for( CFGNode cfg : code._cfg ) {
- cfg.loopDepth();
- for( Node n : cfg._inputs )
- _schedEarly(n,code );
- // In dead infinite loops, entire code blocks may be unreachable
- // from below. Reach down from the CFG to their Phis so their
- // inputs are scheduled too.
- if( cfg instanceof RegionNode )
- for( Node phi : cfg._outputs )
- if( phi instanceof PhiNode )
- _schedEarly(phi,code );
- }
- }
-
- 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 inputs first, except Phis: following their backedges would
- // enter a data cycle before its control has been scheduled.
- for( Node def : n._inputs )
- if( def!=null && !(def instanceof PhiNode) )
- _schedEarly(def,code);
-
- // An existing edge 0 already supplies control (or a Phi/Proj binding).
- if( n.in(0)==null ) {
- // Schedule at deepest input
- CFGNode early = code._start; // Maximally early, lowest idepth
- for( int i=1; i<n.nIns(); i++ )
- if( n.in(i)!=null && n.in(i).cfg0().idepth() > early.idepth() )
- early = n.in(i).cfg0(); // Latest/deepest input
- n.setDef(0,early);
- }
- }
-
- // ------------------------------------------------------------------------
- private static void schedLate( CodeGen code ) {
- CFGNode[] late = new CFGNode[code.UID()];
- Node[] ns = new Node[code.UID()];
- // Record Load NIDs at all their CFG block choices, then check against
- // Store block choices to force a Load above an anti-dependent Store.
- int[] anti = new int[code.UID()];
- Ary<Node> antideps = new Ary<>(Node.class);
- // Breadth-first scheduling
- breadth(code._stop,ns,late,anti, antideps);
-
- // 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, int[] anti, Ary<Node> antideps) {
- // 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.antiDeps(antideps) )
- if( late[memuse._nid]==null )
- continue outer;
-
- // All uses done, schedule
- _doSchedLate(n,ns,late,anti,antideps);
- }
-
- // A use just finished; reconsider its inputs and waiting loads,
- // even when the shared memory input was already scheduled.
- for( Node def : n._inputs ) {
- if( def==null ) continue;
- if( late[def._nid]==null ) work.push(def);
- wakeLoads(def,late,work,new BitSet());
- }
- 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, int[] anti, Ary<Node> antideps) {
- // Walk uses, gathering the LCA (Least Common Ancestor) of uses
- CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
- assert early != null;
- CFGNode lca = null;
- for( Node use : n._outputs )
- if( use != null )
- lca = use_block(n,use, late).domLCA(lca,null);
-
- // Loads may need anti-dependencies, raising their LCA
- if( n instanceof MemOpNode load && load._isLoad )
- lca = find_anti_dep(lca,load,early,late,anti, antideps);
-
- // Walk up from the LCA to the early, looking for best place. This is
- // the lowest execution frequency, approximated by least loop depth and
- // deepest control flow.
- CFGNode best = lca;
- lca = lca.idom(); // Already found best for starting LCA
- for( ; lca != early.idom(); lca = lca.idom() )
- if( better(lca,best) )
- best = lca;
- assert !(best instanceof IfNode);
- 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 )
- return late[use._nid];
- if( !(use instanceof PhiNode phi) )
- return late[use._nid];
- CFGNode found=null;
- for( int i=1; i<phi.nIns(); i++ )
- if( phi.in(i)==n )
- found = phi.region().cfg(i).domLCA(found,null); // Can be more than one matching input.
-
- assert found!=null;
- return found;
- }
-
-
- // Least loop depth first, then largest idepth
- private static boolean better( CFGNode lca, CFGNode best ) {
- return lca.loopDepth() < best.loopDepth() ||
- lca instanceof NeverNode ||
- lca.idepth() > best.idepth() ||
- best instanceof IfNode;
- }
-
- // A writer may consume a load's memory through an aggregate.
- private static void wakeLoads(Node def, CFGNode[] late, WorkList<Node> work, BitSet visit) {
- if( visit.get(def._nid) ) return;
- visit.set(def._nid);
- for( Node out : def._outputs )
- if( out instanceof MemOpNode ld && ld._isLoad && late[ld._nid]==null ) work.push(ld);
- if( def instanceof MemMergeNode )
- for( int i=1; i<def.nIns(); i++ )
- if( def.in(i)!=null ) wakeLoads(def.in(i),late,work,visit);
- }
-
- private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti, Ary<Node> antideps) {
- // We could skip final-field loads here.
- // Walk LCA->early, flagging Load's block location choices
- for( CFGNode cfg=lca; cfg!=early.idom(); cfg = cfg.idom() )
- anti[cfg._nid] = load._nid;
- // Walk load->mem uses, looking for Stores causing an anti-dep
- for( Node mem : load.antiDeps(antideps) ) {
- if( mem instanceof PhiNode phi ) {
- // Inputs carrying different memory might overwrite this alias.
- // Raise the LCA for those paths; the unchanged-memory paths
- // do not clobber the load. No scheduling edge to the Phi.
- for( int i=1; i<phi.nIns(); i++ )
- if( phi.in(i)!=load.mem() )
- lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null,anti);
- } else {
- // Hunt for clobbering stores
- assert mem instanceof MemOpNode || mem instanceof EscapeNode ||
- mem instanceof CallNode || mem instanceof CLInitNode;
- assert late[mem._nid]!=null;
- lca = anti_dep(load,late[mem._nid],mem.cfg0(),lca,mem,anti);
- }
- }
- return lca;
- }
-
- //
- private static CFGNode anti_dep( MemOpNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st, int[] anti ) {
- // A conditional writer can precede the load without dominating its late block.
- // Walk back to the first overlap with the load's placement range.
- // A different Phi input need not be dominated by this memory's
- // definition. Stop at its depth rather than looking for that ancestor.
- // Retain the sentinel too: a function entry has a null idom.
- for( ; stblk != defblk.idom() && stblk.idepth() >= defblk.idepth(); stblk = stblk.idom() ) {
- // Store and Load overlap, need anti-dependence
- if( anti[stblk._nid]==load._nid ) {
- lca = stblk.domLCA(lca,null); // Raise Loads LCA
- if( lca == stblk && st != null && !(st instanceof CallNode) && st._inputs.find(load) == -1 ) // And if something moved,
- st.addDef(load); // Add anti-dep as well (calls already end their block)
- return lca; // Cap this stores' anti-dep to here
- }
- }
- return lca;
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/IFG.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/IFG.java
deleted file mode 100644
index bcdb14bf..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/IFG.java
+++ /dev/null
@@ -1,580 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.Utils;
-import java.util.BitSet;
-import java.util.IdentityHashMap;
-
-// Interference Graph
-abstract public class IFG {
- // Interference Graph Support
-
- // Map from a Basic Block to Live-Out: {a map from a Live Range to a Def}
- private static final IdentityHashMap<CFGNode,IdentityHashMap<LRG,Node>> BBOUTS = new IdentityHashMap<>();
- static void resetBBLiveOut() {
- for( IdentityHashMap<LRG,Node> bbout : BBOUTS.values() )
- bbout.clear();
- }
- static final IdentityHashMap<LRG,Node> TMP = new IdentityHashMap<>();
-
- // Inteference Graph: Array of Bitsets
- static final Ary<BitSet> IFG = new Ary<>(BitSet.class);
- static void resetIFG() {
- for( BitSet bs : IFG )
- if( bs!=null ) bs.clear();
- }
-
- // Set matching bit
- private static void addIFG( LRG lrg0, LRG lrg1 ) {
- short x0 = lrg0._lrg, x1 = lrg1._lrg;
- // Triangulate
- if( x0 < x1 ) _addIFG(x0,x1);
- else _addIFG(x1,x0);
- }
- // Add lrg1 to lrg0's conflict set
- private static void _addIFG( short x0, short x1 ) {
- BitSet ifg = IFG.atX(x0);
- if( ifg==null )
- IFG.setX(x0,ifg = new BitSet());
- ifg.set(x1);
- }
-
-
- private static final Ary<CFGNode> WORK = new Ary<>(CFGNode.class);
- static void pushWork(CFGNode bb) {
- if( WORK.find(bb) == -1 )
- WORK.push(bb);
- }
-
- // ------------------------------------------------------------------------
- // Visit all blocks, using the live-out LRGs per-block and doing a backwards
- // walk over each block. At the end of the walk, push the live-out sets to
- // prior blocks. Due to loops, no single visitation order suffices. This
- // problem can be fairly efficiently solved with a proper LIVE calculation
- // and bitsets. In the interests of simplicity (and assumption of smaller
- // programs) I am skipping LIVE and directly computing it during the IFG
- // building.
-
- // Start by setting the live-out of the exit block (to empty), and putting
- // it on a worklist.
-
- // - Pull from worklist a block who's live-out has changed
- // - Walk backwards, adding interferences and computing live.
- // - At block head, "push" the new live-out to prior blocks.
- // - - If they pick up new live-outs, put them on the worklist.
-
- public static boolean build( int round, RegAlloc alloc ) {
- // Reset all to empty
- resetBBLiveOut();
- resetIFG();
- WORK.clear();
-
- // Last block has nothing live out
- for( CFGNode bb : alloc._code._cfg )
- if( bb.blockHead())
- WORK.push( bb );
-
- // Process blocks until no more changes
- while( !WORK.isEmpty() )
- do_block(round,alloc,WORK.pop());
-
- if( alloc.success() )
- convert2DAdjacency(alloc);
- return alloc.success();
- }
-
- // Walk one block backwards, compute live-in from live-out, and build IFG
- static void do_block( int round, RegAlloc alloc, CFGNode bb ) {
- assert bb.blockHead();
- IdentityHashMap<LRG,Node> live_out = BBOUTS.get(bb);
- TMP.clear();
- if( live_out != null )
- TMP.putAll(live_out); // Copy bits to temp
-
- // A backwards walk over instructions in the basic block
- for( int inum = bb.nOuts()-1; inum >= 0; inum-- ) {
- Node n = bb.out(inum);
- if( n.in(0) != bb ) continue;
- // In a backwards walk, proj users come before the node itself
- if( n instanceof MultiNode )
- for( Node proj : n.outs() )
- if( proj instanceof ProjNode )
- do_node(alloc,proj);
- if( n instanceof MachNode )
- do_node(alloc,n);
- }
-
- // The bb head kills register, e.g. a CallEnd and caller-save registers
- if( bb instanceof MachNode mach )
- kills(alloc,mach);
-
- // Push live-sets backwards to priors in CFG.
- if( bb instanceof RegionNode )
- for( int i=1; i<bb.nIns(); i++ )
- mergeLiveOut(alloc,bb,i);
- else
- mergeLiveOut(alloc,bb,0);
- }
-
- private static void do_node(RegAlloc alloc, Node n) {
- // Defining means killing live LRG
- LRG lrg = alloc.lrg(n);
- if( lrg != null ) {
- // Check for def-side self-conflict live ranges. These must split,
- // and only happens during the first round a particular LRG splits.
- selfConflict(alloc,n,lrg);
- TMP.remove(lrg); // Kill def
- }
-
- // Phis use and define the same live range, i.e. these LRGs already
- // marked conflicted, no need to mark again
- if( n instanceof PhiNode )
- return;
-
- // Kill any killed registers; milli-code routines like New can kill
- // will not being a CFG.
- if( n instanceof MachNode m )
- kills(alloc,m);
-
- // Interfere n with all live
- if( lrg!=null ) {
- // Interfere n with all live
- for( LRG tlrg : TMP.keySet() ) {
- assert tlrg.leader();
- // Skip self
- if( lrg != tlrg &&
- // And register sets overlap
- lrg._mask.overlap(tlrg._mask) )
- // Then tlrg and lrg interfere. If lrg needs the single
- // last tlrg register at some point, either tlrg or lrg
- // must fail. If *n* (a subset of lrg) needs the single
- // last tlrg register then only tlrg must fail.
- if( ((MachNode)n).outregmap().size1() ) {
- if( !tlrg.clr(lrg._mask.firstReg()) ) // Clear bit, no interference
- alloc.fail(tlrg); // Clearing drives mask to empty
- } else addIFG(lrg,tlrg); // Add interference
- }
- }
-
-
- // Check for self-conflict live ranges. These must split, and only
- // happens during the first round when a particular LRG splits.
- // Also record use-side spills for biased coloring.
- // Then make all inputs live.
- for( int i=1; i<n.nIns(); i++ ) {
- Node def = n.in(i);
- if( def==null ) continue;
- LRG lrg1 = alloc.lrg(def);
- if( lrg1==null ) continue; // Anti-dependence, etc, no register
-
- // Check use-side for self-conflict; if so we MUST split
- selfConflict(alloc,def,lrg1);
-
- // 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) ) {
- Node live = TMP.get(tlrg);
- // Disallow clone-ables from living across killed registers.
- // Fail them and let splitting re-clone closer to the target use.
- if( live instanceof MachNode mach && mach.isClone() )
- alloc.fail(tlrg);
- else if( !tlrg.sub(killMask) )
- alloc.fail(tlrg);
- }
- }
- }
-
- // Check for self-conflict live ranges. These must split, and only happens
- // during the first round a particular LRG splits.
- private static void selfConflict(RegAlloc alloc, Node n, LRG lrg) {
- selfConflict(alloc,n,lrg,TMP.get(lrg));
- }
- private static void selfConflict(RegAlloc alloc, Node n, LRG lrg, Node prior) {
- if( prior!=null && prior != n ) {
- lrg.selfConflict(prior);
- lrg.selfConflict(n);
- alloc.fail(lrg); // 2 unrelated values live at once same live range; self-conflict
- }
- }
-
- // Merge TMP into bb's live-out set; if changes put bb on WORK
- private static void mergeLiveOut( RegAlloc alloc, CFGNode priorbb, int i ) {
- CFGNode bb = priorbb.cfg(i);
- if( bb == null ) return; // Start has no prior
- while( !bb.blockHead() ) bb = bb.cfg0();
-
- // Lazy get live-out set for bb
- IdentityHashMap<LRG, Node> lrgs = BBOUTS.computeIfAbsent( bb, k -> new IdentityHashMap<>() );
-
- for( LRG lrg : TMP.keySet() ) {
- Node def = TMP.get(lrg);
- // Effective def comes from phi input from prior block
- if( def instanceof PhiNode phi && phi.cfg0()==priorbb ) {
- assert i!=0;
- def = phi.in( i );
- }
- // The synthetic exit carries no value on this Phi arm.
- if( def._type==Type.TOP ) continue;
- Node def_bb = lrgs.get(lrg);
- if( def_bb==null ) {
- lrgs.put(lrg,def);
- pushWork(bb);
- } else {
- // Alive twice with different definitions; self-conflict
- selfConflict(alloc,def,lrg,def_bb);
- }
- }
- }
-
-
- static void convert2DAdjacency( RegAlloc alloc ) {
- // Convert the 2-D array of bits (a 1-D array of BitSets) into an
- // adjacency matrix.
- int maxlrg = alloc._LRGS.length;
- for( int i=1; i<maxlrg; i++ ) {
- BitSet ifg = IFG.atX(i);
- if( ifg != null ) {
- LRG lrg0 = alloc._LRGS[i];
- for( int lrg = ifg.nextSetBit(0); lrg>=0; lrg=ifg.nextSetBit(lrg+1) ) {
- LRG lrg1 = alloc._LRGS[lrg];
- lrg0.addNeighbor(lrg1);
- lrg1.addNeighbor(lrg0);
- }
- }
- }
- }
-
-
- // ------------------------------------------------------------------------
- // Color the inference graph.
-
- // Coloring works by removing "trivial" live ranges from the IFG - those
- // live ranges with fewer neighbors than available colors (registers).
- // These are trivial because even if every neighbor takes a unique color,
- // there's at least one more available to color this live range.
-
- // If we hit a clique of non-trivial live ranges, we pick one to be "at
- // risk" of not-coloring. Good choices include live ranges with a large
- // area and few hot uses.
-
- // Then we reverse and put-back live ranges - picking a color as we go.
- // If there's no spare color we'll have to spill this at-risk live range.
-
- public static boolean color(int round, RegAlloc alloc) {
- int maxlrg = alloc._LRGS.length, nlrgs=0;
- for( int i=1; i<maxlrg; i++ )
- if( alloc._LRGS[i] != null )
- nlrgs++;
-
- // Simplify
-
- // Walk all the LRGS looking for some trivial ones to start with.
- // During this pass all LRGS are broken into 3 disjoint sets:
- // - trivial, removed from IFG; color_stack[0 to sptr]
- // - trivial, not (yet) removed from IFG; color_stack[sptr to swork]
- // - unknown; color_stack[work to maxlrg]
-
- // Gather all not-unified (not-null); separate trivial and non-trivial set.
- int sptr = 0, swork = 0;
- LRG[] color_stack = new LRG[nlrgs];
- for( int i=0, j=0; i<maxlrg; i++ ) {
- LRG lrg = alloc._LRGS[i];
- if( lrg==null ) continue; // Unified lrgs are null here
- color_stack[j++] = lrg;
- if( lrg.lowDegree() ) swap(color_stack, swork++, j-1);
- }
-
- // Pull all lrgs from IFG, in trivial order if possible
- while( sptr < color_stack.length ) {
- // Swap best color up front
- pickColor(color_stack,sptr,swork,alloc);
-
- // Pick a trivial lrg, and (temporarily) remove from the IFG.
- LRG lrg = color_stack[sptr++];
- // If sptr was swork, then pulled an at-risk lrg
- if( sptr > swork )
- swork = sptr;
-
- // Walk all neighbors and remove
- if( lrg._adj != null ) {
- for( LRG nlrg : lrg._adj ) {
- // Remove and compress out neighbor
- if( nlrg.removeCompress(lrg) ) {
- // Neighbor is just exactly going trivial as 'lrg' is removed from IFG
- // Find "j" position in the color_stack
- int jx = swork; while( color_stack[jx] != nlrg ) jx++;
- // Add trivial neighbor to trivial list. Pull lrg j out of
- // unknown set, since its now in the trivial set
- swap(color_stack, swork++, jx);
- }
- }
- }
- }
-
- // Reverse simplify (unstack the color stack), and set colors (registers) for live ranges
- while( sptr > 0 ) {
- LRG lrg = color_stack[--sptr];
- if( lrg==null ) continue;
- RegMaskRW rmask = lrg._mask.copy();
- // Walk neighbors and remove adjacent colors
- if( lrg._adj!=null ) {
- for( LRG nlrg : lrg._adj ) {
- nlrg.reinsert(lrg);
- int reg = nlrg._reg;
- if( reg!= -1 ) // Failed neighbors do not count
- rmask.clr(reg); // Remove neighbor from my choices
- }
- }
- // At-risk live-range did not color?
- if( rmask.isEmpty() ) {
- alloc.fail(lrg);
- lrg._reg = -1;
- } else {
- // Pick first available register
- short reg = rmask.firstReg();
- // Pick a "good" color from the choices. Typically, biased-coloring
- // removes some over-spilling.
- if( rmask.size() > 1 ) reg = biasColor(alloc,lrg,reg,rmask);
- lrg._reg = reg; // Assign the register
- }
- }
-
- return alloc.success();
- }
-
- // Pick LRG from color stack
- private static void pickColor(LRG[] color_stack, int sptr, int swork, RegAlloc alloc) {
- // Out of trivial colorable, pick an at-risk to pull
- if( sptr==swork )
- swap(color_stack,sptr,pickRisky(color_stack,sptr,alloc));
- // When coloring, we'd like to give more choices; so when coloring we'd
- // like to see the single-def first (since no choices anyway), then
- // non-split related (so more live ranges get colored), then
- // split-related last, so they have more colors to bias towards.
-
- // Working in reverse, pick first split-related with many regs, then
- // those with some regs, then single-def.
- int bidx=sptr;
- LRG best=color_stack[bidx];
- for( int idx = sptr+1; idx < swork; idx++ )
- if( betterLRG(best,color_stack[idx]) )
- best = color_stack[bidx=idx];
- if( bidx != sptr )
- swap(color_stack,sptr,bidx); // Pick best at sptr
- }
-
- // Returns true if 'lrg' is better than 'best'
- private static boolean betterLRG( LRG best, LRG lrg ) {
- // If single-def varies, keep the not-single-def
- if( best.size1() != lrg.size1() )
- return best.size1();
- // If hasSplit varies, keep the hasSplit
- if( best.hasSplit() != lrg.hasSplit() )
- return lrg.hasSplit();
- // If both are single register (so both sizes are 1),
- // return a multi-use over a multi-def
- if( best.size1() )
- return !best._multiUse && lrg._multiUse;
- // Keep large register count
- return best.size() < lrg.size();
- }
-
- // Pick a live range that hasn't already spilled, or has a single-def-
- // single-use that are not adjacent.
- private static int pickRisky( LRG[] color_stack, int sptr, RegAlloc alloc ) {
- int best=sptr;
- int bestScore = pickRiskyScore(color_stack[best],alloc);
- for( int i=sptr+1; i<color_stack.length; i++ ) {
- if( bestScore == 1000000 ) return best; // Already max score
- int iScore = pickRiskyScore(color_stack[i],alloc);
- if( iScore > bestScore )
- { best = i; bestScore = iScore; }
- }
- return best;
- }
-
- // Pick a live range to pull, that might not color.
- //
- // Picking a live range with a very large span, with defs and uses outside
- // loops means spilling a relative cheap live range and getting that
- // register over a large area.
- //
- // Picking a live range that is very close to coloring might allow it to
- // color despite being risky.
- private static int pickRiskyScore( LRG lrg, RegAlloc alloc ) {
- // Callee-save live ranges cover the whole function and are cheap to
- // split. Rank scheduled area recovered against the loop-scaled cost
- // of the def- and use-side moves.
- if( lrg._machDef instanceof CalleeSaveNode )
- return areaScore(functionArea(lrg._fun,alloc),
- splitCost((Node)lrg._machDef,(Node)lrg._machUse));
- if( lrg._splitDef != null && lrg._splitDef. in(1) instanceof CalleeSaveNode &&
- lrg._splitUse != null && lrg._splitUse.out(0) instanceof ReturnNode )
- return areaScore(functionArea(lrg._fun,alloc),
- splitCost(lrg._splitDef,lrg._splitUse));
-
- // Pick single-def clonables that are not right next to their single-use.
- // Rank by the area recovered by cloning closer to the use.
- if( !lrg._multiDef && lrg._machDef!=null && lrg._machUse!=null && lrg._machDef.isClone() ) {
- Node def = ((Node)lrg._machDef);
- Node use = ((Node)lrg._machUse);
- CFGNode cfg = def.cfg0();
- int area = cfg != use.cfg0()
- ? functionArea(lrg._fun,alloc)
- : cfg._outputs.find(use)-cfg._outputs.find(def)-1;
- // A single-use function address can move next to its use without
- // adding an instruction. Prefer that to saving a callee-save
- // register just to hold the address. Stay in the same block and
- // require a gap so the clone makes progress.
- if( def instanceof FunPtrNode && !lrg._multiUse &&
- cfg == use.cfg0() && area > 0 )
- return 1000000;
- if( area > 0 )
- return areaScore(area,loopCost(use));
- }
-
- // Default for ranges without a specific area/cost estimate.
- return 1000 + (lrg._multiUse ? -100 : 0);
- }
-
- private static int functionArea(FunNode fun, RegAlloc alloc) {
- int area=0;
- for( CFGNode bb : alloc._code._cfg )
- if( bb.fun()==fun )
- area += bb.nOuts();
- return area;
- }
-
- private static int areaScore(int area, int cost) {
- return 1000 + (area<<10)/cost;
- }
-
- private static int splitCost(Node def, Node use) {
- return loopCost(def) + (use==null ? 0 : loopCost(use));
- }
-
- private static int loopCost(Node n) {
- return 1 << (n.cfg0().loopDepth()*3);
- }
-
- private static short biasColor( RegAlloc alloc, LRG lrg, short reg, RegMask mask ) {
- if( mask.size1() ) return reg;
- // Check chain of splits up the def-chain. Take first allocated
- // register, and if it's available in the mask, take it.
- Node def = lrg._splitDef, use = lrg._splitUse;
- int tidx=0, cnt=0;
-
- while( def != null || use != null ) {
- if( cnt++ > 10 ) break;
-
- if( def != null ) {
- short bias = biasColor( alloc, def, mask );
- if( bias >= 0 ) return bias; // Good bias
- if( bias == -2 ) def = null; // Kill this side, no more searching
- else if( (tidx=biasable(def)) == 0 ) def = null;
- }
-
- if( use != null ) {
- short bias = biasColor( alloc, use, mask );
- if( bias >= 0 ) return bias; // Good bias
- if( bias == -2 ) use = null; // Kill this side, no more searching
- else if( biasable(use)==0 ) use = null;
- }
-
- if( def != null ) {
- short bias = biasColorNeighbors( alloc, def, mask );
- if( bias >= 0 ) return bias;
- // Advance def side
- def = def.in(tidx);
- if( alloc.lrg(def)==null ) def=null;
- }
-
- if( use != null ) {
- short bias = biasColorNeighbors( alloc, use, mask );
- if( bias >= 0 ) return bias;
- use = use.out(0);
- if( biasable(use)==0 ) use=null;
- }
-
- }
- return mask.firstReg();
- }
-
- private static int biasable(Node split) {
- if( split instanceof SplitNode ) return 1; // Yes biasable, advance is slot 1
- if( split instanceof PhiNode phi ) return phi.region() instanceof LoopNode ? 2 : 1; // Prefer the backedge in loops
- if( !(split instanceof MachNode mach) ) return 0; // Not biasable
- return mach.twoAddress(); // Only biasable if 2-addr
- }
-
- // 3-way return:
- // - good bias reg, take it & exit
- // - this path is cutoff; do not search here anymore
- // - advance this side
- private static short biasColor( RegAlloc alloc, Node split, RegMask mask ) {
- short bias = alloc.lrg(split)._reg;
- if( bias != -1 ) {
- if( mask.test(bias) ) return bias; // Good bias
- else return -2; // Kill this side
- } else return -1; // Advance this side
- }
-
- // Check if we can match "split" color, or else trim "mask" to colors
- // "split" might get.
- private static short biasColorNeighbors( RegAlloc alloc, Node split, RegMask mask ) {
- LRG slrg = alloc.lrg(split);
- if( slrg._adj == null ) return -1; // No trimming
-
- // Can I limit my own choices to valid neighbor choices?
- for( LRG alrg : slrg._adj ) {
- short reg = alrg._reg;
- if( reg == -1 && alrg._mask.size1() )
- reg = alrg._mask.firstReg();
- if( reg != -1 ) {
- mask.clr(reg);
- if( mask.size1() )
- return mask.firstReg();
- }
- }
- return -1; // No obvious color choice, but mask got trimmed
- }
-
- private static void swap( LRG[] ary, int x, int y ) {
- LRG tmp = ary[x]; ary[x] = ary[y]; ary[y] = tmp;
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java
deleted file mode 100644
index db35d524..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java
+++ /dev/null
@@ -1,126 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.*;
-import java.util.BitSet;
-import java.util.IdentityHashMap;
-
-// Replace ideal nodes with machine nodes while preserving CFG/loop metadata.
-// Selection also owns target ABI masks and rebuilding the copied graph's uses.
-class InstSelect {
- private final CodeGen _code;
- private InstSelect(CodeGen code) { _code=code; }
-
- static CodeGen select(CodeGen code, String cpu, String callingConv, String base) {
- // Ideal evaluation schedules the ideal graph without a CPU port.
- if( cpu==null ) return code;
- return code.phase(CodeGen.Phase.Select, code._phase==CodeGen.Phase.LoopTree,
- () -> new InstSelect(code).run(cpu,callingConv,base));
- }
-
- private void run(String cpu, String callingConv, String base) {
- _code._callingConv = callingConv;
-
- // Look for CPU in fixed named place:
- // com.seaofnodes.simple.node.cpus."cpu"."cpu.class"
- String clzFile = base+"."+cpu+"."+cpu;
- try { _code._mach = Class.forName(clzFile).asSubclass(Machine.class).getDeclaredConstructor(CodeGen.class).newInstance(_code); }
- catch( Exception e ) { throw new RuntimeException(e); }
-
- _code._cCallingConv = _code._mach.cCallingConv(callingConv);
-
- // Build global copies of common register masks.
- long callerSave = _code._mach.callerSave();
- long cCallerSave= _code._mach.callerSave(_code._cCallingConv);
- long neverSave = _code._mach. neverSave();
- int maxReg = Math.min(64,_code._mach.regs().length);
- assert maxReg>=64 || (-1L << maxReg & callerSave)==0; // No stack slots in callerSave
- assert maxReg>=64 || (-1L << maxReg & cCallerSave)==0; // No stack slots in cCallerSave
- _code._callerSave = new RegMask(callerSave);
- _code._cCallerSave= new RegMask(cCallerSave);
-
- // Build a Return RegMask array. All returns have the following inputs:
- // 0 - ctrl
- // 1 - memory
- // 2 - varies with returning GPR,FPR
- // 3 - RPC
- // 4+- Caller save registers
- _code._retMasks = new RegMask[(maxReg-_code._callerSave.size()-Long.bitCount( neverSave ))+4];
- for( int reg=0, i=4; reg<maxReg; reg++ )
- if( !_code._callerSave.test(reg) && ((1L<<reg)&neverSave)==0 )
- _code._retMasks[i++] = new RegMask(reg);
- _code._rpcMask = new RegMask(_code._mach.rpc());
- _code._retMasks[3] = _code._rpcMask;
-
- // Convert to machine ops
- _code._uid = 1; // All new machine nodes reset numbering
- var map = new IdentityHashMap<Node,Node>();
- _instSelect( _code._stop, map );
- _code._stop = (StopNode)map.get(_code._stop );
- StartNode start = (StartNode)map.get(_code._start);
- _code._start = start==null ? new StartNode(_code._start) : start;
- _instOuts(_code._stop,_code.visit());
- _code._visit.clear();
-
- // Replace the CompUnit stop (and list of functions)
- // with hardware-specific ones
- for( CompUnit cu : _code._compunits.values() ) {
- cu._start= (StartCUNode)map.get(cu._start);
- cu._stop = (StopCUNode )map.get(cu._stop );
- }
- }
-
- private Node _instSelect( Node n, IdentityHashMap<Node,Node> map ) {
- if( n==null ) return null;
- Node x = map.get(n);
- if( x !=null ) return x; // Been there, done that
-
- // If n is a MachNode already, then its part of a multi-node expansion.
- // It does not need instruction selection (already selected!)
- // but it does need its inputs walked.
- if( n instanceof MachNode ) {
- for( int i=0; i < n.nIns(); i++ )
- n._inputs.set(i, _instSelect(n.in(i),map) );
- return n;
- }
-
- // Produce a machine node from n; map it to flag as done so stops cycles.
- if( n instanceof MemOpNode mem && mem.off() instanceof ExternOffsetNode off ) {
- // External fields replace base+offset with symbol+0. Keep the ideal
- // declaring type on the address for machine load/store width selection.
- TypeMemPtr ptr = (TypeMemPtr)mem.ptr()._type;
- n._inputs.set(2,new ExternNode(TypeMemPtr.make((byte)2,ptr._obj,true),off._extern));
- n._inputs.set(3,ConstantNode.raw(TypeInteger.ZERO));
- }
- map.put(n, x=_code._mach.instSelect(n) );
- // Carry loop-tree and pre-order info across the ideal->mach transition
- if( n instanceof CFGNode ncfg && x instanceof CFGNode xcfg ) {
- xcfg._ltree = ncfg._ltree;
- xcfg._pre = ncfg._pre;
- }
- // Walk machine op and replace inputs with mapped inputs
- for( int i=0; i < x.nIns(); i++ )
- x._inputs.set(i, _instSelect(x.in(i),map) );
- // Post selection action
- if( x instanceof MachNode mach ) {
- if( n instanceof ReturnNode ret )
- ((ReturnNode)mach)._fun = (FunNode)map.get(ret._fun);
- mach.postSelect(_code); // Post selection action
- }
-
- return x;
- }
-
- private void _instOuts( Node n, BitSet visit ) {
- if( visit.get(n._nid) ) return;
- visit.set(n._nid);
- for( Node in : n._inputs )
- if( in!=null ) {
- in._outputs.push(n);
- // CallNode special: outputs are partially ordered; CallEnd in slot 0
- if( in instanceof CallNode call && n instanceof CallEndNode cend && call.out(0) != cend )
- call._outputs.swap(0,call.nOuts()-1);
- _instOuts(in,visit);
- }
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Iter.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Iter.java
deleted file mode 100644
index 1ffd9737..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Iter.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.*;
-
-// Establish a pessimistic fixed point after parsing. Later Iter runs
-// reuse the worklist and types produced by the preceding phase.
-abstract class Iter {
- static void run(CodeGen code) {
- // Close parser-in-progress states. Seed a fresh pessimistic solve;
- // exposure sets start at FULL and can only shrink during Iter.
- code._start.walk(n -> {
- if( !(n instanceof ConstantNode) )
- n._type = iterEscapeReset(n._type);
- code.add(n);
- return null;
- });
- code._iter.iterate(code);
- // Expensive assert.
- assert !CodeGen.expensiveAssert() || Opto.fixedPointCheck(code);
- }
-
- private static Type iterEscapeReset(Type t) {
- if( t instanceof TypeMem mem )
- return mem._one ? mem : mem.makeFrom(XInt.FULL,XInt.FULL);
- if( t instanceof TypeTuple tt ) {
- TypeTuple rez = tt;
- for( int i=0; i<tt._types.length; i++ ) {
- Type tx = iterEscapeReset(tt._types[i]);
- if( tx != tt._types[i] ) rez = rez.makeFrom(i,tx);
- }
- return rez;
- }
- return t;
- }
-
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/LRG.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/LRG.java
deleted file mode 100644
index fdde8838..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/LRG.java
+++ /dev/null
@@ -1,240 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-import java.util.IdentityHashMap;
-
-/** A Live Range
- * <p>
- * A live range is a set of nodes and edges which must get the same register.
- * Live ranges form an interconnected web with almost no limits on their
- * shape. Live ranges also gather the set of register constraints from all
- * their parts.
- * <p>
- * Most of the fields in this class are for spill heuristics, but there are a
- * few key ones that define what a LRG is. LRGs have a unique dense integer
- * `_lrg` number which names the LRG. New `_lrg` numbers come from the
- * `RegAlloc._lrg_num` counter. LRGs can be unioned together -
- * (<a href="https://en.wikipedia.org/wiki/Disjoint-set_data_structure">this is the Union-Find algorithm</a>)
- * - and when this happens the lower numbered `_lrg` wins. Unioning only
- * happens during `BuildLRG` and happens because either a `Phi` is forcing all
- * its inputs and outputs into the same register, or because of a 2-address
- * instruction. LRGs have matching `union` and `find` calls, and a set
- * `_leader` field.
- */
-public class LRG {
-
- // Dense live range numbers
- final short _lrg;
-
- // U-F leader; null if leader
- LRG _leader;
-
- // Choosen register
- short _reg;
-
- // Count of single-register defs and uses
- short _1regDefCnt, _1regUseCnt;
- // Count of all defs, uses. Mostly interested 1 vs many
- boolean _multiDef, _multiUse;
-
- // A sample MachNode def in the live range
- public MachNode _machDef, _machUse;
- short _uidx; // _machUse input
-
- // Some splits used in biased coloring
- MachConcreteNode _splitDef, _splitUse;
-
- // All the self-conflicting defs for this live range
- IdentityHashMap<Node,String> _selfConflicts;
-
- // AND of all masks involved; null if none have been applied yet
- RegMask _mask;
-
- // Adjacent Live Range neighbors. Only valid during coloring
- Ary<LRG> _adj;
-
- // Function fully containing the live range; used to find the stack frame
- // layout, for spill op offsets.
- final public FunNode _fun;
-
- public int nadj() { return _adj==null ? 0 : _adj._len; }
-
- public RegMask useMask() { return _machUse==null ? null : _machUse.regmap(_uidx); }
-
- void addNeighbor(LRG lrg) {
- if( _adj==null ) _adj = new Ary<>(LRG.class);
- _adj.push(lrg);
- }
-
- // Remove and compress neighbor list; store ex-neighbor past
- // end for unwinding Simplify. True if exactly going low-degree
- boolean removeCompress( LRG lrg ) {
- _adj.swap(_adj.find(lrg),_adj._len-1);
- return _adj._len-- == _mask.size();
- }
-
- void reinsert( LRG lrg ) {
- assert _adj._es[_adj._len]==lrg;
- _adj._len++;
- }
-
- // More registers than neighbors
- boolean lowDegree() { return nadj() < _mask.size(); }
-
- LRG( short lrg, FunNode fun ) { _lrg = lrg; _reg = -1; _fun = fun; }
-
- boolean leader() { return _leader == null; }
-
- // Fast-path Find from the Union-Find algorithm
- LRG find() {
- if( _leader==null ) return this; // I am the leader
- if( _leader._leader==null ) // I point to the leader
- return _leader;
- return _rollup();
- }
- // Slow-path rollup of U-F
- private LRG _rollup() {
- LRG ldr = _leader._leader;
- // Roll-up
- while( ldr._leader!=null ) ldr = ldr._leader;
- LRG l2 = this;
- while( l2 != ldr ) {
- LRG l3 = l2._leader;
- l2._leader = ldr;
- l2 = l3;
- }
- return ldr;
- }
-
- // Union `this` and `lrg`, keeping the lower numbered _lrg.
- // Includes a number of fast-path cutouts.
- LRG union( LRG lrg ) {
- assert leader();
- if( lrg==null ) return this;
- lrg = lrg.find();
- if( lrg==this ) return this;
- return _lrg < lrg._lrg ? _union(lrg) : lrg._union(this);
- }
- // Union `this` and `lrg`, folding together all stats.
- private LRG _union( LRG lrg ) {
- assert _fun == lrg._fun;
- // Set U-F leader
- lrg._leader = this;
- // Fold together stats
- if( _machDef==null ) {
- _machDef = lrg._machDef;
- } else if( lrg._machDef!=null ) {
- if( _machDef != lrg._machDef ) _multiDef=true;
- if( _1regDefCnt==0 )
- _machDef = lrg._machDef;
- else if( _machDef==lrg._machDef )
- _1regDefCnt--;
- }
- _1regDefCnt += lrg._1regDefCnt;
- _multiDef |= lrg._multiDef;
-
- if( _machUse==null ) {
- _machUse = lrg._machUse;
- _uidx = lrg._uidx;
- } else if( lrg._machUse!=null ) {
- if( _machUse != lrg._machUse ) _multiUse=true;
- if( _1regUseCnt==0 ) {
- _machUse = lrg._machUse;
- _uidx = lrg._uidx;
- }
- else if( _machUse==lrg._machUse )
- _1regUseCnt--;
- }
- _1regUseCnt += lrg._1regUseCnt;
- _multiUse |= lrg._multiUse;
-
- // Fold deepest Split
- _splitDef = deepSplit(_splitDef,lrg._splitDef);
- _splitUse = deepSplit(_splitUse,lrg._splitUse);
-
- // Fold together masks
- RegMask mask = _mask.and(lrg._mask);
- if( mask==null )
- mask = _mask.copy().and(lrg._mask);
- _mask = mask;
- return this;
- }
-
- private static MachConcreteNode deepSplit( MachConcreteNode s0, MachConcreteNode s1 ) {
- return s0==null || (s1!=null && s0.cfg0().loopDepth() < s1.cfg0().loopDepth()) ? s1 : s0;
- }
-
- // Record any Mach def for spilling heuristics
- LRG machDef( MachNode def, boolean size1 ) {
- if( _machDef!=null && _machDef!=def )
- _multiDef = true;
- if( _machDef==null || size1 )
- _machDef = def;
- if( size1 )
- _1regDefCnt++;
- if( def instanceof SplitNode split )
- _splitDef = deepSplit(_splitDef,split);
- return this;
- }
-
- // Record any Mach use for spilling heuristics
- LRG machUse( MachNode use, short uidx, boolean size1 ) {
- if( _machUse!=null && _machUse!=use )
- _multiUse = true;
- if( _machUse==null || size1 )
- { _machUse = use; _uidx = uidx; }
- if( size1 )
- _1regUseCnt++;
- if( use instanceof SplitNode split )
- _splitUse = deepSplit(_splitUse,split);
- return this;
- }
-
- boolean hasSplit() { return _splitDef != null || _splitUse != null; }
- short size() { return _mask.size(); }
- boolean size1() { return _mask.size1(); }
-
-
- void selfConflict( Node def ) {
- if( _selfConflicts == null ) _selfConflicts = new IdentityHashMap<>();
- _selfConflicts.put(def,"");
- }
-
-
- // Record intersection of all register masks.
- // True if still has registers
- boolean and( RegMask mask ) {
- RegMask mask2 = mask.and(_mask);
- if( mask2==null )
- mask2 = _mask.copy().and(mask);
- _mask = mask2;
- return !_mask.isEmpty();
- }
- // Remove this singular register
- // True if still has registers
- boolean clr( int reg ) {
- if( _mask.clr(reg) ) return true;
- _mask = _mask.copy(); // Need a mutable copy
- return _mask.clr(reg);
- }
- // Subtract mask (AND complement)
- // True if still has registers
- boolean sub( RegMask mask ) {
- RegMask mask2 = _mask.sub(mask);
- if( mask2==null )
- mask2 = _mask.copy().sub(mask);
- _mask = mask2;
- return !_mask.isEmpty();
- }
-
- @Override public String toString() { return toString(new SB()).toString(); }
-
- public SB toString( SB sb ) {
- sb.p("V").p(_lrg);
- if( _mask!=null ) _mask.toString(sb);
- return sb;
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java
deleted file mode 100644
index 252b5bf0..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.type.TypeStruct;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-// In-memory linking
-public class LinkMem {
- final CodeGen _code;
- LinkMem( CodeGen code ) { _code = code; }
-
- public CodeGen link(Encoding enc) {
- // Patch external calls internally
- enc.patchGlobalRelocations();
-
- // Place constant pool after code, static data after constants
- int code = 0; // Code start
- int cpool = (enc._bits .size() + code + 15) & -16; // pad to 16
- int sdata = (enc._cpool.size() + cpool + 15) & -16;
-
-
- // Patch local ops, e.g. loading float constants from the constant pool
- for( Node op : enc._bigCons.keySet() ) {
- Encoding.Relo relo = enc._bigCons.get(op);
- int target = enc._data.address(enc._data.object(relo._t),cpool,sdata);
- ((RIPRelSize)relo._op).patch(enc, relo._opStart, enc.opLen(relo._op), target - relo._opStart);
- }
-
- enc._data.link(cpool,sdata);
- return _code;
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java
deleted file mode 100644
index b6b3a22c..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java
+++ /dev/null
@@ -1,261 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/LocalIds.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/LocalIds.java
deleted file mode 100644
index 462248dd..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/LocalIds.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import java.util.HashMap;
-import java.util.Arrays;
-
-// Dense IDs for this invocation. A class slot can be reserved before its file
-// is parsed; every later reference to that slot gets the same ID.
-public class LocalIds {
- public static final int RESERVED=2;
- private int _next=RESERVED;
- private final com.seaofnodes.simple.util.Ary<String> _owners=new com.seaofnodes.simple.util.Ary<>(String.class);
- private final HashMap<String,int[]> _slots=new HashMap<>();
-
- public int next(String owner) { return next(owner,-1); }
- public int next(String owner, int slot) {
- int[] ids=_slots.get(owner);
- if( ids==null ) ids=new int[]{RESERVED,1};
- if( slot==-1 ) slot=ids[0]++;
- else if( slot>=ids[0] ) ids[0]=slot+1;
- if( slot>=ids.length ) ids=Arrays.copyOf(ids,Math.max(slot+1,ids.length*2));
- if( ids[slot]==0 ) { ids[slot]=_next++; _owners.setX(ids[slot],owner); }
- _slots.put(owner,ids);
- return ids[slot];
- }
- public String owner(int id) { return _owners.atX(id); }
- public String symbol(int id) { return owner(id)+".$fun"+id; }
- public int nextInline() { return _next++; }
- public int next(int old) {
- int id=_next++;
- _owners.setX(id,owner(old));
- return id;
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Machine.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Machine.java
deleted file mode 100644
index 3eee8fc3..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Machine.java
+++ /dev/null
@@ -1,127 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.TypeFunPtr;
-import com.seaofnodes.simple.type.TypeInteger;
-
-abstract public class Machine {
- // Human readable machine name. Something like "x86-64" or "arm" or "risc5"
- public abstract String name();
-
- // Default opcode size for printing; 4 bytes for most 32-bit risc chips
- public int defaultOpSize() { return 4; }
-
- // Human-readable name for a register number, e.g. "RAX" or "R0"
- public abstract String[] regs();
-
- // Create a split op; any register to any register, including stack slots.
- // The FunNode holds the stack layout after RegAlloc, which in turn
- // dictates the stack slot (the frame offset) for stack splits.
- public abstract SplitNode split( LRG lrg, String kind, byte round );
-
- // List of caller-save registers, as a 64bit mask
- public abstract long callerSave();
- public long callerSave(String conv) { return callerSave(); }
-
- // List of never-save registers, e.g. RSP or a ZERO register if you have one
- public abstract long neverSave();
-
- // Call Argument Mask. Passed in the function signature and argument
- // number (2-based; 0 is for control and 1 for memory). Also passed in a 0
- // for the function itself, or for *outgoing* calls, the maximum stack slot
- // given to the incoming function arguments (stack slots reserved for
- // incoming arguments).
- public abstract RegMask callArgMask(TypeFunPtr tfp, int arg, int maxArgSlot);
- public RegMask callArgMask(TypeFunPtr tfp, int arg, int maxArgSlot, String conv) { return callArgMask(tfp,arg,maxArgSlot); }
-
- // Return register mask, based on signature (GPR vs FPR)
- public abstract RegMask retMask(TypeFunPtr tfp);
-
- // Return PC register
- public abstract int rpc();
-
- // Return a MachNode unconditional branch
- public abstract CFGNode jump();
-
- // Instruction select from ideal nodes
- public abstract Node instSelect( Node n );
-
- /** Stack Slot Numbering
-
-RA gives all registers a number, starting at 0. Stack slots continue this
-numbering going up from the last register number. Common register numbers are
-0-15 for GPRs, 16-31 for FPRs, 32 for flags, and stack slots starting at
-register#33 going up. These numbers are machine-specific, YMMV, etc; e.g. RPC
-is only in stack slot 0 on X86; other cpus start with the rpc in a register
-which may spill into any generic spill slot.
-
-For ease of reading and printing, stack slots start again at slot#0 - but
-during RA they are actually biased by CPU.MAX_REG.
-
-Stack Layout during Reg Alloc:
-
- Slot Value Description
- ---- ------- -----------
-MAXREG+ R+1 argR maxArgSlot(calleR tfp), can be 0
- ...
-MAXREG+ 1 arg0
- ---- -- Caller ------
-MAXREG+ 0 RPC
-MAXREG+ R+E+N PAD optional alignment
-
-MAXREG+ + Spills Caller Frame Space; N spills
-MAXREG+ R+E+1 Spills
-
-MAXREG+ R+E argE maxArgSlot(calleE tfp), can be 0
- ...
-MAXREG+ R+2 arg0
- ---- -- Callee -------
- ...
-
-Slots defined by maxArgSlot do not have to contain the actual passed argument;
-they can be "shadow slots". If they exist, they are property of the frame /
-FunNode where the TFP comes from.
-
-For a given calleR, there are generally many calleE's and they all share the
-low-range stack slots.
-
-Example:, Win64, main(int arg) { calloc(1,size); }
-
- Post-Warp: SP+56 shadow-main-arg
-Pre-alloc Post-Alloc SP+48 RPC
---- ------- ---------- -------------
-40 spill#3 SP+40 SP+40 spill#3
-39 spill#2 SP+32 ""
-38 spill#1 SP+24 ""
-37 spill#0 SP+16 "" spill#0
-36 shadow-calloc#size SP+ 8 SP+ 8 shadow-calloc#size
-35 shadow-calloc#1 SP+ 0 SP+ 0 shadow-calloc#1
---- --- FRAME BASE ---------- ---- FRAME BASE -------
-34 shadow-main-arg SP+56
-33 RPC SP+48
-
-
- */
-
-
- // Maximum stack slot (or 0) for the args in this TFP. This will include
- // shadow slots if defined in the ABI, even if all arguments are passed in
- // registers.
- public abstract short maxArgSlot(TypeFunPtr tfp);
- public short maxArgSlot(TypeFunPtr tfp, String conv) { return maxArgSlot(tfp); }
-
- // Calling convention used for external C symbols.
- public String cCallingConv(String callingConv) { return callingConv; }
-
- // Only the declared C result width describes the physical return register.
- // Simple functions already return canonical full-width integer values.
- protected static TypeInteger cReturn(ProjNode prj) {
- if( prj._idx==2 && prj.in(0) instanceof CallEndNode cend &&
- cend.call().fptr()._type instanceof TypeFunPtr tfp && tfp.isConstant() &&
- CodeGen.CODE.externFunc(tfp.fidx())!=null &&
- tfp.ret() instanceof TypeInteger ret && ret.log_size()<3 )
- return ret;
- return null;
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Opto.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Opto.java
deleted file mode 100644
index 4db277df..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/Opto.java
+++ /dev/null
@@ -1,317 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.Ary;
-
-import java.util.BitSet;
-
-// Optimistic Algorithm
-abstract public class Opto {
-
- // Optimistic analysis - extended SCCP
-
- // Start all Types at TOP, and propagate changes until stable. Unlike a
- // normal Sparse Conditional Constant Propagation, this version is
- // interprocedural, computing effectively CFA0 in linear time using the
- // normal SCCP algorithm.
-
- // This algo also builds a Call Graph; as Calls become reachable (CONTROL
- // not TOP nor XCONTROL), they "link" against the reaching function
- // pointers - adding edges from the Call to the Fun, and from the Return to
- // the CallEnd. This allows the normal algorithm to use the normal edge
- // propagation rules as function argument types stabilize. In the end, we
- // get a fairly precise set of Call Graph edges between CallNodes and
- // FunNodes.
- public static void opto(CodeGen code) {
- // Reset Types before running the worklist algorithm
- Ary<Type> oldTypes = new Ary<>(Type.class); // Used for asserts only
- resetToTop(code,oldTypes);
- unlinkStart(code);
-
- // OPTIMISTIC INTERPROCEDURAL SCCP
- sccp(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);
-
- if( !code._iter._work.isEmpty() )
- sccp(code, oldTypes);
-
- // After SCCP add everything changed to the worklist and see if
- // peepholes can make more progress.
- moveChangesToWorklist(code, oldTypes);
- }
-
- // 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. Initializers,
- // including private ones, are called by opaque CLInit
- // obligations whose Call edges are introduced only in Lower.
- if( !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();
- // An unresolved forward call can still carry the infinite, inferred
- // function set into Opto. There is no finite call graph to link yet;
- // type checking will report the unresolved reference afterwards. This
- // can yet be valid if the call is eventually dead.
- // `if( false ) frefCall(args);`
- if( XInt.isHigh(fidxs) ) return;
- for( int fidx = XInt.next(fidxs,0); fidx >=0; fidx = XInt.next(fidxs,fidx) ) {
- // unlinkStart deliberately leaves uncalled functions in a
- // temporarily dead limbo. A newly discovered call revives them;
- // do not use the cleanup lookup which removes dead linker entries.
- FunNode fun = code._link(fidx);
- // null here means an external function; i.e. this Call
- // calls to an outside library and all its arguments escape.
- if( fun != null && !call.linked(fun) ) {
- call.link( fun );
- code._iter.add(fun);
- code._iter.addAll(fun._outputs);
- }
- }
- }
-
- // Start added some FIDXs, the same functions now can be reached from start
- // and called by anybody. Add the CG edges.
- private static void linkStart( CodeGen code, TypeTuple tt ) {
- TypeMem tmem = (TypeMem)tt._types[1];
- int[] fidxs = tmem._escFs;
- if( XInt.isHigh(fidxs) ) {
- // A scalar BOTTOM can flow into memory while compiling an error
- // program. It exposes all public functions, not private fields.
- for( FunNode fun : code._linker )
- if( fun != null && code.publicFIDX(fun.sig().fidx()) )
- linkStart(code,fun);
- return;
- }
- for( int fidx = XInt.next(fidxs,0); fidx >=0; fidx = XInt.next(fidxs,fidx) ) {
- FunNode fun = code._linker.atX(fidx);
- if( fun==null ) assert code._externFunc.containsKey(fidx);
- else linkStart(code,fun);
- }
- }
-
- // Function is now reachable by unknown callers through Start. The
- // default inputs must therefore use conservative declared types.
- private static void linkStart( CodeGen code, FunNode fun ) {
- assert !fun.isDead();
- StartCUNode start = fun._compunit._start;
- if( !fun.unknownCallers() ) {
- assert !start.isDead();
- code.keepInCompUnit(fun);
- // Function is reachable by any *remote* caller who gets the pointer!
- fun.insertDef(1,start);
- code._iter.add(fun);
- for( Node p : fun._outputs )
- if( p instanceof ParmNode parm ) {
- Node defalt = parm._idx==1 ? start.proj(1) : ConstantNode.seed(parm._declaredType).peephole();
- parm.insertDef(1,defalt);
- code.add(parm);
- }
- }
- }
-
-
- // The core SCCP algorithm
- private static void sccp( CodeGen code, Ary<Type> oldTypes ) {
- // 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 && nval == Type.CONTROL && call.fptr()._type instanceof TypeFunPtr )
- linkCG(code,call.tfp(),call);
-
- // If an otherwise-dead function pointer escapes, any future linked
- // caller might find and call it. Force the function to be alive
- // and called by Start.
- if( n instanceof StartNode )
- linkStart(code,(TypeTuple)n._type);
-
- // Since n._type changed, visit all output neighbors
- code._iter.addAll(n._outputs);
- n.moveDepsToWorklist(code._iter);
- // Quadratic (expensive) small-step assert.
- assert !CodeGen.expensiveAssert(count) || worklistCheck(code);
- }
- }
-
- // Quadratic (expensive) fixed-point assert
- public static boolean fixedPointCheck(CodeGen code) {
- return fixedPointCheck(code,code._stop);
- }
- public static boolean fixedPointCheck(CodeGen code, Node root) {
- root.walk( x -> {
- Type xval = x.compute();
- assert xval == x._type;
- return null;
- });
- return true;
- }
-
- // Quadratic (expensive) small-step assert for SCCP, where changed nodes
- // may already be on the worklist.
- private static boolean worklistCheck(CodeGen code) {
- code._midAssert = true;
- Node root = code._start;
- root.walk( x -> {
- Type xval = x.compute();
- assert xval == x._type || (x._type.isa( xval ) && code._iter._work.on( x ))
- : "Non-monotonic or missed SCCP work: "+x.getClass().getSimpleName()+"#"+x._nid+" "+x+" old="+x._type+" new="+xval;
- return null;
- });
- code._midAssert = false;
- return true;
- }
-
- // 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);
- // A closed recursive numeric component can have no remaining
- // concrete evidence after SCCP, e.g. dead all-integer loops.
- // With no float evidence, settle to integer.
- if( mode == 0 )
- mode = 1;
- if( mode != 0 ) {
- // If both evidence, FP wins
- modeNode.setMode((byte)((mode&2)==2 ? 2 : 1));
- code.add(n);
- code.addAll(n.outs());
- }
- }
- 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 ) return 0;
- if( visited.get(n._nid) ) return 0;
- visited.set(n._nid);
-
- Type t = n._type;
- if( t instanceof TypeFloat ) return 2;
- if( t instanceof TypeInteger ) return 1;
- if( t!=Type.BOTTOM && !(t instanceof TypeTuple) )
- return 0; // Something unrelated, like a ptr
-
- int evidence = 0;
- assert !(n instanceof ConvertNode); // No test case
- switch( n ) {
- case ModeNode modeNode when modeNode.mode() != 0 -> { return modeNode.mode(); }
- case ModeNode modeNode -> {}
- case PhiNode phi -> {}
- case ReturnNode ret -> {}
- case ProjNode proj when proj.in(0) instanceof CallEndNode cend -> {
- for( int i=1; i<cend.nIns(); i++ )
- evidence |= numericEvidence(cend.in(i),visited);
- }
- default -> { return 0; }
- }
- for( int i=1; i<n.nIns(); i++ )
- evidence |= numericEvidence(n.in(i),visited);
-
- return evidence;
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
deleted file mode 100644
index 171d9923..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
+++ /dev/null
@@ -1,285 +0,0 @@
-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 {
-
- static void parse(CodeGen code, String src) {
- Parser.TYPES.clear();
- Parser.TYPES.putAll(Parser.INIT_TYPES);
- if( src != null ) {
- // No source file, just the source itself.
- parseSource(code,code._srcName==null ? "Test" : code._srcName,src);
- } else {
- // Path from module root to source file
- parsePath(code,code._srcName);
- }
- }
-
- // Worklist of symbols to search and files to parse
- private static final Ary<CompUnit> WORK = new Ary<>(CompUnit.class);
-
-
- // 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 {
- int idx=name.indexOf('/');
- if( idx>=0 ) return makeCUnit(code,makeCUnit(code,par,name.substring(0,idx)),name.substring(idx+1));
- String fname=par==null ? name : par._fname+"/"+name;
- CompUnit cu=code._compunits.get(fname);
- if( cu==null ) {
- String cname=par==null ? name : par._cname+"."+name;
- code._compunits.put(fname,cu=new CompUnit(par,fname,cname,name));
- }
- return cu;
- }
-
-
- // Parse a top-level string, used for e.g. testing
- static void parseSource( CodeGen code, String src ) {
- parseSource(code,"Test",src);
- }
-
- // Parse a top-level string with an explicit compilation-unit name.
- static void parseSource( CodeGen code, String fname, String src ) {
- CompUnit cunit = new CompUnit(fname,src);
- code._compunits.put(cunit._fname,cunit);
- parseAll(code,cunit);
- }
-
- // module_root/.../src.smp is source file name.
- static void parsePath(CodeGen code, String fname) {
- try {
- // Split and build the parent-path down to the source file.
- CompUnit cu = makeCUnit( code, null, fname );
- // Compile this CompUnit
- parseAll(code,cu);
- } catch( IOException ioe ) {
- throw new RuntimeException(ioe);
- }
- }
-
- private static void enqueue(CompUnit cu) {
- if( cu._par!=null ) enqueue(cu._par);
- if( cu._start==null && WORK.find(cu)==-1 ) WORK.add(cu);
- }
-
- private static void parseAll(CodeGen code, CompUnit cunit) {
- WORK.clear(); // Cleanup from prior tests
- // Prime the pump with the first CompUnit
- enqueue(cunit);
- // Work through all unparsed compilation units and parse
- while( !WORK.isEmpty() ) {
- CompUnit cu = WORK.remove(0);
- if( cu._src==null )
- throw new RuntimeException("Missing source file '"+cu._fname+".smp'");
- parseOne(code,cu);
- }
-
- // Many types will have been declared cyclically either internally or
- // across multiple compilation units.
-
- // Close over all recursive types, and upgrade TYPES
- Ary<TypeStruct> ary = new Ary<>(TypeStruct.class);
- for( Type t : Parser.TYPES.values() )
- if( t instanceof TypeStruct ts )
- ary.add(ts);
- TypeStruct[] tss = Type.closeOver(ary.asAry(),Parser.TYPES);
- for( TypeStruct ts : tss )
- Parser.TYPES.put(ts._name,ts);
- for( CompUnit cu : code._compunits.values() )
- cu._clz = (TypeStruct)Parser.TYPES.get(Parser.addClzPrefix(cu._cname));
-
- // Walk over all Nodes, and upgrade the internal constants to the
- // closed-over types.
- code._stop.walk( (Node n) -> {
- n.upgradeType(Parser.TYPES);
- if( n instanceof FRefNode fref ) {
- boolean alloc = fref._name.endsWith(".<new>");
- String clzName = alloc
- ? fref._name.substring(0,fref._name.length()-".<new>".length()).intern()
- : fref._name;
- TypeStruct clz = (TypeStruct)Parser.TYPES.get(clzName);
- if( clz == null )
- return null;
- Node con;
- if( alloc ) {
- Field ctor = clz.field("<new>");
- if( ctor == null || !(ctor._t instanceof TypeFunPtr tfp) ) {
- String name = clzName.startsWith(Parser.CLZ)
- ? clzName.substring(Parser.CLZ.length()) : clzName;
- int dot = name.lastIndexOf('.');
- throw Parser.error("Unknown struct type '"+(dot==-1 ? name : name.substring(dot+1))+"'",fref._loc);
- }
- FunNode fun = code.link(tfp);
- if( fun == null ) return null;
- con = new FunPtrNode(tfp,code._start,fun.ret()).peephole();
- } else {
- // Class pointer constant:
- TypeMemPtr clzptr = TypeMemPtr.make((byte)2,clz,true);
- con = code.con(clzptr);
- }
- // Replace and optimize
- fref.addDef(con);
- code.add(fref);
- }
- // Adds sort by NIDs, which just got shuffled
- if( n instanceof AddNode add )
- code.add(add);
- 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 ) {
- // 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);
- else
- assert false : "expected to find required type";
- } catch( IOException ioe ) {
- throw new RuntimeException(ioe);
- }
- }
- }
-
- private static CompUnit findRequiredType(CodeGen code, CompUnit cunit, String typeName) throws IOException {
- for( CompUnit cu = cunit; cu != null; cu = cu._par ) {
- String symbol = typeName.startsWith(cu._cname+".")
- ? typeName.substring(cu._cname.length()+1)
- : typeName;
- CompUnit xcunit = findCUnitModule(code,cu,symbol);
- if( xcunit != null )
- return xcunit;
- }
- return null;
- }
-
-
- // 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 null;
- } 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 ) {
- if( !symbol.equals("sys") || !CompUnit.source("sys").exists() ) return null;
- CompUnit sys=makeCUnit(code,null,"sys");
- enqueue(sys);
- return sys;
- }
-
- // 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 = CompUnit.source(fname);
- if( f.exists() ) {
- CompUnit sub = makeCUnit(code,cunit,pre);
- // enqueue also parses parents: their initialization must precede a child.
- 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 = CompUnit.source(fname);
- if( f.exists() ) {
- CompUnit sub = makeCUnit(code,cunit,symbol);
- // If not seen before or out of date, parse the new nested cunit
- if( sub._clz == null && WORK.find(sub) == -1 )
- enqueue(sub);
- return sub;
- }
-
- // Look up the scope
- return findCUnitModule( code, cunit._par, symbol );
- }
-
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java
deleted file mode 100644
index 48319006..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java
+++ /dev/null
@@ -1,14 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
deleted file mode 100644
index ac792988..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
+++ /dev/null
@@ -1,780 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.Utils;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.Type;
-import java.util.Arrays;
-import java.util.BitSet;
-import java.util.IdentityHashMap;
-
-/**
- * "Briggs/Chaitin/Click".
- * Graph coloring.
- * <p>
- * Every def and every use have a bit-set of allowed registers.
- * Fully general to bizarre chips.
- * Multiple outputs fully supported, e.g. add-with-carry or combined div/rem or "xor rax,rax" setting both rax and flags.
- * <p>
- * Intel 2-address accumulator-style ops fully supported.
- * Op-to-stack-spill supported.
- * All addressing modes supported.
- * Register pairs could be supported with some grief.
- * <p>
- * Splitting instead of spilling (simpler implementation).
- * Stack slots are "just another register" (tiny stack frames, simpler implementation).
- * Stack/unstack during coloring with a marvelous trick (simpler implementation).
- * <p>
- * Both bitset and adjacency list formats for the interference graph; one of
- * the few times it's faster to change data structures mid-flight rather than
- * just wrap one of the two.
- * <p>
- * Liveness computation and interference graph built in the same one pass (one
- * fewer passes per round of coloring).
- * <p>
- * Single-register def or use live ranges deny neighbors their required
- * register and thus do not interfere, vs interfering and denying the color
- * and coloring time. Basically all function calls do this, but many oddball
- * registers also, e.g. older div/mod/mul ops. (5x smaller IFG, the only
- * O(n^2) part of this operation).
- */
-
-public class RegAlloc {
- // Main Coloring Algorithm:
- // Repeat until colored:
- // Build Live Ranges (LRGs)
- // - Intersect allowed registers
- // If hard conflicts (LRGs with no allowed registers)
- // - Pre-split conflicted LRGs, and repeat
- // Build Interference Graph (IFG)
- // - Self conflicts split now
- // Color (remove trivial first then any until empty; reverse assign colors
- // - If color fails:
- // - - Split uncolorable LRGs
-
- // Top-level program graph structure
- final CodeGen _code;
-
- public int _spills, _spillScaled;
-
- private boolean _done;
-
- // -----------------------
- // Live ranges with self-conflicts or no allowed registers
- private final IdentityHashMap<LRG,String> _failed = new IdentityHashMap<>();
- // Loop Phis get one chance to color after only cold self-conflict splits.
- // If the same Phi self-conflicts again, split its hot def/backedge also.
- private final IdentityHashMap<PhiNode,String> _deferredLoopSelf = new IdentityHashMap<>();
- void fail( LRG lrg ) {
- assert lrg.leader();
- _failed.put(lrg,"");
- }
- boolean success() { return _failed.isEmpty(); }
- public boolean done() { return _done; }
-
-
- // -----------------------
- // Map from Nodes to Live Ranges
- private final IdentityHashMap<Node,LRG> _lrgs = new IdentityHashMap<>();
- short _lrg_num;
-
- // Define a new LRG, and assign n
- LRG newLRG( Node n, FunNode fun ) {
- LRG lrg = lrg(n);
- if( lrg!=null ) return lrg;
- lrg = new LRG(_lrg_num++, fun);
- LRG old = _lrgs.put(n,lrg); assert old==null;
- return lrg;
- }
-
- // LRG for n
- public LRG lrg( Node n ) {
- LRG lrg = _lrgs.get(n);
- if( lrg==null ) return null;
- LRG lrg2 = lrg.find();
- if( lrg != lrg2 )
- _lrgs.put(n,lrg2);
- return lrg2;
- }
-
- // Find LRG for n.in(idx), and also map n to it
- LRG lrg2( Node n, int idx ) {
- LRG lrg = lrg(n.in(idx));
- return union(lrg,n);
- }
-
- // Union any lrg for n with lrg and map to the union
- LRG union( LRG lrg, Node n ) {
- LRG lrgn = _lrgs.get(n);
- LRG lrg3 = lrg.union(lrgn);
- _lrgs.put(n,lrg3);
- return lrg3;
- }
-
- // Force all unified to roll up; collect live ranges
- LRG[] _LRGS;
- void unify() {
- Ary<LRG> lrgs = new Ary<>(LRG.class);
- for( Node n : _lrgs.keySet() ) {
- LRG lrg = lrg(n);
- lrgs.setX(lrg._lrg,lrg);
- }
- _LRGS = lrgs.asAry();
- // Remove unified lrgs from failed set also
- for( LRG lrg : _failed.keySet().toArray(new LRG[0]) ) {
- if( !lrg.leader() ) {
- LRG lrg2 = lrg.find();
- _failed.remove(lrg);
- _failed.put(lrg2,"");
- }
- }
- }
-
-
- // Diagnostic lookup: do not compress leaders or rewrite the node mapping.
- private LRG _lrg(Node n) {
- LRG lrg = _lrgs.get(n);
- if( lrg != null )
- while( lrg._leader != null ) lrg = lrg._leader;
- return lrg;
- }
-
- public short regnum( Node n ) {
- LRG lrg = _lrg(n);
- return lrg==null ? -1 : lrg._reg;
- }
-
- // Printable register number for node n
- String reg( Node n ) {
- LRG lrg = _lrg(n);
- return lrg==null ? null : reg(lrg);
- }
- String reg( LRG lrg ) {
- // No register yet, use LRG
- if( lrg._reg == -1 ) return "V"+lrg._lrg;
- // Chosen machine register unless stack-slot and past RA
- String[] regs = _code._mach.regs();
- if( lrg._reg < regs.length || _code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() || lrg._fun==null )
- return RegMask.reg(regs,lrg._reg);
- // Stack-slot past RA uses the frame layout logic
- return "[rsp+"+lrg._fun.computeStackOffset(_code,lrg._reg)+"]";
- }
-
- // -----------------------
- RegAlloc( CodeGen code ) { _code = code; }
-
- public void regAlloc() {
- // Insert callee-save registers
- String[] regs = _code._mach.regs();
- long neverSave= _code._mach.neverSave();
- for( CFGNode bb : _code._cfg )
- if( bb instanceof FunNode fun ) {
- ReturnNode ret = fun.ret();
- int len = Math.min(regs.length,64);
- for( int reg=0; reg<len; reg++ )
- if( !_code._callerSave.test(reg) && ((1L<<reg)&neverSave)==0 ) {
- ret.addDef(new CalleeSaveNode(fun,reg,regs[reg]));
- assert ret.regmap(ret.nIns()-1).firstReg()==reg;
- }
- }
- // Cache reg masks for New and Call
- for( CFGNode bb : _code._cfg ) {
- if( bb instanceof CallEndMach cend ) cend.cacheRegs(_code);
- for( Node n : bb._outputs )
- if( n instanceof NewNode nnn ) nnn.cacheRegs(_code);
- }
- // Expensive assert: useful when chasing cross-function allocator edges.
- //assert verifyFunctionLocalEdges();
-
- // Top driver: repeated rounds of coloring and splitting.
- byte round=0;
- while( !graphColor(round) ) {
- split(round);
- if( round >= 7 ) // Really expect to be done soon
- throw new IllegalStateException("Register allocation made no progress after eight rounds");
- round++;
- }
- postColor(); // Remove no-op spills
- _done = true;
- }
-
- boolean verifyFunctionLocalEdges() {
- for( CFGNode bb : _code._cfg )
- for( Node use : bb._outputs ) {
- FunNode useFun = funOf(use);
- for( int i=1; i<use.nIns(); i++ ) {
- Node def = use.in(i);
- if( def==null || def.isConst() || allowedCrossFunctionEdge(use,i,def) )
- continue;
- FunNode defFun = funOf(def);
- if( defFun != null && useFun != null && defFun != useFun )
- throw new AssertionError(crossFunctionMessage(def,defFun,use,useFun,i));
- }
- }
- return true;
- }
-
- private static String crossFunctionMessage(Node def, FunNode defFun, Node use, FunNode useFun, int idx) {
- StringBuilder sb = new StringBuilder("Cross-function value edge: ");
- sb.append(def.uniqueName()).append("(").append(defFun.label()).append(") -> ")
- .append(use.uniqueName()).append("(").append(useFun.label()).append(") input ").append(idx)
- .append(" def=").append(def).append(" use=").append(use).append("\n all uses:");
- for( Node out : def.outs() ) {
- FunNode outFun = funOf(out);
- sb.append("\n ").append(out.uniqueName()).append("(")
- .append(outFun==null ? "?" : outFun.label()).append(")");
- }
- return sb.toString();
- }
-
- private static boolean allowedCrossFunctionEdge(Node use, int idx, Node def) {
- // Function metadata/control edges are not value live ranges.
- if( use instanceof FunNode || use instanceof StopNode || use instanceof StopCUNode )
- return true;
- // Return-linked function pointers carry function identity/lifetime, not
- // an allocator value flowing from the callee body into this function.
- if( use instanceof FunPtrNode && def instanceof ReturnNode )
- return true;
- // Parm defaults and call-linked argument edges are the entry protocol;
- // BuildLRG also skips Parm input 1.
- if( use instanceof ParmNode )
- return true;
- // Linked CallEnd -> Return edges should be gone before RegAlloc, but
- // diagnose allocator value edges rather than call graph metadata here.
- if( use instanceof CallEndNode && def instanceof ReturnNode )
- return true;
- return false;
- }
-
- private static FunNode funOf(Node n) {
- if( n instanceof ParmNode parm ) return parm.fun();
- if( n instanceof ReturnNode ret ) return ret.fun();
- if( n instanceof FunNode fun ) return fun;
- if( n instanceof CFGNode cfg ) return cfg.fun();
- CFGNode cfg = n.cfg0();
- return cfg==null ? null : cfg.fun();
- }
-
- private boolean graphColor(byte round) {
- _failed.clear();
- _lrgs.clear();
- _lrg_num = 1;
- _LRGS=null;
-
- return
- // Build Live Ranges
- BuildLRG.run(round,this) && // if no hard register conflicts
- // Build Interference Graph
- IFG.build(round,this) && // If no self conflicts or uncolorable
- // Conservative coalesce copies
- Coalesce.coalesce(round,this) &&
- // Color attempt
- IFG.color(round,this); // If colorable
- }
-
- // -----------------------
- // Split conflicted live ranges.
- void split(byte round) {
-
- // In C2, all splits are handling in one pass over the program. Here,
- // in the name of clarity, we'll handle each failing live range
- // independently... which generally requires a full pass over the
- // program for each failing live range. i.e., might be a lot of
- // passes.
-
- // Sort, to avoid non-deterministic HashMap ordering
- LRG[] splits = _failed.keySet().toArray(new LRG[0]);
- Arrays.sort(splits, (x,y) -> x._lrg - y._lrg );
- for( LRG lrg : splits )
- split(round,lrg);
- }
-
- // Split this live range, top level heuristic
- boolean split( byte round, LRG lrg ) {
- assert lrg.leader(); // Already rolled up
-
- if( lrg._selfConflicts != null )
- return splitSelfConflict(round,lrg);
-
- // Register mask when empty; split around defs and uses with limited
- // register masks.
- if( lrg._mask.isEmpty() ) {
- if( lrg._1regDefCnt <= 1 &&
- lrg._1regUseCnt <= 1 &&
- (lrg._1regDefCnt + lrg._1regUseCnt) > 0 &&
- // A fixed-register clone with flexible uses must split at the uses.
- !(lrg._1regDefCnt==1 && lrg._machDef.isClone() && lrg._1regUseCnt==0) )
- return splitEmptyMaskSimple(round,lrg);
- // Repeated single-reg uses from a single def. Special for archs
- // with more fixed regs.
- if( !lrg._multiDef && lrg._1regDefCnt <= 1 && lrg._1regUseCnt > 2 )
- if( splitEmptyMaskByUse(round,lrg) )
- return true;
- }
- if( lrg._mask.isEmpty() && lrg._multiDef && lrg._1regUseCnt > 1 && lrg._1regDefCnt <= 1 ) {
- if( splitEmptyMaskSingleUses(round,lrg) )
- return true;
- }
-
- // Generic split-by-loop depth.
- return splitByLoop(round,lrg);
- }
-
- // Split live range with an empty mask. Specifically forces splits at
- // single-register defs or uses and not elsewhere.
- boolean splitEmptyMaskSimple( byte round, LRG lrg ) {
- // Live range has a single-def single-register, and/or a single-use
- // single-register. Split after the def and before the use. Does not
- // require a full pass.
-
- // Compute def nOuts *before* replacing those uses with a split
- int defNouts = ((Node)lrg._machDef).nOuts();
- // Split just after def
- if( lrg._1regDefCnt==1 && !lrg._machDef.isClone() )
- // Force must-split, even if a prior split same block because register
- // conflicts. Example:
- // alloc
- // V1/rax - forced by alloc
- // alloc
- // V2/rax - kills prior RAX
- // st4 [V1],len - No good, must split around
- insertAfterAndReplace( makeSplit("def/empty1",round,lrg), (Node)lrg._machDef, false/*true*/);
- // Split just before use
- if( lrg._machUse!=null && (lrg._1regUseCnt==1 || (lrg._1regDefCnt==1 && defNouts==1)) )
- insertBefore((Node)lrg._machUse,lrg._uidx,"use/empty1",round,lrg);
- return true;
- }
-
- // Popular single-def value: share a split among uses with compatible masks.
- boolean splitEmptyMaskByUse( byte round, LRG lrg ) {
- Node def = (Node)lrg._machDef;
- // Snapshot distinct users: rewriting an input changes def's output list,
- // and a call can use the same value in more than one argument.
- Ary<Node> uses = new Ary<>(Node.class);
- for( Node use : def._outputs )
- if( uses.find(use)==-1 ) uses.push(use);
-
- Ary<RegMask> rclass = new Ary<>(RegMask.class);
- int ncalls=0;
- for( Node use : uses )
- if( use instanceof MachNode mach ) {
- // Sharing copies across several calls usually loses to their kills.
- if( use instanceof CallNode && ++ncalls > 1 ) return false;
- for( int i=1; i<use.nIns(); i++ )
- if( use.in(i)==def && mach.regmap(i)!=null )
- putIntoRegClass(rclass,mach.regmap(i));
- }
- if( rclass._len <= 1 ) return false;
-
- for( RegMask rmask : rclass ) {
- Node split = makeSplit(def,"popular",round,lrg,rmask);
- split.insertAfter(def);
- if( split.nIns()>1 ) split.setDef(1,def);
- for( Node use : uses )
- if( use instanceof MachNode mach )
- for( int i=1; i<use.nIns(); i++ ) {
- RegMask mask = mach.regmap(i);
- if( use.in(i)==def && mask!=null && mask.overlap(rmask) ) {
- assert rmask.and(mask)==rmask;
- use.setDefOrdered(i,split);
- }
- }
- }
- return true;
- }
-
- // Empty-mask live range with several direct single-register uses, often
- // through a Phi with multiple defs. Split those uses directly instead of
- // doing a loop-depth split, which might keep isolating the wrong edge.
- boolean splitEmptyMaskSingleUses( byte round, LRG lrg ) {
- findAllLRG(lrg);
- if( _ns._len <= 5 )
- return false;
- boolean progress = false;
- for( Node n : _ns ) {
- if( !(n instanceof MachNode mach) ) continue;
- for( int i=1; i<n.nIns(); i++ ) {
- RegMask use = mach.regmap(i);
- if( use != null && use.size1() && lrg(n.in(i))==lrg ) {
- insertBefore(n,i,"use/emptyN",round,lrg);
- progress = true;
- }
- }
- }
- return progress;
- }
-
- // Narrowing preserves all earlier users of a class; new classes are disjoint.
- // Each use contains its first overlapping class, so one pass suffices.
- private static void putIntoRegClass( Ary<RegMask> rclass, RegMask rmask ) {
- for( int i=0; i<rclass._len; i++ ) {
- RegMask omask = rclass.at(i);
- if( !omask.overlap(rmask) ) continue;
- if( omask.and(rmask)!=omask )
- rclass.set(i,new RegMask(omask.copy().and(rmask)));
- return;
- }
- rclass.push(rmask);
- }
-
-
- // Self conflicts require Phis (or two-address).
- // Insert a split after every def.
- boolean splitSelfConflict( byte round, LRG lrg ) {
- // Sort conflict set, so we're deterministic
- Node[] conflicts = lrg._selfConflicts.keySet().toArray(new Node[0]);
- Arrays.sort(conflicts, (x,y) -> x._nid - y._nid );
-
- // First encounter with a loop Phi: try only cold splits this round.
- // Remember the Phi across rounds so a persistent self-conflict forces
- // the hot def/backedge splits on the next encounter.
- IdentityHashMap<PhiNode,String> deferred = new IdentityHashMap<>();
- for( Node def : conflicts ) {
- if( def instanceof PhiNode phi && phi.region() instanceof LoopNode )
- deferLoopSelf(phi,deferred);
- for( Node use : def._outputs )
- if( use instanceof PhiNode phi && phi.region() instanceof LoopNode &&
- phi._inputs.find(def)==2 )
- deferLoopSelf(phi,deferred);
- }
-
- // For all conflicts
- for( Node def : conflicts ) {
- assert lrg(def)==lrg; // Might be conflict use-side
- // Split before each use that extends the live range; i.e. is a
- // Phi or two-address
- for( int i=0; i<def._outputs._len; i++ ) {
- Node use = def.out(i);
- if( (use instanceof PhiNode phi &&
- !(deferred.containsKey(phi) && phi.region() instanceof LoopNode && phi._inputs.find(def)==2) &&
- !(phi.region() instanceof LoopNode loop && phi.in(2)==def && def.cfg0().idepth() > loop.idepth() ) ) ||
- (use instanceof MachNode mach && mach.twoAddress()!=0 && use.in(mach.twoAddress())==def) )
- insertBefore( use, use._inputs.find(def), "use/self/use",round,lrg );
- }
- // Split after the Phi and before its entry. Also isolate inputs
- // shared with another Phi: otherwise the shared input keeps the
- // conflicting live ranges joined, even after splitting the entry.
- if( def instanceof PhiNode phi && !(def instanceof ParmNode) ) {
- if( !deferred.containsKey(phi) ) {
- SplitNode split = makeSplit("def/self",round,lrg);
- insertAfterAndReplace(split,def,false);
- if( split.nOuts()==0 )
- split.killOrdered();
- }
- for( int i=1; i<(deferred.containsKey(phi) ? 2 : phi.nIns()); i++ )
- if( i==1 || sharedPhiInput(phi,i) )
- insertBefore(phi,i,"use/self/phi",round,lrg);
- }
- // Split before two-address ops which extend the live range
- if( def instanceof MachNode mach && mach.twoAddress()!= 0 )
- insertBefore(def,mach.twoAddress(),"use/self/two",round,lrg);
- }
- return true;
- }
-
- private void deferLoopSelf(PhiNode phi, IdentityHashMap<PhiNode,String> deferred) {
- if( !_deferredLoopSelf.containsKey(phi) ) {
- _deferredLoopSelf.put(phi,"");
- deferred.put(phi,"");
- }
- }
-
-
- private static boolean sharedPhiInput(PhiNode phi, int i) {
- for( Node use : phi.in(i)._outputs )
- if( use instanceof PhiNode && use!=phi ) return true;
- return false;
- }
-
- // Generic: split around the outermost loop with non-split def/uses. This
- // covers both self-conflicts (once we split deep enough) and register
- // pressure issues.
- boolean splitByLoop( byte round, LRG lrg ) {
- findAllLRG(lrg);
-
- // Find min loop depth for all non-split defs and uses.
- long ld = (-1L<<32) | 9999;
- for( Node n : _ns ) {
- if( lrg(n)==lrg ) // This is a LRG def
- ld = ldepth(ld,n,n.cfg0());
- // PhiNodes check all CFG inputs
- if( n instanceof PhiNode phi ) {
- for( int i=1; i<n.nIns(); i++ )
- if( phi.in(i)._type!=Type.TOP )
- ld = ldepth(ld, phi.in(i), phi.region().cfg(i));
- } else {
- // Others check uses
- for( int i=1; i<n.nIns(); i++ )
- if( lrgSame(n.in(i),lrg) ) // This is a LRG use
- ld = ldepth(ld,n,n.cfg0());
- }
- }
- int min = (int)ld;
- int max = (int)(ld>>32);
-
- boolean emptyMulti = lrg._mask.isEmpty() && lrg._multiDef && lrg._1regDefCnt <= 1 &&
- (lrg._1regUseCnt > 1 || _ns._len > 5);
- // Multi-def empty-mask conflicts need the hot/deep side isolated from
- // fixed register uses. Ordinary pressure splits keep the old shallow
- // boundary behavior.
- for( Node n : _ns ) {
- if( n instanceof SplitNode split && !split.isNonStackSplit() && min!=max ) continue; // Ordinary splits already reach stack storage.
- if( n.isDead() ) continue; // Some Cloneable went dead by other spill changes
- // If this is a 2-address commutable op (e.g. AddX86, MulX86) and the rhs has only a single user,
- // commute the inputs... which chops the LHS live ranges' upper bound to just the RHS.
- if( n instanceof MachNode mach && lrg(n)==lrg && mach.twoAddress()==1 && mach.commutes() && n.in(2).nOuts()==1 )
- n.swap12();
-
- if( lrg(n)==lrg && // This is a LRG def
- // At loop boundary, or splitting in inner loop
- (min==max || (emptyMulti ? n.cfg0().loopDepth() >= max : n.cfg0().loopDepth() <= min)) ) {
- // Cloneable constants will be cloned at uses, not after def
- if( !(n instanceof MachNode mach && mach.isClone()) &&
- // Single user is already a split adjacent
- !(n.nOuts()==1 && n.out(0) instanceof SplitNode split && sameBlockNoClobber(split) ) )
- // Split after def in min loop nest
- insertAfterAndReplace( makeSplit("def/loop",round,lrg), n,true);
- }
-
- // PhiNodes check all CFG inputs
- if( n instanceof PhiNode phi && !(n instanceof ParmNode)) {
- for( int i=1; i<n.nIns(); i++ )
- // No split in front of a split
- if( !(n.in(i) instanceof SplitNode) &&
- // splitting in inner loop or at loop border
- (min==max || (emptyMulti ? phi.region().cfg(i).loopDepth() >= max : phi.region().cfg(i).loopDepth() <= min)) &&
- // and not around the backedge of a loop (bad place to force a split, hard to remove)
- !(phi.region() instanceof LoopNode && i==2 && (phi.in(i) instanceof PhiNode pp && pp.region()==phi.region())) )
- // Split before phi-use in prior block
- insertBefore(phi,i, "use/loop/phi",round,lrg);
-
- } else {
- // Others check uses
- for( int i=1; i<n.nIns(); i++ ) {
- // This is a LRG use
- // splitting in inner loop or at loop border
- if( lrgSame( n.in( i ), lrg ) &&
- (min == max || (n.in(i) instanceof MachNode mach && mach.isClone()) ||
- (emptyMulti ? n.cfg0().loopDepth() >= max : n.cfg0().loopDepth() <= min)) )
- // Split before in this block
- insertBefore( n, i, "use/loop/use", round,lrg, false );
- }
- }
- }
- return true;
- }
-
- private boolean lrgSame(Node x, LRG lrg) {
- LRG xlrg = lrg(x);
- while( xlrg==null && x instanceof SplitNode )
- xlrg=lrg(x = x.in(1));
- return xlrg==lrg;
- }
-
- private static long ldepth( long ld, Node n, CFGNode cfg ) {
- // Ordinary splits already reach stack storage; register-only transfers do not.
- if( n instanceof SplitNode split && !split.isNonStackSplit() ) return ld;
- // Collect min/max loop depth
- int min = (int)ld;
- int max = (int)(ld>>32);
- int d = cfg.loopDepth();
- // if n will lower the min loop and is in the tail end of the loop
- // header, splitting "around" the loop will not help. Treat n as being
- // in the loop.
- if( d < min ) {
- if( cfg.uctrl() instanceof LoopNode loop && loop.entry()==cfg ) {
- for( int i=cfg.nOuts()-2; i>=0; i-- ) {
- Node out = cfg.out(i);
- if( n==out )
- { d = loop.loopDepth(); break; } // Treat n as being "in the loop"
- if( !((out instanceof MachNode mach && mach.isClone()) || out instanceof SplitNode ) )
- break; // Treat b as "normal", out of loop
- }
- }
- }
-
- // lower min, raise max, and re-fold
- min = Math.min(min,d);
- max = Math.max(max,d);
- return ((long)max<<32) | min;
- }
-
- // Find all members of a live range, both defs and uses
- private final Ary<Node> _ns = new Ary<>(Node.class);
- void findAllLRG( LRG lrg ) {
- _ns.clear();
- int wd = 0;
- if( lrg._machDef != null ) _ns.push((Node)lrg._machDef);
- if( lrg._machUse != null ) _ns.push((Node)lrg._machUse);
- while( wd < _ns._len ) {
- Node n = _ns.at(wd++);
- if( lrg(n)!=lrg ) continue;
- for( Node def : n._inputs )
- if( lrg(def)==lrg && _ns.find(def)== -1 )
- _ns.push(def);
- for( Node use : n._outputs )
- if( _ns.find(use)== -1 )
- _ns.push(use);
- }
- for( Node n : _ns ) assert !n.isDead();
- }
-
- void insertBefore(Node n, int i, String kind, byte round, LRG lrg, boolean skip) {
- Node def = n.in(i);
- // Phi splitting may visit the synthetic never-exit arm.
- if( def._type==Type.TOP ) return;
- // Effective block for use
- CFGNode cfg = n instanceof PhiNode phi ? phi.region().cfg(i) : n.cfg0();
- // Use-side RegMask, if available
- RegMask umask = n instanceof MachNode mach ? mach.regmap(i) : null;
- // Def is a split ?
- if( skip && def instanceof SplitNode ) {
- boolean singleReg = umask!=null && umask.size1();
- // Same block, multiple registers, split is only used by n,
- // assume this is good enough and do not split again.
- if( cfg==def.cfg0() && def.nOuts()==1 && !singleReg )
- return;
- }
- makeSplit(def,kind,round,lrg,umask).insertBefore(n, i);
- // Skip split-of-split same block
- if( skip && def instanceof SplitNode && cfg==def.cfg0() )
- n.in(i).setDefOrdered(1,def.in(1));
- }
- void insertBefore(Node n, int i, String kind, byte round, LRG lrg) {
- insertBefore(n,i,kind,round,lrg,true);
- }
-
- // Replace uses of `def` with `split`, and insert `split` immediately after
- // `def` in the basic block.
- public void insertAfterAndReplace( Node split, Node def, boolean must ) {
- split.insertAfter(def);
- if( split.nIns()>1 ) split.setDef(1,def);
- for( int j=def.nOuts()-1; j>=0; j-- ) {
- Node use = def.out(j);
- if( use==split ) continue; // Skip self
- // Can we avoid a split of a split? 'this' split is used by
- // another split in the same block.
- if( !must && use instanceof SplitNode split2 && sameBlockNoClobber(split2) )
- continue;
- int idx = use._inputs.find(def);
- use.setDefOrdered(idx,split);
- if( j < def.nOuts() ) j++;
- }
- }
-
- private Node makeSplit( Node def, String kind, byte round, LRG lrg, RegMask umask ) {
- // Clone simple constants if possible
- Node split = def instanceof MachNode mach && mach.isClone() && (umask==null || mach.outregmap().overlap(umask))
- ? mach.copy()
- : _code._mach.split(lrg,kind,round);
- _lrgs.put(split,lrg);
- return split;
- }
- private SplitNode makeSplit( String kind, byte round, LRG lrg ) {
- SplitNode split = _code._mach.split(lrg,kind,round);
- _lrgs.put(split,lrg);
- return split;
- }
-
-
- // -----------------------
- // POST PASS: Remove empty spills that biased-coloring made
- private void postColor() {
- int maxReg = -1;
- for( CFGNode bb : _code._cfg ) { // For all ops
- if( bb instanceof FunNode )
- maxReg = -1; // Reset for new function
- // Compute frame size, based on arguments and largest reg seen
- if( bb instanceof ReturnNode ret )
- ret.fun().computeFrameAdjust(_code,maxReg);
- // Raise frame size by max stack args passed, even if ignored
- if( bb instanceof CallEndMach cend )
- maxReg = Math.max(maxReg,cend._xslot);
-
- for( int j=0; j<bb.nOuts(); j++ ) {
- Node n = bb.out(j);
- if( lrg(n)!=null )
- maxReg = Math.max(maxReg,lrg(n)._reg+1);
- // Raise frame size by max stack args passed to New
- if( n instanceof NewNode nnn )
- maxReg = Math.max(maxReg,nnn._xslot);
-
- if( !(n instanceof SplitNode ) ) continue;
- int defreg = lrg(n )._reg;
- int usereg = lrg(n.in(1))._reg;
- // Attempt to bypass split
- if( defreg != usereg && splitBypass(bb,j,n,defreg) )
- usereg = lrg(n.in(1))._reg;
-
- // Same register, or the next instruction can use the source directly.
- if( defreg == usereg || splitForward(bb,j,n,usereg) ) {
- n.removeSplit();
- j--;
- continue;
- }
- // Split is kept; count against split score
- _spills++;
- _spillScaled += (1<<bb.loopDepth()*3);
- assert _spillScaled >= 0;
- }
- }
- }
-
- // A sole, adjacent use can read the source register directly. Nothing can
- // clobber it in between; only the operand mask and two-address tie remain.
- private boolean splitForward( CFGNode bb, int j, Node split, int reg ) {
- if( split.nOuts()!=1 || j+1==bb.nOuts() ) return false;
- Node use = split.out(0);
- if( use!=bb.out(j+1) || use instanceof PhiNode || use instanceof CFGNode ||
- !(use instanceof MachNode mach) ) return false;
- int i = use._inputs.find(split);
- RegMask mask = mach.regmap(i);
- return mask!=null && mask.test(reg) && mach.twoAddress()!=i;
- }
-
- private boolean splitBypass( CFGNode bb, int j, Node lo, int defreg ) {
- // Attempt to bypass split
- Node hi = lo.in(1);
- while( true ) {
- if( !(hi instanceof SplitNode && lo.cfg0() == hi.cfg0()) )
- return false;
- if( lrg(hi.in(1))._reg==defreg )
- break;
- hi = hi.in(1);
- }
- // Walk backward from the later copy, including instructions without results.
- for( int idx = j-1; bb.out(idx) != hi; idx--) {
- Node n = bb.out(idx);
- if( clobbers(n,defreg) )
- return false; // Clobbered
- }
- lo.setDefOrdered(1,hi.in(1));
- return true;
- }
-
-
- private boolean clobbers(Node n, int reg) {
- LRG lrg = lrg(n);
- return lrg!=null && (lrg._reg==reg || lrg._reg==-1 && lrg._mask.size1() && lrg._mask.firstReg()==reg) ||
- n instanceof MachNode mach && mach.killmap()!=null && mach.killmap().test(reg);
- }
-
- public boolean sameBlockNoClobber( SplitNode split ) {
- Node def = split.in(1);
- CFGNode cfg = def.cfg0();
- if( cfg != split.cfg0() ) return false; // Not same block
- // Get multinode head
- Node def0 = def instanceof ProjNode ? def.in(0) : def;
- int defreg = lrg(def)._reg;
- if( defreg == -1 ) defreg = lrg(def)._mask.firstReg();
- if( defreg == -1 ) return false; // no allowed registers -> clobbered
- for( int idx = cfg._outputs.find(split) -1; idx >= 0; idx-- ) {
- Node n = cfg.out(idx);
- if( n==def0 ) return true; // No clobbers
- if( lrg(n) == lrg(def) ) return false; // Self conflict
- if( clobbers(n,defreg) )
- return false; // Clobbered
- }
- throw Utils.TODO("Should not reach here");
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RegMask.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RegMask.java
deleted file mode 100644
index 9cb9ec16..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RegMask.java
+++ /dev/null
@@ -1,121 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-/** RegMask
- * A "register mask" - 1 bit set for each allowed register. In addition,
- * "stack slot" registers may be allowed, effectively making the set infinite.
- * <p>
- * For smaller and simpler machines it suffices to make such masks an i64 or
- * i128 (64- or 128-bit integers), and this presentation is by far the better
- * way to go... if all register allocations can fit in this bit limitation.
- * The allocator will need bits for stack-based parameters and for splits
- * which cannot get a register. For a 32-register machine like the X86, add 1
- * for flags - gives 33 registers. Using a Java `long` has 64 bits, leaving
- * 31 for spills and stack passing. This is adequate for nearly all
- * allocations; only the largest allocations will run this out. However, if
- * we move to a chip with 64 registers we'll immediately run out, and need at
- * least a 128 bit mask. Since you cannot *return* a 128 bit value directly
- * in Java, Simple will pick up a `RegMask` class object.
-*/
-public class RegMask {
-
- long _bits0, _bits1;
-
- private static final RegMask EMPTY = new RegMask(0L);
- public static final RegMask FULL = new RegMask(-1L);
-
- public RegMask(int bit) {
- if( bit < 64 ) _bits0 = 1L<<bit;
- else _bits1 = 1L<<(bit-64);
- }
- public RegMask(long bits ) { _bits0 = bits; }
- public RegMask(long bits0, long bits1 ) { _bits0 = bits0; _bits1 = bits1; }
- public RegMask(RegMask r) { _bits0 = r._bits0; _bits1 = r._bits1; }
-
- // AND, with copy-on-write if changing
- RegMask and( RegMask mask ) {
- if( mask==null ) return this;
- long bits0 = _bits0 & mask._bits0;
- long bits1 = _bits1 & mask._bits1;
- if( bits0==_bits0 && bits1==_bits1 ) return this;
- if( bits0==mask._bits0 && bits1==mask._bits1 ) return mask;
- if( bits0==0 && bits1==0 ) return EMPTY;
- // Update-in-place a mutable mask, or make a defensive copy
- return null;
- }
- RegMask sub( RegMask mask ) {
- if( mask==null ) return this;
- long bits0 = _bits0 & ~mask._bits0;
- long bits1 = _bits1 & ~mask._bits1;
- if( bits0==_bits0 && bits1==_bits1 ) return this;
- if( bits0==mask._bits0 && bits1==mask._bits1 ) return mask;
- if( bits0==0 && bits1==0 ) return EMPTY;
- // Update-in-place a mutable mask, or make a defensive copy
- return null;
- }
-
- // Fails if bit is set, because this is immutable
- public boolean clr( int reg ) {
- if( reg < 64 ) return ((_bits0 >> reg)&1)==0;
- if( reg < 128 ) return ((_bits1 >> reg)&1)==0;
- return true;
- }
-
-
- public short firstReg() {
- if( isEmpty() ) return -1;
- return (short)(_bits0 != 0
- ? Long.numberOfTrailingZeros(_bits0)
- : Long.numberOfTrailingZeros(_bits1)+64);
- }
- public short nextReg(short reg) {
- if( reg < 63 ) {
- long bits0 = _bits0 & (-1L << (reg+1));
- if( bits0!=0 ) return (short)Long.numberOfTrailingZeros(bits0);
- }
- long bits1 = reg < 64 ? _bits1 : reg < 127 ? _bits1 & (-1L << (reg-63)) : 0;
- return bits1==0 ? -1 : (short)(64+Long.numberOfTrailingZeros(bits1));
- }
-
- boolean isEmpty() { return _bits0==0 && _bits1==0; }
-
- public boolean test( int reg ) {
- return ((reg<64 ? (_bits0 >> reg) : (_bits1 >> (reg-64))) & 1) != 0;
- }
-
- // checks if the 2 masks have at least 1 bit in common
- public boolean overlap( RegMask mask ) { return (_bits0 & mask._bits0)!=0 || (_bits1 & mask._bits1)!=0; }
-
- // Defensive writable copy
- public RegMaskRW copy() { return new RegMaskRW( _bits0, _bits1 ); }
-
- // Has exactly 1 bit set
- public boolean size1() {
- return !isEmpty() && (((_bits0 & -_bits0)==_bits0 && _bits1==0) ||
- ((_bits1 & -_bits1)==_bits1 && _bits0==0));
- }
-
- // Cardinality
- public short size() { return (short)(Long.bitCount(_bits0)+Long.bitCount(_bits1)); }
-
- @Override public String toString() { return toString(new SB()).toString(); }
- public SB toString(SB sb) {
- Machine mach = CodeGen.CODE._mach;
- if( _bits0==0 && _bits1==0 ) return sb.p("[]");
- sb.p("[");
- String[] regs = mach.regs();
- for( int i=0; i<64; i++ )
- if( ((_bits0 >> i)&1) != 0 )
- sb.p(reg(regs,i)).p(",");
- for( int i=0; i<64; i++ )
- if( ((_bits1 >> i)&1) != 0 )
- sb.p(reg(regs,i+64)).p(",");
- return sb.unchar().p("]");
- }
-
- public static String reg(String[] regs, int i) {
- return i<regs.length ? regs[i] : "[stk#"+i+"]";
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java
deleted file mode 100644
index 1da9a3b1..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/RegMaskRW.java
+++ /dev/null
@@ -1,36 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/SourceError.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/SourceError.java
deleted file mode 100644
index f2f46146..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/SourceError.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import com.seaofnodes.simple.util.SB;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-/** Format one parser byte offset. Source is read and text built only on error. */
-public abstract class SourceError {
- public static String format(CompUnit cu, int pos, String message) {
- String file=cu.sourceFile();
- String name=file==null ? cu._fname+".smp" : file;
- byte[] source;
- try {
- source=cu._src!=null ? cu._src.getBytes(StandardCharsets.UTF_8)
- : file==null ? null : Files.readAllBytes(Path.of(file));
- } catch( IOException ex ) { source=null; }
- if( source==null || pos<0 || pos>source.length )
- return name+": error: "+message+" (source unavailable at byte offset "+pos+")";
-
- int line=1, start=0;
- for( int i=0; i<pos; i++ )
- if( source[i]=='\n' ) { line++; start=i+1; }
- int end=pos;
- while( end<source.length && source[end]!='\n' ) end++;
- if( end>start && source[end-1]=='\r' ) end--;
- int point=Math.min(pos,end);
- String before=new String(source,start,point-start,StandardCharsets.UTF_8);
- String text=new String(source,start,end-start,StandardCharsets.UTF_8);
- int column=before.codePointCount(0,before.length())+1;
- SB sb=new SB().p(name).p(':').p(line).p(':').p(column).p(": error: ").p(message).p("\n ");
- int width=0, caret=0;
- for( int i=0; i<text.length(); ) {
- if( i==before.length() ) caret=width;
- int ch=text.codePointAt(i);
- if( ch=='\t' ) {
- int spaces=8-(width%8);
- for( int j=0; j<spaces; j++ ) sb.p(' ');
- width+=spaces;
- } else { sb.p(new String(Character.toChars(ch))); width++; }
- i+=Character.charCount(ch);
- }
- if( before.length()==text.length() ) caret=width;
- sb.p("\n ");
- for( int i=0; i<caret; i++ ) sb.p('-');
- return sb.p('^').toString();
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/StaticData.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/StaticData.java
deleted file mode 100644
index 616753d4..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/StaticData.java
+++ /dev/null
@@ -1,177 +0,0 @@
-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.BAOS;
-import java.util.*;
-
-// Pool objects have identity independently of their initial field values.
-// References in their contents use the same symbols in ELF and emulator images.
-public final class StaticData {
- public static final class ObjectData {
- public final Type type;
- public final String symbol;
- public final boolean readOnly, external;
- public final int alignment, size;
- public int offset;
- ObjectData(Type t, String name, boolean ro, boolean ext) {
- type=t; symbol=name; readOnly=ro; external=ext;
- alignment=t.alignment();
- int align=1<<alignment;
- size=t instanceof TypeStruct ts ? (Math.max(1,ts.size())+align-1)&-align : align;
- }
- }
- public record Relocation(ObjectData object, int offset, int logSize, Type value) {}
-
- final Encoding enc;
- final LinkedHashMap<Object,ObjectData> objects = new LinkedHashMap<>();
- final ArrayList<Relocation> relocations = new ArrayList<>();
- final String prefix;
-
- public ArrayList<ObjectData> entries() {
- ArrayList<ObjectData> entries=new ArrayList<>(objects.values());
- entries.sort(Comparator.comparingInt((ObjectData obj) -> obj.alignment).reversed());
- return entries;
- }
-
- StaticData(Encoding enc) {
- this.enc=enc;
- prefix=enc._code.entryClinitName()+"$DATA$";
- }
-
- static boolean isClass(Type t) {
- return t instanceof TypeStruct ts && Parser.startsClzPrefix(ts._name);
- }
-
- static CompUnit owner(CodeGen code,TypeStruct ts) {
- return owner(code,ts._name.substring(Parser.CLZ.length()));
- }
- static CompUnit owner(CodeGen code,String name) {
- if(name==null) return null;
- CompUnit owner=null;
- for( CompUnit cu : code._compunits.values() )
- if( cu._cname!=null && (name.equals(cu._cname) || name.startsWith(cu._cname+".")) &&
- (owner==null || cu._cname.length()>owner._cname.length()) ) owner=cu;
- return owner;
- }
-
- ObjectData object(Type t) {
- Object key=isClass(t) ? ((TypeStruct)t)._name
- : t instanceof TypeFunPtr fp ? functionSymbol(fp) : t;
- ObjectData old=objects.get(key);
- if( old!=null ) return old;
- boolean clz=isClass(t);
- if( clz && Parser.TYPES.get(((TypeStruct)t)._name) instanceof TypeStruct canonical ) t=canonical;
- CompUnit owner=clz ? owner(enc._code,(TypeStruct)t)
- : t instanceof TypeFunPtr fp ? owner(enc._code,enc._code._fidxs.owner(fp.fidx())) : null;
- String symbol=clz ? ((TypeStruct)t)._name.substring(Parser.CLZ.length())+".$class"
- : t instanceof TypeFunPtr fp ? functionSymbol(fp) : prefix+objects.size();
- ObjectData obj=new ObjectData(t,symbol,!clz && (!(t instanceof TypeStruct ts) || ts.isConstant()),
- owner!=null && owner._src==null);
- objects.put(key,obj);
- // Register before following fields: class objects may refer to each other.
- if( !obj.external && t instanceof TypeStruct ts )
- for( Field f : ts._fields )
- if( !f._extern && f._t.isConstant() ) target(f._t);
- return obj;
- }
-
- private static boolean pointerConstant(TypeMemPtr ptr) {
- return ptr.notNull() && (isClass(ptr._obj) || (ptr._obj.isConstant() && (!ptr._obj.isAry() || ptr._obj.isConAry())));
- }
-
- private void target(Type t) {
- if( t instanceof TypeMemPtr ptr && pointerConstant(ptr) ) object(ptr._obj);
- if( t instanceof TypeFunPtr fp && fp.notNull() &&
- enc._code.link(fp)==null && !enc._code._externFunc.containsKey(fp.fidx()) )
- // A dead function still needs a unique non-null identity for comparisons.
- object(fp);
- }
-
- void write() {
- ArrayList<Encoding.Relo> refs=new ArrayList<>(enc._bigCons.values());
- refs.sort(Comparator.comparingInt(r -> r._op._nid));
- for( Encoding.Relo ref : refs ) object(ref._t);
- // Definitions belong to their compilation unit, even if every local use folded.
- ArrayList<CompUnit> units=new ArrayList<>(enc._code._compunits.values());
- units.sort(Comparator.comparing(cu -> cu._cname==null ? "" : cu._cname));
- for( CompUnit cu : units ) if( cu._src!=null && cu._clz!=null ) object(cu._clz);
- for( ObjectData obj : entries() ) {
- if( obj.external ) continue;
- BAOS bits=obj.readOnly ? enc._cpool : enc._sdata;
- Encoding.padN(1<<obj.alignment,bits);
- obj.offset=bits.size();
- if( obj.type instanceof TypeStruct ts ) writeStruct(obj,ts,bits);
- else Encoding.addN(obj.alignment,obj.type,bits);
- while( bits.size()<obj.offset+obj.size ) bits.write(0);
- }
- for( Encoding.Relo ref : refs ) {
- ObjectData obj=object(ref._t);
- ref._target=obj.offset;
- ref._opStart=enc.opStart(ref._op);
- }
- }
-
- private void writeStruct(ObjectData obj, TypeStruct ts, BAOS bits) {
- int[] layout=ts.layout();
- for( int idx : layout ) {
- if( idx==ts._fields.length ) continue;
- Field f=ts._fields[idx];
- if( f._extern ) continue;
- int off=ts.offset(idx);
- while( bits.size()<obj.offset+off ) bits.write(0);
- if( f._fname.equals("[]") ) ((TypeConAry)f._t).write(bits);
- else {
- Type value=f._t.isConstant() ? f._t : f._t.makeZero();
- int log=f._t.log_size();
- Encoding.addN(log,value,bits);
- if( (value instanceof TypeFunPtr fp && fp.notNull()) ||
- (value instanceof TypeMemPtr ptr && pointerConstant(ptr)) )
- relocations.add(new Relocation(obj,off,log,value));
- }
- }
- while( bits.size()<obj.offset+ts.size() ) bits.write(0);
- }
-
- String functionSymbol(TypeFunPtr fp) {
- String ext=enc._code._externFunc.get(fp.fidx());
- return ext!=null ? ext : enc._code._fidxs.symbol(fp.fidx());
- }
-
- String targetSymbol(Type value) {
- if( value instanceof TypeMemPtr ptr ) return object(ptr._obj).symbol;
- TypeFunPtr fp=(TypeFunPtr)value;
- return enc._code.link(fp)==null && !enc._code._externFunc.containsKey(fp.fidx())
- ? object(fp).symbol : functionSymbol(fp);
- }
-
- void link(int cpool, int sdata) {
- for( Relocation rel : relocations ) {
- long address;
- if( rel.value instanceof TypeMemPtr ptr ) address=address(object(ptr._obj),cpool,sdata);
- else {
- TypeFunPtr fp=(TypeFunPtr)rel.value;
- String ext=enc._code._externFunc.get(fp.fidx());
- FunNode fun=enc._code.link(fp);
- if( ext!=null ) address=enc.externalAddress(ext);
- else if( fun==null ) address=address(object(fp),cpool,sdata);
- else {
- if( !enc._code.owns(fun) ) throw new IllegalArgumentException("Unlinked function "+functionSymbol(fp));
- address=enc.opStart(fun);
- }
- }
- // Emulator native-call sentinels are signed 32-bit addresses.
- if( rel.logSize==2 && (address<Integer.MIN_VALUE || address>0xffff_ffffL) )
- throw new IllegalArgumentException("Function address outside 32 bits: "+address);
- BAOS bits=rel.object.readOnly ? enc._cpool : enc._sdata;
- int off=rel.object.offset+rel.offset;
- for( int i=0; i<(1<<rel.logSize); i++ ) bits.buf()[off+i]=(byte)(address>>>(8*i));
- }
- }
-
- int address(ObjectData obj,int cpool,int sdata) {
- if( obj.external ) throw new IllegalArgumentException("Unlinked class object "+obj.symbol);
- return obj.offset+(obj.readOnly ? cpool : sdata);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java b/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
deleted file mode 100644
index 9b4b0995..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
+++ /dev/null
@@ -1,57 +0,0 @@
-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.util.Ary;
-
-// Reject invalid optimized programs before lowering erases their source-level
-// obligations. Queue surviving CLInits during the same walk for Lower's Iter.
-abstract class TypeCheck {
- static void run(CodeGen code) {
- // Check for class-init cycles first; such a cycle can prevent other
- // things from resolving types. The dependency graph is temporary.
- String clzInitCycle = new ClassInitDependencies(code).check();
- if( clzInitCycle != null )
- throw Parser.error(clzInitCycle,null);
-
- Ary<Node> errs = new Ary<>(Node.class);
- code._stop.walk( n -> {
- if( n.err() != null )
- errs.add(n);
- if( n instanceof CLInitNode ) code.add(n);
- return null;
- });
-
- if( !errs.isEmpty() ) {
- // Prefer an originating error over a secondary error caused by a
- // BOTTOM input. If all errors are downstream-poisoned, retain
- // the old first-error behavior.
- Node min = null;
- for( Node n : errs )
- if( noBottomInputs(n) )
- { min = n; break; }
- if( min==null )
- min = errs.at(0);
- for( Node n : errs )
- if( n!=min &&
- noBottomInputs(n) &&
- ((n instanceof MemOpNode nmop &&
- min instanceof MemOpNode mmop &&
- nmop._loc.before(mmop._loc)) ||
- (n instanceof CFGNode ncfg &&
- min instanceof CFGNode mincfg &&
- ncfg.idepth() < mincfg.idepth())) )
- min = n;
- if( code._obs != null ) code._obs.error(min, min.err().getMessage());
- throw min.err();
- }
- }
-
- private static boolean noBottomInputs(Node n) {
- for( Node def : n._inputs )
- if( def!=null && !(def instanceof FldOffNode) && def._type==Type.BOTTOM )
- return false;
- return true;
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/AddNode.java
deleted file mode 100644
index 8835208b..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/AddNode.java
+++ /dev/null
@@ -1,179 +0,0 @@
-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 String label() { return "Add"; }
- @Override public String op() { return "+"; }
-
- @Override long doOp( long x, long y ) { return x + y; }
- @Override double doOp(double x, double y ) { return x + y; }
- @Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
- // Fold ranges like {0-1} + {2-3} into {2-4}.
- if( !overflow(x._min,y._min) &&
- !overflow(x._max,y._max) )
- return TypeInteger.make(x._min+y._min,x._max+y._max);
- return TypeInteger.BOT;
- }
-
- @Override
- public Node idealize () {
- // Settle the one-shot arithmetic mode before applying family-specific
- // algebraic identities.
- if( _mode==0 )
- return super.idealize();
-
- Node lhs = in(1);
- Node rhs = in(2);
- if( rhs instanceof AddNode add && add.err()!=null )
- return null;
- Type t2 = rhs._type;
-
- // Add of 0. We do not check for (0+x) because this will already
- // canonicalize to (x+0)
- if( t2 == TypeInteger.ZERO && lhs._type.isa(compute()) )
- return lhs;
-
- // Add of same to a multiply by 2
- if( lhs==rhs && _mode==1 )
- return new ShlNode(null,lhs,CodeGen.CODE.con(1));
-
- // Goal: a left-spine set of adds, with constants on the rhs (which then fold).
-
- // Move non-adds to RHS
- if( !(lhs instanceof AddNode) && rhs instanceof AddNode )
- return swap12();
-
- // x+(-y) becomes x-y
- if( rhs instanceof MinusNode minus )
- return new SubNode(lhs,minus.in(1),_mode);
-
- // Now we might see (add add non) or (add non non) or (add add add) but never (add non add)
-
- // Do we have x + (y + z) ?
- // Swap to (x + y) + z
- // Rotate (add add add) to remove the add on RHS
- if( rhs instanceof AddNode add )
- return copy(copy(lhs,add.in(1)).peephole(), add.in(2));
-
- // Now we might see (add add non) or (add non non) but never (add non add) nor (add add add)
- if( !(lhs instanceof AddNode) ) {
- // Rotate; look for (add (phi cons) con/(phi cons))
- if( spine_cmp(lhs,rhs,this) ) return swap12();
- Node n = phiCon(this,true);
- return n == null ? super.idealize() : n;
- }
-
- // Now we only see (add add non)
-
- // Dead data cycle; comes about from dead infinite loops. Do nothing,
- // the loop will peep as dead after a bit.
- if( lhs.nIns()>=2 && lhs.in(1) == lhs )
- return null;
-
- // Do we have (x + con1) + con2?
- // Replace with (x + (con1+con2) which then fold the constants
- // lhs.in(2) is con1 here
- // If lhs.in(2) is not a constant, we add ourselves as a dependency
- // because if it later became a constant then we could make this
- // transformation.
- if( addDep(lhs.in(2))._type.isConstant() && rhs._type.isConstant() )
- return copy(lhs.in(1),copy(lhs.in(2),rhs).peephole());
-
-
- // Do we have ((x + (phi cons)) + con) ?
- // Do we have ((x + (phi cons)) + (phi cons)) ?
- // Push constant up through the phi: x + (phi con0+con0 con1+con1...)
- Node phicon = phiCon(this,true);
- if( phicon!=null ) return phicon;
-
- // Now we sort along the spine via rotates, to gather similar things together.
-
- // Do we rotate (x + y) + z
- // into (x + z) + y ?
- if( spine_cmp(lhs.in(2),rhs,this) )
- return copy(copy(lhs.in(1),rhs).peephole(),lhs.in(2));
-
- return super.idealize();
- }
-
- // Rotation is only valid for associative ops, e.g. Add, Mul, And, Or, Xor.
- // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)|(op y con)) ?
- // Push constant up through the phi: x + (phi con0+con0 con1+con1...)
- static Node phiCon(Node op, boolean rotate) {
- Node lhs = op.in(1);
- Node rhs = op.in(2);
- if( rhs._type==TypeInteger.TOP ) return null;
- // LHS is either a Phi of constants, or another op with Phi of constants
- PhiNode lphi = pcon(lhs,op);
- if( rotate && lphi==null && lhs.nIns() > 2 ) {
- // Only valid to rotate constants if both are same associative ops
- if( lhs.getClass() != op.getClass() ) return null;
- lphi = pcon(lhs.in(2),op); // Will rotate with the Phi push
- }
- if( lphi==null ) return null;
- if( lphi.region().nIns() <=2 ) return null; // Phi is collapsing
-
- // RHS is a constant or a Phi of constants
- if( !(rhs instanceof ConstantNode) && pcon(rhs,op)==null )
- return null;
-
- // If both are Phis, must be same Region
- if( rhs instanceof PhiNode && lphi.in(0) != rhs.in(0) )
- return null;
-
- // Note that this is the exact reverse of Phi pulling a common op down
- // to reduce total op-count. We don't get in an endless push-up
- // push-down peephole cycle because the constants all fold first.
- Node[] ns = new Node[lphi.nIns()];
- ns[0] = lphi.in(0);
- // Push constant up through the phi: x + (phi con0+con0 con1+con1...)
- for( int i=1; i<ns.length; i++ )
- ns[i] = op.copy(lphi.in(i), rhs instanceof PhiNode ? rhs.in(i) : rhs).peephole();
- String label = lphi._label + (rhs instanceof PhiNode rphi ? rphi._label : "");
- Node phi = new PhiNode(label,ns).peephole();
- // Rotate needs another op, otherwise just the phi
- return lhs==lphi ? phi : op.copy(lhs.in(1),phi);
- }
-
- /**
- * Tests if the op is a phi and has all constant inputs.
- * If not, returns null.
- * If op is a phi, but its inputs are not all constants, then dep is added as
- * a dependency to the phi's non-const input, because if later the input turn into a constant
- * dep can make progress.
- */
- static PhiNode pcon(Node op, Node dep) {
- return op instanceof PhiNode phi && !(phi instanceof ParmNode) && phi.allCons(dep) ? phi : null;
- }
-
- // Compare two off-spine nodes and decide what order they should be in.
- // Do we rotate ((x + hi) + lo) into ((x + lo) + hi) ?
- // Generally constants always go right, then Phi-of-constants, then muls, then others.
- // Ties with in a category sort by node ID.
- // TRUE if swapping hi and lo.
- static boolean spine_cmp( Node hi, Node lo, Node dep ) {
- if( lo._type.isConstant() || lo instanceof ConFldOffNode ) return false;
- if( hi._type.isConstant() || hi instanceof ConFldOffNode ) return true ;
-
- if( lo instanceof PhiNode lphi && lphi.region()._type==Type.XCONTROL ) return false;
- if( hi instanceof PhiNode hphi && hphi.region()._type==Type.XCONTROL ) return false;
-
- if( lo instanceof PhiNode && lo.allCons(dep) ) return false;
- if( hi instanceof PhiNode && hi.allCons(dep) ) return true ;
-
- if( lo instanceof PhiNode && !(hi instanceof PhiNode) ) return true;
- if( hi instanceof PhiNode && !(lo instanceof PhiNode) ) return false;
-
- // Same category of "others"
- return lo._nid > hi._nid;
- }
-
- @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs,_mode); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/AndNode.java
deleted file mode 100644
index a737b703..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/AndNode.java
+++ /dev/null
@@ -1,49 +0,0 @@
-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 String op() { return "&"; }
- @Override long doOp( long x, long y ) { return x & y; }
- @Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
- // Sharpen allowed bits if either value is narrowed
- long mask = x.mask() & y.mask();
- return mask < 0 ? TypeInteger.BOT : TypeInteger.make(0,mask);
- }
-
- @Override
- public Node idealize() {
- Node lhs = in(1);
- Node rhs = in(2);
-
- Type t1 = lhs._type;
- Type t2 = rhs._type;
- if( !(t1 instanceof TypeInteger && t2 instanceof TypeInteger t2i) )
- return null; // Malformed, e.g. (17 & 3.14)
-
- // And of -1. We do not check for (-1&x) because this will already
- // canonicalize to (x&-1). We do not check for zero, because
- // the compute() call will return a zero already.
- if( t2i.isConstant() && t2i.value()==-1 && forceInt(lhs) )
- return lhs;
-
- // Move constants to RHS: con*arg becomes arg*con
- if( t1.isConstant() && !t2.isConstant() )
- return swap12();
-
- // Do we have ((x & (phi cons)) & con) ?
- // Do we have ((x & (phi cons)) & (phi cons)) ?
- // Push constant up through the phi: x & (phi con0&con0 con1&con1...)
- Node phicon = AddNode.phiCon(this,true);
- if( phicon!=null )
- return phicon;
-
- return super.idealize();
- }
- @Override Node copy(Node lhs, Node rhs) { return new AndNode(_loc,lhs,rhs); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ArithNode.java
deleted file mode 100644
index 094b927f..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ArithNode.java
+++ /dev/null
@@ -1,208 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.Utils;
-
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-
-public abstract class ArithNode extends Node implements ModeNode {
- // Source location for late reported errors
- 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; }
-
- abstract String op();
-
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- return p.open().n(in(1)).p(op()).n(in(2)).close();
- }
-
- abstract long doOp(long x, long y);
- abstract TypeInteger doOp(TypeInteger x, TypeInteger y);
- double doOp(double x, double y) { throw Utils.TODO(); }
-
- @Override public byte mode() { return _mode; }
- @Override public Node setMode(byte mode) {
- assert _mode==0 && (mode==1 || mode==2);
- unlock(); // _mode participates in GVN hash and equality
- _mode = mode;
- return this;
- }
- static byte mode( Type t1, Type t2 ) {
- if( t1 instanceof TypeFloat || t2 instanceof TypeFloat ) return 2;
- if( t1 instanceof TypeInteger && t2 instanceof TypeInteger ) return 1;
- if( t1 instanceof TypeMemPtr && t2 instanceof TypeMemPtr ) return 1;
- if( t1 instanceof TypeFunPtr && t2 instanceof TypeFunPtr ) return 1;
- return 0;
- }
-
- // Int-only ops, such as &, | and shifts, can collapse to one input. If
- // that input, or the live arms of a Phi input, are still mode-polymorphic
- // ops, settle them before returning the value so the replacement remains
- // at least as narrow as the int-only op.
- static boolean forceInt(Node n) {
- if( n instanceof ModeNode mode && mode.mode()==0 ) {
- mode.setMode((byte)1);
- n.setType(n.compute());
- // Mode changes remove n from GVN even if the caller's remaining
- // conditions prevent replacing the int-only operation by n.
- CodeGen.CODE.add(n);
- }
- return true;
- }
-
- // Check that a settled mode agrees with all data inputs. Takes the Node
- // instead of a varargs list both to avoid allocation and to keep this ready
- // to become shared mode-aware Node behavior later.
- static Parser.ParseException nodeErr(Node n, 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==null )
- return null;
- if( t.isHigh() )
- return null;
- // A weak input is not evidence of a bad mode while the graph is
- // still being built. Loop Phis commonly await their backedge.
- if( CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() && t == Type.BOTTOM )
- return null;
- bad = switch( t ) {
- case TypeInteger ti -> mode==1 ? null : t;
- case TypeMemPtr ti -> mode==1 ? null : t;
- case TypeFunPtr ti -> mode==1 ? null : t;
- case TypeFloat tf -> mode==2 ? null : t;
- default -> t;
- };
- if( bad != null ) break;
- }
- boolean unsupportedFloat = false;
- if( !allowFloat )
- for( int i=1; i<n.nIns(); i++ )
- if( n.in(i)._type instanceof TypeFloat ) {
- bad = n.in(i)._type;
- unsupportedFloat = true;
- break;
- }
- if( bad == null )
- return null;
- if( unsupportedFloat || n instanceof BoolNode )
- return Parser.error("Cannot '"+op+"' "+bad,loc);
- if( n.nIns()==3 )
- return Parser.error("Cannot "+n.in(1)._type+" "+op+" "+n.in(2)._type,loc);
- return Parser.error("Cannot '"+op+"' "+bad,loc);
- }
-
- // Arithmetic subclasses which have a floating-point interpretation override.
- boolean allowFloat() { return false; }
-
- // Generic arithmetic op math: high returns high; low returns low; 2
- // constants fold; only 2 non-constants call specialized math.
- @Override
- public final Type compute() {
- Type t1 = in(1)._type, t2 = in(2)._type;
- if( t1==null || t2==null ) return Type.TOP;
- if( t1.isHigh() || t2.isHigh() ) return Type.TOP;
- if( t1==Type.BOTTOM || t2==Type.BOTTOM ) return Type.BOTTOM;
- byte mode = _mode==0 ? mode(t1,t2) : _mode;
- if( mode==0 )
- return CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() ? Type.BOTTOM : Type.TOP;
- 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/chapter-modules/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/BoolNode.java
deleted file mode 100644
index dd0bc996..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/BoolNode.java
+++ /dev/null
@@ -1,188 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.Utils;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-import static com.seaofnodes.simple.type.TypeInteger.*;
-
-abstract public class BoolNode extends Node implements ModeNode {
-
- // Mode is 0 for unknown, 1 for int, 2 for flt
- byte _mode;
-
- public BoolNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
- public BoolNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs); _mode = mode; }
-
-
- abstract public String op(); // String opcode name
-
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- return p.open().n(in(1)).p(op()).n(in(2)).close();
- }
-
- @Override
- public TypeInteger compute() {
- Type t1 = in(1)._type;
- Type t2 = in(2)._type;
- if( t1==null || t2==null )
- return BOOL;
- // Exactly equals?
- if( t1.isHigh() || t2.isHigh() )
- return (TypeInteger)BOOL.dual();
- if( in(1)==in(2) )
- // LT fails, both EQ and LE succeed
- return this instanceof LT ? FALSE : TRUE;
- byte mode = _mode==0 ? ArithNode.mode(t1,t2) : _mode;
- if( mode==0 )
- return BOOL;
- // Handle integer compares
- if( mode==1 ) {
- if( t1 instanceof TypeInteger i1 &&
- t2 instanceof TypeInteger i2 )
- return doOp(i1,i2);
- return BOOL;
- }
- // Floating compare. Mixed inputs are converted in idealize().
- if( t1 instanceof TypeFloat f1 &&
- t2 instanceof TypeFloat f2 &&
- f1.isConstant() && f2.isConstant() )
- return doOp(f1.value(), f2.value()) ? TRUE : FALSE;
- return BOOL;
- }
-
- TypeInteger doOp(TypeInteger t1, TypeInteger t2) { throw Utils.TODO(); }
- boolean doOp(double lhs, double rhs) { throw Utils.TODO(); }
- public boolean isFloat() { assert _mode!=0; return _mode==2; }
- @Override public byte mode() { return _mode; }
- @Override public Node setMode(byte mode) {
- assert _mode==0 && (mode==1 || mode==2);
- unlock(); // _mode participates in GVN hash and equality
- _mode = mode;
- return this;
- }
-
- @Override
- public Node idealize() {
- // Compare of same
- if( in(1)==in(2) )
- return this instanceof LT ? CodeGen.CODE.ZERO : ConstantNode.make(TRUE);
- // Can we decide int vs flt?
- Type t1 = in(1)._type, t2 = in(2)._type;
- if( t1==null || t2==null )
- return null;
- if( _mode==0 ) {
- byte mode = ArithNode.mode(t1,t2);
- if( mode!=0 ) return setMode(mode).init();
- }
- if( _mode==2 ) {
- if( t1 instanceof TypeInteger ) return copy(new ToFloatNode(in(1)).peephole(),in(2));
- if( t2 instanceof TypeInteger ) return copy(in(1),new ToFloatNode(in(2)).peephole());
- }
-
-
- // Equals pushes constant to the right; 5==X becomes X==5.
- if( this instanceof EQ ) {
- if( !(in(2) instanceof ConstantNode) ) {
- // con==noncon becomes noncon==con
- if( in(1) instanceof ConstantNode || in(1)._nid > in(2)._nid )
- // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high
- return new EQ(in(2),in(1));
- }
- // Equals X==0 becomes a !X
- if( (in(2)._type == ZERO || in(2)._type == Type.NIL) )
- return new NotNode(in(1));
- }
-
- // Do we have ((phi cons) cmp con) ?
- // Do we have ((phi cons) cmp (phi cons)) ?
- // Push the compare up through the phi: (phi con0 cmp con, con1 cmp con...)
- Node phicon = AddNode.phiCon(this,this instanceof EQ);
- if( phicon!=null ) return phicon;
-
- return null;
- }
-
- @Override public boolean eq( Node n ) { return _mode==((BoolNode)n)._mode; }
- @Override public int hash() { return _mode; }
- @Override public Parser.ParseException err() {
- return ArithNode.nodeErr(this,null,op(),_mode,!(this instanceof ULT));
- }
-
- public static class EQ extends BoolNode {
- public EQ(Node lhs, Node rhs) { super(lhs,rhs); }
- public EQ(Node lhs, Node rhs, byte mode) { super(lhs,rhs, mode); }
-
- 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); }
-
- 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); }
-
- public String op() { return "<" ; }
- TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
- if( i1._max < i2._min ) return TRUE;
- if( i1._min >= i2._max ) return FALSE;
- return BOOL;
- }
- @Override boolean doOp(double lhs, double rhs) { return lhs<rhs; }
- Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs,_mode); }
- }
-
- public static class LE extends BoolNode {
- public LE(Node lhs, Node rhs) { super(lhs,rhs); }
- public LE(Node lhs, Node rhs, byte mode) { super(lhs,rhs,mode); }
-
- public String op() { return "<="; }
- TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
- if( i1._max <= i2._min ) return TRUE;
- if( i1._min > i2._max ) return FALSE;
- return BOOL;
- }
- @Override boolean doOp(double lhs, double rhs) { return lhs<=rhs; }
- Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs,_mode); }
- }
-
- // Unsigned less that, for range checks. Not directly user writable.
- public static class ULT extends BoolNode {
- public ULT(Node lhs, Node rhs) { super(lhs,rhs,(byte)1); }
- public ULT(Node lhs, Node rhs, byte mode) {
- super(lhs,rhs,(byte)1);
- assert mode==1;
- }
-
- public String op() { return "u<" ; }
- TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
- if( Long.compareUnsigned(i1._max,i2._min) < 0 ) return TRUE;
- if( Long.compareUnsigned(i1._min,i2._max) >= 0 ) return FALSE;
- return BOOL;
- }
- Node copy(Node lhs, Node rhs) { return new ULT(lhs,rhs); }
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
deleted file mode 100644
index df9e4fff..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
+++ /dev/null
@@ -1,265 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeMem;
-import com.seaofnodes.simple.util.Utils;
-
-import java.util.BitSet;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-
-/**
- * A Phi for bulk memory. It covers all aliases except {@link #_aliases};
- * those aliases must be represented by parallel precise MemPhis at the same
- * program slice.
- */
-public class BulkMemPhiNode extends PhiNode {
-
- private static final BitSet EMPTY = new BitSet();
-
- // Excluded Aliases; empty means NO exclusions.
- // Directly visible by design. Empty sets share EMPTY; splitAlias performs
- // copy-on-first-write and removes the node from GVN before mutation.
- public BitSet _aliases;
-
- public BulkMemPhiNode(String label, Node... inputs) {
- this(label,EMPTY,inputs);
- }
-
- private BulkMemPhiNode(String label, BitSet aliases, Node... inputs) {
- super(label, inputs);
- _aliases = aliases;
- }
-
- public BulkMemPhiNode(BulkMemPhiNode phi) {
- super(phi,phi._label);
- _aliases = phi._aliases.isEmpty() ? EMPTY : (BitSet)phi._aliases.clone();
- }
-
- public BulkMemPhiNode(RegionNode r, Node sample) {
- super(r,sample);
- _aliases = EMPTY;
- }
-
- public boolean isSplit(int alias) { return _aliases.get(alias); }
-
-
- @Override
- public Type compute() {
- Type t = super.compute();
- return t instanceof TypeMem mem ? mem :
- t==Type.BOTTOM ? TypeMem.BOT :
- TypeMem.TOP;
- }
-
- @Override
- public Node idealize() {
- Node progress = super.idealize();
- if( progress != null ) return progress;
- if( ((RegionNode)region()).inProgress() || region().nIns()<=1 )
- return null; // Input is in-progress
-
- // "Peek through" a MemMerge that covers this alias set on its default
- for( int i=1; i<nIns(); i++ )
- if( in(i) instanceof MemMergeNode mmm && canPeek(mmm) ) {
- setDef(i,mmm.in(1));
- return this;
- }
-
- // If any input or output uses or defines a specific alias we cover,
- // make a private MemPhi for it, and add it to the excluded list.
- for( int i=1; i<nIns(); i++ ) {
- int alias = inputAlias(in(i));
- if( alias!=0 )
- return slice(alias);
- }
-
- for( int i=0; i<nOuts(); i++ ) {
- int alias = outputAlias(out(i));
- if( alias!=0 )
- return slice(alias);
- }
-
- return null;
- }
-
- static public boolean checkMem(Node n, int alias) {
- for( Node m : n._inputs )
- // Bulk memory supplies this alias
- if( m instanceof BulkMemPhiNode bulk ) {
- if( bulk._aliases.get(alias) )
- return false;
- } else if( m instanceof MemMergeNode mmm &&
- mmm.in(1) instanceof BulkMemPhiNode bulk &&
- bulk._aliases.get(alias) &&
- (alias >= mmm.nIns() || mmm.in(alias)==null) )
- return false;
- return true;
- }
-
-
- // True if the MemMerge has no precise slice still covered by this Phi.
- private boolean canPeek(MemMergeNode mmm) {
- return missingAlias(mmm)==0;
- }
-
- // Return an alias required by an input but still covered by this Phi, or 0.
- private int inputAlias(Node n) {
- return switch(n) {
- case ParmNode p -> { assert p._idx==1; yield 0; }
- case ProjNode proj -> 0;
- case ConstantNode con -> {
- assert !(con._con instanceof TypeMem tmem && tmem._alias != 1);
- yield 0;
- }
- case MemMergeNode mmm -> missingAlias(mmm);
- case MemOpNode mem -> unsplit(mem._alias);
- case BulkMemPhiNode bulk -> missingAlias(bulk);
- default -> throw Utils.TODO("Should not reach here");
- };
- }
-
- // Return an alias required by a user but still covered by this Phi, or 0.
- private int outputAlias(Node use) {
- // This decision depends backwards on an immediate user's alias/set.
- // Normal graph-neighbor enqueueing flows from defs to uses, so retain
- // an explicit forward dependency to revisit this Phi when the user
- // sharpens.
- use = addDepForwards(use);
- return switch(use) {
- case ScopeNode scope -> 0;
- case ParmNode parm -> 0;
- case MemMergeNode mmm -> missingAlias(mmm);
- case BulkMemPhiNode bulk -> missingAlias(bulk);
- case MemOpNode mem -> unsplit(mem._alias);
- case MemPhiNode phi -> unsplit(phi._alias);
- case EscapeNode esc -> esc.pub()==this ? unsplit(esc.fld()._alias) : 0;
- default -> {
- assert use instanceof ReturnNode || use instanceof CallNode || use instanceof CLInitNode
- : "Unexpected bulk-memory user "+use.getClass().getSimpleName()+"#"+use._nid+": "+use;
- yield 0;
- }
- };
- }
-
- // Alias zero/one is bulk, and an already excluded precise alias cannot
- // trigger another split of this Phi.
- private int unsplit(int alias) {
- if( alias <= 1 ) return 0;
- assert !_aliases.get(alias);
- return alias;
- }
-
- // First alias excluded by the neighboring bulk Phi but not by this one.
- private int missingAlias(BulkMemPhiNode bulk) {
- for( int alias = bulk._aliases.nextSetBit(0); alias >= 0; alias = bulk._aliases.nextSetBit(alias+1) )
- if( !_aliases.get(alias) )
- return alias;
- return 0;
- }
-
- // First explicit MemMerge slice still covered by this Phi. A user's slice
- // may point to this Phi after a precise Phi folds; splitting is still valid.
- private int missingAlias( MemMergeNode mmm ) {
- for( int alias=2; alias<mmm.nIns(); alias++ )
- if( mmm.in(alias)!=null &&
- mmm.alias(alias)!=mmm.in(1) &&
- !_aliases.get(alias) )
- return alias;
- return 0;
- }
-
- // Slice out given alias
- private MemMergeNode slice( int alias ) {
- assert !_aliases.get(alias);
- // Inputs are the same; MemPhi will sharpen his own inputs. If a
- // parallel precise Phi already exists, reuse it instead of building a
- // duplicate for the same Region/alias pair.
- Node mem = precisePhi(alias);
- assert ((TypeMem)mem._type)._alias==alias;
-
- BitSet aliases = ((BitSet)_aliases.clone());
- aliases.set(alias);
- BulkMemPhiNode bphi = new BulkMemPhiNode(_label,aliases);
- for( int i=0; i<nIns(); i++ )
- bphi.addDef(in(i));
- // Installing bphi creates a new bulk user with a different exclusion
- // set. Queue the original memory inputs before bphi itself can peep
- // away and hide those neighbor relationships.
- for( int i=1; i<bphi.nIns(); i++ )
- CodeGen.CODE.add(bphi.in(i));
- // Do not peephole bphi here, as BulkMemPhi can recursively start a
- // second bulk rewrite before this one has finished. Let the worklist
- // discover any further splits
- Node bulk = bphi.init();
- CodeGen.CODE.add(bulk);
- CodeGen.CODE.add(mem);
- return aggregate(bulk,alias,mem);
- }
-
- // Build aggregate memory over a bulk Phi, preserving every precise slice
- // which the bulk has already excluded. The named alias is newly supplied
- // (or replaces its old slice).
- MemMergeNode aggregate(Node bulk, int alias, Node precise) {
- MemMergeNode mmm = new MemMergeNode(null,bulk);
- for( int old = _aliases.nextSetBit(0); old >= 0; old = _aliases.nextSetBit(old+1) )
- { assert old != alias; mmm.alias(old,precisePhi(old));}
- mmm.alias(alias,precise);
- assert checkMerge(mmm);
- mmm.init();
- // 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.
- TypeMem old = (TypeMem)_type;
- TypeMem mem = (TypeMem) mmm._type;
- mmm._type = TypeMem.make(mem._alias,mem._t,mem._one,mem._clz,
- mem._final,old._escFs,old._escAs);
- return mmm;
- }
-
- // Find or make the precise Phi parallel to this bulk Phi at the same merge.
- private Node precisePhi(int alias) {
- MemPhiNode mphi = _findPhi(alias);
- if( mphi!=null ) return mphi;
-
- mphi = new MemPhiNode("$"+alias,alias);
- mphi.addDef(region());
- // Due to cycles, must set before calling peephole
- mphi.setType(TypeMem.BOT.makeFrom(alias));
- for( int i=1; i<nIns(); i++ )
- mphi.addDef(CodeGen.CODE.add(preciseInput(in(i),alias)));
- return CodeGen.CODE.add(mphi);
- }
-
- MemPhiNode _findPhi(int alias) {
- for( Node use : region().outs() )
- if( use instanceof MemPhiNode mphi && mphi._alias==alias )
- return mphi;
- return null;
- }
-
- // Select an alias already split out of a predecessor's bulk memory.
- private Node preciseInput(Node mem, int alias) {
- if( mem instanceof MemMergeNode mmm )
- return mmm.alias(alias);
- if( mem instanceof BulkMemPhiNode bulk && bulk.isSplit(alias) )
- return bulk.precisePhi(alias);
- return mem;
- }
-
- // Every alias excluded by a bulk default must be supplied explicitly.
- static boolean checkMerge(MemMergeNode mmm) {
- if( !(mmm.in(1) instanceof BulkMemPhiNode bulk) ) return true;
- for( int alias = bulk._aliases.nextSetBit(0); alias >= 0; alias = bulk._aliases.nextSetBit(alias+1) )
- if( alias >= mmm.nIns() || mmm.in(alias)==null )
- return false;
- return true;
- }
-
- @Override public boolean eq(Node n) {
- return _aliases.equals(((BulkMemPhiNode)n)._aliases) && super.eq(n);
- }
-
- @Override int hash() { return _aliases.hashCode(); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/CFGNode.java
deleted file mode 100644
index 4f225fbe..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ /dev/null
@@ -1,303 +0,0 @@
-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;
- _idepthVersion = cfg._idepthVersion;
- _ltree = cfg._ltree;
- _pre = cfg._pre;
- }
- }
-
- public CFGNode cfg(int idx) { return (CFGNode)in(idx); }
-
- // Block head is Start, Region, CProj, but not e.g. If, Return, Stop
- public boolean blockHead() { return false; }
-
- // Get the one control following; error to call with more than one e.g. an
- // IfNode or other multi-way branch.
- public CFGNode uctrl() {
- CFGNode c = null;
- for( Node n : _outputs )
- if( n instanceof CFGNode cfg )
- { assert c==null; c = cfg; }
- return c;
- }
-
- // Used by the encoding / final BB layout
- public CFGNode uctrlSkipEmpty() {
- CFGNode x = this, y;
- while( x.nOuts() == 1 && (y=x.uctrl())!=null && !(y instanceof CallNode) ) // Skip empty blocks
- x = y;
- return x;
- }
-
- // ------------------------------------------------------------------------
- /**
- * Immediate dominator tree depth, used to approximate a real IDOM during
- * parsing where we do not have the whole program, and also peepholes
- * change the CFG incrementally.
- * <p>
- * See {@link <a href="https://en.wikipedia.org/wiki/Dominator_(graph_theory)">...</a>}
- */
- public char _idepth;
- public char _idepthVersion;
- public int idepth() { return validIDepth() ? _idepth : cacheIDepth(idom().idepth()+1); }
- // Zero depth means uncached. Check before narrowing so overflow cannot wrap.
- final int cacheIDepth(int depth) {
- assert 0 <= depth && depth <= Character.MAX_VALUE : "Dominator depth exceeds 65535";
- _idepthVersion = CodeGen.CODE.iDepthVersion();
- return _idepth = (char)depth;
- }
-
- final boolean validIDepth() { return _idepth!=0 && _idepthVersion==CodeGen.CODE.iDepthVersion(); }
-
-
- // Return the immediate dominator of this Node and compute dom tree depth.
- public CFGNode idom(Node dep) { return cfg(0); }
- public final CFGNode idom() { return idom(null); }
-
- // Return the LCA of two idoms
- public CFGNode domLCA(CFGNode rhs, Node dep) {
- if( rhs==null ) return this;
- CFGNode lhs = this;
- while( lhs != rhs ) {
- if( lhs==null || rhs==null ) return null;
- int comp = lhs.idepth() - rhs.idepth();
- if( comp >= 0 ) lhs = (dep==null ? lhs : dep.addDep(lhs)).idom();
- if( comp <= 0 ) rhs = (dep==null ? rhs : dep.addDep(rhs)).idom();
- }
- return lhs;
- }
-
- // Safe read of the control input of a data node.
- public static CFGNode safeCFG(Node n) {
- if( n==null ) return null;
- if( !(n.in(0) instanceof CFGNode cfg) ) return null;
- return cfg._safeCFG();
- }
- private CFGNode _safeCFG() {
- return switch(this) {
- case CLInitNode init when init.bypass() -> init.cfg0()._safeCFG();
- case CallNode call when call.cend()._folding -> call.cfg0()._safeCFG();
- case CallEndNode cend when cend._folding -> cend.cfg(1)._safeCFG();
- case FunNode fun when fun._folding -> fun.cfg(1)._safeCFG();
- case ReturnNode ret when ret.fun()._folding -> ret.cfg0()._safeCFG();
- default -> this;
- };
- }
-
- // True when this node dominates `sub` in the current idom tree.
- public boolean dominates(CFGNode sub, Node dep) {
- for( CFGNode cfg=sub; cfg!=null; cfg=cfg.idom() )
- if( cfg == this )
- return true;
- else if( dep != null ) dep.addDep(cfg);
- return false;
- }
-
- public static CFGNode deepest(CFGNode a, CFGNode b) {
- if( a == null ) return b;
- if( b == null ) return a;
- return b.idepth() > a.idepth() ? b : a;
- }
-
- public static CFGNode earlyCFG(Node n) {
- return earlyCFG(n,new IdentityHashMap<>(),new BitSet());
- }
-
- public static CFGNode earlyCFG(Node n, IdentityHashMap<Node,CFGNode> cache, BitSet active) {
- if( n == null ) return null;
- CFGNode cached = cache.get(n);
- if( cached != null ) return cached;
- if( active.get(n._nid) )
- return safeCFG(n);
- active.set(n._nid);
- CFGNode early = null;
- for( Node x : n._inputs )
- early = deepest(early,defCFG(x,cache,active));
- active.clear(n._nid);
- cache.put(n,early);
- return early;
- }
-
- private static CFGNode defCFG(Node n, IdentityHashMap<Node,CFGNode> cache, BitSet active) {
- if( n == null || n.isConst() ) return null; // no impact on early cfg
- return switch( n ) {
- case PhiNode phi -> phi.region();
- case CFGNode cfg -> cfg;
- case ProjNode projNode -> safeCFG( n );
- default -> earlyCFG( n, cache, active );
- };
- }
-
- public boolean sameFun(CFGNode that) {
- FunNode fun = fun();
- return fun != null && fun == that.fun();
- }
-
- // Find nearest enclosing FunNode
- public FunNode fun() {
- CFGNode cfg = this;
- while( !(cfg instanceof FunNode fun) ) {
- if( cfg==null ) return null;
- cfg = cfg.idom();
- }
- return fun;
- }
-
- // ------------------------------------------------------------------------
- // Loop nesting
- public LoopNode loop() { return _ltree._head; }
- public int loopDepth() { return _ltree==null ? 0 : _ltree.depth(); }
-
- public LoopTree _ltree;
- public int _pre; // Pre-order numbers for loop tree finding
- public static class LoopTree {
- public LoopTree _par;
- public final LoopNode _head;
- public int _depth;
- LoopTree(LoopNode head) { _head = head; }
- @Override public String toString() { return label(_head); }
- public int depth() {
- return _depth==0 ? (_par==null ? 0 : (_depth = _par.depth()+1)) : _depth;
- }
- }
-
- // ------------------------------------------------------------------------
- // Tag all CFG Nodes with their containing LoopNode; LoopNodes themselves
- // also refer to *their* containing LoopNode, as well as have their depth.
- // Start contains the function bodies at depth 0; real loops add depth.
- public void buildLoopTree( Ary<FunNode> funs, StopNode stop) {
- _ltree = stop._ltree = CodeGen.CODE.XCTRL._ltree = new LoopTree((StartNode)this);
- Ary<FunNode> work = new Ary<>(FunNode.class);
-
- BitSet post = 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 = start.cfg(1)._ltree = _ltree;
- post.set(start._nid);
- }
- };
- }
-
- for( int i=0; i<work._len; i++ )
- pre = work.at(i)._bltWalk(pre,work.at(i),stop, post, work);
- }
-
- int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post, Ary<FunNode> work ) {
- // Pre-walked?
- if( _pre!=0 ) return pre;
- _pre = pre++;
- // Seed the function's outer tree before walking its body. Synthetic
- // exits can be added before the post-order walk reaches the entry.
- if( this instanceof FunNode ) _ltree = stop._ltree;
- if( this instanceof ReturnNode ) {
- _ltree = fun._ltree;
- post.set(_nid);
- return pre; // Stop/Start links describe the outside world, not a loop.
- }
- // Pre-walk
- for( Node use : _outputs )
- if( use instanceof CFGNode usecfg ) {
- if( skip( usecfg ) ) {
- if( usecfg._pre==0 && usecfg instanceof FunNode fun2 ) work.add( fun2 );
- } else
- pre = usecfg._bltWalk( pre, use instanceof FunNode fuse ? fuse : fun, stop, post, work );
- }
-
- // Post-order work: find innermost loop
- LoopTree inner = null, ltree;
- for( Node use : _outputs ) {
- if( !(use instanceof CFGNode usecfg) ) continue;
- if( skip(usecfg) ) continue;
- if( usecfg._type == Type.XCONTROL || // Do not walk dead control
- usecfg._type == TypeTuple.IF_NEITHER ) // Nor dead IFs
- continue;
- // Child visited but not post-visited?
- if( !post.get(usecfg._nid) ) {
- // Must be a backedge to a LoopNode then
- ltree = usecfg._ltree = new LoopTree((LoopNode)usecfg);
- } else {
- // Take child's loop choice, which must exist
- ltree = usecfg._ltree;
- // If falling into a loop, use the target loop's parent instead
- if( ltree._head == usecfg ) {
- if( ltree._par == null )
- // This loop never had an If test choose to take its
- // exit, i.e. it is a no-exit infinite loop.
- ltree._par = ltree._head.forceExit(fun,stop)._ltree;
- ltree = ltree._par;
- }
- }
- // Sort inner loops. The decision point is some branch far removed
- // from either loop head OR either backedge so requires pre-order
- // numbers to figure out innermost.
- if( inner == null ) { inner = ltree; continue; }
- if( inner == ltree ) continue; // No change
- LoopTree outer = ltree._head._pre > inner._head._pre ? inner : ltree;
- inner = ltree._head._pre > inner._head._pre ? ltree : inner;
- inner._par = outer;
- }
- // Set selected loop
- if( inner!=null )
- _ltree = inner;
- // Tag as post-walked
- post.set(_nid);
- return pre;
- }
-
- public boolean skip(CFGNode usecfg) {
- // Only walk control users that are alive.
- // Do not 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/chapter-modules/src/main/java/com/seaofnodes/simple/node/CLInitNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/CLInitNode.java
deleted file mode 100644
index 0a0de3cf..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CLInitNode.java
+++ /dev/null
@@ -1,165 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.codegen.CompUnit;
-import com.seaofnodes.simple.type.*;
-import java.util.*;
-
-// Track class initializers - the code just after this point expects the named
-// class to be initialized. If this code path dies, then the dependency dies
-// also - dead code does not force class init.
-//
-public class CLInitNode extends CFGNode implements MultiNode {
- public Parser.Lexer _loc; // For error reporting
-
- // Inputs: control, whole memory, class pointer, loaded $init flag
- // Results: control, whole memory, known-true flag alias memory.
- // For field references, the ptr might be the dst clz ptr or not.
- public CLInitNode(Parser.Lexer loc, Node ctrl, Node mem, Node ptr, Node ready) {
- super(ctrl,mem,ptr,ready);
- _loc = loc;
- }
-
-
- public CLInitNode(CLInitNode init) {
- super(init);
- _loc = init._loc;
- }
-
- public Node mem() { return in(1); }
- public Node ptr() { return in(2); }
- public Node ready() { return in(3); }
-
- // The field/alias can be unresolved until forward references are loaded.
- public Field flag() {
- return ptr()._type instanceof TypeMemPtr tmp && Parser.startsClzPrefix(tmp._obj._name)
- ? tmp._obj.field("$init") : null;
- }
- @Override public String label() { return "CLInit"; }
-
- // Best effort dst compunit, based on ptr - or null
- public CompUnit cu() {
- if( !(ptr()._type instanceof TypeMemPtr ptr && Parser.startsClzPrefix(ptr._obj._name)) )
- return null;
- // Convert "class:sys.ary" into "sys/ary" and lookup CompUnit
- String fname = ptr._obj._name.substring(6).replace(".","/");
- return CodeGen.CODE._compunits.get(fname);
- }
-
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- FunNode fun = fun();
- CompUnit cu = cu();
- return p.p("CLInit ").p(fun._name).p(" -> ").p(cu==null ? "---" : cu._cname);
- }
-
- // Block head is Start, Region, CProj, but not e.g. If, Return, Stop
- public boolean blockHead() { return true; }
-
- private boolean instance() {
- return ptr()._type==Type.NIL ||
- (ptr()._type instanceof TypeMemPtr tmp && tmp._obj!=TypeStruct.BOT &&
- !tmp._obj.isHigh() && !tmp._obj._open && !tmp._obj._fref &&
- !Parser.startsClzPrefix(tmp._obj._name));
- }
-
- public boolean bypass() { return instance() || ready()._type==TypeInteger.TRUE; }
-
- @Override public Type compute() {
- if( in(0)._type.isHigh() || ptr()._type.isHigh() )
- return TypeTuple.make(Type.XCONTROL,TypeMem.TOP,TypeMem.TOP);
- if( instance() )
- return TypeTuple.make(in(0)._type,mem()._type,mem()._type);
- Field flag = flag();
- Type slice = flag==null ? TypeMem.BOT : TypeMem.make(flag._alias,TypeInteger.TRUE);
- // The initializer may write arbitrary public memory. Only its flag
- // has a guaranteed value on the normal continuation.
- Type bulk = ready()._type.isHigh() ? TypeMem.TOP :
- ready()._type==TypeInteger.TRUE ? mem()._type : TypeMem.BOT;
- return TypeTuple.make(in(0)._type,bulk,slice);
- }
-
- @Override public Node pcopy(int idx, Node dep) {
- dep.addDep(ptr());
- dep.addDep(ready());
- if( instance() ) return idx==0 ? in(0) : mem();
- if( ready()._type!=TypeInteger.TRUE ) return null;
- if( idx==0 ) return in(0);
- if( idx==1 ) return mem();
- // Already established on the incoming path; this is a memory fact,
- // not a new store to the class object.
- return CodeGen.CODE.con(((TypeTuple)_type)._types[2]);
- }
-
- @Override public Node idealize() {
- if( CodeGen.CODE._phase!=CodeGen.Phase.Lower || bypass() || in(0)._type.isHigh() ) return null;
- if( CodeGen.CODE._midAssert ) return this;
- // Until entry arguments become arrays, ordinary initializers receive zero.
- return expand(CodeGen.CODE.con(TypeInteger.ZERO));
- }
-
- // Called only after cycle/type checking. The initialized arm skips the
- // call; both arms rejoin with whole memory and a known-true flag slice.
- private Node expand(Node arg) {
- FunNode target = CodeGen.CODE.link(cu());
- assert target!=null && !target.isDead();
- keep();
- Node ctrl = in(0);
- Node memory = mem();
-
- // A child body may assume its parent has finished. Establish that
- // requirement first, then test the child's flag in the resulting memory.
- CompUnit parent = cu()._par;
- if( parent!=null ) {
- Node ptr = CodeGen.CODE.con(TypeMemPtr.make((byte)2,parent._clz,true));
- Field flag = parent._clz.field("$init");
- Node off = CodeGen.CODE.con(parent._clz.offset(parent._clz.find("$init")));
- Node parentReady = CodeGen.CODE.add(new LoadNode(_loc,"$init",flag._alias,ctrl,memory,ptr,off).init());
- CLInitNode init = CodeGen.CODE.add(new CLInitNode(_loc,ctrl,memory,ptr,parentReady).init());
- ctrl = CodeGen.CODE.add(new CProjNode(init,0,ScopeNode.CTRL).init());
- memory = CodeGen.CODE.add(new ProjNode(init,1,ScopeNode.MEM0).init());
- }
-
- Node childOff = CodeGen.CODE.con(cu()._clz.offset(cu()._clz.find("$init")));
- // Read the child's flag after establishing its parent's memory effects.
- Node ready = CodeGen.CODE.add(new LoadNode(_loc,"$init",flag()._alias,ctrl,memory,ptr(),childOff).init());
-
- IfNode iff = CodeGen.CODE.add(new IfNode(ctrl,ready).init());
- Node done = CodeGen.CODE.add(new CProjNode(iff,0,"True").init());
- Node cold = CodeGen.CODE.add(new CProjNode(iff,1,"False").init());
-
- Node fptr = new FunPtrNode(target.sig(),CodeGen.CODE._start,target.ret()).peephole();
- CallNode call = CodeGen.CODE.add(new CallNode(_loc,cold,memory,ptr(),arg,fptr).init());
- CallEndNode end = new CallEndNode(call,TypeRPC.constant(CodeGen.CODE.rpc(_loc._cu._cname)));
- // This is a lowered call, after interprocedural analysis. Do not add
- // new parameter flows to a body already optimized by SCCP.
- CodeGen.CODE.add(end.init());
- Node called = CodeGen.CODE.add(new CProjNode(end,0,ScopeNode.CTRL).init());
- Node after = CodeGen.CODE.add(new ProjNode(end,1,ScopeNode.MEM0).init());
- RegionNode region = CodeGen.CODE.add(new RegionNode(_loc,null,done,called).init());
- Node merged = CodeGen.CODE.add(new BulkMemPhiNode(ScopeNode.MEM0,region,memory,after).init());
-
- // Replace the individual outputs; no tuple-pack node or second
- // representation of CLInit is needed while the projections collapse.
- for( Node use : _outputs.asAry() ) {
- if( use==null ) continue; // Our temporary keep.
- assert use instanceof Proj;
- int idx = ((Proj)use).idx();
- use.subsume(idx==0 ? region : idx==1 ? merged : CodeGen.CODE.con(((TypeTuple)_type)._types[2]));
- }
- CodeGen.CODE.invalidateIDepthCaches();
- return unkeep(); // Iter removes this now-unused node.
- }
-
- // Resolve a precise flag slice even when the parser did not yet know the
- // class/alias and could only install the whole-memory projection.
- static Node slice(Node mem, int alias, Node dep) {
- if( !(mem instanceof ProjNode proj && proj._idx==1 && proj.in(0) instanceof CLInitNode init) )
- return mem;
- dep.addDep(init.ptr());
- Field flag = init.flag();
- if( flag==null || flag._alias!=alias ) return mem;
- return new ProjNode(init,2,Parser.memName(alias)).peephole();
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/CProjNode.java
deleted file mode 100644
index 2bd48d92..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CProjNode.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeTuple;
-
-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 String label() { return _label; }
-
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p(_label); }
-
- @Override public boolean blockHead() { return true; }
-
- public CFGNode ctrl() { return cfg(0); }
-
- @Override
- public Type compute() {
- Type t = ctrl()._type;
- return t instanceof TypeTuple tt ? tt._types[_idx] : (t.isHigh() ? Type.TOP : Type.BOTTOM);
- }
-
- @Override
- public Node idealize() {
- if( ctrl()._type instanceof TypeTuple tt ) {
- if( tt._types[_idx]==Type.XCONTROL )
- return CodeGen.CODE.XCTRL; // We are dead
- if( ctrl() instanceof IfNode && tt._types[1-_idx]==Type.XCONTROL ) // Only true for IfNodes
- return ctrl().in(0); // We become our input control
- }
-
- // Flip a negating if-test, to remove the not
- if( ctrl() instanceof IfNode iff && iff.pred() != null && addDep(iff.pred()) instanceof NotNode not )
- return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True");
-
- // Copy of some other input
- return ((MultiNode)ctrl()).pcopy(_idx,this);
- }
-
- // Only called during basic-block layout, inverts a T/F CProj
- public void invert() {
- _label = _idx == 0 ? "False" : "True";
- _idx = 1-_idx;
- }
-
- @Override
- public boolean eq( Node n ) { return _idx == ((CProjNode)n)._idx; }
-
- @Override
- int hash() { return _idx; }
-
- @Override public int idx() { return _idx; }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CallEndMach.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/CallEndMach.java
deleted file mode 100644
index 05707132..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CallEndMach.java
+++ /dev/null
@@ -1,38 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
deleted file mode 100644
index 4422265f..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
+++ /dev/null
@@ -1,321 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.type.*;
-
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-
-/**
- * CallEnd
- */
-public class CallEndNode extends CFGNode implements MultiNode {
-
- // When set true, this Call/CallEnd/Fun/Return is being trivially inlined
- boolean _folding;
- // +2: must clone, so await all cleanup but OK
- // +1: trivial now, so can inline without cloning and without waiting for cleanup
- // 0: take a look,
- // -1: somethings wrong, but might improve
- // -2: never and stop asking,
- private byte _inline;
- public final TypeRPC _rpc;
-
- public CallEndNode(CallNode call, TypeRPC rpc) {
- super(new Node[]{call});
- _rpc = rpc;
- }
- public CallEndNode(CallEndNode cend) { super(cend); _rpc = cend._rpc; }
- public CallEndNode(TypeRPC rpc) {
- super(new Node[0]);
- _rpc = TypeRPC.constant(CodeGen.CODE._rpcs.next(rpc.rpc()));
- }
- private CallEndNode(int nIns, TypeRPC rpc) {
- super(new Node[nIns]);
- _rpc = rpc;
- }
-
-
- @Override public boolean blockHead() { return true; }
-
- public CallNode call() { return (CallNode)in(0); }
- public boolean folding() { return _folding; }
-
- @Override public CFGNode idom(Node dep) {
- // Folding the idom is the one inlining Return
- return _folding ? cfg(1) : super.idom(dep);
- }
-
- @Override
- protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- p.p("cend( ");
- p.p( in(0) instanceof CallNode ? "Call, " : "----, ");
- for( int i=1; i<nIns()-1; i++ )
- p.n(in(i)).p(",");
- return p.unchar(',').unchar(' ').close();
- }
-
- @Override
- public Type compute() {
- 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());
- }
-
- // 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;
- // The signature bounds widening (e.g. an i32 result stays i32).
- // Use the callee signature, not FunPtrNode's live recursive range.
- Type bound = tfp.ret();
- if( !XInt.isHigh(tfp.fidxs()) ) {
- bound = Type.TOP;
- for( int fidx = XInt.next(tfp.fidxs(),0); fidx >= 0; fidx = XInt.next(tfp.fidxs(),fidx) ) {
- if( CodeGen.CODE.externFunc(fidx)!=null )
- externs++;
- FunNode fun = CodeGen.CODE._link(fidx);
- bound = bound.meet(fun==null ? tfp.ret() : addDep(fun).sig().ret());
- }
- }
- 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 during Opto, assume call won't be called and thus won't return anything.
- // Lowered calls deliberately have no links into optimized bodies.
- boolean opto = CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() &&
- CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Lower.ordinal();
- Type tmem = opto ? TypeMem.TOP : TypeMem.BOT;
- Type ret = opto ? Type.TOP : tfp.ret();
- // Use the same widening before and after linking a callee.
- return TypeTuple.make(Type.CONTROL,tmem,widenResult(ret,bound));
- }
-
- // A linked function for every concrete function
- TypeTuple state = hasExtern
- ? TypeTuple.make(Type.CONTROL,call.exposed(this),tfp.ret())
- : TypeTuple.STATE.dual();
- for( int i=1; i<nIns(); i++ )
- state = (TypeTuple)state.meet(in(i)._type);
- // A reachable call whose presently linked exits are all unreachable
- // keeps a conservative continuation. In particular, do not let a
- // transient no-return result tear down the caller before/during SCCP.
- // If the call remains out-of-line, its continuation can conservatively
- // contribute class-init dependencies even though it never executes.
- if( state.ctl()==Type.XCONTROL )
- return TypeTuple.make(Type.CONTROL,TypeMem.TOP,Type.TOP);
- // Join before widening: FunPtrNode follows the live Return type, so
- // joining it afterward would undo the widening.
- return state.makeFrom(2,widenResult(state.ret().join(tfp.ret()),bound));
- }
-
- private static Type widenResult(Type ret, Type bound) {
- // Recursion is a loop in disguise. For f(x-1)+1, the result
- // flows Return -> CallEnd -> Add -> Phi -> Return, without
- // crossing ParmNode.compute's widening point. Widen here too,
- // using the loop rule in PhiNode.compute. Keep constants and
- // above-center ranges narrow.
- if( ret instanceof TypeInteger ti && !ti.isHigh() && !ti.isConstant() )
- ret = ti.same_but_slightly_wider_than().join(bound);
- return ret;
- }
-
- @Override
- public Node idealize() {
- return null;
- }
-
- public byte maybeInline() {
- byte inline = inlineCandidate();
- if( CodeGen.CODE._midAssert )
- return inline;
- _inline = inline;
- if( _inline > 0 )
- doInline();
-
- return _inline;
- }
-
- // Check if candidate right now, sets no flags
- private byte inlineCandidate() {
- if( _inline < -2 ) return -2; // Never
- if( _folding ) return -2; // No, and will collapse anyways
- if( !(in(0) instanceof CallNode call) ) return -2; // malformed because mid-death
-
- if( nIns()!=2 ) return -1; // Not exactly 1 link, but can change
-
- // Have exactly 1 linked function
- ReturnNode ret = (ReturnNode)in(1);
- FunNode fun = ret.fun();
-
- // Preserve initializer boundaries until post-Opto cycle validation.
- if( fun.isClz() ) return -2;
-
- // Heuristic forced inlining turned off via name
- if( fun._name != null &&
- fun._name.endsWith("_noInline") )
- return -2; // Never, stop asking
-
- // Disallow self-recursive inlining (loop unrolling by another name).
- for( int i=1; i < fun.nIns(); i++ )
- if( fun.cfg(i).fun()==fun ) // Check for linked call input inside "fun"
- { addDep(fun); return -1; } // No, but can lose the recursive edge
-
- // Things that might improve, but not now a candidate
- Node fptr = call.fptr();
- // Constant Function is being called, and its not-null
- if( !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) )
- { addDep(fptr); return -1; }
-
- // Arguments are correct
- if( call.nargs()!=tfp.nargs() || !goodArgs(call,tfp) )
- { addDep(fptr); return -1; }
-
- // Encouraged inlining because small size and constructor.
- int maxSize = fun.inlineLimit();
- // A large parse can leave only a tiny initializer. Recognize that
- // and await cleanup to shrink the body.
- if( fun._approxUIDs >= maxSize ) {
- if( !smallBody(fun) ) return -1;
- } else if( fun.body().cardinality() >= maxSize ) // Expensive size check
- return -1; // Try later (if cleanup makes the real body small enough)
-
- // Can be cloned but not trivial
- if( fun.nIns() > 2 ) { addDep(fun); return 2; }
-
- // A trivial candidate right now
- return 1;
- }
-
- // Bounded, conservative recognition of straight-line initializer boilerplate.
- // Walk uses from the entry and stop at Return, never following callers or
- // constant inputs. This also avoids scanning sparse MemMerge input arrays.
- // Watch only this small prefix, including an unsupported node that may fold.
- private static final Node[] TINYBODY = new Node[15]; // Tiny worklist
- private boolean smallBody(FunNode fun) {
- ReturnNode ret = fun.ret();
- for( Node in : ret._inputs )
- if( in!=null ) addDep(in);
- if( ret.ctrl()!=fun ) return false;
- TINYBODY[0] = fun;
- int len=1;
- for( int i=0; i<len; i++ ) {
- Node n = TINYBODY[i];
- addDep(n);
- if( n==ret ) continue;
- if( n instanceof ParmNode parm && parm.region()!=fun ) return false;
- if( n!=fun && !(n instanceof ParmNode) &&
- !(n instanceof StoreNode) && !(n instanceof MemMergeNode) )
- return false; // Something unexpected
- if( n.nOuts()>TINYBODY.length ) return false; // Too big
- for( Node use : n._outputs ) {
- if( use==null ) continue;
- int j=0;
- while( j<len && TINYBODY[j]!=use ) j++;
- if( j<len ) continue; // Already in TINYBODY
- if( len==TINYBODY.length ) { addDep(use); return false; } // Too big
- TINYBODY[len++] = use; // Add to worklist
- }
- }
- return true;
- }
-
- public void doInline() {
- assert _inline > 0; // Only called when possible to inline
-
- ReturnNode ret = (ReturnNode)in(1);
- FunNode fun = ret.fun();
- CallNode call = call();
- assert fun.sig().isa(call.fptr()._type);
-
- // Clone first
- if( _inline==2 ) {
- assert !CodeGen.CODE._midAssert; // Triggered inlining
- // Remove the existing function linkage
- call.unlink_all();
- // Clone the function body
- FunNode fun2 = fun.copyBody();
- // Call uses the unique new function
- Node fptr2 = new FunPtrNode(fun2.sig(),CodeGen.CODE._start,fun2.ret()).peephole();
- call.setDef(call.nIns()-1,fptr2);
- // Link to the new function
- call.link(fun2);
- fun = fun2;
- }
-
- // Trivial inlining: call site calls a single function; single function
- // is only called by this call site.
- doTrivialInlining(fun);
- }
-
- boolean goodArgs( CallNode call, TypeFunPtr tfp ) {
- for( int i=0; i<tfp.nargs(); i++ ) {
- Node arg = call.arg(i+2);
- // The graph's function-pointer type can still contain open forward
- // references here. Compare against its recursively upgraded form,
- // so deep mutability requirements participate before inlining.
- Type formal = tfp.arg(i).upgradeType(Parser.TYPES);
- if( !arg._type.isa(formal) || !arg._type.accessISA(formal) )
- { addDep(arg); return false; }
- }
- return true;
- }
-
-
- // Do trivial inlining. Inlining does not need to clone code, merely
- // triggers folding the Call/Fun and Return/CallEnd away.
- private boolean doTrivialInlining( FunNode fun ) {
- assert !CodeGen.CODE._midAssert; // Triggered inlining
- // Trivial inline: rewrite
- _folding = true;
- // Rewrite Fun so the normal RegionNode ideal collapses
- fun._folding = true;
- fun.setDef(1,call().ctrl());// Bypass the Call;
- fun.ret().setDef(3,null); // Return is folding also
-
- CodeGen.CODE.addAll(fun._outputs);
- // Repeat defs 1 layer down, for users of Parm (Phis)
- for( Node parm : fun._outputs )
- if( parm instanceof ParmNode )
- CodeGen.CODE.addAll(parm._outputs);
-
- // Inlining immediately blows all cache idepth fields past the inline point.
- // Bump the global version number invalidating them en-masse.
- CodeGen.CODE.invalidateIDepthCaches();
- // More peeps
- CodeGen.CODE.add(fun);
- CodeGen.CODE.add(this);
- CodeGen.CODE.addAll(_outputs);
- CodeGen.CODE.addAll(_inputs);
- return true; // Always return true
- }
-
-
- @Override public Node pcopy(int idx, Node dep) {
- return _folding ? in(1).in(idx) : null;
- }
-
- @Override public Node copy() {
- return _folding ? super.copy() : new CallEndNode(_rpc);
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CallNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/CallNode.java
deleted file mode 100644
index f791225a..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CallNode.java
+++ /dev/null
@@ -1,220 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-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.TypeRPC;
-import com.seaofnodes.simple.type.XInt;
-import com.seaofnodes.simple.util.Utils;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-
-/**
- * Call
- */
-public class CallNode extends CFGNode {
-
- // Source location for late reported errors
- public final Parser.Lexer _loc;
-
- public CallNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc=loc; CodeGen.CODE._calls.add(this); }
- public CallNode(CallNode call) { super(call); _loc = call._loc; }
-
-
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- String fname = name();
- if( fname == null ) p.n(fptr());
- else p.p(fname);
- p.p("( ");
- for( int i=2; i<nIns()-1; i++ )
- p.n(in(i)).p(",");
- return p.unchar(',').unchar(' ').close();
- }
- public String name() {
- if( fptr()._type instanceof TypeFunPtr tfp && tfp._isConstant() && tfp.notNull() ) {
- FunNode fun = CodeGen.CODE._link(tfp);
- if( fun !=null ) return fun._name;
- if( fptr() instanceof ExternNode ex ) return ex._extern;
- }
- return null;
- }
-
-
- Node ctrl() { return in(0); }
- Node mem () { return in(1); }
- // Args mapped 1-to-1 on inputs, so conceptually start at 2
- public Node arg(int idx) { return in(idx); }
- // Same arg accounting as TFPs, although the numbering starts at 2
- public int nargs() { return nIns()-3; } // Minus control, memory, fptr
- // args from input 2 to last; last is function input
- public Node fptr() { return _inputs.last(); }
- // Error if not a TFP
- public TypeFunPtr tfp() { return (TypeFunPtr)fptr()._type; }
-
- // Call is to an externally supplied code
- public boolean external() {
- if( !(fptr()._type instanceof TypeFunPtr tfp) ) return false;
- if( XInt.isHigh(tfp.fidxs()) ) return true;
- for( int fidx=XInt.next(tfp.fidxs(),-1); fidx>=0; fidx=XInt.next(tfp.fidxs(),fidx) )
- if( CodeGen.CODE.externFunc(fidx)!=null ) return true;
- return false;
- }
-
- // As in the escape chapter, native code may retain an argument even if
- // this path never returns. StopCU reads this independently of CallEnd.
- TypeMem exposed(Node dep) {
- dep.addDepForwards(this);
- dep.addDepForwards(fptr());
- if( _type!=Type.CONTROL || !external() ) return TypeMem.START;
- Type input=dep.addDepForwards(mem())._type;
- TypeMem escaped=input instanceof TypeMem m ? m : input.isHigh() ? TypeMem.TOP : TypeMem.BOT;
- for( int i=2; i<nIns()-1; i++ ) escaped=escaped.escapes(dep.addDepForwards(arg(i))._type);
- return TypeMem.make(1,Type.BOTTOM,false,false,false,escaped._escFs,escaped._escAs);
- }
-
-
- // Find the Call End from the Call
- public CallEndNode cend() {
- // Always in use slot 0
- if( nOuts()>0 && out(0) instanceof CallEndNode cend ) {
- assert _cend()==cend;
- return cend;
- } else {
- assert _cend()==null;
- return null;
- }
- }
- private CallEndNode _cend() {
- CallEndNode cend=null;
- for( Node n : _outputs )
- if( n instanceof CallEndNode cend0 )
- { assert cend == null; cend = cend0; }
- return cend;
- }
-
- // Get the one control following; error to call with more than one e.g. an
- // IfNode or other multi-way branch.
- @Override public CFGNode uctrl() { return cend(); }
-
-
- @Override
- public Type compute() {
- return ctrl()._type;
- }
-
- @Override
- public Node idealize() {
- // Lowering may introduce calls after the call graph has been analyzed.
- // These use the callee interface without reopening its parameter flow.
- if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Lower.ordinal() ) return null;
- CallEndNode cend = cend();
- if( cend==null ) return null; // Still building
-
- // Link: call calls target function. Linking makes the target FunNode
- // point to this Call, and all his Parms point to the call arguments;
- // also the CallEnd points to the Return.
- Node progress = null;
- if( fptr()._type instanceof TypeFunPtr tfp && tfp.nargs() == nargs() ) {
- // If fidxs is negative, then infinite unknown functions
- int[] fidxs = tfp.fidxs();
- if( !XInt.isHigh(fidxs) ) {
- // Walk the bits and link
- for( int fidx = XInt.next(fidxs,0); fidx > 0; fidx = XInt.next(fidxs,fidx) ) {
- FunNode fun = CodeGen.CODE.link(fidx);
- if( fun!=null && !fun._folding && !linked(fun) )
- progress = link(fun);
- }
- }
- }
-
- return progress;
- }
-
- // True if Fun is linked to this Call
- public boolean linked( FunNode fun ) {
- for( Node n : fun._inputs )
- if( n == this )
- return true;
- return false;
- }
-
- // Link so this calls fun
- public Node link( FunNode fun ) {
- assert !linked(fun);
- CodeGen.CODE.add(fun).addDef(this);
- for( Node use : fun._outputs )
- if( use instanceof ParmNode parm ) {
- Node def = parm._idx==0 ? ConstantNode.seed(cend()._rpc).peephole() : arg(parm._idx);
- CodeGen.CODE.add(parm).addDef(def);
- // Linking can happen during post-SCCP inlining. Keep an RPC
- // Parm's cached finite set synchronized with its newly added
- // input instead of waiting for the next worklist visit.
- if( parm._idx==0 && parm._type instanceof TypeRPC )
- parm._type = parm._type.meet(def._type);
- }
- // Call end points to function return
- CodeGen.CODE.add(cend()).addDef(fun.ret());
- return this;
- }
-
- // Unlink a single function
- public void unlink( FunNode fun, int path ) {
- assert linked(fun);
- for( Node use : fun._outputs )
- if( use instanceof ParmNode )
- use.delDef(path);
- fun.delDef(path);
- CodeGen.CODE.add(fun);
- CallEndNode cend = cend();
- ReturnNode ret = fun.ret();
- if( cend != null ) {
- ret.keep();
- cend.delDef(cend._inputs.find(ret));
- CodeGen.CODE.add(cend);
- ret.unkeep();
- }
- assert !linked(fun);
- }
-
-
- // Unlink all linked functions
- public void unlink_all() {
- for( int i=0; i<_outputs._len; i++ )
- if( out(i) instanceof FunNode fun ) {
- int path = fun._inputs.find(this);
- unlink(fun,path);
- i--;
- }
- }
-
- @Override
- public Parser.ParseException err() {
- if( fptr()._type == Type.BOTTOM )
- return null; // Wrong, but failing earlier
- if( !(fptr()._type instanceof TypeFunPtr tfp) )
- return Parser.error("Expected a function, but found " + fptr()._type, _loc);
- if( !tfp.notNull() )
- return Parser.error( "Might be null calling "+tfp, _loc);
- if( nargs() != tfp.nargs() )
- return Parser.error( "Expecting "+tfp.nargs()+" arguments, but found "+nargs(), _loc);
-
- // Check for args
- for( int i=0; i<tfp.nargs(); i++ )
- if( !arg(i+2)._type.isa(tfp.arg(i)) || !arg(i+2)._type.accessISA(tfp.arg(i)) ) {
- // Constructors return private memory and carry hidden self and
- // selfMem arguments ahead of the source-level arguments.
- int argno = i - (tfp.ret() instanceof TypeMem ? 2 : 0);
- return Parser.error( "Argument #" + argno + " isa " + arg(i+2)._type + ", but must be a " + tfp.arg(i), _loc );
- }
-
- if( tfp.isHigh() )
- throw Utils.TODO(); // Infinite unknown TFPs? Should be fairly precise CG
-
- return null;
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java
deleted file mode 100644
index 0afc5827..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java
+++ /dev/null
@@ -1,23 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
deleted file mode 100644
index 0b8053e2..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-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.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 String label() {
- return _fname == " len" ? "sizeof("+_ts._name+")" : "#"+_fname;
- }
-
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- return p.p(label());
- }
-
- @Override public Node idealize() {
- // Some array offsets are known early
- if( _ts.isAry() ) {
- // Length always at offset zero
- if( _fname==" len" )
- return CodeGen.CODE.ZERO;
- // The shift depends on the element
- if( _fname=="<<" )
- return Node.con(_ts.aryScale());
- // The base depends on the layout (alignment for large elements may
- // involve padding) and MAY depend on the length if I go for
- // smaller array lengths.
- }
- return null;
- }
-
- // Convert field offset to an integer
- public Node asOffset() {
- int fldx = _fname==" len" ? _ts._fields.length : _ts.find(_fname);
- if( fldx == -1 ) return null;
- // " len" selects the terminal size slot, not a Field.
- if( fldx < _ts._fields.length && _ts._fields[fldx]._extern )
- return new ExternOffsetNode(_fname).peephole();
- return ConstantNode.make(TypeInteger.constant(_ts.offset(fldx))).peephole();
- }
-
- // Upgrade the internal type
- @Override boolean _upgradeType( HashMap<String,Type> TYPES) {
- boolean progress = super._upgradeType(TYPES);
- TypeStruct ts = (TypeStruct)_ts.upgradeType(TYPES);
- if( ts == _ts ) return progress;
- _ts = ts;
- return true;
- }
-
- @Override public boolean eq(Node n) {
- ConFldOffNode off = (ConFldOffNode)n; // Invariant
- return _ts._name==off._ts._name && _fname.equals(off._fname) && super.eq(n);
- }
- @Override int hash() { return super.hash() * _ts._name.hashCode() * _fname.hashCode(); }
-
- @Override public Parser.ParseException err() {
- return _ts._fref ? Parser.error("Unknown struct type '"+_ts._name+"'",null) : null;
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
deleted file mode 100644
index eabf2639..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFunPtr;
-
-import 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); }
-
-
- // Semantic constant construction through Lower. A unique internal
- // function identity carries a lifetime edge to the function's Return.
- public static Node make( Type type ) {
- if( type==Type. CONTROL ) return new CtrlNode();
- if( type==Type.XCONTROL ) return new XCtrlNode();
- if( CodeGen.CODE._phase != null &&
- CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Lower.ordinal() )
- return raw(type);
- if( type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
- FunNode fun = CodeGen.CODE._link(tfp);
- if( fun != null )
- return new FunPtrNode(tfp,CodeGen.CODE._start,fun.ret()).init();
- // TODO: A singleton FIDX can also emerge after merging and
- // guarding several function pointers. Preserve that identity
- // even if its internal Return is no longer available.
- return raw(type);
- }
- return new ConstantNode(type);
- }
-
- // Exact construction for deserialization and graph-incomplete machine
- // selection. No semantic classification, compute, idealize, or GVN.
- public static ConstantNode raw( Type type ) { return new ConstantNode(type,true); }
- public static ConstantNode raw( ConstantNode con ) { return new ConstantNode(con); }
-
- // Analysis input created from whole cloth, rather than a first-class
- // runtime value. In particular, a constructor call descriptor does not
- // own the constructor function; the class FunPtr does.
- public static ConstantNode seed( Type type ) { return new ConstantNode(type,true); }
-
- @Override protected String repeatName() {
- return _con==null || _con.toString().length()<=32 ? null : uniqueName();
- }
-
- @Override public String label() { return "Con"; }
- @Override public Node copy() { return raw(this); }
-
-
- @Override
- protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- if( _con instanceof TypeFunPtr tfp && tfp._isConstant() && tfp.notNull() ) {
- FunNode fun = CodeGen.CODE._link(tfp);
- if( fun!=null && fun._name != null )
- return p.p("{ ").p(fun._name).p("}");
- }
- return p.p(_con==null ? "---" : _con.toString());
- }
-
- @Override public boolean isConst() { return true; }
- @Override public Type compute() { return _con; }
- @Override public Node idealize() { return null; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ConvertNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
deleted file mode 100644
index cdc890a7..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
+++ /dev/null
@@ -1,101 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.type.*;
-
-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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- return p.p(label()).open().n(val()).close();
- }
-
- // The declaration fixes the result family during pessimistic parsing.
- // From SCCP onward, preserve dead inputs and stronger facts in that family.
- @Override public Type compute() {
- Type src = val()._type;
- if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() ) {
- if( src.isHigh() ) return Type.TOP;
- if( (src.isa(_dst) && src.accessISA(_dst)) ) return src;
- }
- return _dst;
- }
-
- @Override public Node idealize() {
- Type src = val()._type;
- if( src==Type.BOTTOM || src==Type.TOP ) return null;
- if( (src.isa(_dst) && src.accessISA(_dst)) ) return val();
-
- // Managed arrays are represented by an object pointer, while C wants
- // the address of the first element. The representation cast itself is
- // zero-code; the Add skips the array header.
- if( _dst==TypeInteger.BOT && src instanceof TypeMemPtr tmp && tmp._obj.isAry() )
- return new AddNode(new PtrToIntNode(val()).peephole(),Parser.off(tmp._obj,"[]"));
-
- // Integer (and nil-as-zero) to floating point.
- if( (src instanceof TypeInteger || src==Type.NIL) && _dst instanceof TypeFloat )
- return new ToFloatNode(val());
-
- // Convert to i64 first, then apply the destination's integer width.
- if( src instanceof TypeFloat && _dst instanceof TypeInteger )
- return new ConvertNode(_dst,new ToIntegerNode(val()).peephole());
-
- // Narrow integers produce the declared sign/zero extension.
- if( src instanceof TypeInteger && _dst instanceof TypeInteger dst ) {
- if( dst._min==0 )
- return new AndNode(null,val(),con(dst._max));
- int shift = Long.numberOfLeadingZeros(dst._max)-1;
- Node shf = con(shift);
- if( shf._type==TypeInteger.ZERO ) return val();
- return new SarNode(null,new ShlNode(null,val(),shf.keep()).peephole(),shf.unkeep());
- }
-
- // Narrow f64 to f32.
- if( src instanceof TypeFloat && _dst instanceof TypeFloat )
- return new RoundF32Node(val());
-
- // Wait for more source information, or report an error after Opto.
- return null;
- }
-
- @Override boolean _upgradeType(HashMap<String,Type> TYPES) {
- Type dst = _dst.upgradeType(TYPES);
- if( dst==_dst ) return false;
- unlock();
- _dst = dst;
- return true;
- }
-
- @Override public Parser.ParseException err() {
- if( val()._type==Type.BOTTOM ||
- val()._type instanceof TypeNil src && src.nullable() && _dst instanceof TypeNil dst && dst.notNull() ) {
- Node out = out(0);
- if( out instanceof PhiNode phi ) out = phi.out(0);
- if( out instanceof StoreNode st )
- return Parser.error( "'" + ((TypeMemPtr)st.ptr()._type)._obj._name + "' is not fully initialized, field '" + st._name + "' is only partially set in the constructor", null );
- }
- return Parser.error("Type "+val()._type.str()+" is not of declared type "+_dst.str(),null);
- }
-
- @Override public boolean eq(Node n) { return _dst==((ConvertNode)n)._dst; }
- @Override int hash() { return _dst.hashCode(); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CtrlNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
deleted file mode 100644
index 813e6fcb..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.type.Type;
-
-public class CtrlNode extends CFGNode {
- public CtrlNode() { super(CodeGen.CODE._start); }
-
- @Override public String label() { return "Ctrl"; }
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("Cctrl"); }
- @Override public boolean isConst() { return true; }
- @Override public Type compute() { return Type.CONTROL; }
- @Override public Node idealize() { return null; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/DivNode.java
deleted file mode 100644
index 0db2b7a2..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/DivNode.java
+++ /dev/null
@@ -1,40 +0,0 @@
-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 String op() { return _mode==1 ? "//" : "/"; }
-
- @Override long doOp( long x, long y ) { return y==0 ? 0 : x / y; }
- @Override double doOp(double x, double y ) { return x / y; }
- @Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
- return TypeInteger.BOT;
- }
-
- @Override
- public Node idealize() {
- // Div of 1.
- if( in(2)._type == TypeInteger.TRUE && in(1)._type.isa(compute()) )
- return in(1);
-
- // Div of constant
- if( _mode==2 ) {
- if( in(2)._type == TypeFloat.constant(1.) && in(1)._type.isa(compute()) )
- return in(1);
- if( in(2)._type instanceof TypeFloat f && f.isConstant() )
- return new MulNode(in(1),ConstantNode.make(TypeFloat.constant(1.0/f.value())).peephole(),_mode);
- }
-
- return super.idealize();
- }
-
- @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs,_mode); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/EscapeNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
deleted file mode 100644
index f7518184..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
+++ /dev/null
@@ -1,109 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.*;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-
-/**
- * Merge public and private aliases.
- * 0 - control
- * 1 - private pointer
- * 2 - private alias
- * 3 - public alias
- */
-public class EscapeNode extends TypeNode {
-
- public EscapeNode(Field fld, Node self, Node priv, Node pub ) {
- super(fld,null,self,priv,pub);
- }
- public EscapeNode(EscapeNode esc ) {
- super(esc); // Copy constructor
- // Except we do not want/need self ptr past Opto.
- // *NOTE* the weird direct stomp; this is correct because this
- // constructor does NOT set the backedges, because it is used to
- // copy-construct an entire graph and ALL edges will get rewritten.
- _inputs.set(1,null);
- _con = esc._con;
- }
-
- // Pointer to some private (unescaped) memory from a NewNode
- public Node self() { return in(1); }
- public Node priv() { return in(2); }
- public Node pub () { return in(3); }
- public Field fld() { return (Field)_con; }
-
-
- @Override
- protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- p.p("Esc#").p(fld()._alias).p(" {");
- if( self()==null ) p.p("---");
- else p.n(self()).p(",");
- p.n(priv()).p("}, ");
- p.n(pub ());
- return p;
- }
-
- @Override
- public Type compute() {
- if( priv()._type.isHigh() ) return TypeMem.TOP;
- if( pub ()._type.isHigh() ) return TypeMem.TOP;
- TypeMem mpriv = (TypeMem)priv()._type;
- TypeMem mpub = (TypeMem)pub ()._type;
- // Private memory comes from an allocation or other non-aliased source.
- // Public memory comes from anywhere, might be bulk memory with a very weak mem type.
- Field fld = fld();
- assert mpriv._alias==1 || mpriv._alias==fld._alias;
- assert mpub ._alias==1 || mpub ._alias==fld._alias;
- Type tpriv = mpriv._t instanceof TypeStruct ts && ts.findAlias(fld._alias)!=-1
- ? ts.at(ts.findAlias(fld._alias))._t
- : mpriv._t;
- assert !(mpub._t instanceof TypeStruct); // Never expect this, but if it starts to happen, need to optimize
- Type tpub = mpub._t;
-
- // t is meet of pub & priv. Pub memory can be very weak (e.g. alias 1)
- // and we're no worse than the declared field type
- Type t = tpriv.meet(tpub).join(fld._t);
- int[] fidxes = XInt.meet(mpriv._escFs, mpub._escFs);
- int[] aliases = XInt.meet(mpriv._escAs, mpub._escAs);
- return TypeMem.make(fld._alias, t, false, false, fld._final, fidxes, aliases).escapesFrom(t);
- }
-
- @Override public Node idealize() {
- // Sharpen private memory input
- if( priv() instanceof MemMergeNode mmm ) {
- setDef(2,mmm.alias(fld()._alias));
- return this;
- }
- // Sharpen public memory input
- if( pub() instanceof MemMergeNode mmm ) {
- setDef(3,mmm.alias(fld()._alias));
- return this;
- }
- // New allocation is dead
- if( self()._type.isHigh() )
- if( pub()._type.isa(_type) ) return pub();
- else addDep(pub());
- return null;
- }
-
-// Upgrade the internal type
-@Override boolean _upgradeType( HashMap<String,Type> TYPES) {
- Type t = _con.upgradeType(TYPES);
- if( t == _con ) return false;
- unlock(); // Unlock before changing _con
- _con = t;
- return true;
-}
-
- @Override public boolean eq(Node n) {
- if( fld()._alias!=((EscapeNode)n).fld()._alias )
- return false;
- assert _con == ((EscapeNode)n)._con;
- return true;
- }
-
- @Override int hash() { return _con.hashCode(); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ExternNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ExternNode.java
deleted file mode 100644
index 2d1dc29f..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ExternNode.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.type.Type;
-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 String label() { return "#"+_con+":"+_extern; }
-
- @Override public boolean eq(Node n) { return this==n; }
-
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java
deleted file mode 100644
index 23ed5468..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeInteger;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-
-// A field address supplied by the C linker, not a displacement from the base.
-// The class base remains available for ideal type/alias analysis. Instruction
-// selection replaces it with this symbol's address and a zero displacement.
-public class ExternOffsetNode extends ExternNode {
- public ExternOffsetNode(String name) { super(TypeInteger.BOT,name); }
-
- @Override public Node copy() { return new ExternOffsetNode(_extern); }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/FRefNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/FRefNode.java
deleted file mode 100644
index 644f1434..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/FRefNode.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.Var;
-import com.seaofnodes.simple.type.Type;
-
-/**
- * A Forward Reference. Its any final constant, including functions. When
- * the Def finally appears its plugged into the forward reference, which then
- * peepholes to the Def.
-
- * FRefs can be external references, to other Simple defined values in ELF
- * files, or external FFI/C libs. If external, a file system lookup needs to
- * find it in another file, and the name (one time) change to match the found
- * location.
- *
- * Example, a ambiguous name "foo" is looked up externally; it might resolve
- * to "A.B.foo" or "libc.foo" or the like; the name is changed to match.
- */
-public class FRefNode extends ConstantNode {
- public static final Type FREF_TYPE = Type.BOTTOM;
- public String _name;
- public final Parser.Lexer _loc; // Source location
-
- public FRefNode( String name, Parser.Lexer loc ) { this(name,loc,FREF_TYPE); }
- public FRefNode( String name, Parser.Lexer loc, Type type ) {
- super(type);
- _name = name;
- _loc = loc;
- }
-
- @Override public String label() { return "FRef"+_name; }
-
-
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- return p.p("FRef_").p(_name);
- }
-
- @Override public Node idealize() {
- // When FRef finds its definition, idealize to it
- return nIns()==1 ? null : in(1);
- }
-
- public Parser.ParseException err() { return Parser.error("Undefined name '"+_name+"'",_loc); }
-
- @Override public boolean eq(Node n) { return this==n; }
- @Override int hash() { return _name.hashCode(); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/FldOffNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/FldOffNode.java
deleted file mode 100644
index 535241a6..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/FldOffNode.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-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.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 String label() {
- return _fname == " len" ? "sizeof()" : "#"+_fname;
- }
-
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- return p.p(label());
- }
-
- @Override public Type compute() {
- return Type.BOTTOM;
- }
-
- @Override public Node idealize() {
- // When base() gets typed, this xforms into a ConFldOffNode
- if( in(0)._type instanceof TypeMemPtr tmp )
- return new ConFldOffNode(tmp._obj,_fname);
- return null;
- }
-
- @Override public boolean eq(Node n) {
- FldOffNode off = (FldOffNode)n; // Invariant
- return _fname.equals(off._fname) && super.eq(n);
- }
- @Override int hash() { return super.hash() * _fname.hashCode(); }
-
- @Override public Parser.ParseException err() {
- if( in(0)._type instanceof TypeMemPtr tmp && tmp._obj._fref )
- return Parser.error("Unknown struct type '"+tmp._obj._name+"'",null);
- return null;
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/FunNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/FunNode.java
deleted file mode 100644
index ad79130e..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/FunNode.java
+++ /dev/null
@@ -1,463 +0,0 @@
-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.SB;
-import com.seaofnodes.simple.util.Utils;
-import java.util.Arrays;
-import java.util.BitSet;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-import static com.seaofnodes.simple.codegen.CodeGen.CODE;
-
-public class FunNode extends RegionNode {
-
- // When set true, this Call/CallEnd/Fun/Return is being trivially inlined
- public boolean _folding;
- public boolean folding() { return _folding; }
-
- private TypeFunPtr _sig; // Initial signature
- private ReturnNode _ret; // Return pointer
-
- public String _name; // Debug name
-
- public int _approxUIDs; // Approximate function size, used as a inlining heuristic
- public CompUnit _compunit; // Defined in source file
- public final boolean _extern; // External function; no code, name only
-
- private FunNode( 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( FunNode fun ) {
- super( fun, fun._loc );
- _sig = fun.sig();
- _name = fun._name;
- _compunit = fun._compunit;
- _extern = fun._extern;
- }
-
-
- @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) || resolvesForwardSelf(sig,_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);
- unlock();
- _sig = sig;
- }
- }
-
- // A class initializer starts parsing with an open singleton receiver and
- // closes that same receiver after discovering its fields. Function
- // arguments are contravariant, so ordinary isa does not describe this
- // parser-time forward-reference resolution even though every other part
- // of the signature sharpens normally.
- private static boolean resolvesForwardSelf(TypeFunPtr sig, TypeFunPtr old) {
- if( !(sig.arg(0) instanceof TypeMemPtr now) ||
- !(old.arg(0) instanceof TypeMemPtr prior) ||
- now._obj._name != prior._obj._name ||
- !prior._obj._open || now._obj._open )
- return false;
- return sig.makeFrom(old.arg(0),0).isa(old);
- }
-
- @Override boolean _upgradeType( HashMap<String,Type> TYPES) {
- TypeFunPtr sig = (TypeFunPtr)_sig.upgradeType(TYPES);
- if( sig == _sig ) return false;
- unlock();
- _sig = sig;
- return true;
- }
-
- public void setName( String name ) {
- if( _name==null )
- _name = _compunit==null || _compunit._cname==null || _compunit._cname.indexOf('.')==-1
- ? name
- : (_compunit._cname+"."+name).intern();
- }
-
- @Override
- public Type compute() {
- // Only dead if no callers after SCCP
- if( unknownCallers() )
- return Type.CONTROL;
- Type t = Type.XCONTROL;
- for( int i = 1; i < nIns(); i++) {
- // Since there are no unknown callers, the Start input is only a
- // fake hook and will be a Tuple with XControl, so ignore it. Need
- // to be called from someplace other than Start.
- if( !(in(i) instanceof StartNode) )
- t = t.meet(in(i)._type);
- }
- return t;
- }
-
- @Override
- public Node idealize() {
-
- // Some linked path dies, except unknown caller hooks never kill Start.
- Node progress = deadPath(unknownCallers());
- if( progress!=null ) {
- if( nIns()==2 && in(1) instanceof CallNode call )
- CODE.add(call.cend()); // If no Start and one call, check for inline
- return progress;
- }
-
- // Upgrade inferred or user-written return type to actual
- if( _ret!=null && _ret._type instanceof TypeTuple tt &&
- tt.ret() != _sig.ret() && tt.ret().isa(_sig.ret()) ) {
- setSig(_sig.makeFrom(tt.ret()));
- return this;
- }
-
- // Attempt to get rid of the unknown caller.
- // - Must be past pessimistic Iter, which discovers and links calls
- // through class-field loads and other initially imprecise values.
- // - FIDX is not in the escape set
- // - Not called anywhere else; post Opto called-not-escaped functions need to still be hooked to start
- // Opaque CLInit obligations need their bodies even without Call edges.
- if( !isClz() && in(1) instanceof StartCUNode start ) {
- boolean preOpto = CodeGen.CODE._phase == null ||
- CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal();
- if( !preOpto && !start.escapedFIDX(_sig.fidx()) && nIns()<=2 && _ret!=null ) {
- _compunit._stop.delDef(_compunit._stop._inputs.find(ret()));
- return removeDeadPath(1);
- } else {
- addDep(start);
- }
- }
-
-
- // If no default/unknown caller, use the normal RegionNode ideal rules
- // to collapse
- if( unknownCallers() ) return null;
-
- // If folding (already inlined) down to a single input, become that input.
- // Cannot collapse if not-inlining, and might not inline if call site
- // calls multiple targets.
- if( _folding && nIns()==2 && !hasPhi() ) {
- CODE.add( CODE._stop ); // Stop will remove dead path
- CODE.add( _ret ); // Return will compute to TOP control
- return in(1); // Collapse if no Phis; 1-input Phis will collapse on their own
- }
-
- return null;
- }
-
- // Bypass Region idom, always assume depth == 1, one more than Start,
- // unless folding
- @Override public int idepth() { return _folding ? super.idepth() : 1; }
-
- // Bypass Region idom, always assume idom is Start
- @Override public CFGNode idom(Node dep) {
- return _folding ? cfg(1) : null;
- }
-
- // Always in-progress until we run out of unknown callers
- public boolean unknownCallers() { return nIns()>=2 && in(1) instanceof StartNode; }
-
- public boolean isModInit( ) {
- // The one top-level <clinit> with no internal dots:
- // "sys.<clinit>" is a module, but "sys.io.<clinit>" is not.
- return isClz() && _name.indexOf('.')==_name.lastIndexOf('.');
- }
- public boolean isClz ( ) { return isClz (_name); }
- public boolean isInit( ) { return isInit(_name); }
- public boolean isInstance() { return isInstance(_name); }
- public static boolean isClz (String name ) { return name!=null && name.endsWith(".<clinit>"); }
- public static boolean isInit(String name ) { return name!=null && name.endsWith("init>"); }
- public static boolean isInstance(String name ) { return name!=null && name.endsWith(".<init>"); }
-
- // Allocation factories and instance initializers carry extra memory boilerplate.
- public int inlineLimit() {
- return isInstance() || (_name!=null && _name.endsWith(".<new>")) ? 200 : 100;
- }
-
- // Function is public (callable from Start directly).
- public boolean isPublic( ) {
- // Never true for anonymous functions
- if( _name == null ) return false;
- // Only public for <clinit> and <init>, all others
- // are found via field loads. The <init> Start hook also supplies its
- // conservative pre-Opto construction inputs; StopCU separately keeps
- // its private-memory return from escaping as a public result.
- if( !isInit() ) return false;
- // Private class, has to be found via program text, not linker
- if( _name.charAt(0)=='_' ) return false;
-
- // No instances of "...clz1._clz2..." where the class name path
- // includes a private class.
- if( _name.indexOf("._") != -1 ) return false;
-
- // Public class <cl/init>
- return true;
- }
-
- @Override public boolean inProgress() {
- return unknownCallers() && CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal();
- }
-
- // Build the function body set
- public BitSet body() {
- // Iter drains ordinary peeps between inlines; body discovery precedes
- // this inline's folding, so function boundaries are stable.
- assert !_folding;
- // Forward control includes paths that never reach Return.
- BitSet cfgs = new BitSet();
- walkCFG(this,cfgs);
- cfgs.set(ret()._nid);
-
- // Top down (start to stop) all flavors. CFG limit to bitmap.
- // If data use bottoms out in wrong CFG, returns false - but tries all outputs.
- // If any output hits an in-CFG use (e.g. phi), then keep node.
- BitSet body = new BitSet();
- // The function owns its Return even when it has no executable exit.
- body.set(ret()._nid);
- BitSet visit = new BitSet();
- int old;
- do {
- old = body.cardinality();
- visit.clear();
- walkDown(this, cfgs, body, visit);
- } while( old != body.cardinality() );
- return body;
- }
-
- private static void walkCFG(CFGNode n, BitSet cfgs) {
- if( cfgs.get(n._nid) ) return;
- cfgs.set(n._nid);
- if( n instanceof ReturnNode ) return;
- for( Node use : n._outputs ) {
- if( !(use instanceof CFGNode cfg) || cfg instanceof FunNode ) continue;
- if( cfg instanceof RegionNode ? cfg._inputs.find(n)>0 : cfg.in(0)==n )
- walkCFG(cfg,cfgs);
- }
- }
-
- private static boolean walkDown( Node n, BitSet cfgs, BitSet body, BitSet visit ) {
- if( n==null ) return false;
- if( visit.get(n._nid) ) return body.get(n._nid);
- visit.set(n._nid);
- // Visit self as CFG outside the function
- if( n instanceof CFGNode && !cfgs.get(n._nid) )
- return false;
- // Pretend the NewNode is a CFG, so its projections stay with it in loops
- if( n instanceof NewNode nnn ) cfgs.set(n._nid);
- // Visit n.cfg() outside of function
- if( n.in(0)!=null && !cfgs.get(n.in(0)._nid) )
- return false;
- // Phis inside the function must have their body flag set BEFORE
- // recursion walks around a loop and finds them again.
- if( n instanceof PhiNode phi && cfgs.get(phi.in(0)._nid) )
- body.set(phi._nid); // Find data cycles
- boolean in = n.in(0)!=null || n instanceof CFGNode;
- for( Node use : n._outputs ) {
- // Will hit a backedge, so 'n' MUST be in the function, and must be
- // set before the cyclic walk finds 'n' again.
- if( use instanceof PhiNode puse && puse.in(0) instanceof LoopNode loop && puse.in(2)==n )
- body.set(n._nid); // Set before walk
- in |= walkDown(use,cfgs,body,visit);
- }
- if( in ) body.set(n._nid);
- return in;
- }
-
- // Clone function body. Give function a new FIDX.
- FunNode copyBody() {
- // Build the function body BitSet
- BitSet body = body();
- assert body.cardinality() < inlineLimit();
- // Walk the body, cloning
- IdentityHashMap<Node,Node> map = new IdentityHashMap<>();
- BitSet visit = CodeGen.CODE.visit();
- bodyCopy( visit, body, map, this );
- bodyCopy( visit, body, map, _ret ); // May be disconnected from executable control.
- visit.clear();
- bodyEdge( visit, body, map, this );
- bodyEdge( visit, body, map, _ret );
- visit.clear();
- assert map.size()==body.cardinality();
-
- // New function/return cross-link each other
- FunNode fun2 = (FunNode )map.get(this);
- ReturnNode ret2 = (ReturnNode)map.get(_ret);
- fun2._ret = ret2;
- ret2._fun = fun2;
- BitSet body2 = new BitSet();
- for( Node n : map.values() )
- body2.set(n._nid);
-
- // Remove non-body callers. Body-local call edges were cloned along
- // with their CallEnds and must stay linked so their cloned types remain
- // monotonic during the post-Opto Iter.
- for( int i=1; i<fun2.nIns(); i++ )
- if( !(fun2.in(i) instanceof CallNode call) || !body2.get(call._nid) )
- fun2.removeDeadPath(i--);
-
- // Linking adds graph edges and worklist entries; identity-map order
- // would make inlining depend on the host JVM's object hashes.
- Node[] olds = map.keySet().toArray(new Node[0]);
- Arrays.sort(olds, (x,y) -> Integer.compare(x._nid,y._nid));
- for( Node old : olds )
- if( old instanceof CallNode oldCall )
- relinkClonedCall(oldCall,(CallNode)map.get(oldCall),map);
-
- // Flip to a new FIDX to avoid confusion with the old one.
- // This is a non-monotonic (sideways) type move, only applicable
- // because fun2 is new. The fidx is also a purely local fiction,
- // since this body will inline and the fidx goes dead.
- fun2._sig = _sig.makeFrom(CodeGen.CODE._fidxs.nextInline());
- return fun2;
- }
-
- private static void relinkClonedCall( CallNode oldCall, CallNode call, IdentityHashMap<Node,Node> map ) {
- CallEndNode oldCend = oldCall.cend();
- if( oldCend==null || oldCend._folding )
- return;
- for( int i=1; i<oldCend.nIns(); i++ ) {
- ReturnNode oldRet = (ReturnNode)oldCend.in(i);
- FunNode oldFun = oldRet.fun();
- FunNode fun = (FunNode)map.get(oldFun);
- if( fun==null ) fun = oldFun;
- if( !call.linked(fun) )
- call.link(fun);
- }
- CallEndNode cend = call.cend();
- if( cend.nIns() > 1 )
- cend._type = cend.compute();
- }
-
- // Clone small function
- private void bodyCopy( BitSet visit, BitSet body, IdentityHashMap<Node,Node> map, Node n ) {
- if( n==null ) return;
- if( !body.get(n._nid) ) return; // Not part of the body
- if( visit.get(n._nid) ) return; // Been there, done that
- visit.set(n._nid);
- Node m = n.copy();
- CodeGen.CODE.add(m);
- map.put(n,m);
- m._type = n._type;
- for( Node x : n._outputs )
- bodyCopy(visit,body,map,x);
- }
- private void bodyEdge( BitSet visit, BitSet body, IdentityHashMap<Node,Node> map, Node n ) {
- if( n==null ) return;
- if( !body.get(n._nid) ) return; // Not part of the body
- if( visit.get(n._nid) ) return; // Been there, done that
- visit.set(n._nid);
- Node m = map.get(n);
- for( Node e : n._inputs ) {
- Node x = e;
- if( e != null ) {
- if( n instanceof CallEndNode cend && e != cend.call() &&
- (!(e instanceof ReturnNode ret) || map.get(ret.fun()) == null) )
- continue;
- x = map.get(e);
- if( x == null ) {
- if( n instanceof CallEndNode cend && e != cend.call() )
- continue;
- x = e;
- }
- }
- m.addDef(x);
- }
- for( Node x : n._outputs )
- bodyEdge(visit,body,map,x);
- }
-
-
- // no 2 FunNodes are ever GVN'able
- @Override public boolean eq( Node n ) {
- return this==n;
- }
-
- @Override public int hash() {
- return _sig.hashCode();
- }
-
- // ------------
- // MachNode specifics, shared across all CPUs
-
- // Function may call other functions? X86 requires 16b aligned SP on
- // outbound calls, but not leaf. Set during an unrelated RA scan.
- public boolean _hasCalls;
-
- // Maximum incoming argument slot, based on function type
- public int _maxArgSlot;
-
- // Frame adjust, including padding, set during early Encoding
- public int _frameAdjust;
-
- public String op() { return _frameAdjust==0 ? "entry" : "subi"; }
- public void postSelect(CodeGen code) {
- code.link(this);
- // Max slot seen past space reserved for incoming arguments
- _maxArgSlot = code._mach.maxArgSlot(sig());
- }
- public RegMask regmap(int i) { return null; }
- public RegMask outregmap() { return null; }
- public void encoding( Encoding enc ) { throw Utils.TODO(); }
- public void asm(CodeGen code, SB sb) {
- if( _frameAdjust!=0 )
- sb.p("rsp -= #").p(_frameAdjust);
- }
-
- public void computeFrameAdjust(CodeGen code, int maxReg) {
- // Max slot seen, or 0.
- int maxSlot = Math.max(maxReg - code._mach.regs().length,_maxArgSlot);
- // Frame adjust before rounding, can be negative for no spills
- int size = maxSlot - _maxArgSlot;
- _frameAdjust = size*8;
- }
-
- // Given a high register number and during encoding compute the stack
- // pointer offset. Pre-RA we lack the fun._frameAdjust. We also need a
- // FunNode.
- public int computeStackOffset(CodeGen code, int reg) {
- String[] regs = code._mach.regs();
- int stackSlot = reg - regs.length;
- int slotRotate = stackSlot < _maxArgSlot
- ? stackSlot + (_frameAdjust>>3)
- : stackSlot - _maxArgSlot;
- return slotRotate*8;
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java
deleted file mode 100644
index 3d6031b8..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.type.*;
-
-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); }
-
-
- public ReturnNode ret() { return (ReturnNode)in(1); }
- public FunNode fun() { return ret().fun(); }
-
- @Override public String label() { return "#"+_con; }
- @Override public Node copy() {
- FunPtrNode fptr = new FunPtrNode((TypeFunPtr)_con,null,null);
- fptr._type = _type;
- return fptr;
- }
-
- @Override
- protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- FunNode fun = CodeGen.CODE._link(((TypeFunPtr)_con).fidx());
- return fun!=null && fun._name!=null
- ? p.p("{ ").p(fun._name).p("}")
- : p.p(_con);
- }
-
- @Override public boolean isConst() { return true; }
- @Override public Type compute() {
- ReturnNode ret = ret();
- if( ret == null ) return _con;
- if( !(ret._type instanceof TypeTuple tt) ) return ret._type.oob();
- TypeFunPtr tfp = ret.fun().sig();
- // Both the sig and the return value must be true, so JOIN both
- Type tret = tfp._ret.join(tt.ret());
- return tfp.makeFrom(tret);
- }
- @Override public Node idealize() {
- ReturnNode ret = ret();
- if( ret==null ) return null;
- // Function died (never executed), but fcn ptr is alive.
- // Can be checked for null, or for unequals another FunPtr.
- FunNode fun = addDep(ret.fun());
- TypeFunPtr sig = fun.sig();
- if( sig != _con )
- { liftType(sig); return this; }
- // TODO: If the function body dies but this value remains live only as
- // a null/equality sentinel, preserve the identity without the Return.
- if( fun.isDead() )
- { setDef(1,null); liftType(sig.makeFrom(Type.TOP)); return this; }
- return null;
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/GuardMach.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/GuardMach.java
deleted file mode 100644
index 202b1ab8..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/GuardMach.java
+++ /dev/null
@@ -1,17 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/GuardNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/GuardNode.java
deleted file mode 100644
index e985da72..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/GuardNode.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeScalar;
-import com.seaofnodes.simple.util.Utils;
-
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-
-// A branch-proven refinement to zero/null or non-zero/non-null. The value
-// family is deliberately taken from the input when it becomes known.
-public class GuardNode extends Node {
- public final boolean _nonZero;
-
- public GuardNode(boolean nonZero, Node ctrl, Node val) {
- super(ctrl,val);
- _nonZero = nonZero;
- }
-
- public GuardNode(GuardNode guard) {
- super(guard);
- _nonZero = guard._nonZero;
- }
-
-
- @Override public String label() { return _nonZero ? "(!=0)" : "(==0)"; }
-
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- return p.p(label()).n(in(1));
- }
-
- @Override public Type compute() {
- // Guard is a pinned, path-local fact. If its control path is not
- // executable, the fact is not executable either, regardless of how the
- // value input sharpens later.
- Type c = in(0)._type;
- if( c != Type.CONTROL && c != Type.BOTTOM )
- return Type.TOP;
- Type t = in(1)._type;
- if( t==Type.BOTTOM || t==Type.TOP ) return t;
- // The scalar envelope does not identify a value family. Preserve it
- // until SCCP learns whether this is an int, float, pointer, or fptr;
- // choosing generic ptr here can be weaker than a later concrete type.
- if( t==TypeScalar.BOT || t==TypeScalar.TOP ) return t;
- Type t2 = _nonZero ? t.nonZero() : t.makeZero();
- return t2==null ? (_type==null ? Type.TOP : _type) : t2; // No sane answer for e.g. not-zero of a zero
- }
-
- @Override public Node idealize() {
- if( in(0)._type != Type.CONTROL && in(0)._type != Type.BOTTOM )
- return null;
- if( in(1)._type==Type.BOTTOM || in(1)._type==Type.TOP ) return null;
- if( _type.isHighOrConst() && in(1)._type.isa(_type) )
- return in(1);
- return null;
- }
-
- @Override public boolean eq(Node n) { return _nonZero==((GuardNode)n)._nonZero; }
- @Override int hash() { return _nonZero ? 1 : 0; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/IfNode.java
deleted file mode 100644
index 471a28fe..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/IfNode.java
+++ /dev/null
@@ -1,100 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.Utils;
-
-public class IfNode extends CFGNode implements MultiNode {
-
- public IfNode(Node ctrl, Node pred) {
- super(ctrl, pred);
- CodeGen.CODE.add(this); // Because idoms are complex, just add it
- }
- public IfNode(IfNode iff) { super(iff); }
-
-
- @Override
- protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- p.p("if( ");
- if( in(1)==null ) p.p("never");
- else p.n(in(1));
- return p.p(" )");
- }
-
- public Node ctrl() { return in(0); }
- public Node pred() { return in(1); }
-
- // No one unique control follows
- @Override public CFGNode uctrl() { return null; }
-
- @Override
- public Type compute() {
- // If the If node is not reachable then neither is any following Proj
- assert !_inputs.isEmpty();
- if (ctrl()._type != Type.CONTROL && ctrl()._type != Type.BOTTOM )
- return TypeTuple.IF_NEITHER;
- Node pred = pred();
- Type t = pred._type;
- // High types mean NEITHER side is reachable.
- // Wait until the type falls to decide which way to go.
- if( t.isHigh() )
- return TypeTuple.IF_NEITHER;
- // If constant is 0 then false branch is reachable
- // Else true branch is reachable
- if( t.isConstant() )
- return (t==Type.NIL || t==TypeInteger.ZERO || (t instanceof TypeFunPtr tfp && tfp._fidxs== XInt.EMPTY) ) ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE;
- // If adding a zero makes a difference, the predicate must not have a zero/null
- if( !t.makeZero().isa(t) )
- return TypeTuple.IF_TRUE;
-
- return TypeTuple.IF_BOTH;
- }
-
- @Override
- public Node idealize() {
- // Hunt up the immediate dominator tree. If we find an identical if
- // test on either the true or false branch, that side wins.
- if( !pred()._type.isHighOrConst() )
- for( CFGNode dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() )
- if( addDep(dom) instanceof IfNode iff &&
- prior instanceof CProjNode prj &&
- (addDep(iff.pred())==pred() ||
- pred() instanceof GuardNode cast && cast._nonZero && iff.pred()==cast.in(1) ) ) {
- setDef(1,con( prj._idx==0 ? 1 : 0 ));
- return this;
- }
- return null;
- }
-
- // MachNode variants need to support this and negate the conditional test.
- // The following CProjs will be inverted by the caller.
- public void negate() { throw Utils.TODO(); }
-
- // Negate the sense of a test
- public static String negate( String bop ) {
- return switch( bop ) {
- case "<" -> ">=";
- case "<=" -> ">" ;
- case "==" -> "!=";
- case "!=" -> "==";
- case ">" -> "<=";
- case ">=" -> "<" ;
- default -> throw Utils.TODO();
- };
- }
-
- // Swap compare operands
- public static String swap( String bop ) {
- return switch( bop ) {
- case "<" -> ">" ;
- case "<=" -> ">=";
- case "==" -> "==";
- case "!=" -> "!=";
- case ">" -> "<" ;
- case ">=" -> "<=";
- default -> throw Utils.TODO();
- };
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/LoadNode.java
deleted file mode 100644
index e1a495ad..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ /dev/null
@@ -1,331 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.type.*;
-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);
- }
-
-
- @Override boolean canDrop(MemOpNode other, Node dep) {
- return super.canDrop(other,dep) && !clobbered(dep);
- }
-
- // Check only immediate memory users. Writers clobber memory; Phis and
- // aggregates might lead to a clobber, so stop rather than search further.
- private boolean clobbered(Node dep) {
- Node mem = mem();
- dep.addDepForwards(mem);
- for( Node use : mem._outputs ) {
- if( use==null ) continue;
- dep.addDepForwards(use);
- if( use instanceof StoreNode || use instanceof PhiNode ||
- use instanceof MemMergeNode ||
- use instanceof NewNode || use instanceof EscapeNode || use instanceof CallNode ||
- use instanceof CLInitNode ) return true;
- }
- return false;
- }
-
- // Debugger label
- @Override public String label() { return "ld_"+mlabel(); }
- @Override
- protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p(".").p(_name); }
-
- @Override
- public Type compute() {
- Type mem0 = mem()._type;
- Type ptr0 = ptr()._type;
- // Validate argument types
- if( ptr0.isHigh() )
- return TypeScalar.TOP;
- if( !(mem0 instanceof TypeMem mem) )
- return mem0.isHigh() ? TypeScalar.TOP : TypeScalar.BOT;
- if( mem._t == Type.TOP )
- return TypeScalar.TOP;
- if( ptr0 == Type.NIL )
- return Type.BOTTOM; // Load from nil is error
- if( ptr0 == Type.BOTTOM )
- return Type.BOTTOM; // Error in is error out
- if( !(ptr0 instanceof TypeMemPtr tmp) )
- return scalar(mem._t);
- if( tmp.nullable() )
- return Type.BOTTOM; // Load from nil-able is error
-
- // Load field from object
- Field pfld = tmp._obj.field(_name);
- // No field? Open objects might yet get the field when falling;
- // closed objects with missing field are an error.
- if( pfld == null )
- return tmp.isHigh() || tmp._obj.isHigh() ? TypeScalar.TOP : TypeScalar.BOT;
-
- Type t = pfld._t;
- boolean readonly = t instanceof TypeMemPtr view && view._ro;
- // Load member of constant array
- if( t instanceof TypeConAry ary )
- t = ary.elem(); // TODO: if offset is known, can peek the constant
- if( mem._alias==_alias )
- t = t.join(mem._t);
-
- // A deeply read-only base produces a deeply read-only value. The
- // generic declared field type must not cast this information away.
- if( ptr0.isFinal() || readonly )
- t = t.makeRO();
- return scalar(t);
- }
-
- // Loads produce ordinary scalar values. Field/memory joins can still
- // expose the enclosing global lattice bounds through an uninitialized
- // Type.TOP/BOTTOM field; keep those results inside the scalar envelope.
- private static Type scalar(Type t) {
- return t==Type.TOP ? TypeScalar.TOP : t==Type.BOTTOM ? TypeScalar.BOT : t;
- }
-
- @Override
- public Node idealize() {
- Node mem = mem();
- Node ptr = ptr();
-
- // Null checks are handled by types. Array elements retain control for
- // range checks, but reading an array's length has no index to check.
- if( in(0)!=null && ptr._type instanceof TypeMemPtr tmp &&
- (!tmp._obj.isAry() || _name.equals("#")) ) {
- setDef(0,null);
- return this;
- }
-
- // Forward-ref loads eventually sharpen to a declared type
- Field fld;
- if( ptr._type instanceof TypeMemPtr tmp &&
- (fld=tmp._obj.field(_name)) != null &&
- _alias != fld._alias) {
- assert _alias==1 || _alias == fld._alias;
- assert !tmp._obj._open && !tmp._obj._fref;
- unlock(); // Alias participates in GVN semantics
- _alias = fld._alias;
- return this;
- }
- // Must sharpen alias first
- if( _alias == 1 )
- return null;
-
- Node slice = CLInitNode.slice(mem,_alias,this);
- if( slice!=mem ) { setDef(1,slice); return this; }
-
- if( mem instanceof MemMergeNode merge ) {
- setDef(1,merge.alias(_alias));
- // In support of uplifting control to a prior dominating load, if
- // we move our memory, recheck other loads to see if they can hoist.
- for( Node use : in(1).outs() )
- if( use instanceof LoadNode ld && use != this && ptr == ld.ptr() )
- CodeGen.CODE.add(ld);
-
- return this;
- }
-
- // Uplift control to a prior dominating load.
- for( Node memuse : mem._outputs )
- // Find a prior load, has same mem,ptr,off but higher ctrl
- if( memuse != this && memuse instanceof LoadNode ld && ptr==ld.ptr() && off()==ld.off() &&
- cfg0()!=null && cfg0().domLCA(ld.cfg0(),this) == ld.cfg0() && // Higher control means load is legal earlier
- addDep(ld)._type.isa(_type) ) // and not rolling backwards
- return ld;
-
- Node win = find(mem,ptr,null,null);
- if( win!=null ) return folded(win);
- if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1);
-
- // Push a Load up through a Phi, as long as it collapses on at least
- // one arm. If at a Loop, the backedge MUST collapse - else we risk
- // spinning the same transform around the loop indefinitely.
- // BEFORE (2 Sts, 1 Ld): AFTER (1 St, 0 Ld):
- // if( pred ) ptr.x = e0; val = pred ? e0
- // else ptr.x = e1; : e1;
- // val = ptr.x; ptr.x = val;
- if( mem() instanceof MemPhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 &&
- // Retain control on both copies only if it dominates both paths.
- // The Region itself, or a later range check, cannot move to its inputs.
- (in(0)==null || cfg0().domLCA(memphi.region().idom(this),this)==cfg0()) &&
- // Offset can be hoisted
- off() instanceof ConstantNode &&
- // Pointer can be hoisted
- hoistPtr(ptr,memphi) ) {
-
- // Returning to this memory means no change only if the pointer
- // is also loop invariant. A pointer Phi may select another object.
- Node stop = memphi.region() instanceof LoopNode &&
- !(ptr instanceof PhiNode p && p.region()==memphi.region()) ? memphi : null;
- if( find(memphi.in(2),ptr,stop,new BitSet())!=null ||
- (!(memphi.region() instanceof LoopNode) && find(memphi.in(1),ptr,null,new BitSet())!=null) ) {
- // Integer loop Phis widen to i64. Keep the load's declared
- // width at the result, as for ordinary typed loop variables.
- if( _type instanceof TypeInteger && !declaredType().isa(_type) ) return null;
- PhiNode phi = new PhiNode(_name,memphi.region()).keep();
- phi.setType(_type instanceof TypeInteger ? TypeInteger.BOT : _type);
- for( int i=1; i<memphi.nIns(); i++ ) {
- Node p = ptr instanceof PhiNode pp && pp.region()==memphi.region() ? pp.in(i) : ptr;
- phi.addDef(load(memphi.in(i),p,stop,phi));
- }
- CodeGen.CODE.add(phi.unkeep());
- return _type instanceof TypeInteger ? new ConvertNode(declaredType(),phi) : phi;
- }
- }
-
- return null;
- }
-
- // Load a flavored zero from a New
- private Node zero(NewNode nnn) {
- Type zero = declaredType().makeZero();
- assert zero.isa(_type); // Catch an uninitialized non-null field
- return castRO(new ConstantNode(zero).peephole());
- }
-
- // Search only; no new nodes or rewiring. Return a Store/New that folds,
- // the unchanged loop memory, or a merge whose every arm folds. Null fails.
- // Register dependencies so a later pointer/offset rewrite retries the query.
- private Node find(Node mem, Node ptr, Node stop, BitSet visit) {
- if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1);
- Field fld;
- boolean external = ptr._type instanceof TypeMemPtr tmp &&
- (fld=tmp._obj.field(_name))!=null && fld._extern;
- while( mem!=null ) {
- addDep(mem);
- if( mem==stop ) return mem;
- switch( mem ) {
- case MemMergeNode merge: mem=merge.alias(_alias); break;
- case StoreNode st:
- // Alias 1 is still unresolved, not a proven different field.
- if( st._alias==1 ) return null;
- addDep(st.ptr());
- if( _alias==st._alias && (ptr==st.ptr() || ptr==st.nnptr()) && off()==st.off() ) {
- // A full-width load cannot narrow the forwarded value.
- if( declaredType() instanceof TypeInteger ti && ti.log_size()==3 &&
- !addDep(st.val())._type.isa(_type) ) return null;
- return st;
- }
- if( (external || _alias==st._alias) && (external || !neverAlias(ptr,st.ptr())) &&
- !addDep(off())._type.join(addDep(st.off())._type).isHigh() ) return null;
- mem=st.mem();
- break;
- case ProjNode proj:
- // New's memory is private; public memory is joined at Escape.
- if( external || !(proj.in(0) instanceof NewNode nnn) ) return null;
- if( declaredType()==Type.BOTTOM ) return null;
- return nnn;
- case EscapeNode esc:
- if( external ) return null;
- addDep(esc.self());
- if( esc.self()==ptr ) { mem=esc.priv(); break; }
- if( neverAlias(ptr,esc.self()) ) { mem=esc.pub(); break; }
- return null;
- case MemPhiNode phi:
- addDep(phi.region());
- // Only the original loop closes a successful cycle. Another
- // loop or an unfinished merge needs a separate proof.
- if( visit==null || !(phi.region() instanceof RegionNode r) || r instanceof LoopNode ||
- r.inProgress() || phi.inProgress() || r._type!=Type.CONTROL ) return null;
- if( visit.get(phi._nid) ) return phi; // Already proved every arm.
- for( int i=1; i<phi.nIns(); i++ )
- if( find(phi.in(i),ptr,stop,visit)==null ) return null;
- visit.set(phi._nid);
- return phi;
- default: return null;
- }
- }
- return null;
- }
-
- private Node folded(Node win) {
- return win instanceof StoreNode st ? extend(castRO(st.val())) : zero((NewNode)win);
- }
-
- // Materialize only after the profitability search succeeds. The value Phi
- // stands for a load which comes back around the loop with unchanged memory.
- private Node load(Node mem, Node ptr, Node stop, PhiNode value) {
- Node win=find(mem,ptr,stop,new BitSet());
- if( win!=null ) {
- if( win==stop ) return value;
- if( win instanceof MemPhiNode mp ) {
- PhiNode phi=new PhiNode(_name,mp.region()).keep();
- for( int i=1; i<mp.nIns(); i++ ) phi.addDef(load(mp.in(i),ptr,stop,value));
- return CodeGen.CODE.add(phi.unkeep().peephole());
- }
- Node fold=folded(win);
- if( fold!=null ) return fold.peephole();
- }
- LoadNode ld=new LoadNode(_loc,_name,_alias,in(0),mem,ptr,off());
- return ld.peephole();
- }
-
- private static boolean neverAlias( Node ptr1, Node ptr2 ) {
- return ptr1.in(0) != ptr2.in(0) &&
- // Unrelated allocations
- ptr1 instanceof ProjNode && ptr1.in(0) instanceof NewNode &&
- ptr2 instanceof ProjNode && ptr2.in(0) instanceof NewNode;
- }
-
- private static boolean hoistPtr(Node ptr, PhiNode memphi ) {
- // Can I hoist ptr above the Region?
- if( !(memphi.region() instanceof RegionNode r) )
- return false; // Dead or dying Region/Phi
- // If ptr from same Region, then yes, just use hoisted split pointers
- if( ptr instanceof PhiNode pphi && pphi.region() == r )
- return true;
-
- // No, so can we lift this ptr?
- CFGNode cptr = ptr.cfg0();
- if( cptr != null )
- // Pointer is controlled high
- // TODO: Really needs to be the LCA of all inputs is high
- return cptr.idepth() <= r.idepth();
-
- // Dunno without a longer walk
- return false;
- }
-
- // Read-Only is a deep property, and cannot be cast-away
- private Node castRO(Node rez) {
- if( (ptr()._type.isFinal() || declaredType().isFinal()) && !rez._type.isFinal() )
- return new ReadOnlyNode(rez).peephole();
- return rez;
- }
-
- // When a load bypasses a store, the store might truncate bits - and the
- // load will need to zero/sign-extend.
- private Node extend(Node val) {
- if( !(declaredType() instanceof TypeInteger ti) ) return val;
- if( ti._min==0 ) // Unsigned
- return new AndNode(null,val,con(ti._max));
- // Signed extension
- int shift = Long.numberOfLeadingZeros(ti._max)-1;
- Node shf = con(shift);
- if( shift==0 ) {
- if( !val._type.isa(_type) )
- { addDep(val); return null; }
- return val;
- }
- Node shl = new ShlNode(null,val,shf.keep()).peephole();
- return new SarNode(null,shl,shf.unkeep());
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/LoopNode.java
deleted file mode 100644
index 005a6624..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/LoopNode.java
+++ /dev/null
@@ -1,106 +0,0 @@
-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 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); }
-
-
- public CFGNode entry() { return cfg(1); }
- public CFGNode back () { return cfg(2); }
-
- @Override
- public Type compute() {
- if( inProgress() ) return Type.CONTROL;
- return entry()._type;
- }
-
- // Bypass Region idom, same as the default idom() using use in(1) instead of in(0)
- @Override public int idepth() { return validIDepth() ? _idepth : cacheIDepth(entry().idepth()+1); }
- // Bypass Region idom, same as the default idom() using use in(1) instead of in(0)
- @Override public CFGNode idom(Node dep) { return entry(); }
-
- // If this is an unreachable loop, it may not have an exit. If it does not
- // (i.e., infinite loop), force an exit to make it reachable.
- public FunNode forceExit( FunNode fun, StopNode stop ) {
- // Walk the backedge, then immediate dominator tree util we hit this
- // Loop again. If we ever hit a CProj from an If (as opposed to
- // directly on the If) we found our exit.
- CFGNode x = back();
- while( x != this ) {
- if( x instanceof CProjNode exit && exit.in(0) instanceof IfNode iff ) {
- CFGNode other = iff.cproj(1-exit._idx);
- if( other!=null && other._ltree != _ltree && nested(_ltree,other._ltree) )
- return fun; // Found an exit, not an infinite loop
- }
- x = x.idom();
- }
- // Found a no-exit loop. Insert an exit
- NeverNode iff = (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 = fun._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();
- memout.addDef(f); // placeholder for control
- memout.addDef(top);
- for( Node u : _outputs )
- if( u instanceof PhiNode phi && phi._type.isa(TypeMem.BOT) )
- memout.addDef(phi);
- memout.init();
-
- Node ctrl = ret.ctrl(), mem = ret.mem(), expr = ret.expr();
- if( ctrl!=null && ctrl._type != Type.XCONTROL ) {
- // Perfect aligned exit?
- if( !(ctrl instanceof RegionNode r &&
- mem instanceof PhiNode pmem && pmem.region()==r &&
- expr instanceof PhiNode prez && prez.region()==r ) ) {
- // Nope, insert an aligned exit layer
- RegionNode r = new RegionNode(_loc,null,ctrl).init();
- ctrl = r; r._ltree = fun._ltree;
- mem = new BulkMemPhiNode(r,mem).init();
- expr = new PhiNode(r,expr).init();
- }
- // Append new Never exit
- ctrl.keep().addDef(f );
- mem .keep().addDef(memout);
- expr.keep().addDef(top);
- } else {
- ctrl = f .keep();
- mem = memout.keep();
- expr = top .keep();
- }
- ret.setDef(0,ctrl.unkeep());
- ret.setDef(1,mem .unkeep());
- ret.setDef(2,expr.unkeep());
- // Force sane types to exit; might locally break monotonicity
- ret.init();
- ret.out(0).init();
- stop.init();
- CodeGen.CODE.add(CodeGen.CODE._start);
- CodeGen.CODE.add(mem);
-
- return fun;
- }
-
- private static boolean nested(LoopTree inner, LoopTree outer) {
- for( ; inner!=null; inner = inner._par )
- if( inner == outer )
- return true;
- return false;
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java
deleted file mode 100644
index b25fcaaf..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.type.Type;
-import java.io.ByteArrayOutputStream;
-
-// Generic machine-specific class, has a few Node implementations that have to
-// exist (abstract) but are not useful past the optimizer.
-public abstract class MachConcreteNode extends Node implements MachNode {
-
- public MachConcreteNode(Node node) { super(node); }
- public MachConcreteNode(Node[]nodes) { super(nodes); }
-
- @Override public String label() { return op(); }
- @Override public Type compute () { throw new AssertionError("Do not call compute on "+getClass().getSimpleName()); }
- @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); }
-
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- p.open().p(op()).p(",");
- for( int i=1; i<nIns(); i++ )
- (in(i)==null ? p.p(" ---") : p.n(in(i))).p(",");
- return p.unchar(',').close();
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MachNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/MachNode.java
deleted file mode 100644
index 025b5dc4..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MachNode.java
+++ /dev/null
@@ -1,60 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
deleted file mode 100644
index cbf19c55..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
+++ /dev/null
@@ -1,154 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.*;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.Utils;
-
-import java.util.*;
-
-/**
- * Memory Merge - a merge of many aliases into a "fat memory". All aliases
- * are here, but most will be lazy - take the default fat memory.
- */
-public class MemMergeNode extends Node {
-
- public MemMergeNode( Node ...nodes) { super(nodes); _type = TypeMem.BOT; }
- public MemMergeNode(MemMergeNode mem) { super(mem); }
-
-
- @Override public String label() { return "ALLMEM"; }
- @Override public boolean isMem() { return true; }
-
- @Override
- protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- p.p("MEM[ ");
- for( int j=2; j<nIns(); j++ ) {
- p.p(j);
- p.p(":");
- Node n = in(j);
- while( n instanceof ScopeNode loop ) {
- p.p("Lazy_");
- n = loop.mem();
- }
- if( n==null ) p.p("___ ");
- else p.n(n).p(" ");
- }
- return p.unchar(' ').p("]");
- }
-
-
- @Override public Type compute() {
- Type tmem = in(1)._type;
- if( !(tmem instanceof TypeMem defmem) )
- // A MemMerge is structurally memory even while its default input
- // is a weak, not-yet-specialized Phi.
- return tmem.isHigh() ? TypeMem.TOP : TypeMem.BOT;
- // Is this a single private instance memory?
- if( defmem._one ) {
- if( !(defmem._t instanceof TypeStruct ts) )
- return defmem;
- // Perfect singleton memory, so all updates are parallel and
- // independent and stack.
- for( int i=2; i<nIns(); i++ ) {
- Node in = in(i);
- if( in !=null && in._type.isHigh() )
- return TypeMem.TOP;
- if( in instanceof StoreNode st ) {
- Type val = ((TypeMem)st._type)._t;
- Field old = ts.field(st._name);
- // Lazy add an expected field for open structs.
- if( old==null ) { assert ts._open;
- old = Field.make(st._name,Type.BOTTOM,st._alias,st._init);
- ts = ts.add(old);
- }
- Field fld = old.makeFrom(val);
- // TODO: val leaks into perfect singleton memory, along with its fidxs and aliases
- ts = ts.replace(fld);
- }
- }
- return TypeMem.makePrivate(ts);
-
- }
-
- // Normal public memory; meet across escaped inputs
- TypeMem mem = defmem;
- for( int i=2; i<nIns(); i++ ) {
- // Has this alias escaped?
- if( XInt.bit(mem._escAs,i) &&
- in(i) != null && !in(i)._type.isHigh() ) {
- mem = (TypeMem)mem.meet(in(i)._type);
- }
- }
- return mem;
- }
-
- @Override public Node idealize() {
- assert nIns() != 0 && in(1)!=null; // Always have a default. if( nIns()==0 ) return null;
-
- // Fold defaults into the default
- boolean progress=false, allDefault=true;
- for( int i=2; i<nIns(); i++ ) {
- if( in(1) == in(i) ) { setDef(i,null); progress=true; }
- else { allDefault=false; }
- }
-
- // If not merging any memory (all memory is just the default)
- if( allDefault )
- return in(1); // Become default memory
-
- // Collapse stacked merged-mem
- if( in(1) instanceof MemMergeNode mem ) {
- // A dead memory cycle can lose its last use when we bypass mem.
- // Keep the peephole's result alive until the worklist sees it.
- keep();
- // Goal is to swap my default mem with mem's default mem
- for( int i=2; i<mem.nIns(); i++ ) {
- if( mem.in(i) != null ) {
- // deeper default mem has a non-default
- if( i>=nIns() || in(i)==null )
- setDefX(i,mem.in(i));
- }
- }
- CodeGen.CODE.add(mem.in(1));
- setDef(1,mem.in(1));
- return unkeep();
- }
-
- return progress ? this : null;
- }
-
-
- public Node in( Var v ) { return in(v._idx); }
-
- public Node alias( int alias ) {
- assert !(in(1) instanceof BulkMemPhiNode bulk && bulk.isSplit(alias) &&
- (alias >= nIns() || in(alias)==null));
- return alias < nIns() && in(alias)!=null ? in(alias) : in(1);
- }
-
- public Node alias( int alias, Node st ) { return setDefX(alias,st); }
-
- // Now one-time do a useless-phi removal
- void _useless( ) {
- for( int i=1; i<nIns(); i++ ) {
- if( in(i) instanceof PhiNode phi ) {
- // Do an eager useless-phi removal
- Node in = phi.peephole();
- CodeGen.CODE.addAll(phi._outputs);
- phi.moveDepsToWorklist();
- if( in != phi ) {
- if( !phi.iskeep() ) // Keeping phi around for parser elsewhere
- phi.subsume(in);
- setDef(i,in); // Set the update back into Scope
- }
- }
- }
- }
-
- @Override public boolean eq( Node n ) {
- return this==n;
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
deleted file mode 100644
index fa399e1d..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
+++ /dev/null
@@ -1,140 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.*;
-import java.lang.StringBuilder;
-import java.util.*;
-
-/**
- * Convenience common base for Load and Store.
- *
- * 0 - Control
- * 1 - Memory
- * 2 - Base of object, OOP
- * 3 - Offset, integer types
- * 4 - Value, for Stores only
- */
-public abstract class MemOpNode extends TypeNode {
-
- // The equivalence alias class
- public int _alias;
-
- // True if load-like, false if store-like.
- // Used on CPU-specific combined memory+arithmetic ops.
- //
- // Stores produce memory (maybe as part of a tuple with other things),
- // loads do not.
- //
- // Loads might pick up anti-dependencies on prior Stores, and never cause an
- // anti-dependence themselves.
- //
- // Stores must maximally sink to the least dominator use. Loads can be
- // opportunistically hoisted.
- public final boolean _isLoad;
-
- // Field name.
- public final String _name;
- // Source location for late reported errors
- public final Parser.Lexer _loc;
-
- public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type decl, Node ctrl, Node mem, Node ptr, Node off) {
- super(decl, ctrl, mem, ptr, off);
- _name = name;
- _alias = alias;
- _loc = loc;
- _isLoad = isLoad;
- }
- public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type decl, Node ctrl, Node mem, Node ptr, Node off, Node value) {
- this(loc, name, alias, isLoad, decl, ctrl, mem, ptr, off);
- addDef(value);
- }
- public MemOpNode( Node ideal, MemOpNode mop ) {
- super(ideal,mop instanceof LoadNode ld ? ld.declaredType() : mop._con);
- _name = mop._name;
- _alias = mop._alias;
- _loc = mop._loc;
- _isLoad= mop._isLoad;
- }
-
-
- static String mlabel(String name) { return "[]"==name ? "ary" : ("#"==name ? "len" : name); }
- String mlabel() { return mlabel(_name); }
-
- public Node mem() { return in(1); }
- public Node ptr() { return in(2); }
- public Node off() { return in(3); }
-
- // Current declared field type. This is deliberately a live lookup:
- // cyclic/forward types can sharpen by replacing the pointer's struct.
- public Type declaredType() {
- if( !(ptr()._type instanceof TypeMemPtr tmp) )
- return Type.BOTTOM;
- Field fld = tmp._obj.field(_name);
- return fld == null || fld._t == null ? Type.BOTTOM : fld._t;
- }
-
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return _printMach(p); }
- protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { throw Utils.TODO(); }
-
-
- // Extra conditions for factoring matching memory operations through a Phi.
- // The caller has already checked the opcode, input shape and control.
- boolean canDrop(MemOpNode other, Node dep) {
- dep.addDep(ptr());
- return _alias>1 && _alias==other._alias && declaredType()==other.declaredType() && err()==null;
- }
-
- @Override
- public boolean eq(Node n) {
- MemOpNode mem = (MemOpNode)n; // Invariant
- return _alias==mem._alias && super.eq(mem);
- }
-
- @Override
- int hash() { return _alias ^ super.hash(); }
-
- @Override
- public Parser.ParseException err() {
- Type ptr = ptr()._type;
- // Already an error, but better error messages come from elsewhere
- if( ptr == Type.BOTTOM ) return null;
- if( ptr.isHigh() ) return null; // Assume it will fall to not-null
- // Better be a not-nil TMP
- if( !(ptr instanceof TypeMemPtr tmp && tmp.notNull()) )
- return Parser.error( "Might be null accessing '" + _name + "'",_loc);
- if( tmp._obj.field(_name)==null &&
- CodeGen.CODE._phase.ordinal() > CodeGen.Phase.FinalizeIR.ordinal() )
- return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc);
- // Sane field
- if( off() instanceof ConFldOffNode && CodeGen.CODE._phase.ordinal() > CodeGen.Phase.FinalizeIR.ordinal() )
- return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc);
- return null;
- }
-
- // Memory writers which must follow this read. A packaging node is not a
- // write; follow this alias through aggregates to its writers.
- public Ary<Node> antiDeps( Ary<Node> antideps ) {
- antideps.clear();
- return antiDeps(mem(),antideps);
- }
-
- private Ary<Node> antiDeps( Node mem, Ary<Node> antideps ) {
- for( Node use : mem._outputs ) {
- if( use instanceof MemMergeNode merge ) {
- if( merge.alias(_alias)==mem )
- antiDeps(merge,antideps);
- } else if( use instanceof CallNode || use instanceof CLInitNode ||
- (use instanceof MemOpNode st && !st._isLoad && st._alias==_alias) ||
- (use instanceof EscapeNode esc && esc.fld()._alias==_alias) ||
- (use instanceof MemPhiNode phi && phi._alias==_alias) ||
- (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) {
- if( !antideps.contains(use) ) antideps.add(use);
- }
- }
- return antideps;
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
deleted file mode 100644
index 5ae716a7..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeMem;
-
-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 Type compute() {
- Type t = super.compute();
- assert BulkMemPhiNode.checkMem(this,_alias);
- if( !(t instanceof TypeMem tmem) ) return t;
- assert tmem._alias==1 || tmem._alias==_alias;
- // This Phi structurally knows its alias, but not whether the field is
- // final. Do not infer slice finality from evolving input types.
- return TypeMem.make(_alias,tmem._t,tmem._one,tmem._clz,false,tmem._escFs,tmem._escAs);
- }
-
- @Override
- public Node idealize() {
- // A precise memory Phi can always bypass a MemMerge and select its
- // matching slice.
- for( int i=1; i<nIns(); i++ ) {
- if( in(i) instanceof MemMergeNode mem ) {
- setDef(i,mem.alias(_alias));
- return this;
- }
- Node slice = CLInitNode.slice(in(i),_alias,this);
- if( slice!=in(i) ) { setDef(i,slice); return this; }
- }
- if( nIns()==3 && region() instanceof LoopNode loop && !loop.inProgress() &&
- in(2) instanceof StoreNode st && st.mem()==this ) {
- Node sink = st.sink(this);
- if( sink!=null ) return sink;
- }
- return super.idealize();
- }
-
- @Override public boolean eq(Node n) {
- return _alias==((MemPhiNode)n)._alias && super.eq(n);
- }
-
- @Override int hash() { return _alias; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/MinusNode.java
deleted file mode 100644
index 4d7e3ddb..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MinusNode.java
+++ /dev/null
@@ -1,77 +0,0 @@
-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.Utils;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-
-public class MinusNode extends Node implements ModeNode {
- // Mode is 0 for unknown, 1 for int, 2 for flt
- byte _mode;
-
- public MinusNode(Node in) { super(null, in); }
- public MinusNode(Node in, byte mode) { super(null, in); _mode = mode; }
-
-
- @Override public byte mode() { return _mode; }
- @Override public Node setMode(byte mode) {
- assert _mode==0 && (mode==1 || mode==2);
- unlock(); // _mode participates in GVN hash and equality
- _mode = mode;
- return this;
- }
-
- @Override protected String format() { return "(-%1)"; }
-
- @Override
- public Type compute() {
- Type t = in(1)._type;
- if( t==null )
- return Type.TOP;
- if( t.isHigh() )
- return _mode==0 ? Type.TOP :
- _mode==1 ? TypeInteger.TOP :
- t instanceof TypeFloat ? t : TypeFloat.F64.dual();
- byte mode = _mode;
- if( mode==0 ) {
- if( t instanceof TypeFloat ) mode=2;
- if( t instanceof TypeInteger ) mode=1;
- }
- if( mode==0 )
- return CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() ? Type.BOTTOM : Type.TOP;
- 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 && addDep(minus.in(1))._type.isa(compute()) )
- return minus.in(1);
-
- // Can we decide int vs flt?
- if( _mode==0 ) {
- byte mode = (byte)(
- in(1)._type instanceof TypeInteger ? 1 :
- in(1)._type instanceof TypeFloat ? 2 : 0);
- if( mode!=0 ) return setMode(mode).init();
- }
-
- return null;
- }
- @Override public Parser.ParseException err() {
- return ArithNode.nodeErr(this,null,"-",_mode,true);
- }
- @Override public boolean eq( Node n ) { return _mode==((MinusNode)n)._mode; }
- @Override public int hash() { return _mode; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ModeNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ModeNode.java
deleted file mode 100644
index dc635447..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ModeNode.java
+++ /dev/null
@@ -1,12 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/MulNode.java
deleted file mode 100644
index 49c6432a..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MulNode.java
+++ /dev/null
@@ -1,65 +0,0 @@
-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 String op() { return "*"; }
-
- @Override long doOp( long x, long y ) { return x * y; }
- @Override double doOp(double x, double y ) { return x * y; }
- @Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
- return TypeInteger.BOT;
- }
-
- @Override
- public Node idealize() {
- Node lhs = in(1);
- Node rhs = in(2);
- Type t1 = lhs._type;
- Type t2 = rhs._type;
-
- // Move constants to RHS: con*arg becomes arg*con
- if ( t1.isConstant() && !t2.isConstant() )
- return swap12();
-
- // Multiply by constant
- if ( t2.isConstant() && t2 instanceof TypeInteger i ) {
- // Mul of 1. We do not check for (1*x) because this will already
- // canonicalize to (x*1)
- long c = i.value();
- if( c==1 ) return lhs._type.isa(compute()) ? lhs : super.idealize();
- if( c==0 ) return CodeGen.CODE.ZERO;
- // Mul by a power of 2, +/-1. Bit patterns more complex than this
- // are unlikely to win on an X86 vs the normal "imul", and so
- // become machine-specific.
- if( (c & (c-1)) == 0 )
- return new ShlNode(null,lhs,con(Long.numberOfTrailingZeros(c)));
- // 2**n + 1, e.g. 9
- long d = c-1;
- if( (d & (d-1)) == 0 )
- return new AddNode(new ShlNode(null,lhs,con(Long.numberOfTrailingZeros(d))).peephole(),lhs);
- // 2**n - 1, e.g. 31
- long e = c+1;
- if( (e & (e-1)) == 0 )
- return new SubNode(new ShlNode(null,lhs,con(Long.numberOfTrailingZeros(e))).peephole(),lhs);
-
- }
-
- // Do we have ((x * (phi cons)) * con) ?
- // Do we have ((x * (phi cons)) * (phi cons)) ?
- // Push constant up through the phi: x * (phi con0*con0 con1*con1...)
- Node phicon = AddNode.phiCon(this,true);
- if( phicon!=null ) return phicon;
-
- return super.idealize();
- }
- @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs,_mode); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/MultiNode.java
deleted file mode 100644
index 152d525a..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/MultiNode.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.util.Ary;
-
-
-public interface MultiNode {
-
- abstract Ary<Node> outs();
-
- // Find a projection by index
- default ProjNode proj( int idx ) {
- for( Node out : outs() )
- if( out instanceof ProjNode prj && prj._idx==idx )
- return prj;
- return null;
- }
-
- // Find a projection by index
- default CProjNode cproj( int idx ) {
- for( Node out : outs() )
- if( out instanceof CProjNode prj && prj._idx==idx )
- return prj;
- return null;
- }
-
- // Return not-null if this projection index is a ideal copy.
- // Called by ProjNode ideal and used to collapse Multis.
- default Node pcopy(int idx, Node dep) { return null; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/NeverNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/NeverNode.java
deleted file mode 100644
index a7b4cf65..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/NeverNode.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.type.TypeInteger;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeTuple;
-
-
-// "Never true" for infinite loop exits
-public class NeverNode extends IfNode {
- public NeverNode(NeverNode ctrl) { super(ctrl); }
- public NeverNode(float ignore, Node ctrl) { super(ctrl,null); }
-
-
- @Override public String label() { return "Never"; }
-
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("Never"); }
-
- @Override public Type compute() { return TypeTuple.IF_BOTH; }
-
- @Override public Node idealize() { return null; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/NewNode.java
deleted file mode 100644
index 5cbe1b61..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/NewNode.java
+++ /dev/null
@@ -1,98 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.SB;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-
-/**
- * Allocation! Allocate a chunk of memory, no init.
- * Takes in ctrl and size, and TypeStruct.
- * Produces a ptr and private memory.
-*/
-public class NewNode extends Node implements MultiNode {
- public TypeStruct _ts;
- public NewNode( TypeStruct ts, Node ctrl, Node size ) { super(ctrl,size); _ts = ts; }
-
- public Node ctrl() { return in(0); }
- public Node size() { return in(1); }
-
- // --- Serialization
- public NewNode(NewNode nnn) { super(nnn); _ts = nnn._ts; }
-
-
- @Override public String label() { return "new_"+(_ts.isAry() ? "ary_"+_ts._fields[1]._t.str() : _ts.str()); }
-
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- return p.p("new ").p(_ts.str());
- }
-
- @Override public TypeTuple compute() {
- if( ctrl()._type.isHigh() )
- return TypeTuple.DEAD_NEW;
- TypeStruct ts = _ts.isAry() ? _ts : _ts.makeInit();
- return TypeTuple.make(TypeMemPtr.make(_ts),
- // Arrays get pre-zeroed by constructor.
- // Objects start with default-zero memory for
- // fields that can be zero-initialized. This is
- // still private memory, so no aliases escape yet.
- TypeMem.make(1,ts,true,false,false,null,null));
- }
-
- @Override public Node idealize() { return null; }
-
- @Override boolean _upgradeType( HashMap<String,Type> TYPES) {
- Type old = _ts; _ts = (TypeStruct)_ts.upgradeType(TYPES); return old != _ts;
- }
-
- // Two NewNodes are never equal; NewNodes always make independent pointers
- // so they are not functional - they internally bump-ptr allocate always a
- // new pointer.
- @Override public boolean eq(Node n) { return this==n; }
-
- @Override int hash() { return _ts.hashCode(); }
-
-
- @Override public Parser.ParseException err() {
- return _ts._fref ? Parser.error("Unknown struct type '"+_ts._name+"'",null) : null;
- }
-
-
- // ------------
- // MachNode specifics, shared across all CPUs
- public int _arg2Reg, _xslot;
- private RegMask _arg3Mask;
- private RegMask _retMask;
- private RegMask _kills;
- public void cacheRegs(CodeGen code) {
- _arg2Reg = code._mach.callArgMask(TypeFunPtr.CALLOC,2,0,code._cCallingConv).firstReg();
- _arg3Mask = code._mach.callArgMask(TypeFunPtr.CALLOC,3,0,code._cCallingConv);
- // Return mask depends on TFP (either GPR or FPR)
- _retMask = code._mach.retMask(TypeFunPtr.CALLOC);
- // Kill mask is all caller-saves, and any mirror stack slots for args
- // in registers.
- RegMaskRW kills = code._cCallerSave.copy();
- // Start of stack slots
- int maxReg = code._mach.regs().length;
- // Incoming function arg slots, all low numbered in the RA
- int fslot = cfg0().fun()._maxArgSlot;
- // Killed slots for this calls outgoing args
- int xslot = code._mach.maxArgSlot(TypeFunPtr.CALLOC,code._cCallingConv);
- _xslot = (maxReg+fslot)+xslot;
- for( int i=0; i<xslot; i++ )
- kills.set((maxReg+fslot)+i);
- _kills = kills;
- }
- public String op() { return "alloc"; }
- public RegMask regmap(int i) { return i==1 ? _arg3Mask : null; }
- public RegMask outregmap() { return null; }
- public RegMask outregmap(int idx) { return idx==0 ? _retMask : null; }
- public RegMask killmap() { return _kills; }
- public void asm(CodeGen code, SB sb) {
- sb.p("#malloc, ").p(code.reg(size()));
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/Node.java
deleted file mode 100644
index de39ca80..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/Node.java
+++ /dev/null
@@ -1,696 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.BaseNode;
-
-import com.seaofnodes.simple.IterPeeps;
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.print.IRPrinter;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.*;
-import java.util.*;
-import java.util.function.Function;
-import static com.seaofnodes.simple.codegen.CodeGen.CODE;
-
-/**
- * All Nodes in the Sea of Nodes IR inherit from the Node class.
- * The Node class provides common functionality used by all subtypes.
- * Subtypes of Node specialize by overriding methods.
- */
-public abstract class Node extends BaseNode<Node> implements Cloneable {
-
- /**
- * Inputs to the node. These are use-def references to Nodes.
- * <p>
- * Generally fixed length, ordered, nulls allowed, no unused trailing space.
- * Ordering is required because e.g. "a/b" is different from "b/a".
- * The first input (offset 0) is often a {@link CFGNode} node.
- */
- public Ary<Node> _inputs;
-
- /**
- * Outputs reference Nodes that are not null and have this Node as an
- * input. These nodes are users of this node, thus these are def-use
- * references to Nodes.
- * <p>
- * Outputs directly match inputs, making a directed graph that can be
- * walked in either direction. These outputs are typically used for
- * efficient optimizations but otherwise have no semantics meaning.
- */
- public Ary<Node> _outputs;
-
- /**
- * Current computed type for this Node. This value changes as the graph
- * changes and more knowledge is gained about the program.
- */
- public Type _type;
-
- Node(Node... inputs) {
- super(CODE.getUID()); // allocate unique dense ID
- _inputs = new Ary<>(Node.class);
- Collections.addAll(_inputs,inputs);
- _outputs = new Ary<>(Node.class);
- for( Node n : _inputs )
- if( n != null )
- n.addUse( this );
- }
-
- // Make a Node using the existing arrays of nodes.
- // Used by any pass rewriting all Node classes but not the edges.
- Node( Node n ) {
- super(CODE.getUID()); // allocate unique dense ID
- assert CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Select.ordinal();
- _inputs = new Ary<>(n==null ? new Node[0] : n._inputs.asAry());
- _outputs = new Ary<>(Node.class);
- _type = n==null ? Type.BOTTOM : n._type;
- _deps = null;
- _hash = 0;
- }
-
- // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ"
- public String label() { return getClass().getSimpleName().replace("Node",""); }
-
- public String p(int depth) { return IRPrinter.prettyPrint(this,depth); }
-
- public boolean isConst() { return false; }
-
- // ------------------------------------------------------------------------
- // Graph Node & Edge manipulation
-
- @Override public String typeName() { return _type==null ? null : _type.toString(); }
-
- /**
- * Gets the ith input node
- * @param i Offset of the input node
- * @return Input node or null
- */
- public Node in(int i) { return _inputs.get(i); }
- public Node out(int i) { return _outputs.get(i); }
- public final Ary<Node> outs() { return _outputs; }
-
- public int nIns() { return _inputs.size(); }
-
- public int nOuts() { return _outputs.size(); }
-
- public boolean isUnused() { return nOuts() == 0; }
-
- public CFGNode cfg0() { return (CFGNode)in(0); }
-
- /**
- * Change a <em>def</em> into a Node. Keeps the edges correct, by removing
- * the corresponding <em>use->def</em> edge. This may make the original
- * <em>def</em> go dead. This function is co-recursive with {@link #kill}.
- * <p>
-
- * This method is the normal path for altering a Node, because it does the
- * proper default edge maintenance. It also <em>immediately</em> kills
- * Nodes that lose their last use; at times care must be taken to avoid
- * killing Nodes that are being used without having an output Node. This
- * definitely happens in the middle of recursive {@link #peephole} calls.
- *
- * @param idx which def to set
- * @param new_def the new definition
- * @return new_def for flow coding
- */
- public <N extends Node> N setDef(int idx, N new_def ) {
- unlock();
- Node old_def = in(idx);
- if( old_def == new_def ) return new_def; // No change
- // If new def is not null, add the corresponding def->use edge
- // This needs to happen before removing the old node's def->use edge as
- // the new_def might get killed if the old node kills it recursively.
- if( new_def != null )
- new_def.addUse(this);
- // Set the new_def over the old (killed) edge
- _inputs.set(idx,new_def);
- if( old_def != null ) { // If the old def exists, remove a def->use edge
- if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead
- old_def.kill(); // Kill old def
- else CODE.add(old_def); // Else old lost a use, so onto worklist
- }
- moveDepsToWorklist();
- // Return new_def for easy flow-coding
- return new_def;
- }
- public <N extends Node> N setDefX(int idx, N new_def ) {
- while( nIns() <= idx ) addDef(null);
- return setDef(idx,new_def);
- }
-
- // Remove the numbered input, compressing the inputs in-place. This
- // shuffles the order deterministically - which is suitable for Region and
- // Phi, but not for every Node. If the def goes dead, it is recursively
- // killed, which may include 'this' Node.
- public Node delDef(int idx) {
- unlock();
- Node old_def = in(idx);
- _inputs.del(idx);
- if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead
- old_def.kill(); // Kill old def
- old_def.moveDepsToWorklist();
- return this;
- }
-
- // Insert the numbered input, sliding other inputs to the right
- public void insertDef(int idx, Node new_def) {
- _inputs.insert(null,idx);
- setDef(idx,new_def);
- }
-
- /**
- * Add a new def to an existing Node. Keep the edges correct by
- * adding the corresponding <em>def->use</em> edge.
- *
- * @param new_def the new definition, appended to the end of existing definitions
- * @return new_def for flow coding
- */
- public Node addDef(Node new_def) {
- unlock();
- // Add use->def edge
- _inputs.add(new_def);
- // If new def is not null, add the corresponding def->use edge
- if( new_def != null )
- new_def.addUse(this);
- return new_def;
- }
-
- // Breaks the edge invariants, used temporarily
- @SuppressWarnings("unchecked")
- protected <N extends Node> N addUse(Node n) { _outputs.add(n); return (N)this; }
-
- // Remove node 'use' from 'def's (i.e. our) output list, by compressing the list in-place.
- // Return true if the output list is empty afterward.
- // Error is 'use' does not exist; ok for 'use' to be null.
- public boolean delUse( Node use ) {
- _outputs.del(_outputs.find(use));
- moveDepsToWorklist(); // User-count and anti-dependence queries can now change.
- return _outputs.isEmpty();
- }
-
- // Shortcut for "popping" until n nodes. A "pop" is basically a
- // setDef(last,null) followed by lowering the nIns() count.
- public void popUntil(int n) {
- unlock();
- while( nIns() > n ) {
- Node old_def = _inputs.pop();
- if( old_def != null && // If it exists and
- old_def.delUse(this) ) // If we removed the last use, the old def is now dead
- old_def.kill(); // Kill old def
- }
- }
- public Node removeLast() {
- Node old_def = _inputs.pop();
- if( old_def != null )
- old_def.delUse(this);
- return old_def;
- }
-
- /**
- * Kill a Node with no <em>uses</em>, by setting all of its <em>defs</em>
- * to null. This may recursively kill more Nodes, and is basically dead
- * code elimination.
- */
- public void kill( ) {
- assert isUnused(); // Has no uses, so it is dead
- unlock();
- moveDepsToWorklist();
- _type=null; // Flag as dead
- while( nIns()>0 ) { // Set all inputs to null, recursively killing unused Nodes
- Node old_def = _inputs.removeLast();
- // Revisit neighbor because removed use
- if( old_def != null ) {
- if( CODE.add(old_def).delUse(this) )
- old_def.kill(); // If we removed the last use, the old def is now dead
- old_def.moveDepsToWorklist(); // Use-count changes can enable distant rewrites.
- }
- }
- assert isDead(); // Really dead now
- }
-
- // Preserve CFG use-ordering when killing
- public void killOrdered() {
- CFGNode cfg = cfg0();
- cfg._outputs.remove(cfg._outputs.find(this));
- _inputs.set(0,null);
- kill();
- }
-
- // Mostly used for asserts and printing.
- public boolean isDead() { return isUnused() && nIns()==0 && _type==null; }
-
- // Shortcuts to stop DCE mid-parse
- // Add bogus null use to keep node alive
- public <N extends Node> N keep() { return addUse(null); }
- // Remove bogus null.
- @SuppressWarnings("unchecked")
- public <N extends Node> N unkeep() {
- delUse(null);
- return (N)this;
- }
- // Test "keep" status
- public boolean iskeep() { return _outputs.find(null) != -1; }
- public void unkill() {
- if( unkeep().isUnused() )
- kill();
- }
- public void isKill() {
- if( isUnused() )
- kill();
- }
-
- // Replace self with nnn in the graph, making 'this' go dead
- public void subsume( Node nnn ) {
- assert nnn!=this;
- while( nOuts() > 0 ) {
- Node n = _outputs.removeLast();
- n.unlock();
- int idx = n._inputs.find(this);
- n._inputs.set(idx,nnn);
- nnn.addUse(n);
- CODE.add(n);
- CODE.addAll(n._outputs);
- }
- kill();
- }
-
- // insert `this` immediately after `def` in the same basic block.
- public void insertAfter( Node def ) {
- CFGNode cfg = def.cfg0();
- int i = cfg._outputs.find(def)+1;
- if( cfg instanceof CallEndNode ) {
- cfg = cfg.uctrl(); i=0;
- } else if( def.in(0) instanceof MultiNode ) {
- assert i==0;
- i = cfg._outputs.find(def.in(0))+1;
- }
-
- while( cfg.out(i) instanceof PhiNode || cfg.out(i) instanceof CalleeSaveNode ) i++;
- cfg._outputs.insert(this,i);
- _inputs.set(0,cfg);
- }
-
- // Insert this in front of use.in(uidx) with this, and insert this
- // immediately before use in the basic block.
- public void insertBefore( Node use, int uidx ) {
- CFGNode cfg = use.cfg0();
- int i;
- if( use instanceof PhiNode phi ) {
- cfg = phi.region().cfg(uidx);
- if( cfg instanceof CProjNode && cfg.in(0) instanceof NeverNode nvr )
- cfg = nvr.cfg0();
- i = cfg.nOuts()-1;
- } else {
- i = cfg._outputs.find(use);
- }
- cfg._outputs.insert(this,i);
- _inputs.set(0,cfg);
- if( _inputs._len > 1 && this instanceof SplitNode )
- setDefOrdered(1,use.in(uidx));
- use.setDefOrdered(uidx,this);
- }
-
- public void setDefOrdered( int idx, Node def) {
- // If old is dying, remove from CFG ordered
- Node old = in(idx);
- if( old!=null && old.nOuts()==1 ) {
- CFGNode cfg = old.cfg0();
- if( cfg!=null ) {
- cfg._outputs.remove(cfg._outputs.find(old));
- old._inputs.set(0,null);
- }
- }
- setDef(idx,def);
- }
-
- public void removeSplit() {
- CFGNode cfg = cfg0();
- cfg._outputs.remove(cfg._outputs.find(this));
- _inputs.set(0,null);
- if( _inputs._len > 1 ) subsume(in(1));
- }
-
- // ------------------------------------------------------------------------
- // Graph-based optimizations
-
- /**
- * Try to peephole at this node and return a better replacement Node.
- * Always returns some not-null Node (often this).
- */
- public final Node peephole( ) {
- var obs = CODE._midAssert ? null : CODE._obs;
- if( obs != null ) obs.before(this);
- Node n = peepholeOpt();
- Node rez = n==null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n);
- if( obs != null ) obs.after(this, n==null ? null : rez, false);
- return rez; // Cannot return null for no-progress
- }
-
- /**
- * Try to peephole at this node and return a better replacement Node if
- * possible. We compute a {@link Type} and then check and replace:
- * <ul>
- * <li>if the Type {@link Type#isConstant}, we replace with a {@link ConstantNode}</li>
- * <li>in a future chapter we will look for a
- * <a href="https://en.wikipedia.org/wiki/Common_subexpression_elimination">Common Subexpression</a>
- * to eliminate.</li>
- * <li>we ask the Node for a better replacement. The "better replacement"
- * is things like {@code (1+2)} becomes {@code 3} and {@code (1+(x+2))} becomes
- * {@code (x+(1+2))}. By canonicalizing expressions we fold common addressing
- * math constants, remove algebraic identities and generally simplify the
- * code. </li>
- *
- * Unlike peephole above, this explicitly returns null for no-change, or not-null
- * for a better replacement (which can be this).
- * </ul>
- */
- public final Node peepholeOpt( ) {
- CODE.iterCnt();
- // Compute initial or improved Type
- Type old = setType(compute());
-
- // Replace constant computations from non-constants with a constant node.
- if( !isConst() && !(this instanceof ParmNode parm && parm.inProgress()) &&
- _type.isHighOrConst() )
- return ConstantNode.make(_type).peephole();
-
- // Global Value Numbering
- if( _hash==0 ) {
- Node n = CODE._gvn.get(this); // Will set _hash as a side effect
- if( n==null )
- CODE._gvn.put(this,this); // Put in table now
- else {
- // Because of random worklist ordering, the two equal nodes
- // might have different types. Because of monotonicity, both
- // types are valid. To preserve monotonicity, the resulting
- // shared Node has to have the best of both types.
- n.setType(n._type.join(_type));
- _hash = 0; // Clear, since it never went in the table
- return deadCodeElim(n);// Return previous; does Common Subexpression Elimination
- }
- }
-
- // Ask each node for a better replacement
- Node n = idealize();
- if( n != null ) // Something changed
- return n; // Report progress
-
- if( old!=_type ) return this; // Report progress;
- CODE.iterNop();
- return null; // No progress
- }
-
- // m is the new Node, self is the old.
- // Return 'm', which may have zero uses but is alive nonetheless.
- // If self has zero uses (and is not 'm'), {@link #kill} self.
- private Node deadCodeElim(Node m) {
- // If self is going dead and not being returned here (Nodes returned
- // from peephole commonly have no uses (yet)), then kill self.
- if( m != this && isUnused() && !isDead() ) {
- // Killing self - and since self recursively kills self's inputs we
- // might end up killing 'm', which we are returning as a live Node.
- // So we add a bogus extra null output edge to stop kill().
- m.keep(); // Keep m alive
- kill(); // Kill self because replacing with 'm'
- m.unkeep(); // Okay to peephole m
- }
- return m;
- }
-
- /**
- * This function needs to be
- * <a href="https://en.wikipedia.org/wiki/Monotonic_function">Monotonic</a>
- * as it is part of a Monotone Analysis Framework.
- * <a href="https://www.cse.psu.edu/~gxt29/teaching/cse597s21/slides/08monotoneFramework.pdf">See for example this set of slides</a>.
- * <p>
- * For Chapter 2, all our Types are really integer constants, and so all
- * the needed properties are trivially true, and we can ignore the high
- * theory. Much later on, this will become important and allow us to do
- * many fancy complex optimizations trivially... because theory.
- * <p>
- * {@link #compute()} needs to be stand-alone, and cannot recursively call compute
- * on its inputs, because programs are cyclic (have loops!) and this will just
- * infinitely recurse until stack overflow. Instead, compute typically
- * computes a new type from the {@link #_type} field of its inputs.
- */
- public abstract Type compute();
-
- // Set the type. Assert monotonic progress.
- // If changing, add users to worklist.
- public Type setType(Type type) {
- Type old = _type;
- assert old == null || type.isa(old) : "Monotonicity test failed: "+this+"#"+_nid+" old="+old+" new="+type;
- //assert !type.hasClosedBuilder0(); // New types should not have closed builders
- if( old == type ) return old;
- _type = type; // Set _type late for easier assert debugging
- CODE.addAll(_outputs);
- moveDepsToWorklist();
- return old;
- }
-
- @SuppressWarnings("unchecked")
- public <N extends Node> N init() { _type = compute(); return (N)this; }
-
- /**
- * This function rewrites the current Node into a more "idealized" form.
- * This is the bulk of our peephole rewrite rules, and we use this to
- * e.g. turn arbitrary collections of adds and multiplies with mixed
- * constants into a normal form that's easy for hardware to implement.
- * Example: An array addressing expression:
- * <pre> ary[idx+1]</pre>
- * might turn into Sea-of-Nodes IR:
- * <pre> (ary+12)+((idx+1) * 4)</pre>
- * This expression can then be idealized into:
- * <pre> ary + ((idx*4) + (12 + (1*4)))</pre>
- * And more folding:
- * <pre> ary + ((idx<<2) + 16)</pre>
- * And during code-gen:
- * <pre> MOV4 Rary,[Ridx<<2 +16] // or some such hardware-specific notation </pre>
- * <p>
- * {@link #idealize} has a very specific calling convention:
- * <ul>
- * <li>If NO change is made, return {@code null}
- * <li>If ANY change is made, return not-null; this can be {@code this}
- * <li>The returned Node does NOT call {@link #peephole} on itself; the {@link #peephole} call will recursively peephole it.
- * <li>Any NEW nodes that are not directly returned DO call {@link #peephole}.
- * </ul>
- * <p>
- * Examples:
- * <table border="3">
- * <tr><th> before </th><th> after </th><th>return </th><th>comment </th></tr>
- * <tr><td>{@code (x+5) }</td><td>{@code (x+5) }</td><td>{@code null }</td><td>No change</td></tr>
- * <tr><td>{@code (5+x) }</td><td>{@code (x+5) }</td><td>{@code this }</td><td>Swapped arguments</td></tr>
- * <tr><td>{@code ((x+1)+2)}</td><td>{@code (x+(1+2))}</td><td>{@code (x+_) }</td><td>Returns 2 new Nodes</td></tr>
- * </table>
- *
- * The last entry deserves more discussion. The new Node {@code (1+2)}
- * created in {@link #idealize} calls {@link #peephole} (which then folds
- * into a constant). The other new Node {@code (x+3)} does not call
- * peephole, because it is returned and peephole itself will recursively
- * call peephole.
- * <p>
- * Since idealize calls peephole and peephole calls idealize, you must be
- * careful that all idealizations are <em>monotonic</em>: all transforms
- * remove some feature, so that the set of available transforms always
- * shrinks. If you don't, you risk an infinite peephole loop!
- *
- * @return Either a new or changed node, or null for no changes.
- */
- public abstract Node idealize();
-
- // Some of the peephole rules get complex, and search further afield than
- // just the nearest neighbor. These peepholes can fail the pattern match
- // on a node some distance away, and if that node ever changes we should
- // retry the peephole. Track a set of Nodes dependent on `this`, and
- // revisit them if `this` changes.
- Ary<Node> _deps;
- public int nDeps() { return _deps == null ? 0 : _deps.size(); }
- public Node dep(int idx) { return _deps.at(idx); }
-
- private boolean addDepImpl( Node dep ) {
- // Running peepholes during the big assert cannot have side effects
- // like adding dependencies.
- if( CODE._midAssert ) return false;
- if( CODE._obs != null ) CODE._obs.dep(this, dep);
- if( dep._deps==null ) dep._deps = new Ary<>(Node.class);
- if( dep._deps .find(this) != -1 ) return false; // Already on list
- if( dep._outputs.find(this) != -1 ) return false;
- return true;
- }
-
- /**
- * Add a node to the list of dependencies. Only add it if it's not an input
- * or output of this node, that is, it is at least one step away. The node
- * being added must benefit from this node being peepholed.
- */
- public <N extends Node> N addDep( N dep ) {
- if( !addDepImpl(dep) ) return dep;
- if( dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors
- dep._deps.add(this);
- return dep;
- }
-
- public <N extends Node> N addDepForwards( N dep ) {
- if( !addDepImpl(dep) ) return dep;
- dep._deps.add(this);
- return dep;
- }
-
- // Move the dependents onto a worklist, and clear for future dependents.
- public void moveDepsToWorklist( ) { moveDepsToWorklist(CODE._iter); }
- public void moveDepsToWorklist( IterPeeps iter ) {
- if( _deps == null ) return;
- iter.addAll(_deps);
- _deps.clear();
- }
-
- // Two nodes are equal if they have the same inputs and the same "opcode"
- // which means the same Java class, plus same internal parts.
- @Override public final boolean equals( Object o ) {
- if( o==this ) return true;
- if( o.getClass() != getClass() ) return false;
- Node n = (Node)o;
- int len = _inputs.size();
- if( len != n._inputs.size() ) return false;
- for( int i=0; i<len; i++ )
- if( in(i) != n.in(i) )
- return false;
- return eq(n);
- }
- // Subclasses add extra checks (such as ConstantNodes have same constant),
- // and can assume "this!=n" and has the same Java class.
- public boolean eq( Node n ) { return true; }
-
- // Cached hash. If zero, then not computed AND this Node is NOT in the GVN
- // table - and can have its edges hacked (which will change his hash
- // anyway). If Non-Zero then this Node is IN the GVN table, or is being
- // probed to see if it can be inserted. His edges are "locked", because
- // hacking his edges will change his hash.
- int _hash;
-
- // If the _hash is set, then the Node is in the GVN table; remove it.
- void unlock() {
- if( _hash==0 ) return;
- Node old = CODE._gvn.remove(this); // Pull from table
- assert old==this;
- _hash=0; // Out of table now
- }
-
- // Hash of opcode and inputs
- @Override public final int hashCode() {
- if( _hash != 0 ) return _hash;
- int hash = hash();
- for( Node n : _inputs )
- if( n != null )
- hash = hash ^ (hash<<17) ^ (hash>>13) ^ n._nid;
- if( hash==0 ) hash = 0xDEADBEEF; // Bad hash, so use some junky thing
- return (_hash=hash);
- }
- // Subclasses add extra hash info (such as ConstantNodes constant)
- int hash() { return 0; }
-
- // ------------------------------------------------------------------------
- //
-
- /** Is this Node Memory related */
- public boolean isMem() { return false; }
-
- // ------------------------------------------------------------------------
- // Peephole utilities
-
- // Swap inputs without letting either input go dead during the swap.
- public Node swap12() {
- unlock(); // Hash is order dependent
- _inputs.swap(1,2);
- return this;
- }
-
- /**
- * Does this node contain all constants? Ignores in(0), as is usually
- * control. In an input is not a constant, we add dep as a dependency to
- * it because dep can make progress if the input becomes a constant later.
- * It is sufficient for one of the non-const inputs to have the dependency,
- * so we don't bother checking the rest.
- */
- boolean allCons(Node dep) {
- for( int i=1; i<nIns(); i++ )
- if( !(in(i)._type.isConstant()) ) {
- dep.addDep(in(i)); // If in(i) becomes a constant later, will trigger some peephole
- return false;
- }
- return true;
- }
-
- // Make a shallow copy (same class) of this Node, with given inputs and
- // empty outputs and a new Node ID. The original inputs are ignored.
- // Does not need to be implemented in isCFG() nodes.
- Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); }
-
- public Node copy() { return copyEmpty(); }
-
- // Exact-class copy with fresh identity and no edges. Unlike machine
- // rematerialization copies, this never registers or copies input edges.
- public final Node copyEmpty() {
- Node n;
- try { n = (Node)clone(); }
- catch( Exception e ) { throw new RuntimeException(e); }
- n._nid = CODE.getUID(); // allocate unique dense ID
- n._inputs = new Ary<>(Node.class);
- n._outputs = new Ary<>(Node.class);
- n._deps = null;
- n._hash = 0;
- if( n instanceof CallNode call ) CODE._calls.add(call);
- 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;
- }
-
-
- // 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/chapter-modules/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/NotNode.java
deleted file mode 100644
index e22d16b6..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/NotNode.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.seaofnodes.simple.node;
-
-
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.Utils;
-
-public class NotNode extends Node {
- public NotNode(Node in) { super(null, in); }
-
-
- @Override protected String format() { return "(!%1)"; }
-
- @Override
- public TypeInteger compute() {
- Type t0 = in(1)._type;
- if( t0.isHigh() ) return (TypeInteger)TypeInteger.BOOL.dual();
- if( t0 == Type.NIL || t0 == TypeInteger.ZERO ) return TypeInteger.TRUE;
- if( t0 instanceof TypeNil tn && tn.notNull() ) return TypeInteger.FALSE;
- if( t0 instanceof TypeInteger i && (i._min > 0 || i._max < 0) ) return TypeInteger.FALSE;
- return TypeInteger.BOOL;
- }
-
- @Override
- public Node idealize() { return null; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/OrNode.java
deleted file mode 100644
index 263c3e87..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/OrNode.java
+++ /dev/null
@@ -1,44 +0,0 @@
-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 String label() { return "Or"; }
- @Override public String op() { return "|"; }
- @Override long doOp( long x, long y ) { return x | y; }
- @Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
- return TypeInteger.BOT;
- }
-
- @Override
- public Node idealize() {
- Node lhs = in(1);
- Node rhs = in(2);
-
- Type t1 = lhs._type;
- Type t2 = rhs._type;
-
- // Or of 0. We do not check for (0|x) because this will already
- // canonicalize to (x|0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && t1.isa(TypeInteger.BOT) && forceInt(lhs) )
- return lhs;
-
- // Move constants to RHS: con*arg becomes arg*con
- if ( t1.isConstant() && !t2.isConstant() )
- return swap12();
-
- // Do we have ((x | (phi cons)) | con) ?
- // Do we have ((x | (phi cons)) | (phi cons)) ?
- // Push constant up through the phi: x | (phi con0|con0 con1|con1...)
- Node phicon = AddNode.phiCon(this,true);
- if( phicon!=null ) return phicon;
-
- return super.idealize();
- }
- @Override Node copy(Node lhs, Node rhs) { return new OrNode(_loc,lhs,rhs); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ParmNode.java
deleted file mode 100644
index 316efb93..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ParmNode.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.type.*;
-
-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 String label() { return MemOpNode.mlabel(_label); }
-
- @Override boolean _upgradeType( HashMap<String,Type> TYPES) {
- Type t = _declaredType.upgradeType(TYPES);
- if( t == _declaredType ) return false;
- unlock();
- _declaredType = t;
- return true;
- }
-
- public FunNode fun() { return (FunNode)in(0); }
-
- @Override
- protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- if( fun()._name!=null && fun()._name.endsWith("<clinit>") && _label.equals("arg") )
- return p.p("arg");
- p.p("Parm_").p(_label).open();
- for( Node in : _inputs ) p.n(in).p(",");
- return p.unchar(',').close();
- }
-
-
- // Always in-progress until we run out of unknown callers
- @Override public boolean inProgress() { return in(0) instanceof FunNode fun && fun.inProgress(); }
-
- @Override
- public Type compute() {
- if( !(region() instanceof RegionNode r) )
- return region()._type==Type.XCONTROL || region()._type==Type.TOP ? (_type instanceof TypeMem ? TypeMem.TOP : Type.TOP) : _type;
- // During parsing Phis have to be computed type pessimistically.
- if( r.inProgress() || in(nIns()-1)==null )
- return _declaredType;
- // Set type to local top of the starting type
- Type t = Type.TOP;
- for( int i = 1; i < nIns(); i++ ) {
- // If the region's control input is live, add this as a dependency
- // to the control because we can be peeped should it become dead.
- Type ctrl = addDep(r.in(i))._type;
- if( ctrl != Type.XCONTROL && ctrl != Type.TOP ) {
- if( in(i)._type==Type.BOTTOM )
- return Type.BOTTOM;
- t = t.meet(in(i)._type);
- }
- }
- // Recursion is a loop in disguise. A Parm merges the first argument
- // with those from recursive calls, just as a loop Phi merges its entry
- // and backedge. Use the widening rule in PhiNode.compute here too.
- // CallEndNode.compute handles the separate cycle through return values;
- // widening this Parm alone cannot stop f(x-1)+1 from growing its result.
- if( t instanceof TypeInteger ti && !ti.isHigh() && !ti.isConstant() )
- t = ti.same_but_slightly_wider_than();
- return t.join(_declaredType);
- }
-
- @Override
- public Node idealize() {
- if( !(region() instanceof FunNode) )
- // A dead function can lose every caller before the Parm itself is
- // removed; there is no replacement input in that transient form.
- return nIns()==1 ? null : in(1); // Input has collapse to e.g. starting control.
- // If function is folding, do all possible peeps
- if( fun()._folding ) return super.idealize();
-
- // Skip most phi optimizations on parms
- return null;
- }
-
- @Override public boolean eq( Node n ) {
- return ((ParmNode)n)._idx==_idx && super.eq(n);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/PhiNode.java
deleted file mode 100644
index 61e4b468..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/PhiNode.java
+++ /dev/null
@@ -1,275 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.*;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.*;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-
-public class PhiNode extends Node {
-
- public final String _label;
-
- public PhiNode( String label, Node... inputs) {
- super(inputs);
- _label = label;
- }
- public static PhiNode make(String label, Type minType, Node... inputs) {
- return minType instanceof TypeMem mem
- ? mem._alias==1
- ? new BulkMemPhiNode(label,inputs)
- : new MemPhiNode(label,mem._alias,inputs)
- : new PhiNode(label, inputs);
- }
- // Used by ParmNode
- public PhiNode(PhiNode phi, String label) { super(phi); _label = label; }
- // Used by instruction Selection
- public PhiNode(PhiNode phi) { this(phi,phi._label); }
- // Used by the infinite-loop exit breaker
- public PhiNode(RegionNode r, Node sample) {
- super(new Node[]{r});
- _label = "";
- _type = sample._type;
- while( nIns() < r.nIns() )
- addDef(sample);
- }
-
-
- @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); }
-
- @Override
- protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- if( !(region() instanceof RegionNode r) || r.inProgress() )
- p.p("Z");
- p.p("Phi(");
- for( Node in : _inputs ) p.n(in).p(",");
- return p.unchar(',').close();
- }
-
- public CFGNode region() { return (CFGNode)in(0); }
- @Override public boolean isMem() { return _type instanceof TypeMem; }
-
- @Override
- public Type compute() {
- if( !(region() instanceof RegionNode r) )
- return Type.TOP;
- // During parsing Phis have to be computed type pessimistically.
- if( r.inProgress() || in(nIns()-1)==null )
- return Type.BOTTOM;
- // Set type to local top of the starting type
- Type t = Type.TOP;
- for( int i = 1; i < nIns(); i++ ) {
- // If the region's control input is live, add this as a dependency
- // to the control because we can be peeped should it become dead.
- Type ctrl = addDep(r.in(i))._type;
- if( ctrl != Type.XCONTROL && ctrl != Type.TOP ) {
- if( in(i)._type==Type.BOTTOM )
- return Type.BOTTOM;
- t = t.meet(in(i)._type);
- }
- }
-
- // Widen loop Phis so integer ranges cannot grow one step per trip forever.
- // Recursion is a loop in disguise: ParmNode.compute widens arguments,
- // and CallEndNode.compute widens results. Both are needed because the
- // recursive result can feed itself without passing through a Parm.
- // Above-center ranges may still fall to unwidened constants.
- // Widen only below-center ranges to preserve monotonicity.
- if( r instanceof LoopNode && // Only around loops
- t instanceof TypeInteger ti && // Only widen integers
- !ti.isHigh() && !ti.isConstant() ) { // Only widen live ranges
- // Widen, to prevent infinite falling of TypeIntegers
- return ti.same_but_slightly_wider_than();
- }
-
- return t;
- }
-
- @Override
- public Node idealize() {
- RegionNode r = (RegionNode)region();
- if( r.inProgress() || nOuts()==0 )
- return null; // Input is in-progress
-
- // If we have only a single unique input, become it.
- Node live = singleUniqueInput();
- if( live != null )
- return live;
-
- // No bother if region is going to fold dead paths soon
- for( int i=1; i<nIns(); i++ )
- if( r.in(i)._type == Type.XCONTROL )
- return null;
-
- // Generic "pull down op"
- Node progress;
- if( same_op() && (progress = drop_same_op()) != null )
- return progress;
-
- // If merging Phi(ZERO, guardNZ(N)) at the matching `if(N)` join, the
- // Phi is exactly N. The guard arm proves N is non-zero, and the other
- // arm contributes N's zero value. This is only legal if N is already
- // available at the Phi region; otherwise a return-scope/live-on-exit
- // Phi can lose the zero arm and export a branch-local value upward.
- Node unguard;
- if( (unguard=matchingGuardMerge(r,1)) != null ) return unguard;
- if( (unguard=matchingGuardMerge(r,2)) != null ) return unguard;
-
- return null;
- }
-
- private Node matchingGuardMerge(RegionNode r, int nzIdx) {
- Node zero = in(3-nzIdx);
- if( !(in(nzIdx) instanceof GuardNode cast && cast._nonZero) ||
- zero._type.makeZero()!=zero._type ||
- cast.in(1)==this )
- return null;
- if( !(r.in(nzIdx) instanceof CProjNode nz && nz._idx==0 && nz.ctrl() instanceof IfNode iff) )
- return null;
- if( !(r.in(3-nzIdx) instanceof CProjNode z && z._idx==1 && z.ctrl()==iff) )
- return null;
- // Predicate was guarded?
- Node n = cast.in(1), tested = null;
- while( true ) {
- if( iff.pred()==n ) tested = n;
- if( !(n instanceof GuardNode guard && guard._nonZero) ) break;
- n = guard.in(1);
- }
- CFGNode early = CFGNode.earlyCFG(n);
- return tested!=null && (early==null || early.dominates(r,this)) ? n : null;
- }
-
- // Same op on all Phi paths; all ops have only the Phi as a use.
- // None have a control input.
- private boolean same_op() {
- Node op = in(1);
- if( op instanceof EscapeNode || op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode ||
- op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false;
- // A bulk Phi must split its aliases before factoring precise Stores.
- if( this instanceof BulkMemPhiNode ) return false;
- // Bulk splitting currently identifies parallel slices by Region/alias.
- // Factoring can introduce another slice at a different memory point;
- // wait until that Region's bulk partitioning has finished.
- if( op instanceof MemOpNode )
- for( Node use : region()._outputs )
- if( use instanceof BulkMemPhiNode ) {
- addDepForwards(use);
- return false;
- }
- Node busy = null;
- for( int i=1; i<nIns(); i++ ) {
- Node n = in(i);
- if( addDep(region().in(i))._type==Type.XCONTROL ||
- op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false;
- if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false;
- // Moving a Store must remove the old effect, not duplicate it.
- // Allow one shared arm: the other arms disappear into the single
- // factored operation, usually reducing the total operation count.
- addDepForwards(n);
- if( n.nOuts()>1 ) {
- for( Node use : n._outputs )
- if( use!=null && use!=this ) addDepForwards(use);
- if( n instanceof StoreNode || busy!=null ) return false;
- busy=n;
- }
- for( int j=1; j<n.nIns(); j++ )
- if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false;
- }
- return true;
- }
-
- private Node drop_same_op() {
- Node op = in(1), cp = op.copyEmpty();
- cp._type = null;
- cp.addDef(null);
- for( int j=1; j<op.nIns(); j++ ) {
- Node x = op.in(j);
- boolean different = false;
- for( int i=2; i<nIns(); i++ )
- if( in(i).in(j)!=x ) different=true;
- if( different ) {
- Node[] ins = new Node[nIns()];
- ins[0] = region();
- Type t = Type.TOP;
- for( int i=1; i<nIns(); i++ ) {
- ins[i] = in(i).in(j);
- t = t.meet(ins[i]._type);
- }
- // Do not introduce an operand Phi mixing incompatible type families.
- if( t==Type.BOTTOM ) {
- for( int i=1; i<ins.length; i++ ) addDep(ins[i]);
- cp.kill();
- return null;
- }
- PhiNode phi = j==1 && op instanceof MemOpNode mem
- ? new MemPhiNode(_label,mem._alias,ins)
- : PhiNode.make(_label,t,ins);
- x = phi.peephole();
- }
- cp.addDef(x);
- }
- // Factoring must not widen the result (e.g. correlated And operands).
- if( cp.compute().isa(compute()) ) return cp;
- for( int i=1; i<nIns(); i++ )
- for( int j=1; j<in(i).nIns(); j++ )
- if( in(i).in(j)!=null ) addDep(in(i).in(j));
- cp.kill();
- return null;
- }
-
- /**
- * If only single unique input, return it
- */
- private Node singleUniqueInput() {
- if( region() instanceof LoopNode loop && loop.entry()._type == Type.XCONTROL )
- return null; // Dead entry loops just ignore and let the loop collapse
- Node live = null;
- for( int i=1; i<nIns(); i++ ) {
- // If the region's control input is live, add this as a dependency
- // to the control because we can be peeped should it become dead.
- if( addDep(region().in(i))._type != Type.XCONTROL && in(i) != this )
- if( live == null || live == in(i) ) live = in(i);
- else return null;
- }
- return live;
- }
-
- @Override
- boolean allCons(Node dep) {
- if( !(region() instanceof RegionNode r) ) return false;
- // When the region completes (is no longer in progress) the Phi can
- // become a "all constants" Phi, and the "dep" might make progress.
- dep.addDep(this);
- if( r.inProgress() ) return false;
- return super.allCons(dep);
- }
-
- // True if last input is null
- public boolean inProgress() {
- return in(nIns()-1) == null;
- }
-
- // Never equal if inProgress.
- @Override public boolean eq( Node n ) {
- return !inProgress() && super.eq(n);
- }
-
- @Override
- public Parser.ParseException err() {
- if( _type != Type.BOTTOM ) return null;
-
- // Global BOTTOM retains the same role for non-scalar families.
- for( int i=1; i<nIns(); i++ )
- // Already an error, but better error messages come from elsewhere
- if( in(i)._type == Type.BOTTOM )
- return null;
-
- SB sb = new SB().p("No common type amongst ");
- for( int i=1; i<nIns(); i++ )
- sb.p(in(i)._type.toString()).p(" and ");
- return Parser.error(sb.unchar(5).toString(),null);
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/Proj.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/Proj.java
deleted file mode 100644
index c8b9378f..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/Proj.java
+++ /dev/null
@@ -1,2 +0,0 @@
-package com.seaofnodes.simple.node;
-public interface Proj { int idx(); }
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ProjNode.java
deleted file mode 100644
index a0142379..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ProjNode.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeMem;
-import com.seaofnodes.simple.type.TypeTuple;
-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 String label() { return _label; }
-
- @Override
- protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- if( _label != null ) return p.p(_label);
- if( in(0) instanceof CallEndNode cend && cend.call()!=null )
- return p.n(cend.call());
- return p.p("LONELY PROJ");
- }
-
- @Override public CFGNode cfg0() {
- return in(0) instanceof CFGNode cfg ? cfg : in(0).cfg0();
- }
-
- @Override public boolean isMem() { return _type instanceof TypeMem; }
-
- @Override
- public Type compute() {
- Type t = in(0)._type;
- return t instanceof TypeTuple tt ? tt._types[_idx] : t.oob();
- }
-
- @Override public Node idealize() { return ((MultiNode)in(0)).pcopy(_idx,this); }
-
- @Override
- public boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; }
-
- @Override
- int hash() { return _idx; }
-
- @Override public int idx() { return _idx; }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java
deleted file mode 100644
index 9a9b94fd..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java
+++ /dev/null
@@ -1,17 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java
deleted file mode 100644
index 094734fa..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java
+++ /dev/null
@@ -1,36 +0,0 @@
-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;
-
-
-/**
- * Reinterpret a non-null managed pointer as its raw integer address.
- *
- * This node changes representation families but emits no code. No
- * control-dependent proof can turn a pointer into an integer; array-to-C-pointer
- * conversion adds the array body offset separately.
- */
-public class PtrToIntNode extends Node {
- public PtrToIntNode(Node ptr) { super(null,ptr); }
- public PtrToIntNode(PtrToIntNode ptr) { super(ptr); }
-
-
- @Override protected String format() { return "(i64*)%1"; }
-
- @Override public Type compute() {
- return in(1)._type.isHigh() ? TypeInteger.TOP : TypeInteger.BOT;
- }
-
- @Override public Node idealize() { return null; }
-
- @Override public Parser.ParseException err() {
- Type t = in(1)._type;
- if( t instanceof TypeMemPtr ptr )
- return ptr.notNull() ? null : Parser.error("Cannot convert a nullable pointer to an integer",null);
- return Parser.error("Cannot convert "+t.str()+" to an integer pointer",null);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java
deleted file mode 100644
index a35f5a1d..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java
+++ /dev/null
@@ -1,19 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java
deleted file mode 100644
index ab7e0ae0..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.seaofnodes.simple.node;
-
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeMemPtr;
-
-/**
- * Cast a pointer to read-only
- */
-public class ReadOnlyNode extends Node {
- public ReadOnlyNode( Node n ) { super(null,n); }
- public ReadOnlyNode( ReadOnlyNode n ) { super(n); }
-
-
- @Override protected String format() { return "(const)%1"; }
- @Override public Type compute() {
- Type t = in(1)._type;
- return t instanceof TypeMemPtr tmp ? tmp.makeRO() : t;
- }
-
- @Override public Node idealize() {
- if( in(1)._type instanceof TypeMemPtr tmp && tmp.isFinal() )
- return in(1);
- return null;
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/RegionNode.java
deleted file mode 100644
index 923773c4..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/RegionNode.java
+++ /dev/null
@@ -1,172 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.type.Type;
-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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- return p.p(label());
- }
-
- @Override public boolean blockHead() { return true; }
-
- @Override
- public Type compute() {
- if( inProgress() ) return Type.CONTROL;
- Type t = Type.XCONTROL;
- for (int i = 1; i < nIns(); i++)
- t = t.meet(in(i)._type);
- return t;
- }
-
- @Override
- public Node idealize() {
- if( inProgress() ) return null;
-
- Node progress = deadPath(false);
- if( progress!=null ) return progress;
-
- // If down to a single input, become that input
- if( nIns()==2 && !hasPhi() )
- return in(1); // Collapse if no Phis; 1-input Phis will collapse on their own
-
- // If a CFG diamond with no merging, delete: "if( pred ) {} else {};"
- if( !hasPhi() && nIns()>3 && // No Phi users, just a control user
- in(1) instanceof CProjNode p1 &&
- in(2) instanceof CProjNode p2 &&
- addDepForwards(p1).in(0)==addDepForwards(p2).in(0) &&
- p1.in(0) instanceof IfNode iff ) {
- // Replace with the iff.ctrl directly
- if( nIns()==3 ) return iff.ctrl();
- // Just delete the path for fat Regions
- setDef(1,iff.ctrl());
- return delDef(2);
- }
-
- // Flatten stacked regions (no loops involved)
- if( getClass() == RegionNode.class && nIns()>2 ) {
- for( int i=1; i<nIns(); i++ )
- if( cfg(i) instanceof RegionNode region && region.getClass() == RegionNode.class && region.nIns()>2 && !hasMidUser(region) ) {
- assert !region.inProgress();
- // Fold Phis
- for( Node use : _outputs ) {
- if( use instanceof PhiNode phi ) {
- PhiNode phi2 = phi.in(i) instanceof PhiNode phi2x && phi2x.region()==region ? phi2x : null;
- for( int j=1; j<region.nIns(); j++ )
- phi.addDef(phi2==null ? phi.in(i) : phi2.in(j));
- CodeGen.CODE.add(phi);
- }
- }
-
- // Fold Region
- for( int j=1; j<region.nIns(); j++ )
- addDef(region.in(j));
- setDef(i,CodeGen.CODE.XCTRL);
- return this;
- }
- }
-
- return null;
- }
-
- Node deadPath(boolean skipStart) {
- // Delete dead paths into a Region
- int path = findDeadInput(skipStart ? 2 :1);
- if( path==0 ) return null;
-
- // Do not delete the entry path of a loop (ok to remove the back edge
- // and make the loop a single-entry Region which folds away the Loop).
- // Folding the entry path confused the loop structure, moving the
- // backedge to the entry point.
- if( this instanceof LoopNode loop && loop.entry()==in(path) )
- return null;
- return removeDeadPath(path);
- }
-
- public Node removeDeadPath(int path) {
- // Cannot use the obvious output iterator here, because a Phi deleting
- // an input might recursively delete *itself*. This shuffles the
- // output array, and we might miss iterating an unrelated Phi. So on
- // rare occasions we repeat the loop to get all the Phis.
- int nouts = 0;
- while( nouts != nOuts() ) {
- nouts = nOuts();
- for( int i=0; i<nOuts(); i++ )
- if( out(i) instanceof PhiNode phi && phi.nIns()==nIns() ) {
- CodeGen.CODE.addAll(phi.in(path)._outputs);
- CodeGen.CODE.addAll(phi.delDef(path)._outputs);
- }
- }
- if( !isDead() ) delDef(path);
- return isDead() ? CodeGen.CODE.XCTRL : this;
- }
-
- private int findDeadInput(int startIdx) {
- for( int i=startIdx; i<nIns(); i++ )
- if( in(i)._type==Type.XCONTROL )
- return i;
- return 0; // All inputs alive
- }
-
- boolean hasPhi() {
- for( Node phi : _outputs )
- if( phi instanceof PhiNode )
- return true;
- return false;
- }
-
- // Is there a value in-between 2 Regions? Difficult to correctly fuse
- // without extensive testing, so just ignore.
- boolean hasMidUser(RegionNode r) {
- for( Node use : r._outputs ) {
- if( use==this ) continue;
- if( use instanceof PhiNode ) {
- for( Node data : use._outputs ) {
- if( !(data instanceof PhiNode phi2) || phi2.region()!=this )
- { addDep(use); if( data!=null ) addDep(data); return true; }
- }
- } else
- { addDep(use); return true; } // Control user
- }
- return false;
- }
-
-
- // Immediate dominator of Region is a little more complicated.
- @Override public int idepth() {
- if( validIDepth() ) return _idepth;
- int depth=0;
- for( Node n : _inputs )
- if( n!=null )
- depth = Math.max(depth,((CFGNode)n).idepth()+1);
- return cacheIDepth(depth);
- }
-
- @Override public CFGNode idom(Node dep) {
- CFGNode lca = null;
- // Recompute from predecessors: CFG edits can change the dominator.
- for( int i=1; i<nIns(); i++ )
- if( !cfg(i)._type.isHigh() )
- lca = cfg(i).domLCA(lca,dep);
- return lca;
- }
-
- // True if last input is null
- public boolean inProgress() { return nIns()>1 && in(nIns()-1) == null; }
-
- // Never equal if inProgress
- @Override public boolean eq( Node n ) { return !inProgress(); }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
deleted file mode 100644
index 0bb090ee..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
+++ /dev/null
@@ -1,129 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-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;
-
-/**
- * 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; }
-
-
- public Node ctrl() { return in(0); }
- public Node mem () { return in(1); }
- public Node expr() { return in(2); }
- public Node rpc () { return in(3); }
- @Override public FunNode fun() { return _fun; }
-
- @Override
- protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- p.p("return ");
- if( expr()==null ) p.p("----");
- else p.n(expr());
- return p.p(";");
- }
-
- // No one unique control follows; can be many call end sites
- @Override public CFGNode uctrl() { return null; }
-
- @Override
- public Type compute() {
- 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));
-
- return null;
- }
-
- @Override public Parser.ParseException err() {
- if( ctrl()._type == Type.CONTROL &&
- expr()._type == Type.TOP )
- return Parser.error("No defined return type",null);
- if( ctrl()._type == Type.CONTROL &&
- expr()._type == TypeScalar.BOT )
- return Parser.error("Cannot return generic scalar",null);
- // With no user constructor, <init> doubles as the public no-arg
- // constructor. Required fields remain TOP in its private memory: a
- // typed parser poison which deliberately emitted no Store or code.
- if( ctrl()._type == Type.CONTROL && _fun.isInstance() ) {
- TypeStruct self = ((TypeMemPtr)_fun.sig().arg(0))._obj;
- if( !CodeGen.hasUserConstructor(self) && expr()._type instanceof TypeMem mem )
- for( Field fld : self._fields ) {
- if( !(fld._t instanceof TypeNil tn && tn.notNull()) ) continue;
- Type actual = mem._alias==1 && mem._t instanceof TypeStruct ts
- ? ts.field(fld._fname)._t
- : mem._alias==fld._alias ? mem._t : Type.TOP;
- if( actual==Type.TOP || actual instanceof TypeNil atn && atn.nullable() )
- return Parser.error("'"+self._name+"' is not fully initialized, field '"+
- fld._fname+"' is only partially set in the constructor",null);
- }
- }
- return null;
- }
-
- @Override public boolean eq( Node n ) {
- return !_fun.isDead() && !((ReturnNode)n)._fun.isDead();
- }
-
- // ------------
- // MachNode specifics, shared across all CPUs
- public String op() {
- return _fun._frameAdjust > 0 ? "epilog" : "ret ";
- }
- // Correct Nodes outside the normal edges
- public void postSelect(CodeGen code) {
- _fun.setRet(this);
- }
- public RegMask regmap(int i) {
- return i==2
- ? CodeGen.CODE._mach.retMask(_fun.sig())
- : CodeGen.CODE._retMasks[i];
- }
- public RegMask outregmap() { return null; }
- public void encoding( Encoding enc ) { throw Utils.TODO(); }
- public void asm(CodeGen code, SB sb) {
- int frameAdjust = fun()._frameAdjust;
- if( frameAdjust>0 )
- sb.p("\n").p("rsp += #").p(frameAdjust).p("\nret ");
- // Post code-gen, just print the "ret"
- if( code._phase.ordinal() < CodeGen.Phase.RegAlloc.ordinal() ||
- (code._phase.ordinal() == CodeGen.Phase.RegAlloc.ordinal() && !code._regAlloc.done()) )
- // Prints return reg (either RAX or XMM0), RPC and then the
- // callee-save registers.
- for( int i=2; i<nIns(); i++ )
- sb.p(code.reg(in(i))).p(" ");
- // If we did not get the expected rpc, print which one we got
- else if( code._regAlloc.regnum(rpc()) != code._mach.rpc() )
- sb.p("[").p(code.reg(rpc())).p("]");
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java
deleted file mode 100644
index 02d3bbaa..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.seaofnodes.simple.node;
-
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-
-
-public class RoundF32Node extends Node {
- public RoundF32Node(Node lhs) { super(null, lhs); }
-
-
- @Override protected String format() { return "((f32)%1)"; }
-
- @Override
- public Type compute() {
- if (in(1)._type instanceof TypeFloat i0 && i0.isConstant() )
- return TypeFloat.constant((float)i0.value());
- if( in(1)._type==TypeFloat.F64 ) return TypeFloat.F32;
- return in(1)._type;
- }
-
- @Override
- public Node idealize() {
- Node lhs = in(1);
- Type t1 = lhs._type;
-
- // RoundF32 of float
- if( t1 instanceof TypeFloat tf && tf._sz==32 )
- return lhs;
-
- return null;
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/SarNode.java
deleted file mode 100644
index fa63334b..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/SarNode.java
+++ /dev/null
@@ -1,37 +0,0 @@
-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 String op() { return ">>"; }
- @Override long doOp( long x, long y ) { return x >> y; }
- @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) {
- if( y.isConstant() ) {
- int log = (int)y.value();
- return TypeInteger.make(-1L<<(63-log),(1L<<(63-log))-1);
- }
- return TypeInteger.BOT;
- }
-
- @Override
- public Node idealize() {
- Node lhs = in(1);
- Node rhs = in(2);
-
- Type t2 = rhs._type;
-
- // Sar of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && forceInt(lhs) && lhs._type instanceof TypeInteger )
- return lhs;
-
- // TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5)
-
- return super.idealize();
- }
- @Override Node copy(Node lhs, Node rhs) { return new SarNode(_loc,lhs,rhs); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
deleted file mode 100644
index 694d5dcb..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
+++ /dev/null
@@ -1,719 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.Var;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.codegen.CompUnit;
-import com.seaofnodes.simple.codegen.ParseAll;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.Utils;
-import java.util.*;
-
-/**
- * The Scope node is purely a parser helper - it tracks names to nodes with a
- * stack of variables.
- */
-public class ScopeNode extends MemMergeNode {
-
- /**
- * The control is a name that binds to the currently active control
- * node in the graph
- */
- public static final String CTRL = "$ctrl";
- public static final String ARG0 = "arg";
- public static final String MEM0 = "$mem";
-
- // All active/live variables in all nested scopes, all run together.
- // Maps 1-to-1 to the _inputs array.
- public final Ary<Var> _vars;
-
- // Lexical scope is typed:
- public static class Kind {
- // Basic block scoping
- public static class Block extends Kind { }
- // Constructor/initializer block for an allocated struct.
- public static class Constructor extends Block {
- public Constructor(TypeStruct self) { _self = self; }
- public final TypeStruct _self;
- }
- // Function scope
- public static class Func extends Kind {
- public final String _name;
- public final boolean _constructor;
- public Func(String name) { this(name,false); }
- public Func(String name, boolean constructor) { _name=name; _constructor=constructor; }
- }
-
- public int _lexSize; // Number of Vars in this scope
- }
- public final Ary<Kind> _kinds;
- // Lexical scope nesting depth
- public int depth() { return _kinds._len; }
- public Kind klast() { return _kinds.last(); }
-
- // Extra guards; tested predicates and cast results
- private final Ary<Node> _guards;
-
- // A new ScopeNode
- public ScopeNode() {
- super();
- _vars = new Ary<>(Var .class);
- _kinds = new Ary<>(Kind.class);
- _guards = new Ary<>(Node.class);
- }
-
- @Override public String label() { return "Scope"; }
-
- @Override
- protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- p.p("Scope[ ");
- int j=1;
- for( int i=0; i<nIns(); i++ ) {
- if( j < depth() && i == _kinds.at(j)._lexSize ) { p.p("| "); j++; }
- Var v = var(i);
- p.p(v._type());
- p.p(" ");
- if( v._final ) p.p("!");
- p.p(v._name);
- p.p("=");
- Node n = in(i);
- while( n instanceof ScopeNode loop ) {
- p.p("Lazy_");
- n = loop.in(i);
- }
- p.n(n);
- p.p(", ");
- }
- return p.unchar(", ").p("]");
- }
-
-
- public Node ctrl() { return in(0); }
- public Node mem() { return in(1); }
- public Var var(int i) { return _vars.at(i); }
-
- /**
- * The ctrl of a ScopeNode is always bound to the currently active
- * control node in the graph, via a special name '$ctrl' that is not
- * a valid identifier in the language grammar and hence cannot be
- * referenced in Simple code.
- *
- * @param n The node to be bound to '$ctrl'
- *
- * @return Node that was bound
- */
- public <N extends Node> N ctrl(N n) { return setDef(0,n); }
- public Node mem(Node n) { return setDef(1,n); }
-
- public void push( Kind kind ) {
- kind._lexSize = _vars.size();
- _kinds.push(kind);
- }
-
- // Pop a lexical scope
- public void pop() {
- promote(null); // Promote forward references to the next outer scope
- _pop();
- }
- // Pop a scope, no promote FRefs
- public void _pop() {
- int n = _kinds.pop()._lexSize;
- popUntil(n); // Pop off inputs going out of scope
- _vars.setLen(n); // Pop off variables going out of scope
- }
-
-
- // Look for forward references in the last lexical scope and promote to the
- // next outer lexical scope. At the last scope declare them an error.
- public void promote(CodeGen code) {
- Kind kind = klast();
- int n = kind._lexSize;
- for( int i=n; i<nIns(); i++ ) {
- Var v = var(i);
- Node vn = in(i);
- if( !v.isFRef() ) continue;
- // Shuffle vars and inputs to move the forward reference to
- // *before* the start of the using scope ("as if" it was not
- // actually a forward reference).
- for( int j=n; j<=i; j++ ) {
- Var vtmp = var(j);
- Node ntmp = in (j);
- v._idx = j;
- _vars .set(j,v );
- _inputs.set(j,vn);
- v = vtmp;
- vn = ntmp;
- }
- kind._lexSize = ++n;
- }
- }
-
-
- public boolean inFunction () { return klast() instanceof Kind.Func; }
- // Is any enclosing scope a constructor?
- public boolean inConstructor() { return constructorSelf() != null; }
- // Nearest constructor self type, if any.
- public TypeStruct constructorSelf() {
- for( int i=depth()-1; i>=0; i-- )
- if( _kinds.at(i) instanceof Kind.Constructor ctor )
- return ctor._self;
- else if( _kinds.at(i) instanceof Kind.Func func && FunNode.isInstance(func._name) )
- return ((TypeMemPtr)var(_kinds.at(i)._lexSize).type())._obj;
- return null;
- }
-
- // Find nearest enclosing function scope
- public int enclosingFunction() {
- for( int i=depth()-1; i>=0; i-- )
- if( _kinds.at(i) instanceof Kind.Func func && !FunNode.isInstance(func._name) )
- return i;
- throw Utils.TODO("Should not reach here");
- }
-
- // Find nearest enclosing function or declaration scope
- public int enclosingFuncOrDecl() {
- for( int i=depth()-1; i>=0; i-- )
- if( _kinds.at(i) instanceof Kind.Func func )
- return i;
- throw Utils.TODO("Should not reach here");
- }
-
- // Which lexical scope contains this Var?
- public int kindx( Var v ) {
- for( int i=depth()-1; i>=0; i-- )
- if( v._idx >= _kinds.at(i)._lexSize )
- return i;
- return -1;
- }
-
- // Which lexical scope contains this Var?
-
-
- public int kindFcnx( Var v ) {
- for( int i=depth()-1; i>=0; i-- )
- if( v._idx >= _kinds.at(i)._lexSize && _kinds.at(i) instanceof Kind.Func )
- return i;
- throw Utils.TODO("Should not reach here");
- }
-
- // Which function scope contains this Var?
- public Kind kindFcn( Var v ) {
- int kx = kindFcnx(v);
- return kx == -1 ? null : _kinds.at(kx);
- }
-
- // Final fields are writable local values inside their own initializer or
- // constructor. Final parameters, local variables, and captured bindings
- // remain final. Use the same rule when deciding which loop Phis to build.
- public boolean canInit(Var v) {
- int fx=enclosingFuncOrDecl();
- Kind.Func fun=(Kind.Func)_kinds.at(fx);
- if( !fun._constructor || v._extern!=0 ) return false;
- int dx=FunNode.isInstance(fun._name) ? fx : fx-1;
- return dx>=0 && kindx(v)==dx && v._idx>=_kinds.at(dx)._lexSize+2;
- }
-
- private boolean loopVar(Var v) {
- return !v._final || (canInit(v) && !v.type().isHighOrConst());
- }
-
- // Find name in reverse, return an index into _vars or -1. Linear scan
- // instead of hashtable, but probably doesn't matter until the scan
- // typically hits many dozens of variables.
- public int find( String name ) {
- for( int i=_vars.size()-1; i>=0; i-- )
- if( var(i)._name.equals(name) )
- return i;
- return -1;
- }
-
- /**
- * Create a new variable name in the current scope
- */
- public boolean define( String name, Type declaredType, boolean xfinal, Node init, 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. Append a forward ref in the current
- // lexical scope. Scope pops promote it outwards "as if" the final constant
- // had been declared before the using scope.
-
- public Var defineFRef( String id, Type t, boolean xfinal, Parser.Lexer loc ) {
- Var var = new Var(nIns(),id,t,xfinal,loc,true);
- FRefNode fref = new FRefNode(id,loc).init();
- fref._type = fref._con = t;
- _vars.add(var);
- addDef(fref);
- return var;
- }
-
-
- // Parser state carries one complete, conservative memory value. Precise
- // alias partitions belong to the graph and are introduced by peepholes.
- public Node mem( int alias ) {
- assert alias == 1;
- return in(update(var(1),null));
- }
- public void mem( int alias, Node st ) {
- assert alias == 1;
- update(var(1),st);
- }
-
-
- /**
- * Lookup a name in all scopes starting from most deeply nested.
- *
- * @param name Name to be looked up
- * @return null if not found, or the implementing Node
- */
- public Var lookup( String name ) {
- int idx = find(name);
- // -1 is missed in all scopes, not found
- return idx == -1 ? null : update(var(idx),null);
- }
-
- /**
- * Redefine an existing name
- *
- * @param name Name being redefined
- * @param n The node to bind to the name
- */
- public void update( String name, Node n ) {
- int idx = find(name);
- assert idx>=0;
- update(var(idx),n);
- }
-
- public Var update( Var v, Node st ) {
- Node old = in(v._idx);
- if( old instanceof ScopeNode loop ) {
- // Lazy Phi!
- Node def = loop.in(v._idx);
- old = def instanceof PhiNode phi && loop.ctrl()==phi.region()
- // Loop already has a real Phi, use it
- ? def
- // Set real Phi in the loop head
- // The phi takes its one input (no backedge yet) from a recursive
- // lookup, which might have insert a Phi in every loop nest.
- : loop.setDef(v._idx,PhiNode.make(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).init());
- setDef(v._idx,old);
- }
- //assert !v._final || st==null;
- if( st!=null ) setDef(v._idx,st); // Set new value
- return v;
- }
-
- /**
- * Duplicate a ScopeNode; including all levels, up to Nodes. So this is
- * neither shallow (would dup the Scope but not the internal HashMap
- * tables), nor deep (would dup the Scope, the HashMap tables, but then
- * also the program Nodes).
- * <p>
- * If the {@code loop} flag is set, the edges are filled in as the original
- * Scope, as an indication of Lazy Phis at loop heads. The goal here is to
- * not make Phis at loop heads for variables which are never touched in the
- * loop body.
- * <p>
- * The new Scope is a full-fledged Node with proper use<->def edges.
- */
- public ScopeNode dup() { return dup(false); }
- public ScopeNode dup(boolean loop) {
- ScopeNode dup = new ScopeNode();
- // Our goals are:
- // 1) duplicate the name bindings of the ScopeNode across all stack levels
- // 2) Make the new ScopeNode a user of all the nodes bound
- // 3) Ensure that the order of defs is the same to allow easy merging
- dup._vars .addAll(_vars );
- dup._kinds .addAll(_kinds );
- dup._guards .addAll(_guards );
- // The dup'd guards all need dup'd keepers, to keep proper accounting
- // when later removing all guards
- for( Node n : _guards )
- if( !(n instanceof CFGNode) )
- n.keep();
- dup.addDef(ctrl()); // Control input is just copied
-
- // Memory is one ordinary parser-state value. At loop heads the
- // Scope sentinel triggers the same lazy-Phi machinery as variables.
- dup.addDef(loop ? this : mem());
-
- // Copy of other inputs
- for( int i=2; i<nIns(); i++ )
- // For lazy phis on loops we use a sentinel
- // that will trigger phi creation on update
- dup.addDef(loop && loopVar(var(i)) ? this : in(i));
- return dup;
- }
-
- /**
- * Merges the names whose node bindings differ, by creating Phi node for such names
- * The names could occur at all stack levels, but a given name can only differ in the
- * innermost stack level where the name is bound.
- *
- * @param that The ScopeNode to be merged into this
- * @return A new node representing the merge point
- */
- public RegionNode mergeScopes(ScopeNode that, 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);
- }
- }
-
- public void balanceLoopFRefs( ScopeNode scope ) {
- for( int i = nIns(); i < scope.nIns(); i++ ) {
- Var n = scope.var(i);
- if( n.isFRef() ) { // Loop body has forward refs
- Var v = new Var(nIns(),n._name,n.type(),n._final,n._loc,true);
- v._uninit = n._uninit;
- _vars.add(v);
- addDef(scope.in(i));
- }
- }
- }
-
-
- // ------------------------------------------------
- // peephole the backedge scope into this loop head scope
- // We set the second input to the phi from the back edge (i.e. loop body)
- public void endLoop(ScopeNode back, ScopeNode exit ) {
- Node ctrl = ctrl();
- assert ctrl instanceof LoopNode loop && loop.inProgress();
- ctrl.setDef(2,back.ctrl());
-
- this._endLoop( this, back, exit);
- back.kill(); // Loop backedge is dead
- // Now one-time do a useless-phi removal
- this._useless();
- }
-
- // Fill in the backedge of any inserted Phis
- void _endLoop( ScopeNode scope, Node back, Node exit ) {
- for( int i=1; i<nIns(); i++ ) {
- if( !loopVar(var(i)) ) continue;
- Node n = in(i);
- if( var(i).type().isHighOrConst() && !(n instanceof PhiNode phi && phi.inProgress()) ) { // Cannot lift higher than a constant, so no Phi
- Node con = ConstantNode.make(var(i).type()).init();
- n.subsume(con);
- n = con;
- } else if( back.in(i) != scope ) {
- PhiNode phi = (PhiNode)n;
- assert phi.region()==scope.ctrl() && phi.in(2)==null;
- phi.setDef(2,back.in(i)); // Fill backedge
- }
- if( exit.in(i) == scope ) // Replace a lazy-phi on the exit path also
- exit.setDef(i,n);
- }
- }
-
- // ------------------------------------------------
- // Up-casting: using the results of an If to improve a value.
- // E.g. "if( ptr ) ptr.field;" is legal because ptr is known not-null.
- //
- // This operation is lexical, not a full semantic theorem prover, and thus
- // its behavior changes the language spec. Optimizations that hide the
- // lexical structure (e.g. constant folding control flow) can hide what is
- // guarded or how the guard is applied, making it somewhat fragile. Several
- // good use cases are covered here, but not everything you might like.
- //
- // Simple lexical facts work well:
- //
- // ```
- // if( ptr )
- // return ptr.field; // `ptr` is guarded non-null here.
- //
- // if( a && a.ptr )
- // return a.ptr.x; // `a` is guarded while parsing the RHS load,
- // // and remains guarded on the true arm.
- // ```
- //
- // Short-circuit expressions are lowered as nested Phis. We recursively
- // decompose the recognized Phi shape, so chains such as:
- //
- // ```
- // if( x && y && z )
- // use(x,y,z);
- // ```
- //
- // discover the nested `x && y` and then `x`, `y`, and `z`; this is not
- // merely a one-level lookup. Likewise, nested tests carry lexical guards
- // inwards:
- //
- // ```
- // if( x && y )
- // if( z && a )
- // if( x.ptr && z.ptr )
- // ...
- // ```
- //
- // Here `x` is already guarded while parsing the outer true arm, `z` while
- // parsing the inner true arm, and both are available before the matching
- // loads. The later post-test decomposition is just a convenience for
- // values still schedulable at the post-test control.
- //
- // The hard limit is that a semantic implication is not always a
- // schedulable Node. For `(x && y)`, the true side implies that `y` was
- // true, but `y` might be a value computed only on the RHS control arm:
- //
- // ```
- // val ptr = ...;
- // if( ptr && (ptr=fcn() ? 0 : arg ) {}
- // ```
- //
- // A Guard pinned after the Phi merge and using the branch-local `ptr`
- // value would give that value a use above its defining arm. GCM then has
- // no legal block between the value's early control and the LCA of its
- // uses. Thus `_addGuards` first checks that the predicate's effective
- // early CFG dominates the control where the Guard would be pinned. If it
- // does not, we skip that guard even if the fact is semantically true.
- //
- // A more complete answer would need a richer logical/proof node, or would
- // need to rematerialize predicates on the guarded path instead of using
- // branch-local nodes directly. Plain GuardNodes are intentionally kept to
- // facts represented by values already available at the guard control.
- //
- // TODO: Make this more robust for shapes like:
- // ```
- // if( !ary || !ary# ) exit(); // facts about ary are lost, despite the early exit
- // for( ...; ary# ) ...;
- // ```
- // Here the guard ends up guarding the whole expression (both ary and ary#
- // are not-null), but loses that ary is not-null. A better answer might be
- // a dedicated logical expression node, or some sort of gather-of-checked
- // values to Guard post-test.
-
- public void addGuards( CFGNode ctrl, Node pred, boolean invert ) {
- _guards.add(ctrl); // Marker between guard sets
- // add pred & its cast to the normal input list, with special Vars
- if( pred==null || pred.isDead() )
- return; // Dead, do not add any guards
- _addGuards(ctrl,pred,invert);
- }
-
- private void _addGuards( CFGNode ctrl, Node pred, boolean invert ) {
- if( pred==null || pred.isDead() )
- return;
- CFGNode early = CFGNode.earlyCFG(pred);
- if( early != null && !early.dominates(ctrl,null) )
- return;
- // A single negation is handled below. For !!p (including p != null)
- // or a negated short-circuit Phi, also discover the underlying facts.
- if( pred instanceof NotNode not &&
- (not.in(1) instanceof NotNode || not.in(1) instanceof PhiNode) ) {
- pred.keep(); // Recursive peepholes may rediscover this Not.
- _addGuards(ctrl,not.in(1),!invert);
- pred.unkeep();
- }
- // Short-circuit logic is represented by a Phi. For `a || b` being
- // false, or `a && b` being true, both individual operands have the
- // same proven value as the whole expression. The "skipped RHS" path
- // is the Region input whose Phi value is the controlling If predicate.
- if( pred instanceof PhiNode phi && phi.nIns()==3 && phi.region() instanceof RegionNode r && r.nIns()==3 )
- for( int i=1; i<3; i++ )
- if( r.in(i) instanceof CProjNode prj && prj.ctrl() instanceof IfNode iff ) {
- Node skip = shortCircuitSkippedPred(phi.in(i),iff.pred(),prj,invert);
- if( skip != null ) {
- _addGuards(ctrl,skip,invert);
- _addGuards(ctrl,phi.in(3-i),invert);
- break;
- }
- }
- // Invert the If conditional
- if( invert )
- pred = pred instanceof NotNode not ? not.in(1) : CodeGen.CODE.add(new NotNode(pred).peephole());
- Node zeroPred = pred instanceof NotNode not ? not.in(1) : null;
- // This is a zero/null test.
- // Compute the positive test type.
- _addGuard(true,ctrl,pred);
-
- // Compute the negative test type.
- if( zeroPred != null )
- _addGuard(false,ctrl,zeroPred);
- }
-
- private static Node shortCircuitSkippedPred( Node val, Node pred, CProjNode prj, boolean invert ) {
- if( val == null )
- return null;
- if( val == pred )
- return (prj._idx==0)==invert ? val : null;
- if( val instanceof NotNode not && not.in(1)==pred )
- return (prj._idx==1)==invert ? val : null;
- if( val._type == TypeInteger.TRUE && invert )
- return prj._idx==0 ? pred : CodeGen.CODE.add(new NotNode(pred).peephole());
- if( val._type == TypeInteger.FALSE && !invert )
- return prj._idx==1 ? pred : CodeGen.CODE.add(new NotNode(pred).peephole());
- return null;
- }
-
- private void _addGuard(boolean nonZero, Node ctrl, Node pred) {
- Node guard = new GuardNode(nonZero,ctrl,pred).peephole();
- if( guard != pred ) {
- pred.keep();
- guard.keep();
- _guards.add(pred);
- _guards.add(guard);
- replace(pred,guard,nonZero);
- }
- }
-
-
- // Remove matching pred/cast pairs from this guarded region.
- public void removeGuards( Node ctrl ) {
- assert ctrl instanceof CFGNode;
- // Pop the guards up to this region's marker.
- while( true ) {
- Node g = _guards.pop();
- if( g == ctrl ) break;
- if( g instanceof CFGNode ) continue;
- g .unkill(); // Pop/kill cast
- _guards.pop().unkill(); // Pop/kill pred
- }
- }
-
- // If we find a guarded instance of pred, replace with the upcasted version
- public Node upcastGuard( Node pred ) {
- // If finding an instanceof pred, replace with cast.
- // Otherwise, just pred itself.
- int i = _guards._len;
- while( i > 0 ) {
- Node cast = _guards.at(--i);
- if( cast instanceof CFGNode ) continue; // Marker between guard sets
- Node xpred = _guards.at(--i);
- if( xpred == pred )
- return cast;
- }
- return pred;
- }
-
- // Kill guards also
- @Override public void kill() {
- for( Node n : _guards )
- if( !(n instanceof CFGNode) )
- n.unkill();
- _guards.clear();
- // Can have lazy uses remaining
- if( isUnused() )
- super.kill();
- }
-
-
- private void replace( Node old, Node cast, boolean nonZero ) {
- assert old!=null && old!=cast;
- for( int i=0; i<nIns(); i++ )
- if( in(i)==old || nonZero && nullCheckedPhi(in(i),old) )
- setDef(i,cast);
- }
-
- private static boolean nullCheckedPhi( Node n, Node old ) {
- if( !(n instanceof PhiNode phi) || phi.nIns()!=3 )
- return false;
- if( phi.in(1)==null || phi.in(2)==null )
- return false;
- int zero = phi.in(1)._type == phi.in(1)._type.makeZero() ? 1 :
- phi.in(2)._type == phi.in(2)._type.makeZero() ? 2 : -1;
- if( zero == -1 )
- return false;
- Node val = phi.in(3-zero);
- return val==old ||
- val instanceof GuardNode guard && guard._nonZero && guard.in(1)==old;
- }
-
- // ------------------------------------------------------------------------
- // Insert a top-level public external symbol, "as-if" some #include/import
- // was executed at the top of tile.
- public Var insertExtern( ExternNode ext ) {
- // Size of top-level scope
- int lexSize = _kinds.at(0)._lexSize;
- Var var = new Var(lexSize,ext._extern,ext._con,true,null);
- insert(var,ext,0);
- return var;
- }
-
- // ------------------------------------------------------------------------
- // Insert a Var with Node n in the lex scope just prior to kidx
- private void insert( Var var, Node n, int kidx ) {
- // Insert just prior to scope kidx
- int nidx = _kinds.at(kidx)._lexSize;
- _vars.insert(var,nidx);
- insertDef(nidx,n);
- // All outer Kinds lexical offsets bump by one
- for( int i = kidx; i < depth(); i++ )
- _kinds.at(i)._lexSize++;
- // All Vars outside the innermost lexical scope bump out one
- for( int i=nidx+1; i<_vars._len; i++ )
- var(i)._idx++;
- }
-
- // Save the existing Node state to an array
- public Node[] save() {
- Node[] olds = new Node[nIns()];
- for( int i=0; i<nIns(); i++ )
- olds[i] = in(i)==null ? null : in(i).keep();
- return olds;
- }
-
- // Restore saved Node state back into scope
- public void restore( Node[] olds ) {
- assert olds.length<=nIns();
- for( int i=0; i<olds.length; i++ )
- setDef(i,olds[i]==null ? null : olds[i].unkeep());
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ShlNode.java
deleted file mode 100644
index 380fc246..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ShlNode.java
+++ /dev/null
@@ -1,49 +0,0 @@
-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 String op() { return "<<"; }
- @Override long doOp( long x, long y ) { return x << y; }
- @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) {
- if( x == TypeInteger.ZERO )
- return TypeInteger.ZERO;
- if( y.isConstant() ) {
- int shf = (int)y.value();
- // If no overflow, shift endpoints
- if( !(((x._min<<shf)>>shf) != x._min ||
- ((x._max<<shf)>>shf) != x._max ) )
- return TypeInteger.make(x._min<<shf,x._max<<shf);
- }
- return TypeInteger.BOT;
- }
-
- @Override
- public Node idealize() {
- Node lhs = in(1);
- Node rhs = in(2);
-
- if( rhs._type instanceof TypeInteger shl && shl.isConstant() ) {
- // Shl of 0.
- if( (shl.value()&63)==0 && forceInt(lhs) && lhs._type instanceof TypeInteger )
- return lhs;
- // (x + c) << i => (x << i) + (c << i)
- if( lhs instanceof AddNode add && addDep(add).in(2)._type instanceof TypeInteger c && c.isConstant() ) {
- long sum = c.value() << shl.value();
- if( Integer.MIN_VALUE <= sum && sum <= Integer.MAX_VALUE )
- return new AddNode(new ShlNode(_loc,add.in(1),rhs).peephole(), CodeGen.CODE.con(sum) );
- }
- }
-
- // TODO: x << 3 << (y ? 1 : 2) ==> x << (y ? 4 : 5)
-
- return super.idealize();
- }
- @Override Node copy(Node lhs, Node rhs) { return new ShlNode(_loc,lhs,rhs); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ShrNode.java
deleted file mode 100644
index 17170a1f..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ShrNode.java
+++ /dev/null
@@ -1,32 +0,0 @@
-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 String op() { return ">>>"; }
- @Override long doOp( long x, long y ) { return x >>> y; }
- @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) {
- return x == TypeInteger.ZERO ? x : TypeInteger.BOT;
- }
-
- @Override
- public Node idealize() {
- Node lhs = in(1);
- Node rhs = in(2);
- Type t2 = rhs._type;
-
- // Shr of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && forceInt(lhs) && lhs._type instanceof TypeInteger )
- return lhs;
-
- // TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5)
-
- return super.idealize();
- }
- @Override Node copy(Node lhs, Node rhs) { return new ShrNode(_loc,lhs,rhs); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/SplitNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/SplitNode.java
deleted file mode 100644
index 3042f857..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/SplitNode.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.codegen.Encoding;
-import com.seaofnodes.simple.codegen.LRG;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.util.SB;
-
-public abstract class SplitNode extends MachConcreteNode {
- public final LRG _lrg; // Live range for split
- public final String _kind; // Kind of split
- public final byte _round; // Debugging info for which RegAlloc round
- public SplitNode(LRG lrg,String kind, byte round, Node[] nodes) { super(nodes); _lrg=lrg; _kind = kind; _round = round; }
- // A register-only transfer may need another split to reach stack storage.
- public boolean isNonStackSplit() { return false; }
- @Override public String op() { return "mov"; }
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- p.p("mov(");
- if( in(1) == null ) p.p("---");
- else p.n(in(1));
- return p.close();
- }
- public FunNode fun(Encoding enc) { return enc._code._regAlloc.lrg(this)._fun; }
- @Override public Type compute() { return in(0)._type; }
- @Override public Node idealize() { return null; }
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
- }
- @Override public String comment() { return _kind + " #"+ _round; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/StartCUNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/StartCUNode.java
deleted file mode 100644
index 0f7a322a..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/StartCUNode.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.type.Type;
-
-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 CFGNode cfg0() { return (CFGNode)in(0); }
-
- // IDom is cfg0() and depth is always Start+1 which is 0+1 or 1
- @Override public int idepth() { return 1; }
- @Override public CFGNode idom(Node dep) { return cfg0(); }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/StartNode.java
deleted file mode 100644
index 3c605e30..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/StartNode.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-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.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; }
-
- public Type arg() { return _arg; }
-
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- return p.p(label());
- }
-
- @Override public CFGNode cfg0() { return null; }
-
- // Get the one control following; error to call with more than one e.g. an
- // IfNode or other multi-way branch. For Start, it's the class init for
- // this compilation unit.
- @Override public CFGNode uctrl() {
- // Find module.<clinit>, it's the start.
- CFGNode C = null;
- for( Node use : _outputs )
- if( use instanceof FunNode fun && CodeGen.CODE.entryClinitName().equals(fun._name) )
- { assert C==null; C = fun; }
- if( C==null )
- for( FunNode fun : CodeGen.CODE._linker )
- if( fun!=null && !fun.isDead() && CodeGen.CODE.entryClinitName().equals(fun._name) )
- { assert C==null; C = fun; }
- return C;
- }
-
- boolean escapedFIDX(int fidx) {
- // 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 0; }
- @Override public CFGNode idom(Node dep) { return null; }
-
- @Override public boolean eq( Node n ) { return true; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/StopCUNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
deleted file mode 100644
index 53c2325c..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
+++ /dev/null
@@ -1,126 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.type.*;
-
-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); }
-
-
- 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.ordinal() >= CodeGen.Phase.Opto.ordinal() &&
- CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.Lower.ordinal() &&
- _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()) ) {
- // External callers already hold their object arguments, so
- // writes through those arguments expose addresses too.
- for( Node use : fun._outputs )
- if( use instanceof ParmNode p && p._idx>=2 )
- xmem=publicBottom(xmem.escapes(addDepForwards(p)._type));
- 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));
- }
- }
- // Native retention is shared by the whole program, including calls
- // on paths with no Return. A class boundary must not hide it.
- for( CallNode call : CodeGen.CODE._calls )
- if( !call.isDead() ) xmem=publicBottom(xmem.escapes(call.exposed(this)));
- 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.ordinal() < CodeGen.Phase.Opto.ordinal() ||
- CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Lower.ordinal() ) return mem;
- int[] escFs = XInt.isHigh(mem._escFs)
- ? XInt.meet(prior._escFs,CodeGen.CODE.publicFIDXs())
- : mem._escFs;
- int[] escAs = XInt.isHigh(mem._escAs)
- ? XInt.meet(prior._escAs,CodeGen.CODE.publicAliases())
- : mem._escAs;
- return escFs==mem._escFs && escAs==mem._escAs ? mem : mem.makeFrom(escFs,escAs);
- }
-
- @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/chapter-modules/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/StopNode.java
deleted file mode 100644
index 7238f1f9..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/StopNode.java
+++ /dev/null
@@ -1,94 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.type.*;
-
-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
- protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- // For the sake of many old tests, and single value prints as "return val"
- ReturnNode ret1 = null;
- for( Node n : _inputs ) {
- if( n instanceof ReturnNode ret ) {
- if( ret1 != null ) { ret1=null; break; }
- ret1 = ret;
- }
- }
- if( ret1!=null ) return p.n(ret1);
-
- p.p("Stop[ ");
- for( Node n : _inputs )
- if( n instanceof ReturnNode ret ) {
- String name = ret.fun()._name;
- if( name== null || !name.startsWith("sys.") )
- p.n(ret).p(" ");
- }
- return p.p("]");
- }
-
- @Override public boolean blockHead() { return true; }
-
-
- // If a single Return, return it.
- // Otherwise, null because ambiguous.
- public ReturnNode ret() {
- ReturnNode ret1 = null;
- for( Node n : _inputs ) {
- // TODO: remove isPublic
- if( n instanceof ReturnNode ret && ret.fun().isPublic() ) {
- if( ret1 != null ) return null; // Ambiguous
- ret1 = ret;
- }
- }
- return ret1;
- }
-
- @Override
- public TypeTuple compute() {
- // During Parsing, new Stops and Returns are being added. This Stop
- // cannot know if more are coming, so must assume the worst.
- if( CodeGen.CODE._phase ==null || CodeGen.CODE._phase == CodeGen.Phase.Parse )
- return TypeTuple.STATE;
-
- // Just meet-over-inputs.
- TypeTuple tt = TypeTuple.STOP_HIGH;
- for( Node def : _inputs )
- tt = (TypeTuple)tt.meet(def._type);
- assert tt.mem()._alias==1 && !tt.mem()._one;
- return tt;
- }
-
- @Override public Node idealize() {
- int len = nIns();
- for( int i=0; i<nIns(); i++ )
- if( in(i).nIns()==0 )
- delDef(i--);
- if( len != nIns() ) return this;
- return null;
- }
-
- @Override public int idepth() {
- if( validIDepth() ) return _idepth;
- int d=0;
- for( Node ret : _inputs )
- // All public results are hooked on the Stop, including data
- // values, so must check CFG
- if( ret instanceof CFGNode cfg )
- d = Math.max(d,cfg.idepth()+1);
- return cacheIDepth(d);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/StoreNode.java
deleted file mode 100644
index bdd07c8d..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ /dev/null
@@ -1,326 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.*;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.Utils;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-
-/**
- * Store represents setting a value to a memory based object, in chapter 10
- * this means a field inside a struct.
- */
-public class StoreNode extends MemOpNode {
-
- final boolean _init; // Initializing writes are allowed to write null
- // Semantic store width in bytes. Zero is the sole undecided state;
- // otherwise hardware and ideal Stores are the 1/2/4/8-byte variants.
- private byte _size;
-
- /**
- * @param name The struct field we are assigning to
- * @param mem The memory alias node - this is updated after a Store
- * @param ptr The ptr to the struct base where we will store a value
- * @param off The offset inside the struct base
- * @param value Value to be stored
- */
- public StoreNode(Parser.Lexer loc, String name, int alias, Type decl, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init) {
- this(loc,name,alias,decl,ctrl,mem,ptr,off,value,init,(byte)0);
- }
- private StoreNode(Parser.Lexer loc, String name, int alias, Type decl, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init, byte size) {
- super(loc, name, alias, false, decl, ctrl, mem, ptr, off, value);
- _init = init;
- _size = size;
- }
-
-
- @Override boolean canDrop(MemOpNode other, Node dep) {
- return super.canDrop(other,dep) && _init==((StoreNode)other)._init;
- }
-
- // Debugger label
- @Override public String label() { return "st_"+mlabel(); }
- @Override public boolean isMem() { return true; }
-
- public Node nnptr() {
- if( !(ptr() instanceof GuardNode cast) ) return ptr();
- assert cast._nonZero;
- return cast.in(1);
- }
- public Node val() { return in(4); }
- public int storeSize() { assert _size==1 || _size==2 || _size==4 || _size==8; return _size; }
- public int log_size() { return Integer.numberOfTrailingZeros(storeSize()); };
-
- @Override
- protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- return p.p(".").p(_name).p("=").p( val()).p(";");
- }
-
- @Override
- public Type compute() {
- Type val = val()._type;
- Type mem0 = mem()._type;
- Type ptr0 = ptr()._type;
- // Validate argument types
- if( ptr0.isHigh() )
- return TypeMem.TOP;
- if( !(mem0 instanceof TypeMem mem) ||
- !(ptr0 instanceof TypeMemPtr ptr) )
- return mem0.isHigh() || ptr0==Type.NIL ? TypeMem.TOP : TypeMem.BOT;
-
- // Sharpen memory value; required for narrowing stores where the parser
- // inserts zero/sign masking and somebody reads the TypeMem type.
- Type decl = declaredType();
- if( decl != Type.BOTTOM )
- { assert decl != Type.TOP; val = val.join(decl); }
- // A singleton class's final constant field lives in the static image.
- // Its precise memory slice is a constant too; ordinary peepholes can
- // replace this Store before scheduling or instruction selection.
- // A constant runtime value alone is not enough: mutable fields (notably
- // $init) still need their writes, and instance fields need allocation.
- Field fld = ptr._obj.field(_name);
- if( _alias!=1 && ptr._one && Parser.startsClzPrefix(ptr._obj._name) &&
- fld!=null && fld._final && decl.isConstant() )
- return TypeMem.make(_alias,val,true,true,true,mem._escFs,mem._escAs).escapesFrom(val);
-
- // Allocation uses a private TypeMem and nothing else does. This
- // memory is truly private; a temporary singleton until it escapes -
- // which is never does in a constructor.
- if( !_name.equals("[]") && mem._one )
- // Just track the stored value
- return TypeMem.make(_alias,val,true,false,_init,null,null).escapesFrom(val);
-
- // Normal aliasing Store.
- assert mem._alias==1 || mem._alias==_alias; // Perfect aliasing
-
- // Single alias store. If memory is fat, attempt to get a narrow slice.
- Type tfld = mem._alias==1 && mem._t instanceof TypeStruct ts
- ? ts._fields[ts.findAlias(_alias)]._t
- : mem._t;
-
- // Same alias, meet into other fields.
- Type t = val.meet(tfld);
- // Bulk memory's final bit describes its conservative remainder, not
- // this newly precise slice. Slice finality comes from the field.
- boolean xfinal = fld != null && fld._final;
- return TypeMem.make(_alias,t,mem._one,mem._clz,xfinal,mem._escFs,mem._escAs).escapesFrom(val);
- }
-
- @Override
- public Node idealize() {
- // Freeze the semantic Store variant as soon as the pointer exposes a
- // canonical field declaration. Waiting until after structural memory
- // peeps can lose the declaration to a flow-sensitive constant field
- // state.
- if( _size==0 && (_size= storageType()) != 0 ) {
- unlock(); // Width participates in the cached hash.
- return init();
- }
-
- assert !(mem()._type instanceof TypeMem mem) || _alias == 1 || mem._alias == 1 || _alias == mem._alias;
-
- // Simple store-after-MemMerge to a known alias can bypass. Happens when inlining.
- Node mem = mem();
- if( _alias != 1 && mem instanceof MemMergeNode merge ) {
- setDef(1,merge.alias(_alias));
- return this;
- }
-
- Field fld;
- if( _alias==1 && ptr()._type instanceof TypeMemPtr tmp && (fld=tmp._obj.field(_name)) != null && _size != 0 ) {
- // Expand Store(bulkMem,alias#1) into MemMerge(bulkMem,#N:Store(bulkMem,alias#N)).
-
- // Normally I'd like to just drop in this xform and move on, but
- // users of the bulk Store memory might nontheless depend on the
- // sharp memory type (e.g. I64) from the store's memory. Using the
- // MemMerge means using the bulk memory which e.g. might be BOT.
- // Check for store users with the same alias, and use the new store
- // directly, preserving the sharper graph.
- Node st = new StoreNode(_loc,_name,fld._alias,fld._t,in(0),mem,ptr(),off(),val(),_init,_size).peephole();
- for( int i=0; i<nOuts(); i++ ) {
- Node use = out(i);
- if( useAlias(use)==fld._alias ) {
- int idx = use._inputs.find(this);
- assert idx != -1;
- CodeGen.CODE.add(use).setDef(idx,st);
- i--; // setDef removed use from this Store's outputs
- }
- }
- MemMergeNode mmm = mem instanceof BulkMemPhiNode bulk
- ? bulk.aggregate(mem,fld._alias,st)
- : new MemMergeNode(null,mem);
- if( !(mem instanceof BulkMemPhiNode) )
- mmm.alias(fld._alias,st);
- return mmm.init();
- }
-
- // Simple store-after-store on same address. Should pick up the
- // required init-store being stomped by a first user store.
- if( mem instanceof StoreNode st &&
- ptr()==st.ptr() && // Must check same object
- off()==st.off() && // And same offset (could be "same alias" but this handles arrays to same index)
- ptr()._type instanceof TypeMemPtr && // No bother if weird dead pointers
- // Must have exactly one use of "this" or you get weird
- // non-serializable memory effects in the worse case.
- checkOnlyUse(st) ) {
- assert _name==st._name; // Equiv class aliasing is perfect
- setDef(1,st.mem());
- return this;
- }
-
- // Private constructor memory describes the value that the next helper
- // reads. Keep its conversion, even if a machine store could truncate it.
- // This is the same rule as in the construction and escape chapters.
- boolean priv = mem()._type instanceof TypeMem tm && tm._one;
- // Value is automatically truncated by narrow store
- if( !priv && _size!=0 && val() instanceof AndNode and && and.in(2)._type.isConstant() ) {
- int log = log_size();
- if( log<3 ) { // And-mask vs narrow store
- long mask = ((TypeInteger)and.in(2)._type).value();
- long bits = (1L<<(8<<log))-1;
- // Mask does not mask any of the stored bits
- if( (bits&mask)==bits )
- // So and-mask is already covered by the store
- { setDef(4,and.in(1)); return this; }
- }
- }
-
- // Store will chop high order bits off; math to change those bits can be dropped.
- if( !priv && _size!=0 && val() instanceof SarNode shr &&
- shr.in(1) instanceof ShlNode shl &&
- shr.in(2)._type.isConstant() &&
- shl.in(2)._type.isConstant() ) {
- TypeInteger shrC = (TypeInteger) shr.in(2)._type;
-
- // size of the thing that sign-extends
- int base_size = (1 << shr.in(1)._type.log_size()) << 3;
- int not_affected_bits = base_size - (int) shrC.value();
- int store_size = (1 << log_size()) << 3;
- // if the store is unrelated to the shift amount, then get rid of the shift
- if( shl.in(2)._type == shr.in(2)._type && shrC.value() >= store_size && not_affected_bits >= store_size) {
- setDef(4, shl.in(1));
- return this;
- }
- }
-
- return null;
- }
-
- // Flow-sensitive pointer types can carry a current field value (e.g. the
- // constant zero in a u32 field). Storage width comes from the canonical
- // struct declaration. If the pointer/definition is not available yet,
- // leave the Store undecided instead of consulting the value or `_con`.
- private byte storageType() {
- if( !(ptr()._type instanceof TypeMemPtr tmp) )
- return 0;
- TypeStruct obj = (TypeStruct)Parser.TYPES.get(tmp._obj._name);
- Field fld = obj.field(_name);
- if( fld == null || fld._t == null )
- return 0;
- if( fld._t == Type.BOTTOM )
- return 0; // Store into a final field
- assert fld._t != Type.TOP;
- // Freshly parsed field stores retain the declaring structure on their
- // symbolic offset even after the pointer's flow type sharpens.
- if( fld._t.isConstant() ) {
- if( !(off() instanceof ConFldOffNode coff) )
- return 0;
- fld = coff._ts.field(_name);
- if( fld == null || fld._t == null )
- return 0;
- }
- // Compute storage size from field type
- byte size = (byte)(1 << fld._t.log_size());
- assert size==1 || size==2 || size==4 || size==8;
- return size;
- }
-
- // Alias required by a direct consumer of this Store's memory result.
- // Keep in sync with BulkMemPhiNode.outputAlias.
- private int useAlias(Node use) {
- return switch( use ) {
- case MemOpNode mem -> mem._alias;
- case MemPhiNode phi -> phi._alias;
- case EscapeNode esc -> esc.pub()==this ? esc.fld()._alias : 0;
- default -> 0;
- };
- }
-
- // Phi(mem, Store(phi,ptr,off,val)) -> Store(mem,ptr,off,Phi(load(mem),val)).
- // With no observers of the backedge store, only the last value matters.
- // The entry load preserves the field even when the loop takes zero trips.
- Node sink(MemPhiNode phi) {
- phi.addDepForwards(this);
- if( nOuts()!=1 || _alias==1 || _size==0 || _init ) return null;
- phi.addDep(ptr());
- // Start with a fixed field of an allocation dominating the loop.
- // In particular, do not sink stores through a loop-varying address.
- if( !(ptr() instanceof ProjNode p) || !(p.in(0) instanceof NewNode obj) ||
- !(ptr()._type instanceof TypeMemPtr tmp) || tmp._obj.isAry() ||
- !(off() instanceof ConstantNode) || err()!=null ) return null;
- LoopNode loop = (LoopNode)phi.region();
- // Bulk splitting identifies precise slices by Region/alias. Finish it
- // before replacing this memory point with a Store.
- for( Node use : loop._outputs )
- if( use instanceof BulkMemPhiNode ) {
- phi.addDepForwards(use);
- return null;
- }
- phi.addDep(obj);
- CFGNode ctrl = loop.entry();
- while( ctrl!=null && ctrl!=obj.cfg0() ) {
- phi.addDep(ctrl);
- ctrl=ctrl.idom(phi);
- }
- if( ctrl==null ) return null;
-
- Node mem = phi.in(1);
- Node init = new LoadNode(_loc,_name,_alias,null,mem,ptr(),off()).peephole();
- Node value = CodeGen.CODE.add(new PhiNode(_name,loop,init,val()).peephole());
- Node sink = new StoreNode(_loc,_name,_alias,_con,null,mem,ptr(),off(),value,_init,_size);
- // Break the memory cycle, killing the old store. Keep the Phi alive
- // even if that store was its last use, until our caller replaces it.
- phi.keep();
- phi.setDef(2,mem);
- phi.unkeep();
- return sink;
- }
-
- // Check that "mem" has no uses except "this"
- private boolean checkOnlyUse(Node mem) {
- if( mem.nOuts()==1 ) return true;
- // Add deps on the other uses (can be e.g. ScopeNode mid-parse) so that
- // when the other uses go away we can retry.
- for( Node use : mem._outputs )
- if( use != this )
- addDep(use);
- return false;
- }
-
- @Override
- public Parser.ParseException err() {
- Parser.ParseException err = super.err();
- if( err != null ) return err;
- assert _size>0 || CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.FinalizeIR.ordinal();
- if( ptr()._type == Type.TOP ) // Dead store?
- return null;
- TypeMemPtr tmp = (TypeMemPtr)ptr()._type;
- Field f = tmp._obj.field(_name);
- if( f!=null && (tmp._ro || f._final) && !_init )
- return Parser.error("Cannot modify final field '"+_name+"'",_loc);
- return null;
- }
-
- // Do not GVN Stores. Two equal Stores on two of three paths are the easy
- // failure: sharing them makes GCM place the Store on all three paths.
- // Sharing is safe only if every exit path goes through an original Store,
- // or GCM splits the shared Store to keep it on those paths. Stores must
- // sink, not run on extra paths. GVN does not check this; GCM does not split.
- // Identity also disables generic Phi factoring of Stores.
- // The closed-loop Store sink is a separate peep and still applies.
- @Override public boolean eq(Node n) { return this == n; }
- @Override int hash() { return super.hash() ^ _size; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/SubNode.java
deleted file mode 100644
index adad2ed5..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ /dev/null
@@ -1,56 +0,0 @@
-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 String op() { return "-"; }
-
- @Override long doOp( long x, long y ) { return x - y; }
- @Override double doOp(double x, double y ) { return x - y; }
- @Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
- // Sub of same is 0
- if( in(1)==in(2) )
- return TypeInteger.ZERO;
- // Fold ranges like {2-3} - {0-1} into {1-3}.
- if( !AddNode.overflow(x._min,-y._max) &&
- !AddNode.overflow(x._max,-y._min) &&
- y._min != Long.MIN_VALUE )
- return TypeInteger.make(x._min-y._max,x._max-y._min);
-
- return TypeInteger.BOT;
- }
-
- @Override
- public Node idealize() {
- Node lhs = in(1);
- Node rhs = in(2);
- Type t1 = lhs._type;
- Type t2 = rhs._type;
-
- // Keep invalid operands and unresolved numeric modes for type checking.
- if( _mode==1 && t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
- if( y.isConstant() && y.value()==0 ) return lhs;
- if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
- }
-
- // x - (-y) is x+y
- if( rhs instanceof MinusNode minus )
- return new AddNode(lhs,minus.in(1));
-
- // (-x) - y is -(x+y)
- if( lhs instanceof MinusNode minus )
- return new MinusNode(new AddNode(minus.in(1),rhs).peephole());
-
- return super.idealize();
- }
-
- @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs,_mode); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java
deleted file mode 100644
index 1acf55b6..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.seaofnodes.simple.node;
-
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-import com.seaofnodes.simple.type.TypeInteger;
-
-
-public class ToFloatNode extends Node {
- public ToFloatNode(Node lhs) { super(null, lhs); }
-
-
- @Override protected String format() { return "(flt)%1"; }
-
- @Override
- public Type compute() {
- if( in(1)._type.isHigh() ) return TypeFloat.F64.dual();
- if( in(1)._type == Type.NIL ) return TypeFloat.FZERO;
- if( in(1)._type instanceof TypeInteger i0 && i0.isConstant() )
- return TypeFloat.constant(i0.value());
- return TypeFloat.F64;
- }
-
- @Override public Node idealize() { return null; }
- @Override Node copy(Node lhs, Node rhs) { return new ToFloatNode(lhs); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java
deleted file mode 100644
index 9136aacc..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-import com.seaofnodes.simple.type.TypeInteger;
-
-// Truncate toward zero, saturating overflow; NaN converts to zero.
-public class ToIntegerNode extends Node {
- public ToIntegerNode(Node lhs) { super(null,lhs); }
-
- @Override public String label() { return "ToInteger"; }
- @Override protected String format() { return "(int)%1"; }
-
- @Override public Type compute() {
- Type t = in(1)._type;
- if( t.isHigh() ) return TypeInteger.TOP;
- if( t instanceof TypeFloat tf && tf.isConstant() )
- return TypeInteger.constant((long)tf.value());
- return TypeInteger.BOT;
- }
-
- @Override public Node idealize() { return null; }
- @Override Node copy(Node lhs, Node rhs) { return new ToIntegerNode(lhs); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/TypeNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/TypeNode.java
deleted file mode 100644
index 970472da..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/TypeNode.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.type.*;
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
deleted file mode 100644
index 3792e224..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.type.Type;
-
-public class XCtrlNode extends CFGNode {
- public XCtrlNode() { super(new Node[]{CodeGen.CODE._start}); }
-
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("Xctrl"); }
- @Override public boolean isConst() { return true; }
- @Override public Type compute() { return Type.XCONTROL; }
- @Override public Node idealize() { return null; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/XorNode.java
deleted file mode 100644
index e9cd13a5..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/XorNode.java
+++ /dev/null
@@ -1,43 +0,0 @@
-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 String op() { return "^"; }
- @Override long doOp( long x, long y ) { return x ^ y; }
- @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) {
- return TypeInteger.BOT;
- }
-
- @Override
- public Node idealize() {
- Node lhs = in(1);
- Node rhs = in(2);
-
- Type t1 = lhs._type;
- Type t2 = rhs._type;
-
- // Xor of 0. We do not check for (0^x) because this will already
- // canonicalize to (x^0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && forceInt(lhs) && lhs._type instanceof TypeInteger )
- return lhs;
-
- // Move constants to RHS: con*arg becomes arg*con
- if ( t1.isConstant() && !t2.isConstant() )
- return swap12();
-
- // Do we have ((x ^ (phi cons)) ^ con) ?
- // Do we have ((x ^ (phi cons)) ^ (phi cons)) ?
- // Push constant up through the phi: x ^ (phi con0^con0 con1^con1...)
- Node phicon = AddNode.phiCon(this,true);
- if( phicon!=null ) return phicon;
-
- return super.idealize();
- }
- @Override Node copy(Node lhs, Node rhs) { return new XorNode(_loc,lhs,rhs); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java
deleted file mode 100644
index 76e47ca2..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class AddARM extends MachConcreteNode implements MachNode {
- AddARM( Node add) { super(add); }
- @Override public String op() { return "add"; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.WMASK; }
-
- // ADD (shifted register)
- @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_ADD); }
- // General form: "rd = rs1 + rs2"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2)));
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java
deleted file mode 100644
index 67a10b48..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class AddFARM extends MachConcreteNode implements MachNode {
- AddFARM(Node addf) { super(addf); }
- @Override public String op() { return "addf"; }
- @Override public RegMask regmap(int i) { return arm.DMASK; }
- @Override public RegMask outregmap() { return arm.DMASK; }
-
- //FADD (scalar)
- @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_OP_ADD);}
-
- // Default on double precision for now(64 bits)
- // General form: "addf rd = src1 + src2
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2)));
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java
deleted file mode 100644
index 4c635d07..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-
-public class AddIARM extends MachConcreteNode implements MachNode {
- final int _imm;
- AddIARM(Node add, int imm) {
- super(add);
- _inputs.pop();
- _imm = imm;
- }
- @Override public String op() {
- return _imm == 1 ? "inc" : (_imm == -1 ? "dec" : "addi");
- }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.WMASK; }
- //ADD (immediate)
- @Override public void encoding( Encoding enc ) {
- arm.imm_inst(enc,this, in(1), Arm64.OPI_ADD,_imm);
- }
- // General form: "addi rd = rs1 + imm"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + #").p(_imm);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java
deleted file mode 100644
index 1a0365ad..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.MachConcreteNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.util.SB;
-
-public class AndARM extends MachConcreteNode implements MachNode {
- AndARM(Node and) { super(and); }
- @Override public String op() { return "and"; }
- @Override public String glabel() { return "&"; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.WMASK; }
- // AND (shifted register)
- @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_AND); }
- // General form: #rd = rs1 & rs2
- @Override public void asm(CodeGen code, SB sb){
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & ").p(code.reg(in(2)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java
deleted file mode 100644
index 6292e98e..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class AndIARM extends MachConcreteNode implements MachNode {
- final int _imm;
- AndIARM(Node and, int imm) {
- super(and);
- _inputs.pop();
- _imm = imm;
- }
- @Override public String op() { return "andi"; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.WMASK; }
- @Override public void encoding( Encoding enc ) {
- arm.imm_inst_n(enc,this, in(1), Arm64.OPI_AND,_imm);
- }
- // General form: "andi rd = rs1 & imm"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & #").p(Arm64.decodeImm12(_imm));
- }
-} \ No newline at end of file
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java
deleted file mode 100644
index 77f14d4c..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-// Arithmetic Shift Right (register)
-public class AsrARM extends MachConcreteNode implements MachNode {
- AsrARM(Node asr) {super(asr);}
- @Override public String op() { return "sar"; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.DMASK; }
- @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,Arm64.OP_ASR); }
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> ").p(code.reg(in(2)));
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java
deleted file mode 100644
index 7cb42eec..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.codegen.Encoding;
-import com.seaofnodes.simple.codegen.RegMask;
-import com.seaofnodes.simple.node.MachConcreteNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.util.SB;
-
-public class AsrIARM extends MachConcreteNode implements MachNode {
- final int _imm;
- AsrIARM(Node asr, int imm) {
- super(asr);
- _inputs.pop();
- _imm = imm;
- }
- @Override public String op() { return "asri"; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.WMASK; }
- @Override public void encoding( Encoding enc ) {
- short rd = enc.reg(this);
- short rn = enc.reg(in(1));
- assert _imm > 0;
- enc.add4(Arm64.imm_shift(Arm64.OPI_ASR,_imm, 0b111111, rn, rd));
- }
- // General form: "asri rd = rs1 >> imm"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> #").p(_imm);
- }
-} \ No newline at end of file
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java
deleted file mode 100644
index 31bbde86..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-// Jump on flags, uses flags
-public class BranchARM extends IfNode implements MachNode, RIPRelSize {
- String _bop;
- final boolean _fp;
- BranchARM(IfNode iff, String bop, boolean fp ) {
- super(iff);
- _bop = bop;
- _fp = fp;
- }
- @Override public String op() { return "j"+_bop; }
- @Override public String label() { return op(); }
-
- @Override public void postSelect(CodeGen code) {
- Node set = in(1);
- if( set==null ) return; // Never-node cutout
- Node cmp = set.in(1);
- // Bypass an expected Set and just reference the cmp directly
- if( set instanceof SetARM)
- _inputs.set(1,cmp);
- else
- throw Utils.TODO();
- }
-
- @Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; }
- @Override public RegMask outregmap() { return null; }
- @Override public void negate() { _bop = negate(_bop); }
-
- @Override public void encoding( Encoding enc ) {
- // Assuming that condition flags are already set. These flags are set
- // by comparison (or sub). No need for regs because it uses flags
- if( in(1)!=null ) {
- // B.cond
- enc.add4( Arm64.b_cond(Arm64.OP_BRANCH, 0, Arm64.make_condition(_bop, _fp)) );
- } else {
- if( _bop=="!=" ) return; // Inverted, no code
- enc.add4(Arm64.b(Arm64.OP_UJMP, 0));
- }
- enc.jump(this,cproj(0));
- }
-
- // Delta is from opcode start
- @Override public byte encSize(int delta) {
- if( in(1)==null && _bop=="!=" ) return 0; // Inverted never-node, no code
- if( -(1<<19) <= delta && delta < (1<<19) ) return 4;
- // 2 word encoding needs a tmp register, must teach RA
- throw Utils.TODO();
- }
-
- // Delta is from opcode start
- @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- assert !( in(1)==null && _bop=="!=" ); // Inverted never-node, no code no patch
- if( opLen==4 ) {
- enc.patch4(opStart,Arm64.b_cond(Arm64.OP_BRANCH, delta, Arm64.make_condition(_bop, _fp)));
- } else {
- throw Utils.TODO();
- }
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- if( in(1)!=null ) { // Never-node
- String src = code.reg(in(1));
- if( src!="flags" ) sb.p(src).p(" ");
- } else if( _bop=="!=" ) {
- sb.p("never");
- return;
- }
- CFGNode prj = cproj(0).uctrlSkipEmpty();
- if( !prj.blockHead() ) prj = prj.cfg0();
- sb.p(label(prj));
- }
- @Override public String comment() { return "L"+cproj(1)._nid; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java
deleted file mode 100644
index 4de28f3a..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.TypeFunPtr;
-import com.seaofnodes.simple.util.SB;
-
-public class CallARM extends CallNode implements MachNode, RIPRelSize {
- final TypeFunPtr _tfp;
- final String _name;
- CallARM(CallNode call, TypeFunPtr tfp) {
- super(call);
- _inputs.pop(); // Pop constant target
- assert tfp.isConstant();
- _tfp = tfp;
- _name = CodeGen.CODE.funcName(tfp.fidx());
- }
-
- @Override public String op() { return "call"; }
- @Override public String label() { return op(); }
- @Override public String name() { return _name; }
- @Override public TypeFunPtr tfp() { return _tfp; }
- @Override public RegMask regmap(int i) { return arm.callInMask(_tfp,i,fun()._maxArgSlot); }
- @Override public RegMask outregmap() { return null; }
- @Override public int nargs() { return nIns()-2; } // Minus control, memory, fptr
-
- @Override public void encoding( Encoding enc ) {
- FunNode fun = CodeGen.CODE.link(_tfp);
- if( fun==null ) enc.external(this,_name);
- else enc.relo(this);
- // BL
- enc.add4(Arm64.b(Arm64.OP_CALL,0)); // Target patched at link time
- }
-
- // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding
- @Override public byte encSize(int delta) { return 4; }
-
- // Delta is from opcode start
- @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- enc.patch4(opStart,Arm64.b(Arm64.OP_CALL,delta));
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(_name).p(" ");
- for( int i=0; i<nargs(); i++ )
- sb.p(code.reg(arg(i+2))).p(", ");
- sb.unchar(2);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java
deleted file mode 100644
index 1949ae46..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class 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(Arm64.blr(Arm64.OP_CALLRARM, self));
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(fptr())).p(" ");
- for( int i=0; i<nargs(); i++ )
- sb.p(code.reg(arg(i))).p(", ");
- sb.unchar(2);
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java
deleted file mode 100644
index 1bd9c372..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class CmpARM extends MachConcreteNode implements MachNode {
- CmpARM( Node cmp ) { super(cmp); }
- @Override public String op() { return "cmp"; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.FLAGS_MASK; }
-
- // SUBS (shifted register)
- @Override public void encoding( Encoding enc ) { arm.r_reg_subs(enc,this,Arm64.OP_CMP); }
-
- // General form: "cmp rs1, rs2"
- @Override public void asm(CodeGen code, SB sb) {
- String dst = code.reg(this);
- if( dst!="flags" ) sb.p(dst).p(" = ");
- sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java
deleted file mode 100644
index 67ffc27b..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java
+++ /dev/null
@@ -1,22 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java
deleted file mode 100644
index cd4dd9cf..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-// Compare with immediate.
-// CMP (immediate) - SUBS
-public class CmpIARM extends MachConcreteNode implements MachNode {
- final int _imm;
- final String _bop;
- CmpIARM(BoolNode bool, int imm) {
- super(bool);
- _inputs.pop();
- _bop = bool.op();
- _imm = imm;
- }
- CmpIARM( CmpIARM cmp ) {
- super((Node)null);
- addDef(null);
- addDef(cmp.in(1));
- _bop = cmp._bop;
- _imm = cmp._imm;
- }
- @Override public String op() { return _imm==0 ? "test" : "cmp"; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.FLAGS_MASK; }
- @Override public boolean isClone() { return true; }
- @Override public Node copy() { return new CmpIARM(this); }
-
- @Override public void encoding( Encoding enc ) { arm.imm_inst_subs(enc,in(1),in(1), Arm64.OPI_CMP,_imm); }
-
- // General form: "cmp rs1, 1"
- @Override public void asm(CodeGen code, SB sb) {
- String dst = code.reg(this);
- if( dst!="flags" ) sb.p(dst).p(" = ");
- sb.p(code.reg(in(1)));
- if( _imm != 0 ) sb.p(", #").p(_imm);
- }
- }
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java
deleted file mode 100644
index e94b6491..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class DivARM extends MachConcreteNode implements MachNode {
- DivARM( Node div ) { super(div); }
- @Override public String op() { return "div"; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.WMASK; }
- // SDIV
- @Override public void encoding( Encoding enc ) { arm.madd(enc,this,Arm64.OP_DIV,3); }
- // General form: "div dst /= src"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2)));
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java
deleted file mode 100644
index 68ad6047..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class DivFARM extends MachConcreteNode implements MachNode {
- DivFARM( Node divf) { super(divf); }
- @Override public String op() { return "divf"; }
- @Override public RegMask regmap(int i) { return arm.DMASK; }
- @Override public RegMask outregmap() { return arm.DMASK; }
-
- // FDIV (scalar)
- @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_DIV); }
- // General form: "VDIF = dst /= src"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java
deleted file mode 100644
index 814a82c2..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.TypeInteger;
-import com.seaofnodes.simple.util.SB;
-
-// Convert a narrow C return register to Simple's full-width integer value.
-public class ExtendARM extends MachConcreteNode {
- final int _bits;
- final boolean _signed;
- ExtendARM(ProjNode prj, TypeInteger ret) {
- super(prj);
- _inputs.set(0,null);
- _inputs.add(new ProjARM(prj));
- _bits = 8 << ret.log_size();
- _signed = ret._min < 0;
- }
- @Override public String op() { return (_signed ? "sext" : "zext")+_bits; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.WMASK; }
- @Override public void encoding(Encoding enc) {
- short dst = enc.reg(this), src = enc.reg(in(1));
- // SBFM/UBFM aliases: sign/zero extend the low byte, short or int.
- enc.add4(Arm64.imm_shift(_signed ? Arm64.OPI_ASR : Arm64.OPI_LSR,0,_bits-1,src,dst));
- }
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java
deleted file mode 100644
index 31dcfc36..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class F8ToIARM extends MachConcreteNode implements MachNode {
- F8ToIARM(Node f8toi) { super(f8toi); }
- @Override public String op() { return "cvti"; }
- @Override public RegMask regmap(int i) { return arm.DMASK; }
- @Override public RegMask outregmap() { return arm.WMASK; }
- @Override public void encoding( Encoding enc ) {
- enc.add4(Arm64.floatToInteger(enc.reg(in(1))-arm.D_OFFSET,enc.reg(this)));
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1)));
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java
deleted file mode 100644
index db2540f7..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.ConstantNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-//FMOV (scalar, immediate)
-//Floating-point move immediate.
-public class FltARM extends ConstantNode implements MachNode, RIPRelSize {
- final String _ext;
- FltARM( ConstantNode con, String ext ) { super(con); _ext = ext; }
- @Override public String op() { return "ld8"; }
- @Override public RegMask regmap(int i) { return null; }
- @Override public RegMask outregmap() { return arm.DMASK; }
- @Override public boolean isClone() { return true; }
- @Override public FltARM copy() { return new FltARM(this,_ext); }
-
- @Override public void encoding( Encoding enc ) {
- if( _ext!=null ) throw Utils.TODO();
- enc.largeConstant(this,_con,0,-1/*TODO: ARM-style ELF patching*/);
- short dst = (short)(enc.reg(this) - arm.D_OFFSET);
- enc.add4(Arm64.load_pc(Arm64.OPF_ARM, 0, dst));
- }
-
- // Delta is from opcode start.
- @Override public byte encSize(int delta) { return 4; }
-
- // Delta is from opcode start
- @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- short dst = (short)(enc.reg(this) - arm.D_OFFSET);
- enc.patch4(opStart, Arm64.load_pc(Arm64.OPF_ARM, delta, dst));
- }
-
- // Human-readable form appended to the SB. Things like the encoding,
- // indentation, leading address or block labels not printed here.
- // Just something like "ld4\tR17=[R18+12] // Load array base".
- // General form: "op\tdst=src+src"
- @Override public void asm(CodeGen code, SB sb) {
- _con.print(sb.p(code.reg(this)).p(" = #"));
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java
deleted file mode 100644
index 17cafcde..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.FunNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-public class FunARM extends FunNode implements MachNode {
- FunARM(FunNode fun) { super(fun); }
- @Override public void computeFrameAdjust(CodeGen code, int maxReg) {
- super.computeFrameAdjust(code,maxReg);
- _frameAdjust = (_frameAdjust+15) & -16; // Keep SP aligned, including leaf spills.
- }
- @Override public void encoding( Encoding enc ) {
- int sz = _frameAdjust;
- if( sz == 0 ) return; // Skip if no frame adjust
- if( sz >= 1L<<12 ) throw Utils.TODO();
- enc.add4(Arm64.imm_inst(Arm64.OPI_SUB, sz, arm.RSP, arm.RSP));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java
deleted file mode 100644
index b1c30afc..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class I2F8ARM extends MachConcreteNode implements MachNode {
- I2F8ARM(Node i2f8) { super(i2f8); }
- @Override public String op() { return "cvt"; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.DMASK; }
- @Override public void encoding( Encoding enc ) {
- // SCVTF
- short self = (short)(enc.reg(this )-arm.D_OFFSET);
- short reg1 = enc.reg(in(1));
- int body = Arm64.float_cast(Arm64.OP_FLOAT_C, 1, reg1, self);
- enc.add4(body);
- }
-
- // General form: "i2f8 (flt)int_value"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1)));
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java
deleted file mode 100644
index 72b76edd..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.ConstantNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeInteger;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-// Integer constants
-public class IntARM extends ConstantNode implements MachNode {
- final String _ext;
- IntARM( ConstantNode con, String ext ) { super(con); _ext = ext; }
-
- @Override public String op() { return "ldi"; }
- @Override public RegMask regmap(int i) { return null; }
- @Override public RegMask outregmap() { return arm.WMASK; }
-
- @Override public boolean isClone() { return true; }
- @Override public IntARM copy() { return new IntARM(this,_ext); }
-
- @Override public void encoding( Encoding enc ) {
- if( _ext!=null ) throw Utils.TODO();
- short self = enc.reg(this);
- long x = _con==Type.NIL ? 0 : ((TypeInteger)_con).value();
- Arm64.constant(enc,self,x);
- }
-
- // Human-readable form appended to the SB. Things like the encoding,
- // indentation, leading address or block labels not printed here.
- // Just something like "ld4\tR17=[R18+12] // Load array base".
- // General form: "op\tdst=src+src"
- @Override public void asm(CodeGen code, SB sb) {
- String reg = code.reg(this);
- _con.print(sb.p(reg).p(" = #"));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
deleted file mode 100644
index 53494092..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.LoadNode;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.util.SB;
-
-// Load memory addressing on ARM
-// Support imm, reg(direct), or reg+off(indirect) addressing
-// Base = base - base pointer, offset is added to base
-// idx = null
-// off = off - offset added to base
-
-public class LoadARM extends MemOpARM {
- LoadARM(LoadNode ld, Node base, Node idx, int off) {
- super(ld, base, idx, off, 0);
- }
- @Override public String op() { return "ld"+_sz; }
- @Override public RegMask outregmap() { return size() >= 4 ? arm.MEM_MASK : arm.WMASK; }
-
- private static final int[] OP_LOADS = new int[]{ Arm64.OP_LOAD_IMM_8, Arm64.OP_LOAD_IMM_16, Arm64.OP_LOAD_IMM_32, Arm64.OP_LOAD_IMM_64, };
-
- private static final int[] OP_LOAD_RS = new int[]{ Arm64.OP_LOAD_R_8 , Arm64.OP_LOAD_R_16 , Arm64.OP_LOAD_R_32 , Arm64.OP_LOAD_R_64, };
-
- // ldr(immediate - unsigned offset) | ldr(register)
- @Override public void encoding( Encoding enc ) {
- ldst_encode(enc, OP_LOADS[Integer.numberOfTrailingZeros(size())], OP_LOAD_RS[_con.log_size()], this, size());
- }
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(",");
- asm_address(code,sb);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java
deleted file mode 100644
index 954cfac2..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-// Logical Shift Left (register)
-public class LslARM extends MachConcreteNode implements MachNode {
- LslARM(Node asr) {super(asr);}
- @Override public String op() { return "shr"; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.WMASK; }
-
- @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,Arm64.OP_LSL); }
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << ").p(code.reg(in(2)));
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java
deleted file mode 100644
index b32153ab..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.codegen.Encoding;
-import com.seaofnodes.simple.codegen.RegMask;
-import com.seaofnodes.simple.node.MachConcreteNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.util.SB;
-
-public class LslIARM extends MachConcreteNode implements MachNode {
- final int _imm;
- LslIARM(Node lsl, int imm) {
- super(lsl);
- _inputs.pop();
- _imm = imm;
- }
- @Override public String op() { return "lsli"; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.WMASK; }
- @Override public void encoding( Encoding enc ) {
- short rd = enc.reg(this);
- short rn = enc.reg(in(1));
- assert _imm > 0;
- // UBFM <Xd>, <Xn>, #(-<shift> MOD 64), #(63-<shift>)
- // immr must be (-<shift> MOD 64) = 64 - shift
- enc.add4(Arm64.imm_shift(Arm64.OPI_LSL, 64 - _imm, (64 - _imm) - 1, rn, rd));
- }
- // General form: "lsli rd = rs1 << imm"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << #").p(_imm);
- }
-} \ No newline at end of file
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java
deleted file mode 100644
index 91be34e3..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-// Logical Shift Right (register)
-public class LsrARM extends MachConcreteNode implements MachNode {
- LsrARM(Node asr) {super(asr);}
- @Override public String op() { return "shr"; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.WMASK; }
-
- @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,Arm64.OP_LSR); }
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> ").p(code.reg(in(2)));
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java
deleted file mode 100644
index 243ce968..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-// logical right shift immediate
-public class LsrIARM extends MachConcreteNode implements MachNode {
- final int _imm;
- LsrIARM(Node lsr, int imm) {
- super(lsr);
- _inputs.pop();
- _imm = imm;
- }
- @Override public String op() { return "lsri"; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.WMASK; }
- @Override public void encoding( Encoding enc ) {
- short rd = enc.reg(this);
- short rn = enc.reg(in(1));
- assert _imm > 0;
- enc.add4(Arm64.imm_shift(Arm64.OPI_LSR,_imm, 0b111111, rn,rd));
- }
- // General form: "lsri rd = rs1 >>> imm"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> #").p(_imm);
- }
-} \ No newline at end of file
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java
deleted file mode 100644
index 14eaf510..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.SB;
-import java.lang.StringBuilder;
-
-public abstract class MemOpARM extends MemOpNode implements MachNode {
- final int _off; // Limit 9 bits sized, or (13 bits<<logsize) unsigned
- final int _imm; // Limit ? bits
- final int _bytes;
- final char _sz;
- MemOpARM(MemOpNode mop, Node ptr, Node idx, int off, int imm) {
- super(mop,mop);
- assert ptr._type instanceof TypeMemPtr && !(ptr instanceof AddNode);
- assert ptr() == ptr;
- _inputs.setX(2, ptr);
- _inputs.setX(3, idx);
- _off = off;
- _imm = imm;
- _bytes = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size();
- _sz = (char)('0'+_bytes);
- }
-
- // Store-based flavors have a value edge
- MemOpARM( MemOpNode mop, Node ptr, Node idx, int off, int imm, Node val ) {
- this(mop,ptr,idx,off,imm);
- _inputs.setX(4,val);
- }
-
- @Override public String label() { return op();}
- Node val() { return in(4); } // Only for stores
-
- @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { return p.p(".").p(_name); }
-
- @Override public Type compute() { throw new AssertionError("Do not call compute on "+getClass().getSimpleName()); }
- @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); }
-
- int size() { return _bytes; }
-
- // Wider mask to store both GPRs and FPRs
- @Override public RegMask regmap(int i) {
- // 0 - ctrl
- if( i==1 ) return null; // memory
- if( i==2 ) return arm.RMASK; // ptr/base
- if( i==3 ) return arm.RMASK; // off/index
- if( i==4 ) return size() >= 4 ? arm.MEM_MASK : arm.RMASK; // value
- return null; // Anti-dependence
- }
-
- // Shared encoding for loads and stores(int and float)
- public void ldst_encode( Encoding enc, int opcode_imm, int opcode_reg, Node xval, int size) {
- short ptr = enc.reg(ptr());
- short off = enc.reg(off());
- short val = enc.reg(xval);
- // The allocated register selects integer vs SIMD/FP encoding, even for
- // bitwise copies. ARM numbers each register bank from zero in the opcode.
- boolean fp = val >= arm.D0;
- assert !fp || size >= 4;
- if( fp ) {
- opcode_imm |= 1<<4;
- opcode_reg |= 1<<5;
- val -= arm.D0;
- }
- int body = off() == null
- ? Arm64.load_str_imm(opcode_imm, _off, ptr, val, size)
- : Arm64.indr_adr(opcode_reg, off, Arm64.STORE_LOAD_OPTION.SXTX, 0, ptr, val);
- enc.add4(body);
- }
-
- SB asm_address(CodeGen code, SB sb) {
- sb.p("[").p(code.reg(ptr())).p("+");
- if( off() != null ) sb.p(code.reg(off()));
- else sb.p(_off);
- return sb.p("]");
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java
deleted file mode 100644
index f598cefb..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-// mulh signed multiply instruction(no-imm form)
-public class MulARM extends MachConcreteNode implements MachNode {
- MulARM(Node mul) {super(mul);}
- @Override public String op() { return "mul"; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.WMASK; }
-
- @Override public void encoding( Encoding enc ) { arm.madd(enc,this,Arm64.OP_MUL,31); }
- // General form: "rd = rs1 * rs2"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java
deleted file mode 100644
index a4f81fd4..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class MulFARM extends MachConcreteNode implements MachNode{
- MulFARM(Node mulf) {super(mulf);}
- @Override public String op() { return "mulf"; }
- @Override public RegMask regmap(int i) { return arm.DMASK; }
- @Override public RegMask outregmap() { return arm.DMASK; }
- @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_MUL); }
- // Default on double precision for now(64 bits)
- // General form: "VMUL.f32 rd = src1 * src2
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java
deleted file mode 100644
index 881b41f9..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java
+++ /dev/null
@@ -1,25 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java
deleted file mode 100644
index 8bb8983e..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-public class NewARM extends NewNode implements MachNode, RIPRelSize {
- // A pre-zeroed chunk of memory.
- NewARM(NewNode nnn) { super(nnn); }
- @Override public void encoding( Encoding enc ) {
- // BL(branch with link)
- enc.external(this,"calloc").
- add4(Arm64.mov(Arm64.OP_MOVZ, 0, 1, 0)). // movz x0,#1
- add4(Arm64.b(Arm64.OP_CALL, 0));
- }
-
- // Patch is for running "new" in a JIT.
- // Delta is from opcode start
- @Override public byte encSize(int delta) { return 4*2; }
-
- // Patch is for running "new" in a JIT.
- // Delta is from opcode start
- @Override public void patch(Encoding enc, int opStart, int opLen, int delta ) {
- // Negative patches are JIT emulator targets.
- if( opStart+delta < 0 ) {
- enc.patch4(opStart+4, Arm64.b_calloc(Arm64.OP_CALL, delta-4));
- } else {
- throw Utils.TODO();
- }
- }
- // General form: "alloc #bytes PC"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p("\n"); // Multi-op trigger
- sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n");
- sb.p("call #calloc");
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
deleted file mode 100644
index f8e450f6..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.node.MachConcreteNode;
-import com.seaofnodes.simple.util.SB;
-
-public class NotARM extends MachConcreteNode implements MachNode{
- NotARM(NotNode not) {super(not);}
- @Override public String op() { return "not"; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.WMASK; }
- @Override public RegMask killmap() { return arm.FLAGS_MASK; }
- @Override public void encoding( Encoding enc ) {
- // subs xzr, rs, #0
- // cset rd, eq // Set rd to 1 if rs == 0 (equal), else 0
- // subtracting zero from rs will just yield rs, it sets the zero flag and then it's used in cset
- short self = enc.reg(this );
- short reg1 = enc.reg(in(1));
- int subs = Arm64.imm_inst(Arm64.OP_SUBS, 0, reg1, 31);
- enc.add4(subs);
- int cset = Arm64.cset(Arm64.OP_CSET, 31, Arm64.COND.EQ, 31, self);
- enc.add4(cset);
- }
-
- @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java
deleted file mode 100644
index 5b7264ec..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-// Not using the ORRS variant.
-public class OrARM extends MachConcreteNode implements MachNode {
- OrARM(Node and) { super(and); }
- @Override public String op() { return "or"; }
- @Override public String glabel() { return "|"; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.WMASK; }
- @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_OR); }
- // General form: #rd = rs1 & rs2
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | ").p(code.reg(in(2)));
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java
deleted file mode 100644
index 86d07bee..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class OrIARM extends MachConcreteNode implements MachNode {
- final int _imm;
- OrIARM(Node or, int imm) {
- super(or);
- _inputs.pop();
- _imm = imm;
- }
- @Override public String op() { return "ori"; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.WMASK; }
- @Override public void encoding( Encoding enc ) {
- arm.imm_inst_n(enc, this, in(1), Arm64.OPI_OR, _imm);
- }
- // General form: "ori rd = rs1 | imm"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | #").p(_imm);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java
deleted file mode 100644
index b419fe0a..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java
+++ /dev/null
@@ -1,17 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java
deleted file mode 100644
index 4b7d6f13..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java
+++ /dev/null
@@ -1,15 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java
deleted file mode 100644
index a7ed5073..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.*;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-
-public class RetARM extends ReturnNode implements MachNode {
- RetARM(ReturnNode ret, FunNode fun) { super(ret, fun); fun.setRet(this); }
- @Override public void encoding( Encoding enc ) {
- int frameAdjust = ((FunARM)fun())._frameAdjust;
- if( frameAdjust > 0 )
- enc.add4(Arm64.imm_inst(Arm64.OPI_ADD, frameAdjust, arm.RSP, arm.RSP));
- enc.add4(Arm64.ret(Arm64.OP_RET));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java
deleted file mode 100644
index e5b06c39..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-// conditional set?
-// e.g CSET, stores flag info into GPRS after compare
-public class SetARM extends MachConcreteNode implements MachNode {
- final String _bop; // One of <,<=,==
- final boolean _fp; // FP compare (unsigned encoding)
- SetARM(Node cmp, String bop, boolean fp) {
- super(cmp);
- _inputs.setLen(1); // Pop the cmp inputs
- // Replace with the matched cmp
- _inputs.push(cmp);
- _bop = bop;
- _fp = fp;
- }
- @Override public String op() { return "set"+_bop; }
- @Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; }
- @Override public RegMask outregmap() { return arm.WMASK; }
-
- @Override public void encoding( Encoding enc ) {
- // Using RM==RN==31 implies a *zero* value, not R31.
- int body = Arm64.cset(Arm64.OP_CSET,0b11111, Arm64.make_condition(_bop, _fp), 0b011111, enc.reg(this));
- enc.add4(body);
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this));
- String src = code.reg(in(1));
- if( src!="flags" ) sb.p(" = ").p(src);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java
deleted file mode 100644
index dcfeb5d8..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.FunNode;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.node.SplitNode;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-public class SplitARM extends SplitNode {
- SplitARM(LRG lrg, String kind, byte round) { super(lrg,kind,round,new Node[2]);}
- @Override public RegMask regmap(int i) { return arm.SPLIT_MASK; }
- @Override public RegMask outregmap() { return arm.SPLIT_MASK; }
-
- @Override public void encoding( Encoding enc ) {
- short dst = enc.reg(this );
- short src = enc.reg(in(1));
-
- boolean dstX = dst >= arm.D_OFFSET;
- boolean srcX = src >= arm.D_OFFSET;
-
- if (dst >= arm.MAX_REG) {
- // store to sp
- if(src >= arm.MAX_REG) {
- throw Utils.TODO();
- }
- int off = fun(enc).computeStackOffset(enc._code,dst);
- if( srcX ) src -= arm.D_OFFSET;
- enc.add4(Arm64.load_str_imm(Arm64.OP_STORE_IMM_64, off, arm.RSP, src, 8));
- return;
- }
-
- if(src >= arm.MAX_REG) {
- // Load from SP
- int off = fun(enc).computeStackOffset(enc._code,src);
- if( dstX ) dst -= arm.D_OFFSET;
- enc.add4(Arm64.load_str_imm(Arm64.OP_LOAD_IMM_64, off, arm.RSP, dst, 8));
- return;
- }
-
- // pick opcode based on regs
- if(!dstX && !srcX) {
- // GPR->GPR
- enc.add4(Arm64.mov_reg(Arm64.OP_MOV, src, dst));
- } else if(dstX && srcX) {
- // FPR->FPR
- // fmov reg
- enc.add4(Arm64.f_mov_reg(Arm64.OP_FMOV_REG, src - arm.D_OFFSET,dst - arm.D_OFFSET));
- } else if(!srcX && dstX) {
- // GPR->FPR
- // FMOV(general) 64 bits to DOUBLE-PRECISION
- enc.add4(Arm64.f_mov_general(Arm64.OP_FMOV, 0b01, 0, 0b111, src, dst - arm.D_OFFSET));
- } else if( srcX && !dstX) {
- //FPF->GPR
- // FMOV(general) DOUBLE-PRECISION to 64 bits
- enc.add4(Arm64.f_mov_general(Arm64.OP_FMOV, 0b01, 0, 0b110, src - arm.D_OFFSET, dst));
- }
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java
deleted file mode 100644
index 25e5d501..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-
-// NZCV transfers require a GPR. Further splits can spill that GPR to the stack.
-public class SplitFlagsARM extends SplitARM {
- private static final RegMask MASK = new RegMask(arm.WR_BITS,1L);
- SplitFlagsARM(LRG lrg,String kind,byte round) { super(lrg,kind,round); }
- @Override public boolean isNonStackSplit() { return true; }
- @Override public RegMask regmap(int i) { return MASK; }
- @Override public RegMask outregmap() { return MASK; }
-
- @Override public void encoding(Encoding enc) {
- short dst=enc.reg(this), src=enc.reg(in(1));
- if( dst==src ) return;
- if( src==arm.FLAGS ) enc.add4(Arm64.flags(dst,false));
- else if( dst==arm.FLAGS ) enc.add4(Arm64.flags(src,true));
- else super.encoding(enc);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java
deleted file mode 100644
index c9b2127a..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.simple.codegen.*;
-
-// Keep a saved NZCV value in GPRs and stack slots through subsequent spills.
-public class SplitGPRARM extends SplitARM {
- private static final RegMask MASK = new RegMask(arm.WR_BITS,-2L);
- SplitGPRARM(LRG lrg,String kind,byte round) { super(lrg,kind,round); }
- @Override public RegMask regmap(int i) { return MASK; }
- @Override public RegMask outregmap() { return MASK; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java
deleted file mode 100644
index 0b90ac8b..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.node.StoreNode;
-import com.seaofnodes.simple.util.SB;
-
-// Store memory addressing on ARM
-// Support imm, reg(direct), or reg+off(indirect) addressing
-// base - base pointer, offset is added to base
-// null - index never allowed (no [reg+reg] mode)
-// off - offset added to base
-// imm - immediate value to store, only if val is null
-// val - value to store or null
-
-//e.g s.cs[0] = 67; // C
-// base = s.cs, off = 4, imm = 67, val = null
-public class StoreARM extends MemOpARM {
- StoreARM(StoreNode st, Node base, Node idx, int off, Node val) {
- super(st, base, idx, off, 0, val);
- }
- @Override public String op() { return "st"+_sz; }
- @Override public RegMask outregmap() { return null; }
-
- private static final int[] OP_STORES = new int[]{ Arm64.OP_STORE_IMM_8, Arm64.OP_STORE_IMM_16, Arm64.OP_STORE_IMM_32, Arm64.OP_STORE_IMM_64, };
-
- private static final int[] OP_STORE_RS = new int[]{ Arm64.OP_STORE_R_8, Arm64.OP_STORE_R_16, Arm64.OP_STORE_R_32, Arm64.OP_STORE_R_64, };
-
- @Override public void encoding( Encoding enc ) {
- ldst_encode(enc, OP_STORES[Integer.numberOfTrailingZeros(size())], OP_STORE_RS[Integer.numberOfTrailingZeros(_bytes)], val(), size());
- }
- @Override public void asm(CodeGen code, SB sb) {
- asm_address(code,sb).p(",");
- if( val()==null ) sb.p("#").p(_imm);
- else sb.p(code.reg(val()));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java
deleted file mode 100644
index fd5cd4de..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class SubARM extends MachConcreteNode implements MachNode {
- SubARM(Node sub) { super(sub); }
- @Override public String op() { return "sub"; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.WMASK; }
-
- @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_SUB); }
-
- // General form: "sub # rd = rs1 - rs2"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java
deleted file mode 100644
index 1a751434..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class SubFARM extends MachConcreteNode implements MachNode {
- SubFARM(Node subf) {super(subf);}
- @Override public String op() { return "subf"; }
- @Override public RegMask regmap(int i) { return arm.DMASK; }
- @Override public RegMask outregmap() { return arm.DMASK; }
- @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_SUB); }
- // Default on double precision for now(64 bits)
- // General form: "vsub.f32 rd = src1 + src2
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java
deleted file mode 100644
index b2c0b2ed..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.node.MachConcreteNode;
-import com.seaofnodes.simple.type.TypeInteger;
-import com.seaofnodes.simple.util.SB;
-
-public class SubIARM extends MachConcreteNode implements MachNode {
- final int _imm;
- SubIARM(Node sub, int imm) {
- super(sub);
- _inputs.pop();
- _imm = imm;
- }
- @Override public String op() { return _imm == -1 ? "dec" : "subi"; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.WMASK; }
-
- @Override public void encoding( Encoding enc ) {
- arm.imm_inst(enc,this, in(1), Arm64.OPI_SUB,_imm);
- }
-
- // General form: "subi rd = rs1 - imm"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - #").p(_imm);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java
deleted file mode 100644
index a053ba46..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.TypeFunPtr;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-public class TFPARM extends FunPtrNode implements MachNode, RIPRelSize {
- final String _ext;
- // Pointer to a Simple function
- TFPARM( FunPtrNode fptr ) { super(fptr); _ext = null; }
- // Pointer to an Extern "C" function
- TFPARM( TypeFunPtr fptr, String ext ) { super(fptr,CodeGen.CODE._start,null); _type = fptr; _ext = ext; }
- @Override public String op() { return "ldx"; }
- @Override public RegMask regmap(int i) { return null; }
- @Override public RegMask outregmap() { return arm.WMASK; }
- @Override public boolean isClone() { return true; }
- @Override public TFPARM copy() { return new TFPARM((TypeFunPtr)_con,_ext); }
- @Override public void encoding( Encoding enc ) {
- if( _ext==null ) enc.relo(this); // Internal patch
- else enc.external(this,_ext); // ELF-file external patch
- short dst = enc.reg(this);
- // adrp x0, 0
- enc.add4(Arm64.adrp(1,0, Arm64.OP_ADRP, 0,dst));
- // add x0, x0, 0
- arm.imm_inst(enc,Arm64.OPI_ADD,0, dst);
- }
-
- @Override public byte encSize(int delta) {
- return 8;
- }
-
- // Delta is from opcode start
- @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- short rpc = enc.reg(this);
- if(opLen == 8 ) {
- arm.patch_adrp_add(enc, opStart, delta, rpc);
- } else {
- // should not happen as one instruction is 4 byte, and TFP arm encodes 2.
- throw Utils.TODO();
- }
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- String reg = code.reg(this);
- _type.print(sb.p(reg).p(" #"));
- }
- @Override public boolean eq(Node n) { return this==n; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java
deleted file mode 100644
index b2531f97..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.ConstantNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.type.TypeMemPtr;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-public class TMPARM extends ConstantNode implements MachNode, RIPRelSize {
- final String _ext;
- TMPARM( ConstantNode con, String ext ) { super(con); _ext = ext; }
- @Override public String op() { return "ldp"; }
- @Override public RegMask regmap(int i) { return null; }
- @Override public RegMask outregmap() { return arm.WMASK; }
- @Override public boolean isClone() { return true; }
- @Override public TMPARM copy() { return new TMPARM(this,_ext); }
- @Override public void encoding( Encoding enc ) {
- if( _ext!=null ) enc.externalData(this,_ext);
- else enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1);
- short dst = enc.reg(this);
- // adrp x0, 0
- enc.add4(Arm64.adrp(1,0, Arm64.OP_ADRP, 0,dst));
- // add x0, x0, 0
- arm.imm_inst(enc,Arm64.OPI_ADD,0, dst);
- }
-
- @Override public byte encSize(int delta) {
- return 8;
- }
-
- // Delta is from opcode start
- @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- short dst = enc.reg(this);
- if(opLen == 8 ) {
- arm.patch_adrp_add(enc, opStart, delta, dst);
- } else {
- throw Utils.TODO();
- }
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- String reg = code.reg(this);
- _con.print(sb.p(reg).p(" #"));
- }
- @Override public boolean eq(Node n) { return this==n; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java
deleted file mode 100644
index aecd02bb..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-// unconditional jump
-public class UJmpARM extends CFGNode implements MachNode, RIPRelSize {
- UJmpARM() { }
- @Override public String op() { return "jmp"; }
- @Override public String label() { return op(); }
- @Override protected String format() { return "jmp "; }
- @Override public RegMask regmap(int i) {return null; }
- @Override public RegMask outregmap() { return null; }
- @Override public Type compute() { throw Utils.TODO(); }
- @Override public Node idealize() { throw Utils.TODO(); }
- @Override public void encoding( Encoding enc ) {
- enc.jump(this,uctrl());
- int body = Arm64.b(Arm64.OP_UJMP, 0);
- enc.add4(body);
- }
-
- // Delta is from opcode start
- @Override public byte encSize(int delta) {
- if( -(1<<26) <= delta && delta < (1<<26) ) return 4;
- // 2 word encoding needs a tmp register, must teach RA
- throw Utils.TODO();
- }
-
- // Delta is from opcode start
- @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- if( opLen==4 ) {
- enc.patch4(opStart,Arm64.b(Arm64.OP_UJMP, delta));
- } else {
- throw Utils.TODO();
- }
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- CFGNode target = uctrl();
- //assert target.nOuts() > 1; // Should optimize jmp to empty targets
- sb.p(label(target));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java
deleted file mode 100644
index d56397a3..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class XorARM extends MachConcreteNode implements MachNode{
- XorARM(Node xor) {super(xor);}
- @Override public String op() { return "xor"; }
- @Override public String glabel() { return "^"; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.WMASK; }
- @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_XOR); }
- // General form: "rd = x1 ^ x2"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ ").p(code.reg(in(2)));
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java
deleted file mode 100644
index 12143ccd..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.node.MachConcreteNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.util.SB;
-
-public class XorIARM extends MachConcreteNode implements MachNode {
- final int _imm;
- XorIARM(Node xor, int imm) {
- super(xor);
- _inputs.pop();
- _imm = imm;
- }
-
- @Override public String op() { return "xori"; }
- @Override public RegMask regmap(int i) { return arm.RMASK; }
- @Override public RegMask outregmap() { return arm.WMASK; }
-
- // General form: "xori rd = rs1 ^ imm"
- @Override public void encoding( Encoding enc ) { arm.imm_inst_n(enc,this, in(1), Arm64.OPI_XOR,_imm); }
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ #").p(_imm);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
deleted file mode 100644
index 3c965da5..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
+++ /dev/null
@@ -1,503 +0,0 @@
-package com.seaofnodes.simple.node.cpus.arm;
-
-import com.seaofnodes.isa.Arm64;
-import static com.seaofnodes.isa.Arm64.*;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.Utils;
-
-public class arm extends Machine {
- public arm( CodeGen code ) {
- if( !"SystemV".equals(code._callingConv) )
- throw new IllegalArgumentException("Unknown calling convention "+code._callingConv);
- }
-
- // ARM64
- @Override public String name() { return "arm"; }
-
- // GPR(S)
- public static final int X0 = 0, X1 = 1, X2 = 2, X3 = 3, X4 = 4, X5 = 5, X6 = 6, X7 = 7;
- public static final int X8 = 8, X9 = 9, X10 = 10, X11 = 11, X12 = 12, X13 = 13, X14 = 14, X15 = 15;
- public static final int X16 = 16, X17 = 17, X18 = 18, X19 = 19, X20 = 20, X21 = 21, X22 = 22, X23 = 23;
- public static final int X24 = 24, X25 = 25, X26 = 26, X27 = 27, X28 = 28, X29 = 29, X30 = 30, RSP = 31;
-
- // Floating point registers
- public static final int D0 = 32, D1 = 33, D2 = 34, D3 = 35, D4 = 36, D5 = 37, D6 = 38, D7 = 39;
- public static final int D8 = 40, D9 = 41, D10 = 42, D11 = 43, D12 = 44, D13 = 45, D14 = 46, D15 = 47;
- public static final int D16 = 48, D17 = 49, D18 = 50, D19 = 51, D20 = 52, D21 = 53, D22 = 54, D23 = 55;
- public static final int D24 = 56, D25 = 57, D26 = 58, D27 = 59, D28 = 60, D29 = 61, D30 = 62, D31 = 63;
-
- static final int FLAGS = 64;
- static final int MAX_REG = 65;
- public static final int D_OFFSET = 32;
-
- static final String[] REGS = new String[] {
- "X0", "X1", "X2", "X3", "X4", "X5", "X6", "X7",
- "X8", "X9", "X10", "X11", "X12", "X13", "X14", "X15",
- "X16", "X17", "X18", "X19", "X20", "X21", "X22", "X23",
- "X24", "X25", "X26", "X27", "X28", "X29", "RPC", "RSP",
- "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7",
- "D8", "D9", "D10", "D11", "D12", "D13", "D14", "D15",
- "D16", "D17", "D18", "D19", "D20", "D21", "D22", "D23",
- "D24", "D25", "D26", "D27", "D28", "D29", "D30", "D31",
- "flags"
- };
- @Override public String[] regs() { return REGS; }
-
- // from (x0-x30)
- // General purpose register mask: pointers and ints, not floats
- static final long RD_BITS = 0xFFFFFFFFL;
- static final RegMask RMASK = new RegMask(RD_BITS);
- static final long WR_BITS = 0x7FFFFFFFL; // All the GPRs, not RSP
- static final RegMask WMASK = new RegMask(WR_BITS);
-
- // Float mask from(d0–d31)
- static final long FP_BITS = 0xFFFFFFFFL<<D0;
- static final RegMask DMASK = new RegMask(FP_BITS);
-
- // Load/store mask; both GPR and FPR
- static final RegMask MEM_MASK = new RegMask(WR_BITS | FP_BITS);
-
- static final RegMask SPLIT_MASK = new RegMask(WR_BITS | FP_BITS, -2L/*skip flags*/);
-
- static final RegMask FLAGS_MASK = new RegMask(FLAGS);
-
- // Arguments masks
- static final RegMask X0_MASK = new RegMask(X0);
- static final RegMask X1_MASK = new RegMask(X1);
- static final RegMask X2_MASK = new RegMask(X2);
- static final RegMask X3_MASK = new RegMask(X3);
- static final RegMask X4_MASK = new RegMask(X4);
- static final RegMask X5_MASK = new RegMask(X5);
- static final RegMask X6_MASK = new RegMask(X6);
- static final RegMask X7_MASK = new RegMask(X7);
-
- // Arguments(float) masks
- static final RegMask D0_MASK = new RegMask(D0);
- static final RegMask D1_MASK = new RegMask(D1);
- static final RegMask D2_MASK = new RegMask(D2);
- static final RegMask D3_MASK = new RegMask(D3);
- static final RegMask D4_MASK = new RegMask(D4);
- static final RegMask D5_MASK = new RegMask(D5);
- static final RegMask D6_MASK = new RegMask(D6);
- static final RegMask D7_MASK = new RegMask(D7);
-
- // Calling convention; returns a machine-specific register
- // for incoming argument idx.
- // index 0 for control, 1 for memory, real args start at index 2
- static final RegMask[] CALLINMASK = new RegMask[] {
- X0_MASK,
- X1_MASK,
- X2_MASK,
- X3_MASK,
- X4_MASK,
- X5_MASK,
- X6_MASK,
- X7_MASK,
- };
- static final RegMask[] XMMS = new RegMask[] {
- D0_MASK,
- D1_MASK,
- D2_MASK,
- D3_MASK,
- D4_MASK,
- D5_MASK,
- D6_MASK,
- D7_MASK,
- };
-
- // ARM ENCODING
-
- // True if signed 9-bit immediate
- private static boolean imm9(TypeInteger ti) {
- // 55 = 64-9
- return ti.isConstant() && ((ti.value()<<55)>>55) == ti.value();
- }
- // True if signed 12-bit immediate
- private static boolean imm12(TypeInteger ti) {
- // 52 = 64-12
- return ti.isConstant() && ((ti.value()<<52)>>52) == ti.value();
- }
-
- // Can we encode this in ARM's 12-bit LOGICAL immediate form?
- // Some combination of shifted bit-masks.
- private static int imm12Logical(TypeInteger ti) {
- return ti.isConstant() ? Arm64.logicalImmediate(ti.value()) : -1;
- }
-
- public static void imm_inst(Encoding enc, Node n,Node n2, int opcode, int imm12) {
- short self = enc.reg(n);
- short reg1 = enc.reg(n2);
-
- // ADD/SUB encode an unsigned magnitude; switch operation for a negative addend.
- if( imm12<0 && (opcode==OPI_ADD || opcode==OPI_SUB) ) {
- opcode ^= OPI_ADD ^ OPI_SUB;
- imm12 = -imm12;
- }
- int body = Arm64.imm_inst(opcode, imm12&0xFFF, reg1, self);
- enc.add4(body);
- }
-
- public static void imm_inst_subs(Encoding enc, Node n,Node n2, int opcode, int imm12) {
- short reg1 = enc.reg(n2);
- // 31 = 11111
- int body = Arm64.imm_inst(opcode, imm12&0xFFF, reg1, 31);
- enc.add4(body);
- }
-
- // for cases where rs1 and dst are the same, eg add x0, x0, 1
- public static void imm_inst(Encoding enc, int opcode, int imm12, int self) {
- int body = Arm64.imm_inst(opcode, imm12&0xFFF, self, self);
- enc.add4(body);
- }
-
- public static void imm_inst_n(Encoding enc, Node n, Node n2, int opcode, int imm13) {
- short self = enc.reg(n);
- short reg1 = enc.reg(n2);
- int body = Arm64.imm_inst_n(opcode, imm13, reg1, self);
- enc.add4(body);
- }
-
- // for cases where rs1 and dst are the same, eg add x0, x0, 1
- public static int imm_inst_l(Encoding enc, Node n, int opcode, int imm12) {
- short self = enc.reg(n);
- short reg1 = enc.reg(n.in(1));
- int body = Arm64.imm_inst(opcode, imm12&0xFFF, reg1, self);
- return body;
- }
-
- // for normal add, reg1, reg2 cases (reg-to-reg)
- // using shifted-reg form
-
- public static void r_reg(Encoding enc, Node n, int opcode) {
- short self = enc.reg(n);
- short reg1 = enc.reg(n.in(1));
- short reg2 = enc.reg(n.in(2));
- int body = Arm64.r_reg(opcode, 0, reg2, 0, reg1, self >= 32 ? reg1: self);
- enc.add4(body);
- }
-
- public static void r_reg_subs(Encoding enc, Node n, int opcode) {
- short reg1 = enc.reg(n.in(1));
- short reg2 = enc.reg(n.in(2));
- // 31 = 11111
- int body = Arm64.r_reg(opcode, 0, reg2, 0, reg1, 31);
- enc.add4(body);
- }
-
- public static void shift_reg(Encoding enc, Node n, int op2) {
- short self = enc.reg(n);
- short reg1 = enc.reg(n.in(1));
- short reg2 = enc.reg(n.in(2));
- int body = Arm64.shift_reg(0b10011010110, reg2, op2, reg1, self);
- enc.add4(body);
- }
-
- // MUL can be considered an alias for MADD with the third operand Ra being set to 0
-
- public static void madd(Encoding enc, Node n, int opcode, int ra) {
- short self = enc.reg(n);
- short reg1 = enc.reg(n.in(1));
- short reg2 = enc.reg(n.in(2));
- int body = Arm64.madd(opcode, reg2, ra, reg1, self);
- enc.add4(body);
- }
-
- public static void f_scalar(Encoding enc, Node n, int op ) {
- short self = (short)(enc.reg(n) -D_OFFSET);
- short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET);
- short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET);
- int body = Arm64.f_scalar(0b00011110, 1, reg2, op, reg1, self);
- enc.add4(body);
- }
-
- public static void patch_adrp_add(Encoding enc, int opStart, int delta, int rd) {
- int target = opStart + delta;
- int pageDelta = ((target & ~0xFFF) - (opStart & ~0xFFF)) >> 12;
- int imm21 = pageDelta & 0x1FFFFF;
- enc.patch4(opStart , Arm64.adrp(1, imm21 & 3, OP_ADRP, imm21 >>> 2, rd));
- enc.patch4(opStart+4, Arm64.imm_inst_l(OPI_ADD, target & 0xFFF, rd));
- }
-
- public static void f_cmp(Encoding enc, Node n) {
- short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET);
- short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET);
- int body = Arm64.f_cmp(0b00011110, 3, reg2, reg1);
- enc.add4(body);
- }
-
- @Override public RegMask callArgMask(TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); }
- static RegMask callInMask(TypeFunPtr tfp, int idx, int maxArgSlot ) {
- if( idx==0 ) return CodeGen.CODE._rpcMask;
- if( idx==1 ) return null;
- // Count floats in signature up to index
- if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence
- boolean hidden = hiddenSelf(tfp);
- int sigidx = idx-2;
- if( hidden ) {
- if( sigidx==0 ) return null;
- sigidx--;
- }
- // Count floats in signature up to index
- int fcnt=0;
- for( int i=hidden ? 1 : 0; i<idx-2; i++ )
- if( tfp.arg(i) instanceof TypeFloat)
- fcnt++;
- // Floats up to XMMS in XMM registers
- if( tfp.arg(idx-2) instanceof TypeFloat ) {
- if( fcnt < XMMS.length )
- return XMMS[fcnt];
- } else {
- RegMask[] cargs = CALLINMASK;
- if( sigidx-fcnt < cargs.length )
- return cargs[sigidx-fcnt];
- }
- // Pass on stack slot (8 and higher)
- if( maxArgSlot>0 ) throw Utils.TODO();
- return new RegMask(MAX_REG + 1 + sigidx);
- }
-
- // Return the max stack slot used by this signature, or 0
- @Override public short maxArgSlot( TypeFunPtr tfp ) {
- int icnt=0, fcnt=0; // Count of ints, floats
- for( int i=hiddenSelf(tfp) ? 1 : 0; i<tfp.nargs(); i++ ) {
- if( tfp.arg(i) instanceof TypeFloat ) fcnt++;
- else icnt++;
- }
- int nstk = Math.max(icnt-8,0)+Math.max(fcnt-8,0);
- return (short)nstk;
- }
- private static boolean hiddenSelf(TypeFunPtr tfp) {
- return tfp.nargs() > 0 && tfp.arg(0) == TypePtr.PTR;
- }
-
- private static final long CALLEE_SAVE =
- 1L<<X19 |
- 1L<<X20 | 1L<<X21 | 1L<<X22 | 1L<<X23 |
- 1L<<X24 | 1L<<X25 | 1L<<X26 | 1L<<X27 |
- 1L<<X28 |
- 1L<<D9 | 1L<<D10 | 1L<<D11 |
- 1L<<D12 | 1L<<D13 | 1L<<D14 | 1L<<D15;
- static final long CALLER_SAVE = ~CALLEE_SAVE & ~(1L<<RSP);
- @Override public long callerSave() { return CALLER_SAVE; }
- @Override public long neverSave() { return 1L<<RSP; }
- @Override public RegMask retMask( TypeFunPtr tfp ) {
- return tfp.ret() instanceof TypeFloat ? D0_MASK : X0_MASK;
- }
- @Override public int rpc() { return X30; }
-
- // Create a split op; any register to any register, including stack slots
- @Override public SplitNode split(LRG lrg, String kind, byte round) {
- RegMask def = lrg._machDef==null ? null : lrg._machDef.outregmap(), use = lrg.useMask();
- if( (def!=null && def.size1() && def.test(FLAGS)) ||
- (use!=null && use.size1() && use.test(FLAGS)) )
- return new SplitFlagsARM(lrg,kind,round);
- // Spilling the saved flags requires a GPR between NZCV and the stack.
- if( lrg._machDef instanceof SplitFlagsARM || lrg._machDef instanceof SplitGPRARM )
- return new SplitGPRARM(lrg,kind,round);
- return new SplitARM(lrg,kind,round);
- }
-
- // Return a MachNode unconditional branch
- @Override public CFGNode jump() {
- return new UJmpARM();
- }
-
- // Instruction selection
- @Override public Node instSelect(Node n ) {
- return switch( n ) {
- case AddNode add -> add(add);
- case AndNode and -> and(and);
- case BoolNode bool -> cmp(bool);
- case CallNode call -> call(call);
- case PtrToIntNode ptr -> new PtrToIntMach(ptr);
- case GuardNode guard -> new GuardMach(guard);
- case CallEndNode cend-> new CallEndMach(cend);
- case CLInitNode init -> new CLInitNode(init);
- case CProjNode c -> new CProjNode(c);
- case ConstantNode con-> con(con);
- case DivNode div -> div.mode()==2 ? new DivFARM(div) : new DivARM(div);
- case EscapeNode esc -> new EscapeNode(esc);
- case FunNode fun -> new FunARM(fun);
- case FunPtrNode fptr -> fptr(fptr);
- case IfNode iff -> jmp(iff);
- case LoadNode ld -> ld(ld);
- case MemMergeNode mem-> new MemMergeNode(mem);
- case MinusNode neg -> {
- if( neg.mode()==2 ) throw Utils.TODO();
- yield new NegARM(neg);
- }
- case MulNode mul -> mul.mode()==2 ? new MulFARM(mul) : new MulARM(mul);
- case NewNode nnn -> new NewARM(nnn);
- case NotNode not -> new NotARM(not);
- case OrNode or -> or(or);
- case ParmNode parm -> new ParmARM(parm);
- case BulkMemPhiNode phi -> new BulkMemPhiNode(phi);
- case MemPhiNode phi -> new MemPhiNode(phi);
- case PhiNode phi -> new PhiNode(phi);
- case ProjNode prj -> prj(prj);
- case ReadOnlyNode read -> new ReadOnlyMach(read);
- case ReturnNode ret -> new RetARM(ret,ret.fun());
- case SarNode sar -> asr(sar);
- case ShlNode shl -> lsl(shl);
- case ShrNode shr -> lsr(shr);
- case StartCUNode start -> new StartCUNode(start);
- case StopCUNode stop -> new StopCUNode(stop);
- case StartNode start -> new StartNode(start);
- case StopNode stop -> new StopNode(stop);
- case StoreNode st -> st(st);
- case SubNode sub -> sub.mode()==2 ? new SubFARM(sub) : sub(sub);
- case ToFloatNode tfn -> new I2F8ARM(tfn);
- case ToIntegerNode cvt -> new F8ToIARM(cvt);
- case XorNode xor -> xor(xor);
-
- case LoopNode loop -> new LoopNode(loop);
- case RegionNode region-> new RegionNode(region);
- default -> throw Utils.TODO();
- };
- }
-
- private Node cmp(BoolNode bool){
- Node cmp = _cmp(bool);
- return new SetARM(cmp, bool.op(), cmp instanceof CmpFARM);
- }
- private Node _cmp(BoolNode bool) {
- if( bool.isFloat() )
- return new CmpFARM(bool);
- return bool.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti
- ? new CmpIARM(bool, (int)ti.value())
- : new CmpARM(bool);
- }
-
- private Node ld(LoadNode ld) {
- return new LoadARM(address(ld), ld.ptr(), idx, off);
- }
-
- private Node jmp(IfNode iff) {
- // If/Bool combos will match to a Cmp/Set which sets flags.
- // Most general arith ops will also set flags, which the Jmp needs directly.
- // Loads do not set the flags, and will need an explicit TEST
- String op = "!=";
- boolean fp = false;
- if( iff.in(1) instanceof BoolNode bool ) { op = bool.op(); fp = bool.isFloat(); }
- else if( iff.in(1)==null ) op = "=="; // Never-node cutout
- else iff.setDef(1, new BoolNode.NE(iff.in(1),
- ConstantNode.raw(TypeInteger.ZERO),
- (byte)(iff.in(1)._type instanceof TypeFloat ? 2 : 1)));
- return new BranchARM(iff, op, fp);
- }
-
- private Node add(AddNode add) {
- if( add.mode()==2 )
- return new AddFARM(add);
- return add.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && imm12(ti)
- ? new AddIARM(add, (int)ti.value())
- : new AddARM(add);
- }
-
- private Node sub(SubNode sub) {
- return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)
- ? new SubIARM(sub, (int)(ti.value()))
- : new SubARM(sub);
- }
-
- private Node con( ConstantNode con ) {
- if( !con._con.isConstant() )
- return ConstantNode.raw(con); // Default unknown caller inputs
- String ext = con instanceof ExternNode ext0 ? ext0._extern : null;
- return switch( con._con ) {
- case TypeInteger ti -> new IntARM(con,ext);
- case TypeFloat tf -> new FltARM(con,ext);
- case TypeMemPtr tmp -> new TMPARM(con,ext);
- case TypeFunPtr tfp -> new TFPARM(tfp,ext);
- case TypeNil tn -> throw Utils.TODO();
- // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
- case Type t -> t==Type.NIL ? new IntARM(con,ext) : ConstantNode.raw(con);
- };
- }
-
- private Node fptr( FunPtrNode con ) {
- return new TFPARM(con);
- }
-
- private Node call(CallNode call){
- return call.fptr() instanceof FunPtrNode fptr
- ? new CallARM(call, (TypeFunPtr)fptr._type)
- : call.fptr() instanceof ConstantNode con
- ? new CallARM(call, (TypeFunPtr)con._type)
- : new CallRRARM(call);
- }
-
- private Node or(OrNode or) {
- int imm12;
- return or.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1
- ? new OrIARM(or, imm12)
- : new OrARM(or);
- }
-
- private Node xor(XorNode xor) {
- int imm12;
- return xor.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1
- ? new XorIARM(xor, imm12)
- : new XorARM(xor);
- }
-
- private Node and(AndNode and) {
- int imm12;
- return and.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && (imm12 = imm12Logical(ti)) != -1
- ? new AndIARM(and, imm12)
- : new AndARM(and);
- }
-
- private Node asr(SarNode asr) {
- return asr.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && ti.value() >= 0 && ti.value() < 63
- ? new AsrIARM(asr, (int)ti.value())
- : new AsrARM(asr);
- }
-
- private Node lsl(ShlNode lsl) {
- return lsl.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && ti.value() >= 0 && ti.value() < 63
- ? new LslIARM(lsl, (int)ti.value())
- : new LslARM(lsl);
- }
-
- private Node lsr(ShrNode lsr) {
- return lsr.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && ti.value() >= 0 && ti.value() < 63
- ? new LsrIARM(lsr, (int)ti.value())
- : new LsrARM(lsr);
- }
-
- private Node prj(ProjNode prj) {
- if( prj.in(0) instanceof CLInitNode ) return new ProjNode(prj);
- TypeInteger ret = cReturn(prj);
- if( ret!=null ) return new ExtendARM(prj,ret);
- return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjARM(prj);
- }
-
- private static int off;
- private static Node idx;
- private Node st(StoreNode st) {
- return new StoreARM(address(st),st.ptr(),idx,off,st.val());
- }
-
- // Gather addressing mode bits prior to constructing. This is a builder
- // pattern, but saving the bits in a *local* *global* here to keep mess
- // contained.
- private <N extends MemOpNode> N address(N mop ) {
- off = 0; // Reset
- idx = null;
- Node base = mop.ptr();
- // Skip/throw-away a ReadOnly, only used to typecheck
- if( base instanceof ReadOnlyNode read ) base = read.in(1);
- assert !(base instanceof AddNode) && base._type instanceof TypeMemPtr; // Base ptr always, not some derived
- if( mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm9(ti) ) {
- off = (int)ti.value();
- assert off == ti.value(); // In 32-bit range
- } else {
- idx = mop.off();
- }
- return mop;
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java
deleted file mode 100644
index 59bad64e..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.node.FunPtrNode;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-// Add upper 20bits to PC. Immediate comes from the relocation info.
-public class AUIPC extends FunPtrNode implements MachNode, RIPRelSize {
- AUIPC( FunPtrNode fptr ) { super(fptr); }
- @Override public RegMask regmap(int i) { return null; }
- @Override public RegMask outregmap() { return riscv.WMASK; }
- @Override public boolean isClone() { return true; }
- @Override public AUIPC copy() { return new AUIPC(this); }
- @Override public String op() { return "auipc"; }
- @Override public void encoding( Encoding enc ) {
- enc.relo(this);
- short dst = enc.reg(this);
- enc.add4(RiscV.u_type(RiscV.OP_AUIPC, dst, 0));
- }
-
- // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding
- @Override public byte encSize(int delta) { return 4; }
-
- // Delta is from opcode start
- @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- //short rpc = enc.reg(this);
- //// High half is where the TFP constant used to be, the last input
- //short auipc = enc.reg(in(_inputs._len-1));
- //enc.patch4(opStart,RiscV.i_type(0x67, rpc, 0, auipc, delta));
- throw Utils.TODO();
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- String reg = code.reg(this);
- sb.p(reg).p(" = PC+#");
- _type.print(sb);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java
deleted file mode 100644
index d87e49b9..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-// fadd.d
-public class AddFRISC extends MachConcreteNode implements MachNode {
- AddFRISC(Node addf) {super(addf);}
- @Override public String op() { return "addf"; }
- @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; }
- @Override public RegMask outregmap() { return riscv.FMASK; }
- @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,RiscV.RM.RNE,1); }
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java
deleted file mode 100644
index 8012b61e..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.util.Utils;
-
-public class AddIRISC extends ImmRISC {
- // Used to inst-selection as a direct match against an ideal Add/Sub
- public AddIRISC( Node add, int imm12, boolean pop ) { super(add,imm12,pop); }
- @Override public String op() { return "addi"; }
- @Override public String glabel() { return "+"; }
- @Override int opcode() { return RiscV.OP_IMM; }
- @Override int func3() {return 0;}
- @Override public AddIRISC copy() {
- // Clone the AddI, using the same inputs-only code used during inst select.
- // Output edges are missing.
- AddIRISC add = new AddIRISC(in(1),_imm12,false);
- // Copys happen when output edges should be valid, so correct missing output edges.
- in(1)._outputs.add(add);
- return add;
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java
deleted file mode 100644
index 7010916f..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java
+++ /dev/null
@@ -1,21 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java
deleted file mode 100644
index 9fd65e7d..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.util.Utils;
-
-public class AndIRISC extends ImmRISC {
- public AndIRISC( Node and, int imm) { super(and,imm); }
- @Override public String op() { return "andi"; }
- @Override public String glabel() { return "&"; }
- @Override int opcode() { return RiscV.OP_IMM; }
- @Override int func3() {return 7;}
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java
deleted file mode 100644
index 1845985e..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java
+++ /dev/null
@@ -1,16 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java
deleted file mode 100644
index a9d03da5..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-// Conditional branch such as: BEQ
-public class BranchRISC extends IfNode implements MachNode, RIPRelSize {
- String _bop;
- // label is obtained implicitly
- public BranchRISC( IfNode iff, String bop, Node n1, Node n2 ) {
- super(iff);
- _bop = bop;
- _inputs.setX(1,n1);
- _inputs.setX(2,n2);
- }
-
- @Override public String op() { return "b"+_bop; }
- @Override public String label() { return op(); }
- @Override public String comment() { return "L"+cproj(1)._nid; }
- @Override public RegMask regmap(int i) { return riscv.RMASK; }
- @Override public RegMask outregmap() { return null; }
- @Override public void negate() {
- _bop = negate(_bop);
- // Cannot encode ">" or "<=", so flip these
- if( _bop.equals(">") || _bop.equals("<=") ) {
- swap12(); // Cannot negate the test, so swap the operands
- _bop = swap(_bop); // Swap test to match
- }
- }
-
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- p.p("if( ");
- if( in(1)==null ) p.p("0");
- else p.n(in(1));
- p.p(_bop);
- if( in(2)==null ) p.p("0");
- else p.n(in(2));
- return p.p(" )");
- }
-
- @Override public void encoding( Encoding enc ) {
- if( in(1)==null && _bop=="!=" ) return; // Inverted never-node, no code
- enc.jump(this,cproj(0));
- if( in(1)==null ) // Never node
- enc.add4(RiscV.j_type(RiscV.OP_JAL, 0, 0));
- else {
- // Todo: relocs (for offset - immf)
- short src1 = enc.reg(in(1));
- short src2 = in(2)==null ? (short)riscv.ZERO : enc.reg(in(2));
- enc.add4(RiscV.b_type(RiscV.OP_BRANCH, RiscV.jumpop(_bop), src1, src2, 0));
- }
- }
-
- // Delta is from opcode start
- @Override public byte encSize(int delta) {
- if( in(1)==null && _bop=="!=" ) return 0; // Inverted never-node, no code
- if( -4*1024 <= delta && delta < 4*1024 ) return 4;
- // 2 word encoding needs a tmp register, must teach RA
- throw Utils.TODO();
- }
-
- // Delta is from opcode start
- @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- assert !( in(1)==null && _bop=="!=" ); // Inverted never-node, no code no patch
- if( in(1)==null ) { // Never node
- enc.patch4(opStart,RiscV.j_type(RiscV.OP_JAL, 0, delta));
- } else {
- short src1 = enc.reg(in(1));
- short src2 = in(2)==null ? (short)riscv.ZERO : enc.reg(in(2));
- if( opLen==4 ) {
- enc.patch4(opStart,RiscV.b_type(RiscV.OP_BRANCH, RiscV.jumpop(_bop), src1, src2, delta));
- } else {
- throw Utils.TODO();
- }
- }
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- if( in(1)==null && _bop=="!=" ) {
- sb.p("never");
- return;
- }
- String src1 = in(1)==null ? "#0" : code.reg(in(1));
- String src2 = in(2)==null ? "#0" : code.reg(in(2));
- sb.p(src1).p(" ").p(_bop).p(" ").p(src2).p(" ");
- CFGNode prj = cproj(0).uctrlSkipEmpty();
- if( !prj.blockHead() ) prj = prj.cfg0();
- sb.p(label(prj));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java
deleted file mode 100644
index b1426a3f..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.TypeFunPtr;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-public class CallRISC extends CallNode implements MachNode, RIPRelSize {
- final TypeFunPtr _tfp;
- final String _name;
- CallRISC( CallNode call, TypeFunPtr tfp ) {
- super(call);
- assert tfp.isConstant();
- _inputs.pop(); // Pop constant target
- _tfp = tfp;
- _name = CodeGen.CODE.funcName(tfp.fidx());
- }
-
- @Override public String op() { return "call"; }
- @Override public String label() { return op(); }
- @Override public String name() { return _name; }
- @Override public TypeFunPtr tfp() { return _tfp; }
- @Override public RegMask regmap(int i) { return riscv.callInMask(_tfp,i,fun()._maxArgSlot); }
- @Override public RegMask outregmap() { return riscv.RPC_MASK; }
- @Override public int nargs() { return nIns()-2; } // Minus control, memory, fptr
-
- @Override public void encoding( Encoding enc ) {
- // Short form +/-4K: beq r0,r0,imm12
- // Long form: auipc rX,imm20/32; jal r0,[rX+imm12/32]
- FunNode fun = CodeGen.CODE.link(_tfp);
- if( fun==null ) enc.external(this,_name);
- else enc.relo(this);
- short rpc = enc.reg(this);
- enc.add4(RiscV.j_type(RiscV.OP_JAL, rpc, 0));
- }
-
- // Delta is from opcode start
- @Override public byte encSize(int delta) {
- if( -(1L<<20) <= delta && delta < (1L<<20) ) return 4;
- // 2 word encoding needs a tmp register, must teach RA
- throw Utils.TODO();
- }
-
- // Delta is from opcode start
- @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- short rpc = enc.reg(this);
- if( opLen==4 ) {
- enc.patch4(opStart,RiscV.j_type(RiscV.OP_JAL, rpc, delta));
- } else {
- throw Utils.TODO();
- }
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(_name).p(" ");
- for( int i=0; i<nargs(); i++ )
- sb.p(code.reg(arg(i+2))).p(", ");
- sb.unchar(2);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java
deleted file mode 100644
index cda4bc79..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class 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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java
deleted file mode 100644
index 8eb4fb3b..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-// fdiv.d
-public class DivFRISC extends MachConcreteNode implements MachNode{
- DivFRISC(Node divf) {super(divf);}
- @Override public String op() { return "divf"; }
- @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; }
- @Override public RegMask outregmap() { return riscv.FMASK; }
- @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,RiscV.RM.RUP,13); }
- // Default on double precision for now(64 bits)
- // General form: "fdiv.d rd = src1 / src2
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java
deleted file mode 100644
index 515ffa47..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java
+++ /dev/null
@@ -1,17 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java
deleted file mode 100644
index 5bd3ec1c..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.TypeInteger;
-import com.seaofnodes.simple.util.SB;
-
-// Convert a narrow C return register to Simple's full-width integer value.
-public class ExtendRISC extends MachConcreteNode {
- final int _bits;
- final boolean _signed;
- ExtendRISC(ProjNode prj, TypeInteger ret) {
- super(prj);
- _inputs.set(0,null);
- _inputs.add(new ProjRISC(prj));
- _bits = 8 << ret.log_size();
- _signed = ret._min < 0;
- }
- @Override public String op() { return (_signed ? "sext" : "zext")+_bits; }
- @Override public RegMask regmap(int i) { return riscv.RMASK; }
- @Override public RegMask outregmap() { return riscv.WMASK; }
- @Override public void encoding(Encoding enc) {
- short dst = enc.reg(this), src = enc.reg(in(1));
- int shift = 64-_bits;
- enc.add4(RiscV.i_type(RiscV.OP_IMM,dst,1,src,shift));
- enc.add4(RiscV.i_type(RiscV.OP_IMM,dst,5,dst,shift | (_signed ? 0x400 : 0)));
- }
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java
deleted file mode 100644
index a56ad216..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class F8ToIRISC extends MachConcreteNode implements MachNode {
- F8ToIRISC(Node f8toi) {super(f8toi);}
- @Override public String op() { return "cvti"; }
- @Override public RegMask regmap(int i) { assert i==1; return riscv.FMASK; }
- @Override public RegMask outregmap() { return riscv.WMASK; }
- @Override public void encoding( Encoding enc ) {
- RiscV.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-riscv.F_OFFSET);
- }
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java
deleted file mode 100644
index a110d156..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.ConstantNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.type.TypeFloat;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-public class FltRISC extends ConstantNode implements MachNode, RIPRelSize {
- final String _ext;
- FltRISC( ConstantNode con, String ext ) { super(con); _ext = ext; }
- @Override public String op() { return "ld8"; }
- @Override public RegMask regmap(int i) { return null; }
- @Override public RegMask outregmap() { return riscv.FMASK; }
- @Override public boolean isClone() { return true; }
- @Override public FltRISC copy() { return new FltRISC(this,_ext); }
-
- @Override public void encoding( Encoding enc ) {
- enc.largeConstant(this,_con, 0, -1);
- short dst = (short)(enc.reg(this) - riscv.F_OFFSET);
- short tmp = (short)riscv.T6;
- // AUIPC dst,#hi20_constant_pool
- enc.add4(RiscV.u_type(RiscV.OP_AUIPC, tmp, 0));
- // Load dst,[dst+#low12_constant_pool]
- enc.add4(RiscV.i_type(RiscV.OP_LOADFP, dst, 0b11, tmp, 0));
- }
- @Override public RegMask killmap() { return new RegMask(riscv.T6); }
-
- // Delta is from opcode start.
- @Override public byte encSize(int delta) { return 8; }
-
- // Delta is from opcode start
- @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- short dst = (short)(enc.reg(this) - riscv.F_OFFSET);
- short tmp = (short)riscv.T6;
- // AUIPC dst,#hi20_constant_pool
- enc.patch4(opStart , RiscV.u_type(RiscV.OP_AUIPC, tmp, delta>>12));
- // Load dst,[dst+#low12_constant_pool]
- enc.patch4(opStart+4, RiscV.i_type(RiscV.OP_LOADFP, dst, 0b11, tmp, delta & 0xFFF));
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- _con.print(sb.p(code.reg(this)).p(" = #"));
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java
deleted file mode 100644
index d3d64b06..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.FunNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-public class FunRISC extends FunNode implements MachNode {
- FunRISC(FunNode fun) {super(fun);}
- @Override public void computeFrameAdjust(CodeGen code, int maxReg) {
- super.computeFrameAdjust(code,maxReg);
- if( _hasCalls ) // If non-leaf, pad to 16b
- _frameAdjust = ((_frameAdjust+8) & -16);
- }
- @Override public void encoding( Encoding enc ) {
- int sz = _frameAdjust;
- if( sz == 0 ) return; // Skip if no frame adjust
- if( sz >= 1L<<12 ) throw Utils.TODO();
- enc.add4(RiscV.i_type(RiscV.OP_IMM, riscv.RSP, 0, riscv.RSP, -sz & 0xFFF));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java
deleted file mode 100644
index 1b53ab0f..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-// fcvt.d.w
-// Converts a 32-bit signed integer, in integer register rs1 into a double-precision floating-point number in floating-point register rd.
-public class I2F8RISC extends MachConcreteNode implements MachNode {
- I2F8RISC(Node i2f8) {super(i2f8);}
- @Override public String op() { return "cvtf"; }
- @Override public RegMask regmap(int i) { assert i==1; return riscv.RMASK; }
- @Override public RegMask outregmap() { return riscv.FMASK; }
- @Override public void encoding( Encoding enc ) {
- short dst = (short)(enc.reg(this )-riscv.F_OFFSET);
- short src1 = enc.reg(in(1));
- int body = RiscV.r_type(RiscV.OP_FP,dst,RiscV.RM.RNE.ordinal(),src1,0,0x69);
- enc.add4(body);
- }
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java
deleted file mode 100644
index ee247c3e..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.TypeInteger;
-import com.seaofnodes.simple.util.SB;
-
-abstract public class ImmRISC extends MachConcreteNode implements MachNode {
- public final int _imm12;
- ImmRISC( Node add, int imm12, boolean pop ) {
- super(add);
- _imm12 = imm12;
- // If pop, we are moving from an ideal Node to MachNode; the ideal Node
- // inputs got copied here, but we are absorbing the immediate into the
- // MachNode and so do not want the constant input.
- if( pop ) _inputs.pop();
- // if NOT pop, we are the 2nd-half of a 2-part expansion, reset the
- // input edges to no-control
- else { _inputs.set(0,null); _inputs.setX(1,add); }
- }
- ImmRISC( Node add, int imm12 ) { this(add,imm12,true); }
- @Override public RegMask regmap(int i) { return riscv.RMASK; }
- @Override public RegMask outregmap() { return riscv.WMASK; }
- @Override public boolean isClone() {
- // Allow cloning of the 2nd half of a 2-part constant; this might
- // trigger cloning the first part also.
- return in(1) instanceof LUI || in(1) instanceof AUIPC;
- }
-
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- int imm12 = (_imm12<<20)>>20; // Sign extend 12 bits
- p.p("( ").n(in(1));
- return p.p(String.format(" %s #%d )",glabel(),imm12));
- }
-
- abstract int opcode();
- abstract int func3();
-
- @Override public void encoding( Encoding enc ) {
- short dst = enc.reg(this );
- short src = enc.reg(in(1));
- enc.add4(RiscV.i_type(opcode(), dst, func3(), src, _imm12 & 0xFFF));
- }
-
- // General form: "addi rd = rs1 + imm"
- @Override public void asm(CodeGen code, SB sb) {
- int imm12 = (_imm12<<20)>>20; // Sign extend 12 bits
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ").p(glabel()).p(" #").p(imm12);
- if( in(1) instanceof LUI lui )
- sb.p(" // #").hex4((int)(((TypeInteger)lui._con).value()) + imm12);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java
deleted file mode 100644
index dd4a02a3..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.ConstantNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-// Special instruction for loading 8 byte constants from the constant pool.
-
-public class Int8RISC extends ConstantNode implements MachNode, RIPRelSize {
- final String _ext;
- Int8RISC( ConstantNode con, String ext ) { super(con); _ext = ext; }
- @Override public String op() { return "ld8"; }
- @Override public RegMask regmap(int i) { return null; }
- @Override public RegMask outregmap() { return riscv.WMASK; }
- @Override public boolean isClone() { return true; }
- @Override public Int8RISC copy() { return new Int8RISC(this,_ext); }
-
- @Override public byte encSize(int delta) { return 8; }
-
- @Override public void encoding( Encoding enc ) {
- if( _ext!=null ) throw Utils.TODO();
- short dst = enc.reg(this);
- short tmp = (short)riscv.T6;
- enc.largeConstant(this,_con, 0, -1);
- // AUIPC dst,#hi20_constant_pool
- enc.add4(RiscV.u_type(RiscV.OP_AUIPC, tmp, 0));
- // Load dst,[dst+#low12_constant_pool]
- enc.add4(RiscV.i_type(RiscV.OP_LOAD, dst, 0b11, tmp, 0));
- }
- @Override public RegMask killmap() { return new RegMask(riscv.T6); }
-
- // Delta is from opcode start
- @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- short dst = enc.reg(this);
- short tmp = (short)riscv.T6;
- // AUIPC dst,#hi20_constant_pool
- enc.patch4(opStart , RiscV.u_type(RiscV.OP_AUIPC, tmp, delta>>12));
- // Load dst,[dst+#low12_constant_pool]
- enc.patch4(opStart+4, RiscV.i_type(RiscV.OP_LOAD, dst, 0b11, tmp, delta & 0xFFF));
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- _con.print(sb.p(code.reg(this)).p(" = #"));
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java
deleted file mode 100644
index 35d2bc65..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.ConstantNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeInteger;
-import com.seaofnodes.simple.util.SB;
-
-// 12-bit integer constant. Larger constants are made up in the instruction
-// selection by adding with a LUI.
-public class IntRISC extends ConstantNode implements MachNode {
- IntRISC(ConstantNode con) { super(con); }
- @Override public String op() { return "ldi"; }
- @Override public RegMask regmap(int i) { return null; }
- @Override public RegMask outregmap() { return riscv.WMASK; }
- @Override public boolean isClone() { return true; }
- @Override public IntRISC copy() { return new IntRISC(this); }
- @Override public void encoding( Encoding enc ) {
- short dst = enc.reg(this);
- int val = _con==Type.NIL ? 0 : (int)(((TypeInteger)_con).value() & 0xFFF);
- // Explicit truncation of larger immediates; this will sign-extend on
- // load and this is handled during instruction selection.
- enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 0, riscv.ZERO, val));
- }
- @Override public void asm(CodeGen code, SB sb) {
- String reg = code.reg(this);
- _con.print(sb.p(reg).p(" = #"));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java
deleted file mode 100644
index aedd8b47..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.ConstantNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.type.TypeInteger;
-import com.seaofnodes.simple.util.SB;
-
-// Load upper 20bits.
-public class LUI extends ConstantNode implements MachNode {
- public LUI( int imm20 ) {
- super(TypeInteger.constant(imm20));
- assert (imm20 & 0xFFF)==0;
- }
- @Override public RegMask regmap(int i) { return null; }
- @Override public RegMask outregmap() { return riscv.WMASK; }
- @Override public boolean isClone() { return true; }
- @Override public LUI copy() { return new LUI((int)((TypeInteger)_con).value()); }
- @Override public String op() { return "lui"; }
- @Override public void encoding( Encoding enc ) {
- long x = ((TypeInteger)_con).value();
- int imm20 = (int)(x>>12) & 0xFFFFF;
- short dst = enc.reg(this);
- int lui = RiscV.u_type(RiscV.OP_LUI, dst, imm20);
- enc.add4(lui);
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- String reg = code.reg(this);
- sb.p(reg).p(" = #").hex4((int)(((TypeInteger)_con).value()));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java
deleted file mode 100644
index e9f3ec02..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-// Load memory addressing on RISC
-// Support imm, reg(direct), or reg+off(indirect) addressing
-// Base = base - base pointer, offset is added to base
-// idx = null
-// off = off - imm12 added to base
-public class LoadRISC extends MemOpRISC {
- public LoadRISC(LoadNode ld, Node base, int off) { super(ld, base, off, null); }
- @Override public String op() { return "ld" +_sz; }
- @Override public RegMask regmap(int i) { return riscv.RMASK; }
- @Override public RegMask outregmap() { return riscv.MEM_MASK; }
- @Override public void encoding( Encoding enc ) {
- short dst = enc.reg(this );
- short ptr = enc.reg(ptr());
- int op = dst >= riscv.F_OFFSET ? RiscV.OP_LOADFP : RiscV.OP_LOAD;
- if( dst >= riscv.F_OFFSET ) dst -= riscv.F_OFFSET;
- enc.add4(RiscV.i_type(op, dst, func3(), ptr, _off));
- }
- @Override int func3() {
- int func3 = -1;
- // no unsigned flavour for store, so both signed and unsigned trigger the same
- Type declType = _con;
- if( declType == TypeInteger. I8 ) func3=0; // LB
- if( declType == TypeInteger.I16 ) func3=1; // LH
- if( declType == TypeInteger.I32 ) func3=2; // LW
- if( declType == TypeInteger.BOT ) func3=3; // LD
- if( declType == TypeInteger. U8 ) func3=4; // LBU
- if( declType == TypeInteger.BOOL) func3=4; // LBU
- if( declType == TypeInteger.U16 ) func3=5; // LHU
- if( declType == TypeInteger.U32 ) func3=6; // LWU
- if( declType instanceof TypeInteger ti ) {
- if( -128 <= ti._min && ti._max < 128 ) func3 = 0; // LB
- }
-
- // float
- if( declType == TypeFloat.F32) func3 = 2; // fLW fSW
- if( declType == TypeFloat.F64) func3 = 3; // fLD fSD
-
- if( declType instanceof TypeMemPtr) func3 = 3; // 8 byte pointers (pick ld)
- if( declType instanceof TypeFunPtr) func3 = 3; // 8 byte function pointers
- if( declType instanceof TypeConAry ary )
- func3 = switch( ary.log_size() ) {
- case 0 -> 4; // LBU
- case 1 -> 5; // LHU
- case 2 -> 2; // LW
- case 3 -> 3; // LD
- default -> throw Utils.TODO("RISC-V constant-array load for "+declType);
- };
- if( func3 == -1 ) throw Utils.TODO("RISC-V load for "+declType);
- return func3;
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(",");
- asm_address(code,sb);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java
deleted file mode 100644
index e0bbc219..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.*;
-
-public abstract class MemOpRISC extends MemOpNode implements MachNode {
- final int _off; // Limit 12 bits
- final char _sz;
- final int _bytes;
- MemOpRISC(MemOpNode mop, Node base, int off, Node val) {
- super(mop,mop);
- assert base._type instanceof TypeMemPtr;
- _inputs.setX(2, base ); // Base can be an Add, is no longer raw object base
- _inputs.setX(3, null); // Never an index
- _inputs.setX(4, val );
- _off = off;
- _bytes = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size();
- _sz = (char)('0'+_bytes);
- }
-
- @Override public String label() { return op(); }
- Node val() { return in(4); } // Only for stores
-
- @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { return p.p(".").p(_name); }
-
- @Override public Type compute() { throw new AssertionError("Do not call compute on "+getClass().getSimpleName()); }
- @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); }
-
- // func3 is based on load/store size and extend
- abstract int func3();
-
- // Register mask allowed on input i.
- @Override public RegMask regmap(int i) {
- // 0 - ctrl
- // 1 - memory
- if( i==2 ) return riscv.RMASK; // base
- // 2 - index
- if( i==4 ) return riscv.MEM_MASK; // value
- throw Utils.TODO();
- }
-
- SB asm_address(CodeGen code, SB sb) {
- sb.p("[").p(code.reg(ptr())).p("+");
- return sb.p(_off).p("]");
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java
deleted file mode 100644
index dacec2b5..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-// fmul.d
-public class MulFRISC extends MachConcreteNode implements MachNode {
- MulFRISC(Node mulf) {super(mulf);}
- @Override public String op() { return "mulf"; }
- @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; }
- @Override public RegMask outregmap() { return riscv.FMASK; }
- @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,RiscV.RM.RNE,9); }
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java
deleted file mode 100644
index 9cb9ace7..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java
+++ /dev/null
@@ -1,17 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java
deleted file mode 100644
index 3070c56c..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class NegRISC extends MachConcreteNode implements MachNode {
- public NegRISC( Node neg ) { super(neg); }
- @Override public String op() { return "neg"; }
- @Override public RegMask regmap(int i) { assert i==1; return riscv.RMASK; }
- @Override public RegMask outregmap() { return riscv.WMASK; }
- @Override public void encoding( Encoding enc ) {
- short dst = enc.reg(this );
- short src = enc.reg(in(1));
- RiscV.r_type(RiscV.OP,dst,0,0,src,0x20);
- }
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = -").p(code.reg(in(1)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java
deleted file mode 100644
index 40d0359c..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.print.ASMPrinter;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-public class NewRISC extends NewNode implements MachNode, RIPRelSize {
- // A pre-zeroed chunk of memory.
- NewRISC( NewNode nnn ) { super(nnn); }
- @Override public void encoding( Encoding enc ) {
- // Generic external encoding; 2 ops.
- enc.external(this,"calloc");
- // A1 is a caller-save, allowed to crush building external address
- // auipc
- enc.add4(RiscV.i_type(RiscV.OP_IMM , _arg2Reg , 0, riscv.ZERO, 1));
- enc.add4(RiscV.u_type(RiscV.OP_AUIPC, riscv.A2 , 0));
- enc.add4(RiscV.i_type(RiscV.OP_JALR , riscv.RPC, 0, riscv.A2, 0));
- }
-
- // Patch is for running "new" in a JIT.
- // Delta is from opcode start
- @Override public byte encSize(int delta) { return 4*3; }
-
- // Patch is for running "new" in a JIT.
- // Delta is from opcode start
- @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- // Negative patches are JIT emulator targets.
- if( opStart+delta < 0 ) {
- delta -= 4; // Offset from the AUIPC start
- int imm20 = delta>>12;
- if( ((delta>>11)&1)==1 ) imm20++; // Correct accidental sign extension
- int imm12 = delta&0xFFF;
- enc.patch4(opStart+4,RiscV.u_type(RiscV.OP_AUIPC, riscv.A2 , imm20));
- enc.patch4(opStart+8,RiscV.i_type(RiscV.OP_JALR , riscv.RPC, 0, riscv.A2, imm12));
- } else
- throw Utils.TODO();
- }
-
- // General form: "alloc #bytes PC"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p("\n"); // Multi-op trigger
- sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n");
- sb.p("auipc a2=#calloc\n");
- sb.p("call a2+#calloc");
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
deleted file mode 100644
index 6f499db1..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.MachConcreteNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.node.NotNode;
-import com.seaofnodes.simple.util.SB;
-
-public class NotRISC extends MachConcreteNode implements MachNode {
- public NotRISC(NotNode not) { super(not); }
- @Override public String op() { return "not"; }
- @Override public RegMask regmap(int i) { return riscv.RMASK; }
- @Override public RegMask outregmap() { return riscv.WMASK; }
- @Override public void encoding( Encoding enc ) {
- short dst = enc.reg(this );
- short src = enc.reg(in(1));
- // SEQZ is unsigned: negative nonzero inputs must also produce false.
- enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 3, src, 1));
- }
- @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java
deleted file mode 100644
index 937595a3..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.util.Utils;
-
-public class OrIRISC extends ImmRISC {
- public OrIRISC( Node and, int imm) { super(and,imm); }
- @Override int opcode() { return RiscV.OP_IMM; }
- @Override int func3() { return 6; }
- @Override public String glabel() { return "|"; }
- @Override public String op() { return "ori"; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java
deleted file mode 100644
index a310a1f4..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java
+++ /dev/null
@@ -1,16 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java
deleted file mode 100644
index 0f8db4e5..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java
+++ /dev/null
@@ -1,17 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java
deleted file mode 100644
index 2f9ac469..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java
+++ /dev/null
@@ -1,15 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java
deleted file mode 100644
index 33143c6b..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.codegen.Encoding;
-import com.seaofnodes.simple.codegen.RegMask;
-import com.seaofnodes.simple.node.FunNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.node.ReturnNode;
-import com.seaofnodes.simple.util.Utils;
-
-public class RetRISC extends ReturnNode implements MachNode {
- public RetRISC(ReturnNode ret, FunNode fun) { super(ret, fun); fun.setRet(this); }
- @Override public void encoding( Encoding enc ) {
- int sz = fun()._frameAdjust;
- if( sz >= 1L<<12 ) throw Utils.TODO();
- if( sz != 0 )
- enc.add4(RiscV.i_type(RiscV.OP_IMM, riscv.RSP, 0, riscv.RSP, sz & 0xFFF));
- short rpc = enc.reg(rpc());
- enc.add4(RiscV.i_type(RiscV.OP_JALR, riscv.ZERO, 0, rpc, 0));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java
deleted file mode 100644
index d037dd5c..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.BoolNode;
-import com.seaofnodes.simple.node.MachConcreteNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.util.SB;
-
-// corresponds to feq.d = equal
-// fle.d = less than or equal
-// flt.d = less than
-public class SetFRISC extends MachConcreteNode implements MachNode {
- final String _bop; // One of <,<=,==
- SetFRISC( BoolNode bool ) {
- super(bool);
- assert bool.isFloat();
- _bop = bool.op();
- }
- @Override public String op() { return "set"+_bop; }
- @Override public RegMask regmap(int i) { return riscv.FMASK; }
- @Override public RegMask outregmap() { return riscv.WMASK; }
- @Override public void encoding( Encoding enc ) {
- short dst = enc.reg(this );
- short src1 = (short)(enc.reg(in(1))-riscv.F_OFFSET);
- short src2 = (short)(enc.reg(in(2))-riscv.F_OFFSET);
- int body = RiscV.r_type(RiscV.OP_FP,dst,RiscV.fsetop(_bop),src1,src2,0b1010001/*FCMP*/);
- enc.add4(body);
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ").p(_bop).p(" ").p(code.reg(in(2)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
deleted file mode 100644
index 9bc86565..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.simple.util.Utils;
-import com.seaofnodes.simple.node.Node;
-
-// corresponds to slti,sltiu
-public class SetIRISC extends ImmRISC {
- final boolean _unsigned; // slti vs sltiu
- public SetIRISC( Node src, int imm12, boolean unsigned ) {
- this(src,imm12,unsigned,true);
- }
- public SetIRISC( Node src, int imm12, boolean unsigned, boolean pop ) {
- super(src,imm12,pop);
- if( !pop ) _inputs.setLen(2);
- _unsigned = unsigned;
- }
- @Override public String op() { return "slt" + (_unsigned ? "u":"") + "i"; }
- @Override public String glabel() { return "<"+ (_unsigned ? "u":""); }
- @Override int opcode() { return 0b0010011; }
- @Override int func3() { return _unsigned ? 0b011 : 0b010; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java
deleted file mode 100644
index 848cc59f..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java
+++ /dev/null
@@ -1,20 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java
deleted file mode 100644
index 1425caa2..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.util.Utils;
-
-public class SllIRISC extends ImmRISC {
- public SllIRISC( Node and, int imm) { super(and,imm); }
- @Override int opcode() { return RiscV.OP_IMM; }
- @Override int func3() { return 1; }
- @Override public String glabel() { return "<<"; }
- @Override public String op() { return "slli"; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java
deleted file mode 100644
index 5a4d4cc6..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java
+++ /dev/null
@@ -1,17 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java
deleted file mode 100644
index d455337b..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.FunNode;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.node.SplitNode;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-public class SplitRISC extends SplitNode {
- SplitRISC( LRG lrg, String kind, byte round ) { super(lrg, kind,round, new Node[2]); }
- @Override public RegMask regmap(int i) { return riscv.SPLIT_MASK; }
- @Override public RegMask outregmap() { return riscv.SPLIT_MASK; }
- @Override public void encoding( Encoding enc ) {
- short dst = enc.reg(this );
- short src = enc.reg(in(1));
-
- // Flag for being either XMM or stack
- boolean dstX = dst >= riscv.F_OFFSET;
- boolean srcX = src >= riscv.F_OFFSET;
-
- if( dst >= riscv.MAX_REG ) {
- // Store to SP
- if( src >= riscv.MAX_REG ) {
- throw Utils.TODO(); // Very rare stack-stack move
- }
- int off = fun(enc).computeStackOffset(enc._code,dst);
- int op = srcX ? RiscV.OP_STOREFP : RiscV.OP_STORE;
- if( srcX ) src -= riscv.F_OFFSET;
- enc.add4(RiscV.s_type(op, 0b011, riscv.RSP, src, off));
- return;
- }
- if( src >= riscv.MAX_REG ) {
- // Load from SP
- int off = fun(enc).computeStackOffset(enc._code,src);
- int op = dstX ? RiscV.OP_LOADFP : RiscV.OP_LOAD;
- if( dstX ) dst -= riscv.F_OFFSET;
- enc.add4(RiscV.i_type(op, dst, 0b011, riscv.RSP, off));
- return;
- }
- // pick opcode based on regs
- if( !dstX && !srcX ) {
- // GPR->GPR
- enc.add4(RiscV.r_type(RiscV.OP,dst,0,src,riscv.ZERO,0));
- } else if( dstX && srcX ) {
- // FPR->FPR
- src -= riscv.F_OFFSET;
- dst -= riscv.F_OFFSET;
- enc.add4(RiscV.r_type(RiscV.OP_FP, dst, 0b000, src, src, 0b0010101)); // dst = FMIN(src,src)
- } else if(!srcX && dstX) {
- //GPR->FPR
- // fmv.d.x
- enc.add4(RiscV.r_type(RiscV.OP_FP, dst - riscv.F_OFFSET, 0, src, 0, 0b1111001));
- } else if(srcX && !dstX) {
- //FPR->GPR
- //fmv.x.d
- enc.add4(RiscV.r_type(RiscV.OP_FP, dst, 0, src - riscv.F_OFFSET, 0, 0b1110001));
- }
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java
deleted file mode 100644
index b316b303..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.util.Utils;
-
-public class SraIRISC extends ImmRISC {
- public SraIRISC( Node and, int imm) {
- super(and,imm | (0x20 << 5));
- }
- @Override int opcode() { return RiscV.OP_IMM; }
- @Override int func3() { return 5;}
- @Override public String glabel() { return ">>"; }
- @Override public String op() { return "srai"; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java
deleted file mode 100644
index e2e34415..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java
+++ /dev/null
@@ -1,17 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java
deleted file mode 100644
index d0ea6b18..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.util.Utils;
-
-public class SrlIRISC extends ImmRISC {
- public SrlIRISC( Node and, int imm, boolean pop) { super(and,imm,pop); }
- @Override int opcode() { return RiscV.OP_IMM; }
- @Override int func3() { return 5; }
- @Override public String glabel() { return ">>>"; }
- @Override public String op() { return "srli"; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java
deleted file mode 100644
index 1eccc1c8..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java
+++ /dev/null
@@ -1,17 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
deleted file mode 100644
index c3fcf4d3..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.codegen.RegMask;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-// Store memory addressing on ARM
-// Support imm, reg(direct), or reg+off(indirect) addressing
-// Base = base - base pointer, offset is added to base
-// idx = null
-// off = off - offset added to base)
-// imm = imm - immediate value to store
-// val = Node of immediate value to store(null if its a constant immediate)
-
-//e.g s.cs[0] = 67; // C
-// base = s.cs, off = 4, imm = 67, val = null
-
-// sw rs2,offset(rs1)
-public class StoreRISC extends MemOpRISC {
- StoreRISC( StoreNode st, Node base, int off, Node val ) { super(st, base, off, val); }
- @Override public String op() { return "st"+_sz; }
- @Override public RegMask regmap(int i) {
- // 0 - ctrl
- if( i==1 ) return null; // mem
- if( i==2 ) return riscv.RMASK; // ptr
- // 2 - index
- if( i==4 ) return _bytes >= 4
- ? riscv.MEM_MASK // Word/dword value in GPR or FPR
- : riscv.RMASK; // Byte/short stores require a GPR
- return null; // Anti-dependence
- }
- @Override public RegMask outregmap() { return null; }
-
- @Override public void encoding( Encoding enc ) {
- short val = enc.reg(val());
- short ptr = enc.reg(ptr());
- int op = val >= riscv.F_OFFSET ? RiscV.OP_STOREFP : RiscV.OP_STORE;
- if( val >= riscv.F_OFFSET ) val -= riscv.F_OFFSET;
- enc.add4(RiscV.s_type(op, func3()&7, ptr, val == -1 ? riscv.ZERO : val, _off));
- }
-
- // func3 is based on load/store size and extend
- @Override int func3() {
- return Integer.numberOfTrailingZeros(_bytes);
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- asm_address(code,sb).p(",");
- if( val()==null ) sb.p("#").p("0");
- else sb.p(code.reg(val()));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java
deleted file mode 100644
index 044820cf..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-// fsub.d
-public class SubFRISC extends MachConcreteNode implements MachNode {
- SubFRISC(Node subf) {super(subf);}
- @Override public String op() { return "subf"; }
- @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; }
- @Override public RegMask outregmap() { return riscv.FMASK; }
- @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,RiscV.RM.RNE,5); }
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java
deleted file mode 100644
index 2adfce23..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java
+++ /dev/null
@@ -1,16 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java
deleted file mode 100644
index 44ef1a95..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.TypeFunPtr;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-public class TFPRISC extends FunPtrNode implements MachNode, RIPRelSize {
- final String _ext;
- // Pointer to a Simple function
- TFPRISC( FunPtrNode fptr ) { super(fptr); _ext = null; }
- // Pointer to an Extern "C" function
- TFPRISC( TypeFunPtr fptr, String ext ) { super(fptr,CodeGen.CODE._start,null); _type = fptr; _ext = ext; }
- @Override public String op() { return "ldx"; }
- @Override public RegMask regmap(int i) { return null; }
- @Override public RegMask outregmap() { return riscv.WMASK; }
- @Override public boolean isClone() { return true; }
- @Override public TFPRISC copy() { return new TFPRISC((TypeFunPtr)_con,_ext); }
- @Override public void encoding( Encoding enc ) {
- if( _ext==null ) enc.relo(this); // Internal patch
- else enc.external(this,_ext); // ELF-file external patch
- // TODO: 1 op encoding, plus a TODO if it does not fit
- short dst = enc.reg(this);
- // auipc t0,0
- enc.add4(RiscV.u_type(RiscV.OP_AUIPC, dst, 0));
- // addi t1,t0 + #0
- enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 0, dst, 0));
- }
-
- @Override public byte encSize(int delta) { return 8; }
-
- // Delta is from opcode start
- @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- short rpc = enc.reg(this);
- if( opLen==8 ) {
- int hi20 = (delta + 0x800) >> 12;
- int lo12 = delta - (hi20 << 12);
- // AUIPC (upper 20 bits)
- // opstart of add
- enc.patch4(opStart ,RiscV.u_type(RiscV.OP_AUIPC, rpc, hi20));
- // addi(low 12 bits)
- enc.patch4(opStart+4,RiscV.i_type(RiscV.OP_IMM, rpc, 0, rpc, lo12 & 0xFFF));
- // addi
- } else {
- // should not happen as one instruction is 4 byte, and TFP arm encodes 2.
- throw Utils.TODO();
- }
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- _type.print(sb.p(code.reg(this)).p(" #"));
- }
- @Override public boolean eq(Node n) { return this==n; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java
deleted file mode 100644
index a5ff3753..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.ConstantNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.type.TypeMemPtr;
-import com.seaofnodes.simple.util.SB;
-
-public class TMPRISC extends ConstantNode implements MachNode, RIPRelSize {
- final String _ext;
- TMPRISC( ConstantNode con, String ext ) { super(con); _ext = ext; }
- @Override public String op() { return "ldp"; }
- @Override public RegMask regmap(int i) { return null; }
- @Override public RegMask outregmap() { return riscv.WMASK; }
- @Override public boolean isClone() { return true; }
- @Override public TMPRISC copy() { return new TMPRISC(this,_ext); }
- @Override public void encoding( Encoding enc ) {
- if( _ext!=null ) enc.externalData(this,_ext);
- else enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1);
- short dst = enc.reg(this);
- // AUIPC dst,#hi20_constant_pool
- enc.add4(RiscV.u_type(RiscV.OP_AUIPC, dst, 0));
- // addi dst,[dst+#low12_constant_pool]
- enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 0, dst, 0));
- }
-
- @Override public byte encSize(int delta) { return 8; }
-
- // Delta is from opcode start
- @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- short dst = enc.reg(this);
- // AUIPC dst,#hi20_constant_pool
- enc.patch4(opStart , RiscV.u_type(RiscV.OP_AUIPC, dst, (delta+0x800)>>12));
- // ADDI's low 12 bits are signed; round the AUIPC page accordingly.
- enc.patch4(opStart+4, RiscV.i_type(RiscV.OP_IMM, dst, 0, dst, delta & 0xFFF));
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- _con.print(sb.p(code.reg(this)).p(" #"));
- }
- @Override public boolean eq(Node n) { return this==n; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java
deleted file mode 100644
index 448d9ec3..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-// unconditional jump
-public class UJmpRISC extends CFGNode implements MachNode, RIPRelSize {
- @Override public String op() { return "jmp"; }
- @Override public String label() { return op(); }
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("jmp "); }
- @Override public RegMask regmap(int i) {return null; }
- @Override public RegMask outregmap() { return null; }
- @Override public Type compute() { throw Utils.TODO(); }
- @Override public Node idealize() { throw Utils.TODO(); }
- @Override public void encoding( Encoding enc ) {
- // Short form +/-4K: beq r0,r0,imm12
- // Long form: auipc rX,imm20/32; jal r0,[rX+imm12/32]
- enc.jump(this,uctrl());
- enc.add4(RiscV.j_type(RiscV.OP_JAL, 0, 0));
- }
-
- // Delta is from opcode start
- @Override public byte encSize(int delta) {
- if( -(1L<<20) <= delta && delta < (1L<<20) ) return 4;
- // 2 word encoding needs a tmp register, must teach RA
- throw Utils.TODO();
- }
-
- // Delta is from opcode start
- @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- if( opLen==4 ) {
- enc.patch4(opStart,RiscV.j_type(RiscV.OP_JAL, 0, delta));
- } else {
- throw Utils.TODO();
- }
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- CFGNode target = uctrl();
- //assert target.nOuts() > 1; // Should optimize jmp to empty targets
- sb.p(label(target));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java
deleted file mode 100644
index f4323cae..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.util.Utils;
-
-public class XorIRISC extends ImmRISC {
- public XorIRISC( Node and, int imm) { super(and,imm); }
- public XorIRISC( Node and, int imm, boolean pop) { super(and,imm,pop); }
- @Override int opcode() { return RiscV.OP_IMM; }
- @Override int func3() { return 4; }
- @Override public String glabel() { return "^"; }
- @Override public String op() { return "xori"; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java
deleted file mode 100644
index 413a7c3b..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java
+++ /dev/null
@@ -1,16 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
deleted file mode 100644
index 0195542c..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
+++ /dev/null
@@ -1,450 +0,0 @@
-package com.seaofnodes.simple.node.cpus.riscv;
-
-import com.seaofnodes.isa.RiscV;
-import static com.seaofnodes.isa.RiscV.*;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.Utils;
-
-public class riscv extends Machine {
-
- public riscv(CodeGen code) {
- if( !"SystemV".equals(code._callingConv) )
- throw new IllegalArgumentException("Unknown calling convention "+code._callingConv);
- }
-
- @Override public String name() {return "riscv";}
-
- // Using ABI names instead of register names
- public static int ZERO = 0, RPC= 1, RSP= 2, S12= 3, S13= 4, T0 = 5, T1 = 6, T2 = 7;
- public static int S0 = 8, S1 = 9, A0 = 10, A1 = 11, A2 = 12, A3 = 13, A4 = 14, A5 = 15;
- public static int A6 = 16, A7 = 17, S2 = 18, S3 = 19, S4 = 20, S5 = 21, S6 = 22, S7 = 23;
- public static int S8 = 24, S9 = 25, S10= 26, S11= 27, T3 = 28, T4 = 29, T5 = 30, T6 = 31;
-
- // FP registers
- public static int F0 = 32, F1 = 33, F2 = 34, F3 = 35, F4 = 36, F5 = 37, F6 = 38, F7 = 39;
- public static int FS0 = 40, FS1 = 41, FA0 = 42, FA1 = 43, FA2 = 44, FA3 = 45, FA4 = 46, FA5 = 47;
- public static int FA6 = 48, FA7 = 49, FS2 = 50, FS3 = 51, FS4 = 52, FS5 = 53, FS6 = 54, FS7 = 55;
- public static int FS8 = 56, FS9 = 57, FS10 = 58, FS11 = 59, FT8 = 60, FT9 = 61, FT10 = 62, FT11 = 63;
-
- static final int MAX_REG = 64;
-
- public static final int F_OFFSET = 32;
-
- static final String[] REGS = new String[] {
- //"zero","rpc" , "rsp" , "s12" , "s13" , "t0" , "t1" , "t2" ,
- //"s0" , "s1" , "a0" , "a1" , "a2" , "a3" , "a4" , "a5" ,
- //"a6" , "a7" , "s2" , "s3" , "s4" , "s5" , "s6" , "s7" ,
- //"s8" , "s9" , "s10" , "s11" , "t3" , "t4" , "t5" , "t6" ,
- //"f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" ,
- //"fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" ,
- //"fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" ,
- //"fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11"
- // Register-based naming hella easier to debug than "official" register
- // names when single-stepping in debugger.
- "zero", "r1" , "rsp" , "r3" , "r4" , "r5" , "r6" , "r7" ,
- "r8" , "r9" , "r10" , "r11", "r12", "r13", "r14", "r15",
- "r16" , "r17" , "r18" , "r19", "r20", "r21", "r22", "r23",
- "r24" , "r25" , "r26" , "r27", "r28", "r29", "r30", "r31",
- "f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" ,
- "fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" ,
- "fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" ,
- "fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11"
- };
- @Override public String[] regs() { return REGS; }
-
- // General purpose register mask: pointers and ints, not floats
- static final long RD_BITS = 0b11111111111111111111111111111111L; // All the GPRs
- static final RegMask RMASK = new RegMask(RD_BITS);
-
- static final long WR_BITS = 0b11111111111111111111111111111010L; // All the GPRs, minus ZERO and RSP
- static final RegMask WMASK = new RegMask(WR_BITS);
- // Float mask from(f0-ft10). TODO: ft10,ft11 needs a larger RegMask
- static final long FP_BITS = 0b11111111111111111111111111111111L<<F0;
- static final RegMask FMASK = new RegMask(FP_BITS);
-
- // Load/store mask; both GPR and FPR
- static final RegMask MEM_MASK = new RegMask(WR_BITS | FP_BITS);
-
- static final RegMask SPLIT_MASK = new RegMask( WR_BITS | FP_BITS, -1L );
-
- // Arguments masks
- public static RegMask A0_MASK = new RegMask(A0);
- public static RegMask A1_MASK = new RegMask(A1);
- public static RegMask A2_MASK = new RegMask(A2);
- public static RegMask A3_MASK = new RegMask(A3);
- public static RegMask A4_MASK = new RegMask(A4);
- public static RegMask A5_MASK = new RegMask(A5);
- public static RegMask A6_MASK = new RegMask(A6);
- public static RegMask A7_MASK = new RegMask(A7);
-
- public static final RegMask RPC_MASK = new RegMask(RPC);
-
- // Float arguments masks
- public static RegMask FA0_MASK = new RegMask(FA0);
- public static RegMask FA1_MASK = new RegMask(FA1);
- public static RegMask FA2_MASK = new RegMask(FA2);
- public static RegMask FA3_MASK = new RegMask(FA3);
- public static RegMask FA4_MASK = new RegMask(FA4);
- public static RegMask FA5_MASK = new RegMask(FA5);
- public static RegMask FA6_MASK = new RegMask(FA6);
- public static RegMask FA7_MASK = new RegMask(FA7);
-
- // Int/ptr arguments calling convention
- static RegMask[] CALLINMASK = new RegMask[] {
- A0_MASK,
- A1_MASK,
- A2_MASK,
- A3_MASK,
- A4_MASK,
- A5_MASK,
- A6_MASK,
- A7_MASK
- };
-
- static RegMask[] XMMS = new RegMask[] {
- FA0_MASK,
- FA1_MASK,
- FA2_MASK,
- FA3_MASK,
- FA4_MASK,
- FA5_MASK,
- FA6_MASK,
- FA7_MASK
- };
-
- // Since riscv instructions are fixed we can just or them together
-
- public static void r_type(Encoding enc, Node n, int func3, int func7) {
- short dst = enc.reg(n);
- short src1 = enc.reg(n.in(1));
- short src2 = enc.reg(n.in(2));
- int body = RiscV.r_type(OP,dst,func3,src1,src2,func7);
- enc.add4(body);
- }
-
- public static void rf_type(Encoding enc, Node n, RM func3, int func7) {
- short dst = (short)(enc.reg(n )-F_OFFSET);
- short src1 = (short)(enc.reg(n.in(1))-F_OFFSET);
- short src2 = (short)(enc.reg(n.in(2))-F_OFFSET);
- int body = RiscV.r_type(OP_FP,dst,func3.ordinal(),src1,src2,func7);
- enc.add4(body);
- }
-
- @Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); }
- static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot ) {
- if( idx==0 ) return RPC_MASK;
- if( idx==1 ) return null;
- if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence
- boolean hidden = hiddenSelf(tfp);
- int sigidx = idx-2;
- if( hidden ) {
- if( sigidx==0 ) return null;
- sigidx--;
- }
- // Count floats in signature up to index
- int fcnt=0;
- for( int i=hidden ? 1 : 0; i<idx-2; i++ )
- if( tfp.arg(i) instanceof TypeFloat )
- fcnt++;
- // Floats up to XMMS in XMM registers
- if( tfp.arg(idx-2) instanceof TypeFloat ) {
- if( fcnt < XMMS.length )
- return XMMS[fcnt];
- } else {
- RegMask[] cargs = CALLINMASK;
- if( sigidx-fcnt < cargs.length )
- return cargs[sigidx-fcnt];
- }
- // Pass on stack slot(8 and higher)
- if( maxArgSlot>0 ) throw Utils.TODO();
- return new RegMask(MAX_REG + 1 + sigidx);
- }
-
- @Override public short maxArgSlot( TypeFunPtr tfp ) {
- int icnt=0, fcnt=0; // Count of ints, floats
- for( int i=hiddenSelf(tfp) ? 1 : 0; i<tfp.nargs(); i++ ) {
- if( tfp.arg(i) instanceof TypeFloat ) fcnt++;
- else icnt++;
- }
- int nstk = Math.max(icnt-8,0)+Math.max(fcnt-8,0);
- return (short)nstk;
- }
- private static boolean hiddenSelf(TypeFunPtr tfp) {
- return tfp.nargs() > 0 && tfp.arg(0) == TypePtr.PTR;
- }
-
- static final long CALLER_SAVE =
- (1L<<RPC) |
- (1L<<A0 ) | (1L<<A1 ) | (1L<<A2 ) | (1L<<A3 ) | (1L<<A4 ) | (1L<<A5 ) | (1L<<A6 ) | (1L<<A7 ) |
- (1L<<T0 ) | (1L<<T1 ) | (1L<<T2 ) | (1L<<T3 ) | (1L<<T4 ) | (1L<<T5 ) | (1L<<T6 ) |
- (1L<<FA0) | (1L<<FA1) | (1L<<FA2) | (1L<<FA3) | (1L<<FA4) | (1L<<FA5) | (1L<<FA6) | (1L<<FA7) |
- (1L<<F0 ) | (1L<<F1 ) | (1L<<F2 ) | (1L<<F3 ) | (1L<<F4 ) | (1L<<F5 ) | (1L<<F6 ) | (1L<<F7 ) |
- (1L<<FT8) | (1L<<FT9) | (1L<<FT10)| (1L<<FT11);
-
- @Override public long callerSave() { return CALLER_SAVE; }
- @Override public long neverSave() { return (1L<<RSP) | (1L<<ZERO); }
- @Override public RegMask retMask( TypeFunPtr tfp ) {
- return tfp.ret() instanceof TypeFloat ? FA0_MASK : A0_MASK;
- }
- @Override public int rpc() { return RPC; }
-
- // Return a MachNode unconditional branch
- @Override public CFGNode jump() { return new UJmpRISC(); }
-
- // Create a split op; any register to any register, including stack slots
- @Override public SplitNode split(LRG lrg, String kind, byte round ) { return new SplitRISC(lrg, kind,round); }
-
- // True if signed 12-bit immediate
- public static boolean imm12(TypeInteger ti) {
- // 52 = 64-12
- return ti.isConstant() && ((ti.value()<<52)>>52) == ti.value();
- }
- // True if HIGH 20-bit signed immediate, with all zeros low.
- public static boolean imm20Exact(TypeInteger ti) {
- // shift left 32 to clear out the upper 32 bits.
- // shift right SIGNED to sign-extend upper 32 bits; then shift 12 more to clear out lower 12 bits.
- // shift left 12 to re-center the bits.
- return ti.isConstant() && (((ti.value()<<32)>>44)<<12) == ti.value();
- }
-
- @Override public Node instSelect( Node n ) {
- return switch (n) {
- case AddNode add -> add(add);
- case AndNode and -> and(and);
- case BoolNode bool -> cmp(bool);
- case CallNode call -> call(call);
- case PtrToIntNode ptr -> new PtrToIntMach(ptr);
- case GuardNode guard -> new GuardMach(guard);
- case CallEndNode cend -> new CallEndMach(cend);
- case CLInitNode init -> new CLInitNode(init);
- case CProjNode c -> new CProjNode(c);
- case ConstantNode con -> con(con);
- case DivNode div -> div.mode()==2 ? new DivFRISC(div) : new DivRISC(div);
- case EscapeNode esc -> new EscapeNode(esc);
- case FunNode fun -> new FunRISC(fun);
- case FunPtrNode fptr -> fptr(fptr);
- case IfNode iff -> jmp(iff);
- case LoadNode ld -> ld(ld);
- case MemMergeNode mem -> new MemMergeNode(mem);
- case MinusNode neg -> {
- if( neg.mode()==2 ) throw Utils.TODO();
- yield new NegRISC(neg);
- }
- case MulNode mul -> mul.mode()==2 ? new MulFRISC(mul) : new MulRISC(mul);
- case NewNode nnn -> nnn(nnn);
- case NotNode not -> new NotRISC(not);
- case OrNode or -> or(or);
- case ParmNode parm -> new ParmRISC(parm);
- case BulkMemPhiNode phi-> new BulkMemPhiNode(phi);
- case MemPhiNode phi -> new MemPhiNode(phi);
- case PhiNode phi -> new PhiNode(phi);
- case ProjNode prj -> prj(prj);
- case ReadOnlyNode read-> new ReadOnlyMach(read);
- case ReturnNode ret -> new RetRISC(ret, ret.fun());
- case SarNode sar -> sra(sar);
- case ShlNode shl -> sll(shl);
- case ShrNode shr -> srl(shr);
- case StartCUNode start -> new StartCUNode(start);
- case StopCUNode stop -> new StopCUNode(stop);
- case StartNode start -> new StartNode(start);
- case StopNode stop -> new StopNode(stop);
- case StoreNode st -> st(st);
- case SubNode sub -> sub.mode()==2 ? new SubFRISC(sub) : sub(sub);
- case ToFloatNode tfn -> i2f8(tfn);
- case ToIntegerNode cvt -> new F8ToIRISC(cvt);
- case XorNode xor -> xor(xor);
-
- case LoopNode loop -> new LoopNode(loop);
- case RegionNode region -> new RegionNode(region);
- default -> throw Utils.TODO();
- };
- }
-
- private Node add(AddNode add) {
- if( add.mode()==2 )
- return new AddFRISC(add);
- if( add.in(2) instanceof ConstantNode off2 && off2._con instanceof TypeInteger ti && imm12(ti) )
- return new AddIRISC(add, (int)ti.value(),true);
- return new AddRISC(add);
- }
-
- private Node and(AndNode and) {
- if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti ) {
- if( imm12(ti) )
- return new AndIRISC(and, (int)ti.value());
- // Could be any size low bit mask
- if( ti.value() == 0xFFFFFFFFL )
- return new SrlIRISC(new SllIRISC(and,32),32,false);
- }
- return new AndRISC(and);
- }
-
- private Node call(CallNode call) {
- return call.fptr() instanceof FunPtrNode fptr
- ? new CallRISC(call, (TypeFunPtr)fptr._type)
- : call.fptr() instanceof ConstantNode con
- ? new CallRISC(call, (TypeFunPtr)con._type)
- : new CallRRISC(call);
- }
- private Node nnn(NewNode nnn) {
- // TODO: pass in the TFP for alloc
- return new NewRISC(nnn);
- }
-
- private Node cmp(BoolNode bool) {
- // Float variant directly implemented in hardware
- if( bool.isFloat() )
- return new SetFRISC(bool);
-
- // Only < and <u are implemented in hardware.
- // x < y - as-is
- // x <= y - flip and negate; !(y < x); `slt tmp=y,x; xori dst=tmp,#1`
- // x == y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1`
-
- // x > y - swap; y < x
- // x >= y - swap and negate; !(x < y); `slt tmp=y,x;` then NOT.
- // x != y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1` then NOT.
-
- // The ">", ">=" and "!=" in Simple include a NotNode, which can be
- // implemented with a XOR. If one of the above is followed by a NOT
- // we can remove the double XOR in the encodings.
-
- return switch( bool.op() ) {
- case "<" -> bool.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)
- ? new SetIRISC(bool, (int)ti.value(),false)
- : new SetRISC(bool, false);
- // x <= y - flip and negate; !(y < x); `slt tmp=y,x; xori dst=tmp,#1`
- case "<=" -> new XorIRISC(new SetRISC(bool.swap12(), false),1,false);
- // x == y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1`
- case "==" -> new SetIRISC(new SubRISC(bool),1,true,false);
- default -> throw Utils.TODO();
- };
- }
-
- private Node con( ConstantNode con ) {
- if( !con._con.isConstant() )
- return ConstantNode.raw(con); // Default unknown caller inputs
- String ext = con instanceof ExternNode ext0 ? ext0._extern : null;
- return switch( con._con ) {
- case TypeInteger ti -> {
- // External; without knowing the size assume we have to load something
- if( ext!=null ) yield new Int8RISC(con,ext);
- if( imm12(ti) ) yield new IntRISC(con);
- long x = ti.value();
- if( imm20Exact(ti) ) yield new LUI((int)x);
- if( (x<<32)>>32 == x ) { // Signed lower 32-bit immediate
- // Here, the low 12 bits get sign-extended, which means if
- // bit11 is set, the value is negative and lowers the LUI
- // value. Add a bit 12 to compensate
- if( ((x>>11)&1)==1 ) x += 0x1000;
- yield new AddIRISC(new LUI((int)(x & ~0xFFF)), (int)(x & 0xFFF),false);
- }
- // Need more complex sequence for larger constants... or a load
- // from a constant pool, which does not need an extra register
- yield new Int8RISC(con,null);
- }
- // Load from constant pool
- case TypeFloat tf -> new FltRISC(con,ext);
- case TypeMemPtr tmp -> new TMPRISC(con,ext);
- case TypeFunPtr tfp -> new TFPRISC(tfp,ext);
- case TypeNil tn -> throw Utils.TODO();
- // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
- case Type t -> t==Type.NIL ? new IntRISC(con) : ConstantNode.raw(con);
- };
- }
-
- private Node fptr( FunPtrNode con ) {
- return new TFPRISC(con);
- }
-
- private Node jmp( IfNode iff ) {
- if( iff.in(1) instanceof BoolNode bool && !bool.isFloat() ) {
- // if less than or equal switch inputs
- String bop = bool.op();
- if( bop.equals("<=") || bop.equals(">") )
- return new BranchRISC(iff, IfNode.swap(bop), bool.in(2), bool.in(1));
- return new BranchRISC(iff, bop, bool.in(1), bool.in(2));
- }
- // Vs zero
- return new BranchRISC(iff, iff.in(1)==null ? "==" : "!=", iff.in(1), null);
- }
-
- private Node or(OrNode or) {
- if( or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti))
- return new OrIRISC(or, (int)ti.value());
- return new OrRISC(or);
- }
-
- private Node xor(XorNode xor) {
- if( xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti))
- return new XorIRISC(xor, (int)ti.value());
- return new XorRISC(xor);
- }
-
- private Node sra(SarNode sar) {
- if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti))
- return new SraIRISC(sar, (int)ti.value());
- return new SraRISC(sar);
- }
-
- private Node srl(ShrNode shr) {
- if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti))
- return new SrlIRISC(shr, (int)ti.value(),true);
- return new SrlRISC(shr);
- }
-
- private Node sll(ShlNode sll) {
- if( sll.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti))
- return new SllIRISC(sll, (int)ti.value());
- return new SllRISC(sll);
- }
-
- private Node sub(SubNode sub) {
- return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti)
- ? new AddIRISC(sub, (int)(-ti.value()),true)
- : new SubRISC(sub);
- }
-
- private Node i2f8(ToFloatNode tfn) {
- assert tfn.in(1)._type instanceof TypeInteger ti;
- return new I2F8RISC(tfn);
- }
-
- private Node prj(ProjNode prj) {
- if( prj.in(0) instanceof CLInitNode ) return new ProjNode(prj);
- TypeInteger ret = cReturn(prj);
- if( ret!=null ) return new ExtendRISC(prj,ret);
- return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjRISC(prj);
- }
-
- private Node ld(LoadNode ld) {
- return new LoadRISC(ld,address(ld),off);
- }
-
- private Node st(StoreNode st) {
- Node xval = st.val() instanceof ConstantNode con && con._con == TypeInteger.ZERO ? null : st.val();
- return new StoreRISC(st,address(st), off, xval);
- }
-
- // Gather addressing mode bits prior to constructing. This is a builder
- // pattern, but saving the bits in a *local* *global* here to keep mess
- // contained.
- private static int off;
- private Node address( MemOpNode mop ) {
- off = 0; // Reset
- Node base = mop.ptr();
- // Skip/throw-away a ReadOnly, only used to typecheck
- if( base instanceof ReadOnlyNode read ) base = read.in(1);
- assert !(base instanceof AddNode) && base._type instanceof TypeMemPtr; // Base ptr always, not some derived
- if( mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm12(ti) ) {
- off = (int)ti.value();
- } else {
- base = new AddRISC(base,mop.off());
- base._type = mop.ptr()._type;
- }
- return base;
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java
deleted file mode 100644
index 74af9266..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-public class AddFMemX86 extends MemOpX86 {
- AddFMemX86( AddNode add, LoadNode ld , Node base, Node idx, int off, int scale, Node val ) {
- super(add,ld, base, idx, off, scale, 0, val );
- }
- @Override public String op() { return "addf"+_sz; }
- @Override public RegMask regmap(int i) {
- if( i==1 ) return null; // Memory
- if( i==2 ) return x86_64_v2.RMASK; // base
- if( i==3 ) return x86_64_v2.RMASK; // index
- if( i==4 ) return x86_64_v2.XMASK; // value
- throw Utils.TODO();
- }
- @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
- @Override public int twoAddress() { return 4; }
- @Override public void encoding( Encoding enc ) {
- X86.memory(enc,0xF2,0x0F58,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(ptr()),enc.reg(idx()),_off,_scale);
- }
-
- // General form: "add dst = src + [base + idx<<2 + 12]"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ");
- sb.p(val()==null ? "#"+_imm : code.reg(val())).p(" + ");
- asm_address(code,sb);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java
deleted file mode 100644
index c7d0cf9c..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class AddFX86 extends MachConcreteNode implements MachNode {
- AddFX86( Node addf ) { super(addf); }
- @Override public String op() { return "addf"; }
- @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; }
- @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
- @Override public int twoAddress() { return 1; }
- @Override public boolean commutes() { return true; }
-
- @Override public void encoding( Encoding enc ) {
- X86.sse(enc,0xF2,0x0F58,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET);
- }
-
- // General form: "addf dst += src"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" += ").p(code.reg(in(2)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java
deleted file mode 100644
index 0e652a93..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.Encoding;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-public class AddIX86 extends ImmX86 {
- AddIX86( Node add, int imm ) { super(add,imm); }
- @Override public String op() {
- return _imm == 1 ? "inc" : (_imm == -1 ? "dec" : "addi");
- }
- @Override public String glabel() { return "+"; }
- @Override int opcode() { return 0x81; }
- @Override int mod() { return 0; }
- @Override public final void encoding( Encoding enc ) {
- if( _imm== 1 || _imm== -1 ) {
- short dst = enc.reg(this); // Also src1
- X86.unary(enc,0xFF,_imm==1 ? 0 : 1,dst);
- } else super.encoding(enc);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java
deleted file mode 100644
index ed65b0a8..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class AddMemX86 extends MemOpX86 {
- AddMemX86( AddNode add, LoadNode ld , Node base, Node idx, int off, int scale, int imm, Node val ) {
- super(add,ld, base, idx, off, scale, imm, val );
- }
- @Override public String op() { return "add"+_sz; }
- @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
- @Override public int twoAddress() { return 4; }
- @Override public void encoding( Encoding enc ) {
- X86.memory(enc,0,0x03,true,enc.reg(this),enc.reg(ptr()),enc.reg(idx()),_off,_scale);
- }
-
- // General form: "add dst += [base + idx<<2 + 12]"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" += ");
- asm_address(code,sb);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java
deleted file mode 100644
index 84543e8f..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java
+++ /dev/null
@@ -1,11 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java
deleted file mode 100644
index 3c1b567c..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java
+++ /dev/null
@@ -1,11 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java
deleted file mode 100644
index dedbce45..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java
+++ /dev/null
@@ -1,11 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java
deleted file mode 100644
index f97163eb..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.CallNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.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 ) {
- X86.callRegister(enc,enc.reg(fptr()));
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(fptr())).p(" ");
- for( int i=0; i<nargs(); i++ )
- sb.p(code.reg(arg(i))).p(" ");
- sb.unchar(2);
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java
deleted file mode 100644
index 6bafbf37..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.TypeFunPtr;
-import com.seaofnodes.simple.util.SB;
-
-public class CallX86 extends CallNode implements MachNode, RIPRelSize {
- final TypeFunPtr _tfp;
- final String _name;
- final boolean _extern;
- CallX86( CallNode call, TypeFunPtr tfp ) {
- super(call);
- _inputs.pop(); // Pop constant target
- assert tfp.isConstant();
- _tfp = tfp;
- _name = CodeGen.CODE.funcName(tfp.fidx());
- _extern = CodeGen.CODE.externFunc(tfp.fidx()) != null;
- }
- @Override public String op() { return "call"; }
- @Override public String label() { return op(); }
- @Override public String name() { return _name; }
- @Override public TypeFunPtr tfp() { return _tfp; }
- @Override public RegMask regmap(int i) {
- return _extern
- ? x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot,CodeGen.CODE._cCallingConv)
- : x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot);
- }
- @Override public RegMask outregmap() { return null; }
- @Override public int nargs() { return nIns()-2; } // Minus control, memory, fptr
- @Override public boolean external() { return _extern; }
-
- @Override public void encoding( Encoding enc ) {
- FunNode fun = CodeGen.CODE.link(_tfp);
- if( fun==null ) enc.external(this,_name);
- else enc.relo(this);
- X86.call(enc);
- }
-
- // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding
- @Override public byte encSize(int delta) { return 5; }
-
- // Delta is from opcode start
- @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- enc.patch4(opStart+1, delta-5);
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(_name).p(" ");
- for( int i=0; i<nargs(); i++ )
- sb.p(code.reg(in(i+2))).p(", ");
- sb.unchar(2);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java
deleted file mode 100644
index 5ef7a476..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class CmpFX86 extends MachConcreteNode implements MachNode {
- CmpFX86( Node cmp ) { super(cmp); }
- @Override public String op() { return "cmpf"; }
- @Override public RegMask regmap(int i) { return x86_64_v2.XMASK; }
- @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; }
-
- @Override public void encoding( Encoding enc ) {
- X86.sse(enc,0x66,0x0F2E,false,enc.reg(in(1))-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET);
- }
-
- // General form: "cmp src1,src2"
- @Override public void asm(CodeGen code, SB sb) {
- String dst = code.reg(this);
- if( dst!="flags" ) sb.p(dst).p(" = ");
- sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java
deleted file mode 100644
index 151f8ca8..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-// Compare immediate. Sets flags.
-public class CmpIX86 extends MachConcreteNode implements MachNode {
- final int _imm;
- CmpIX86( Node cmp, int imm, boolean swap ) {
- super(cmp);
- _inputs.del(swap ? 1 : 2);
- _imm = imm;
- }
- CmpIX86( CmpIX86 cmp ) {
- super((Node)null);
- addDef(null);
- addDef(cmp.in(1));
- _imm = cmp._imm;
- }
-
- @Override public String op() { return _imm==0 ? "test" : "cmp"; }
- @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
- @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; }
- @Override public boolean isClone() { return true; }
- @Override public Node copy() { return new CmpIX86(this); }
-
- @Override public final void encoding( Encoding enc ) {
- X86.imm(enc,0x81,7,enc.reg(in(1)),_imm);
- }
- @Override public void asm(CodeGen code, SB sb) {
- String dst = code.reg(this);
- if( dst!="flags" ) sb.p(dst).p(" = ");
- sb.p(code.reg(in(1)));
- if( _imm != 0 ) sb.p(", #").p(_imm);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java
deleted file mode 100644
index 4014045b..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-import com.seaofnodes.simple.util.SB;
-
-public class CmpMemX86 extends MemOpX86 {
- final boolean _swap; // Op switched LHS, RHS
- CmpMemX86( BoolNode bool, LoadNode ld, Node base, Node idx, int off, int scale, int imm, Node val, boolean swap ) {
- super(bool,ld, base, idx, off, scale, imm, val );
- _swap = swap;
- assert !_swap || val() != null || bool.op()=="==" || bool.op()=="!="; // Cannot swap with immediate
- }
- @Override public String op() { return ((val()==null && _imm==0) ? "test" : "cmp") + _sz; }
- @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; }
- @Override public void encoding( Encoding enc ) {
- short ptr=enc.reg(ptr()), idx=enc.reg(idx()), src=enc.reg(val());
- if( src==-1 ) {
- assert !_swap;
- X86.cmpImm(enc,_con.log_size(),_imm,ptr,idx,_off,_scale);
- } else encVal(enc,_con,ptr,idx,src,_off,_scale,_swap);
- }
-
- static void encVal(Encoding enc, Type decl, short ptr, short idx, short src, int off, int scale, boolean _swap) {
- assert !(decl instanceof TypeFloat); // Selected separately as CmpFX86.
- X86.cmpMem(enc,decl.log_size(),src,ptr,idx,off,scale,_swap);
- }
-
- // General form: "cmp dst = src, [base + idx<<2 + 12]"
- @Override public void asm(CodeGen code, SB sb) {
- String dst = code.reg(this);
- if( dst!="flags" ) sb.p(dst).p(" = ");
- if( _swap ) asm_address(code,sb).p(",");
- if( val()==null ) {
- if( _imm!=0 ) sb.p("#").p(_imm);
- } else {
- sb.p(code.reg(val()));
- }
- if( !_swap ) asm_address(code,sb.p(", "));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java
deleted file mode 100644
index 7faea17b..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class CmpX86 extends MachConcreteNode implements MachNode {
- CmpX86( Node add ) { super(add); }
- @Override public String op() { return "cmp"; }
- @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
- @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; }
- // This one is marginal: cloning a Cmp[reg,reg] means stretching the
- // lifetimes of 2 normal registers in exchange for shortening a flags
- // register lifetime. Spilling flags is (probably) relatively expensive
- // compared to some normal registers - and those normal registers might not
- // be spilling!
- @Override public boolean isClone() { return true; }
-
- @Override public void encoding( Encoding enc ) {
- X86.reg(enc,0x3B,enc.reg(in(1)),enc.reg(in(2)));
- }
- @Override public void asm(CodeGen code, SB sb) {
- String dst = code.reg(this);
- if( dst!="flags" ) sb.p(dst).p(" = ");
- sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java
deleted file mode 100644
index 7bbb5102..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class DivFX86 extends MachConcreteNode implements MachNode {
- DivFX86( Node divf) { super(divf); }
- @Override public String op() { return "divf"; }
- @Override public RegMask regmap(int i) { return x86_64_v2.XMASK; }
- @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
- @Override public int twoAddress() { return 1; }
-
- @Override public void encoding( Encoding enc ) {
- X86.sse(enc,0xF2,0x0F5E,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET);
- }
-
-
- // General form: "divf dst /= src"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" /= ").p(code.reg(in(2)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java
deleted file mode 100644
index 9f375a80..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class DivX86 extends MachConcreteNode implements MachNode {
- DivX86( Node div ) { super(div); }
- @Override public String op() { return "div"; }
- @Override public RegMask regmap(int i) {
- return (i==1) ? x86_64_v2.RAX_MASK : x86_64_v2.DIV_MASK;
- }
- @Override public RegMask outregmap() { return x86_64_v2.RAX_MASK; }
- @Override public RegMask killmap() { return x86_64_v2.RDX_MASK; }
-
- @Override public void encoding( Encoding enc ) {
- X86.divide(enc,enc.reg(in(2)));
- }
- // General form: "div dst /= src"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2)));
- }
- @Override public String comment() { return "kill rdx"; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java
deleted file mode 100644
index 1a6ae123..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.TypeInteger;
-import com.seaofnodes.simple.util.SB;
-
-// Convert a narrow C return register to Simple's full-width integer value.
-public class ExtendX86 extends MachConcreteNode {
- final int _bits;
- final boolean _signed;
- ExtendX86(ProjNode prj, TypeInteger ret) {
- super(prj);
- _inputs.set(0,null);
- _inputs.add(new ProjX86(prj));
- _bits = 8 << ret.log_size();
- _signed = ret._min < 0;
- }
- @Override public String op() { return (_signed ? "sext" : "zext")+_bits; }
- @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
- @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
- @Override public void encoding(Encoding enc) {
- X86.extend(enc,_bits,_signed,enc.reg(this),enc.reg(in(1)));
- }
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java
deleted file mode 100644
index a79a1017..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class F8ToIX86 extends MachConcreteNode implements MachNode {
- F8ToIX86(Node f8toi ) { super(f8toi); }
- @Override public String op() { return "cvti"; }
- @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.XMASK; }
- @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; }
- @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
-
- @Override public void encoding( Encoding enc ) {
- X86.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-x86_64_v2.XMM_OFFSET);
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java
deleted file mode 100644
index 59f06341..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-public class FltX86 extends ConstantNode implements MachNode, RIPRelSize{
- private byte _encSize;
- final String _ext;
- FltX86( ConstantNode con, String ext ) { super(con); _ext = ext; }
- @Override public String op() { return "ld8"; }
- @Override public RegMask regmap(int i) { return null; }
- @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
- @Override public boolean isClone() { return true; }
- @Override public Node copy() { return new FltX86(this,_ext); }
-
- @Override public void encoding( Encoding enc ) {
- if( _ext!=null ) throw Utils.TODO();
- _encSize=X86.rip(enc,0xF2,0x0F10,false,enc.reg(this)-x86_64_v2.XMM_OFFSET);
- enc.largeConstant(this,_con,_encSize-4,2/*ELF PC32*/);
- }
-
- // Delta is from opcode start
- @Override public byte encSize(int delta) {
- // TODO: 1-byte RIP offset?
- return (byte)_encSize;
- }
-
- // Delta is from opcode start
- @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- enc.patch4(opStart+opLen-4,delta-opLen);
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- _con.print(sb.p(code.reg(this)).p(" = #"));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java
deleted file mode 100644
index 0e44aee7..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.codegen.Encoding;
-import com.seaofnodes.simple.node.FunNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.util.SB;
-
-public class FunX86 extends FunNode implements MachNode {
- FunX86( FunNode fun ) { super(fun); }
- @Override public void postSelect(CodeGen code) {
- super.postSelect(code);
- _maxArgSlot = code._mach.maxArgSlot(sig(),code._callingConv);
- // One more for RPC pre-pushed; X86 special
- _maxArgSlot++;
- }
-
- @Override public void computeFrameAdjust(CodeGen code, int maxReg) {
- super.computeFrameAdjust(code,maxReg);
- // Alignment, but off by RPC
- if( _hasCalls ) // If non-leaf, pad to 16b
- _frameAdjust = ((_frameAdjust+8+8) & -16)-8;
- }
-
- @Override public void encoding( Encoding enc ) {
- int sz = _frameAdjust;
- if( sz == 0 ) return; // Skip if no frame adjust
- X86.imm(enc,0x81,5,x86_64_v2.RSP,sz);
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java
deleted file mode 100644
index ee357901..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class I2f8X86 extends MachConcreteNode implements MachNode {
- I2f8X86(Node i2f8 ) { super(i2f8); }
- @Override public String op() { return "cvtf"; }
- @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.WMASK; }
- @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
-
- @Override public void encoding( Encoding enc ) {
- X86.sse(enc,0xF2,0x0F2A,true,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(1)));
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java
deleted file mode 100644
index 5a4ff4b2..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.TypeInteger;
-import com.seaofnodes.simple.util.SB;
-
-public abstract class ImmX86 extends MachConcreteNode implements MachNode {
- final int _imm;
- ImmX86( Node add, int imm ) {
- super(add);
- _inputs.pop(); // Pop constant input off
- _imm = imm;
- }
- @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
- @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
- @Override public int twoAddress() { return 1; }
- @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; }
-
- abstract int opcode();
- abstract int mod();
-
- @Override public void encoding( Encoding enc ) {
- X86.imm(enc,opcode(),mod(),enc.reg(this),_imm);
- }
-
- // General form: "addi dst += #imm"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java
deleted file mode 100644
index 2ffaa3d1..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeInteger;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-// Integer constants
-public class IntX86 extends ConstantNode implements MachNode {
- final String _ext;
- IntX86( ConstantNode con, String ext ) { super(con); _ext = ext; }
- @Override public String op() {
- return _con == Type.NIL || _con == TypeInteger.ZERO ? "xor" : "ldi";
- }
- @Override public boolean isClone() { return true; }
- @Override public Node copy() { return new IntX86(this,_ext); }
- @Override public RegMask regmap(int i) { return null; }
- @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
- // Zero-set uses XOR kills flags
- @Override public RegMask killmap() {
- return _con == Type.NIL || _con == TypeInteger.ZERO ? x86_64_v2.FLAGS_MASK : null;
- }
-
- @Override public void encoding( Encoding enc ) {
- if( _ext!=null ) throw Utils.TODO();
- X86.constant(enc,enc.reg(this),_con==Type.NIL ? 0 : ((TypeInteger)_con).value());
- }
-
- // Human-readable form appended to the SB. Things like the encoding,
- // indentation, leading address or block labels not printed here.
- // Just something like "ld4\tR17=[R18+12] // Load array base".
- // General form: "op\tdst=src+src"
- @Override public void asm(CodeGen code, SB sb) {
- String reg = code.reg(this);
- if( _con == Type.NIL || _con == TypeInteger.ZERO )
- sb.p(reg).p(",").p(reg);
- else
- _con.print(sb.p(reg).p(" = #"));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java
deleted file mode 100644
index 10cee059..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-// Jump on flags, uses flags
-public class JmpX86 extends IfNode implements MachNode, RIPRelSize {
- String _bop;
- JmpX86( IfNode iff, String bop ) {
- super(iff);
- _bop = bop;
- }
- @Override public String op() { return "j"+_bop; }
- @Override public String label() { return op(); }
- @Override public void postSelect(CodeGen code) {
- Node set = in(1);
- if( set==null ) return; // Never-node cutout
- Node cmp = set.in(1);
- // Bypass an expected Set and just reference the cmp directly
- if( set instanceof SetX86 setx && (cmp instanceof CmpX86 || cmp instanceof CmpIX86 || cmp instanceof CmpMemX86 || cmp instanceof CmpFX86) ) {
- _inputs.set( 1, cmp );
- _bop = setx._bop;
- } else
- throw Utils.TODO();
- }
- @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.FLAGS_MASK; }
- @Override public RegMask outregmap() { return null; }
- @Override public void negate() { _bop = negate(_bop); }
-
- @Override public void encoding( Encoding enc ) {
- if( in(1)==null && _bop=="!=" ) return;
- X86.branch(enc,in(1)==null ? -1 : x86_64_v2.jumpop(_bop)&15);
- enc.jump(this,cproj(0));
- }
-
- // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding
- @Override public byte encSize(int delta) {
- if( in(1)==null && _bop=="!=" ) return 0;
- return X86.branchSize(delta,in(1)!=null);
- }
-
- // Delta is from opcode start
- @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- assert !(in(1)==null && _bop=="!=");
- X86.patchBranch(enc.bits(),opStart,opLen,delta,in(1)==null ? -1 : x86_64_v2.jumpop(_bop)&15);
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- if( in(1)!=null ) { // Never-node
- String src = code.reg(in(1));
- if( src!="flags" ) sb.p(src).p(" ");
- } else if( _bop=="!=" ) {
- sb.p("never");
- return;
- }
- CFGNode prj = cproj(0).uctrlSkipEmpty(); // 0 is True is jump target
- if( !prj.blockHead() ) prj = prj.cfg0();
- sb.p(label(prj));
- }
-
- @Override public String comment() { return "L"+cproj(1)._nid; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java
deleted file mode 100644
index 4b00e4dd..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class LeaX86 extends MachConcreteNode implements MachNode {
- final int _scale;
- final int _offset;
- LeaX86( Node add, Node base, Node idx, int scale, int offset ) {
- super(add);
- assert scale==0 || scale==1 || scale==2 || scale==3;
- _inputs.pop();
- _inputs.pop();
- _inputs.push(base);
- _inputs.push(idx);
- _scale = scale;
- _offset = offset;
- }
-
- @Override public String op() { return "lea"; }
- @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; }
- @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
-
- @Override public void encoding( Encoding enc ) {
- X86.lea(enc,enc.reg(this),enc.reg(in(1)),enc.reg(in(2)),_offset,_scale);
- }
-
- // General form: "lea dst = base + 4*idx + 12"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ");
- if( in(1) != null )
- sb.p(code.reg(in(1))).p(" + ");
- sb.p(code.reg(in(2))).p("<<").p(_scale);
- if( _offset!=0 ) sb.p(" + #").p(_offset);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java
deleted file mode 100644
index 5c472dbe..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.LoadNode;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-public class LoadX86 extends MemOpX86 {
- LoadX86( LoadNode ld, Node base, Node idx, int off, int scale ) {
- super(ld,ld, base, idx, off, scale, 0);
- }
- @Override public String op() { return "ld"+_sz; }
- @Override public RegMask outregmap() { return _con instanceof TypeFloat ? x86_64_v2.XMASK : x86_64_v2.WMASK; }
- @Override public void encoding( Encoding enc ) {
- // REX.W + 8B /r MOV r64, r/m64
- // Zero extension for u8, u16 and u32 but sign extension i8, i16, i32
- // Use movsx and movzx
- short dst = enc.reg(this );
- short ptr = enc.reg(ptr());
- short idx = enc.reg(idx());
- enc(enc, _con, dst, ptr, idx, _off, _scale);
- }
-
- static void enc( Encoding enc, Type decl, short dst, short ptr, short idx, int off, int scale ) {
- // The chapter chooses the register bank and extension from its own type facts.
- boolean xmm=dst>=x86_64_v2.XMM_OFFSET;
- if( xmm && decl.log_size()==3 ) decl=TypeFloat.F64;
- else if( !xmm && decl instanceof TypeFloat )
- decl=decl==TypeFloat.F32 ? TypeInteger.U32 : TypeInteger.BOT;
- boolean signed=decl.isa(TypeInteger.I8) || decl==TypeInteger.I16 || decl==TypeInteger.I32;
- X86.load(enc,decl.log_size(),signed,xmm,
- xmm ? dst-x86_64_v2.XMM_OFFSET : dst,ptr,idx,off,scale);
- }
-
-
- // General form: "ldN dst,[base + idx<<2 + 12]"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(",");
- asm_address(code,sb);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java
deleted file mode 100644
index 2e18a058..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class MemAddX86 extends MemOpX86 {
- MemAddX86( StoreNode st, Node base, Node idx, int off, int scale, int imm, Node val ) {
- super(st, st, base, idx, off, scale, imm, val );
- }
- @Override public String op() {
- return (_imm == 1 ? "inc" : (_imm == -1 ? "dec" : "add")) + _sz;
- }
- // Register mask allowed as a result. 0 for no register.
- @Override public RegMask outregmap() { return null; }
- @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; }
- @Override public void encoding( Encoding enc ) {
- X86.addMem(enc,enc.reg(val()),_imm,enc.reg(ptr()),enc.reg(idx()),_off,_scale);
- }
- // General form: "add [base + idx<<2 + 12] += src"
- @Override public void asm(CodeGen code, SB sb) {
- asm_address(code,sb);
- if( val()==null ) {
- if( _imm != 1 && _imm != -1 ) sb.p(" += #").p(_imm);
- } else sb.p(" += ").p(code.reg(val()));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
deleted file mode 100644
index fcca7f8f..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.SB;
-import java.lang.StringBuilder;
-
-// Generic X86 memory operand base.
-// inputs:
-// ctrl - will appear for possibly RCE (i.e. array ops)
-// mem - memory dependence edge
-// base - Basic object pointer
-// idx/null - Scaled index offset, or null if none
-// val/null - Value to store, as part of an op-to-mem or op-from-mem. Null for loads, or if an immediate is being used
-// Constants:
-// offset - offset added to base. Can be zero.
-// scale - scale on index; only 0,1,2,4,8 allowed, 0 is only when index is null
-// imm - immediate value to store or op-to-mem, and only when val is null
-public abstract class MemOpX86 extends MemOpNode implements MachNode {
- final int _off; // Limit 32 bits
- final int _scale; // Limit 0,1,2,3
- final int _imm; // Limit 32 bits
- final char _sz; // Handy print name
- MemOpX86( Node op, MemOpNode mop, Node base, Node idx, int off, int scale, int imm ) {
- super(op,mop);
- assert base._type instanceof TypeMemPtr && !(base instanceof AddNode);
- assert (idx==null && scale==0) || (idx!=null && 0<= scale && scale<=3);
-
- // Copy memory parts from e.g. the LoadNode over the opcode, e.g. an Add
- if( op != mop ) {
- _inputs.set(0,mop.in(0)); // Control from mem op
- _inputs.set(1,mop.in(1)); // Memory from mem op
- _inputs.set(2,base); // Base handed in
- }
-
- assert ptr() == base;
- _inputs.setX(3,idx);
- _off = off;
- _scale = scale;
- _imm = imm;
- int size = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size();
- _sz = (char)('0'+size);
- }
-
- // Store-based flavors have a value edge
- MemOpX86( Node op, MemOpNode mop, Node base, Node idx, int off, int scale, int imm, Node val ) {
- this(op,mop,base,idx,off,scale,imm);
- _inputs.setX(4,val);
- }
-
- Node idx() { return in(3); }
- Node val() { return in(4); } // Only for stores, including op-to-memory
-
- @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) {
- return p.p(".").p(_name);
- }
-
- @Override public String label() { return op(); }
- @Override public Type compute() { throw new AssertionError("Do not call compute on "+getClass().getSimpleName()); }
- @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); }
-
- // Register mask allowed on input i.
- @Override public RegMask regmap(int i) {
- if( i==1 ) return null; // Memory
- if( i==2 ) return x86_64_v2.RMASK; // base in GPR
- if( i==3 ) return x86_64_v2.RMASK; // index in GPR
- if( i==4 ) return _sz >= '4'
- ? x86_64_v2.MEM_MASK // value in GPR or XMM
- : x86_64_v2.RMASK; // Bytes and shorts in GPR only
- return null; // Anti-dependence
- }
-
- // "[base + idx<<2 + 12]"
- SB asm_address(CodeGen code, SB sb) {
- sb.p("[").p(code.reg(ptr()));
- if( idx() != null ) {
- sb.p("+").p(code.reg(idx()));
- if( _scale != 0 )
- sb.p("*").p((1<<_scale));
- }
- if( _off != 0 )
- sb.p("+").p(_off);
- return sb.p("]");
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java
deleted file mode 100644
index 74616db7..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class MulFX86 extends MachConcreteNode implements MachNode {
- MulFX86( Node mulf) { super(mulf); }
- @Override public String op() { return "mulf"; }
- @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; }
- @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
- @Override public int twoAddress() { return 1; }
- @Override public boolean commutes() { return true; }
-
- @Override public void encoding( Encoding enc ) {
- X86.sse(enc,0xF2,0x0F59,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET);
- }
-
- // General form: "mulf dst *= src"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" *= ").p(code.reg(in(2)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java
deleted file mode 100644
index 5dcb253d..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class MulIX86 extends MachConcreteNode implements MachNode {
- final int _imm;
- MulIX86( Node add, int imm ) {
- super(add);
- _inputs.pop(); // Pop the constant input
- _imm = imm; // Record the constant
- }
- @Override public String op() { return "muli"; }
- @Override public String glabel() { return "*"; }
- @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
- @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
- // Rare 3-address form
- @Override public final void encoding( Encoding enc ) {
- X86.imul(enc,enc.reg(this),enc.reg(in(1)),_imm);
- }
- // General form: "addi dst += #imm"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * #").p(_imm);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java
deleted file mode 100644
index 6f3fcdeb..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class MulX86 extends MachConcreteNode implements MachNode {
- MulX86( Node mul ) { super(mul); }
- @Override public String op() { return "mul"; }
- @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; }
- @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
- @Override public int twoAddress() { return 1; }
- @Override public boolean commutes() { return true; }
-
- @Override public void encoding( Encoding enc ) {
- X86.reg(enc,0x0FAF,enc.reg(this),enc.reg(in(2)));
- }
-
- // General form: "mul dst *= src"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java
deleted file mode 100644
index 86610f6d..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class NegX86 extends MachConcreteNode implements MachNode {
- NegX86(MinusNode not) { super(not); }
- @Override public String op() { return "neg"; }
- @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
- @Override public RegMask outregmap() { return x86_64_v2.RMASK; }
- @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; }
- @Override public int twoAddress() { return 1; }
-
- @Override public void encoding( Encoding enc ) {
- X86.unary(enc,0xF7,3,enc.reg(this));
- }
- @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java
deleted file mode 100644
index d6a06c2f..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.TypeFunPtr;
-import com.seaofnodes.simple.util.SB;
-
-public class NewX86 extends NewNode implements MachNode {
- // A pre-zeroed chunk of memory.
- NewX86( NewNode nnn ) { super(nnn); }
- @Override public void encoding( Encoding enc ) {
- enc.external(this,"calloc");
- X86.constant(enc,_arg2Reg,1);
- X86.call(enc);
- }
- // General form: "alloc #bytes PC"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p("\n"); // Multi-op trigger
- sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n");
- sb.p("call #calloc");
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java
deleted file mode 100644
index 1e4e7c26..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class NotX86 extends MachConcreteNode implements MachNode {
- NotX86(NotNode not) { super(not); }
- @Override public String op() { return "not"; }
- @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
- @Override public RegMask outregmap() { return x86_64_v2.RMASK; }
- @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; }
-
- @Override public void encoding( Encoding enc ) {
- assert !(in(1) instanceof NotNode); // Cleared out by peeps
- assert !(in(1) instanceof BoolNode);
- X86.not(enc,enc.reg(this),enc.reg(in(1)));
- }
- @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java
deleted file mode 100644
index 15f4824d..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java
+++ /dev/null
@@ -1,11 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java
deleted file mode 100644
index bc8f0ea8..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java
+++ /dev/null
@@ -1,11 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java
deleted file mode 100644
index 5cfdd804..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java
+++ /dev/null
@@ -1,17 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java
deleted file mode 100644
index 046c7b4c..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java
+++ /dev/null
@@ -1,15 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java
deleted file mode 100644
index 06755143..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public abstract class RegX86 extends MachConcreteNode {
- RegX86( Node add ) { super(add); }
- @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
- @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
- @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; }
- @Override public int twoAddress() { return 1; }
- abstract int opcode();
- @Override public void encoding( Encoding enc ) {
- X86.reg(enc,opcode(),enc.reg(in(1)),enc.reg(in(2)));
- }
- // General form: "add dst += src"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" ").p(glabel()).p("= ").p(code.reg(in(2)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java
deleted file mode 100644
index e4467bb4..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.*;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-
-public class RetX86 extends ReturnNode implements MachNode {
- RetX86( ReturnNode ret, FunNode fun ) { super(ret, fun); fun.setRet(this); }
- @Override public void encoding( Encoding enc ) {
- int sz = fun()._frameAdjust;
- if( sz!=0 ) X86.imm(enc,0x81,0,x86_64_v2.RSP,sz);
- X86.ret(enc);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java
deleted file mode 100644
index cf6275dc..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.codegen.Encoding;
-import com.seaofnodes.simple.codegen.RegMask;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.util.SB;
-
-// Arithmetic Right Shift
-public class SarIX86 extends MachConcreteNode implements MachNode {
- final int _imm;
- SarIX86( Node sari, int imm ) { super(sari); assert x86_64_v2.imm8(imm); _inputs.pop(); _imm = imm; }
- @Override public String op() { return "sari"; }
- @Override public String glabel() { return ">>"; }
- int opcode() { return 0xC1; }
- int mod() { return 7; }
-
- @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
- @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
- @Override public int twoAddress() { return 1; }
-
- @Override public void encoding(Encoding enc) {
- X86.shift(enc,mod(),enc.reg(this),_imm);
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java
deleted file mode 100644
index 92493712..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.util.Utils;
-
-public class SarX86 extends RegX86 {
- SarX86( Node add ) { super(add); }
- @Override public String op() { return "sar"; }
- @Override public String glabel() { return ">>"; }
- @Override public RegMask regmap(int i) {
- if (i == 1) return x86_64_v2.WMASK;
- if (i == 2) return x86_64_v2.RCX_MASK;
- throw Utils.TODO();
- }
- @Override int opcode() { return 0xD3; }
- @Override public void encoding( Encoding enc ) {
- X86.unary(enc,opcode(),7,enc.reg(this));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java
deleted file mode 100644
index 864c1b55..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-// Corresponds to the x86 instruction "sete && setne".
-// Use result of comparison without jump.
-public class SetX86 extends MachConcreteNode implements MachNode {
- final String _bop; // One of <,<=,==
- final boolean _unsigned;
- // Constructor expects input is an X86 and not an Ideal node.
- SetX86( Node cmp, String bop, boolean unsigned ) {
- super(cmp);
- _inputs.setLen(1); // Pop the cmp inputs
- // Replace with the matched cmp
- _inputs.push(cmp);
- _bop = bop;
- _unsigned = unsigned;
- }
- @Override public String op() { return "set"+_bop; }
- @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.FLAGS_MASK; }
- @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
-
- @Override public void encoding( Encoding enc ) {
- int op=switch (_bop) {
- case "==" -> 0x94; // SETE
- case "<" -> _unsigned ? 0x92 : 0x9C; // SETB /SETL
- case "<=" -> _unsigned ? 0x96 : 0x9E; // SETBE/SETLE
- case ">=" -> _unsigned ? 0x93 : 0x9D; // SETAE/SETGE
- case ">" -> _unsigned ? 0x97 : 0x9F; // SETA /SETG
- default -> throw Utils.TODO();
- };
- X86.set(enc,op&15,enc.reg(this));
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this));
- String src = code.reg(in(1));
- if( src!="flags" ) sb.p(" = ").p(src);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java
deleted file mode 100644
index 1e62243e..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.util.SB;
-
-public class ShlIX86 extends MachConcreteNode implements MachNode {
- final int _imm;
- ShlIX86( Node shli, int imm ) {
- super(shli);
- assert x86_64_v2.imm8(imm);
- _inputs.pop(); // Pop constant input off
- _imm = imm;
- }
- public int opcode() { return 0xC1; }
- public int mod() { return 4; }
-
- @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
- @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
- @Override public int twoAddress() { return 1; }
-
- @Override public String op() { return "shli"; }
- @Override public String glabel() { return "<<"; }
- @Override public void encoding(Encoding enc) {
- X86.shift(enc,mod(),enc.reg(this),_imm);
- }
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java
deleted file mode 100644
index 768d97e7..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.util.Utils;
-
-public class ShlX86 extends RegX86 {
- ShlX86( Node add ) { super(add); }
- @Override public String op() { return "shl"; }
- @Override public String glabel() { return "<<"; }
- @Override public RegMask regmap(int i) {
- if (i == 1) return x86_64_v2.WMASK;
- if (i == 2) return x86_64_v2.RCX_MASK;
- throw Utils.TODO();
- }
- @Override int opcode() { return 0xD3; }
- @Override public final void encoding( Encoding enc ) {
- X86.unary(enc,opcode(),4,enc.reg(this));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java
deleted file mode 100644
index 8a0d1b58..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.util.SB;
-
-public class ShrIX86 extends MachConcreteNode implements MachNode {
- final int _imm;
- ShrIX86( Node shri, int imm ) { super(shri); assert x86_64_v2.imm8(imm); _inputs.pop(); _imm = imm; }
- @Override public String op() { return "shri"; }
- @Override public String glabel() { return ">>>"; }
- int opcode() { return 0xC1; }
- int mod() { return 5; }
-
- @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
- @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
- @Override public int twoAddress() { return 1; }
-
- @Override public void encoding(Encoding enc) {
- X86.shift(enc,mod(),enc.reg(this),_imm);
- }
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java
deleted file mode 100644
index 1ba20fee..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.util.Utils;
-
-public class ShrX86 extends RegX86 {
- ShrX86( Node add ) { super(add); }
- @Override public String op() { return "shr"; }
- @Override public String glabel() { return ">>>"; }
- @Override public RegMask regmap(int i) {
- if (i == 1) return x86_64_v2.WMASK;
- if (i == 2) return x86_64_v2.RCX_MASK;
- throw Utils.TODO();
- }
- @Override int opcode() { return 0xD3; }
- @Override public final void encoding( Encoding enc ) {
- X86.unary(enc,opcode(),5,enc.reg(this));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java
deleted file mode 100644
index cb7694f9..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-
-// Flag snapshots can use GPRs or stack slots, but never XMM registers.
-public class SplitFlagsX86 extends SplitX86 {
- private static final RegMask MASK = new RegMask(x86_64_v2.WR_BITS | (1L<<x86_64_v2.FLAGS) | x86_64_v2.SPILLS,-1L);
- SplitFlagsX86(LRG lrg,String kind,byte round) { super(lrg,kind,round); }
- @Override public RegMask regmap(int i) { return MASK; }
- @Override public RegMask outregmap() { return MASK; }
-
- @Override public void encoding(Encoding enc) {
- short dst=enc.reg(this), src=enc.reg(in(1));
- if( dst==src ) return;
- if( dst==x86_64_v2.FLAGS || src==x86_64_v2.FLAGS ) {
- boolean toFlags = dst==x86_64_v2.FLAGS;
- int reg = toFlags ? src : dst;
- if( reg>=x86_64_v2.MAX_REG )
- X86.flagsStack(enc,fun(enc).computeStackOffset(enc._code,reg),toFlags);
- else
- X86.flags(enc,reg,toFlags);
- } else super.encoding(enc);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
deleted file mode 100644
index 46f2cb16..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.FunNode;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.node.SplitNode;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-import com.seaofnodes.simple.type.TypeInteger;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-public class SplitX86 extends SplitNode {
- SplitX86( LRG lrg, String kind, byte round ) { super(lrg,kind,round, new Node[2]); }
- @Override public RegMask regmap(int i) { return x86_64_v2.SPLIT_MASK; }
- @Override public RegMask outregmap() { return x86_64_v2.SPLIT_MASK; }
-
- // Moves among GPRs, XMM registers, and stack slots.
- @Override public void encoding( Encoding enc ) {
- short dst=enc.reg(this), src=enc.reg(in(1));
- assert dst!=x86_64_v2.FLAGS && src!=x86_64_v2.FLAGS;
- boolean dstX=dst>=x86_64_v2.XMM_OFFSET, srcX=src>=x86_64_v2.XMM_OFFSET;
- // Stack spills
- if( dst >= x86_64_v2.MAX_REG ) {
- int off = fun(enc).computeStackOffset(enc._code,dst);
- if( src >= x86_64_v2.MAX_REG ) {
- // Rare stack-stack move. push [RSP+soff]; pop [RSP+doff]
- int soff = fun(enc).computeStackOffset(enc._code,src);
- X86.stackCopy(enc,soff,off);
- return;
- }
- StoreX86.encVal(enc, srcX ? TypeFloat.F64 : TypeInteger.BOT, (short)x86_64_v2.RSP, (short)-1/*index*/, src, off, 0);
- return;
- }
- if( src >= x86_64_v2.MAX_REG ) {
- int off = fun(enc).computeStackOffset(enc._code,src);
- LoadX86.enc(enc, dstX ? TypeFloat.F64 : TypeInteger.BOT, dst, (short)x86_64_v2.RSP, (short)-1, off, 0);
- return;
- }
-
- X86.move(enc,dstX,srcX,dstX ? dst-x86_64_v2.XMM_OFFSET : dst,
- srcX ? src-x86_64_v2.XMM_OFFSET : src);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java
deleted file mode 100644
index 9ffa25fc..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.ConstantNode;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.node.StoreNode;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-import com.seaofnodes.simple.util.SB;
-
-public class StoreX86 extends MemOpX86 {
- StoreX86( StoreNode st, Node base, Node idx, int off, int scale, int imm, Node val ) {
- super(st,st, base, idx, off, scale, imm, val);
- }
- @Override public String op() { return "st"+_sz; }
- @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) {
- Node val = val();
- p.p(".").p(_name).p("=");
- if( val==null ) p.p(_imm);
- else p.n(val);
- return p.p(";");
- }
- // Register mask allowed as a result. 0 for no register.
- @Override public RegMask outregmap() { return null; }
- @Override public void encoding( Encoding enc ) {
- short ptr=enc.reg(ptr()), idx=enc.reg(idx()), src=enc.reg(val());
- if( src==-1 ) X86.storeImm(enc,Integer.numberOfTrailingZeros(_sz-'0'),_imm,ptr,idx,_off,_scale);
- else encVal(enc,_con,ptr,idx,src,_off,_scale);
- }
-
- // Non-immediate encoding
- static void encVal( Encoding enc, Type decl, short ptr, short idx, short src, int off, int scale ) {
- boolean xmm=src>=x86_64_v2.XMM_OFFSET;
- X86.store(enc,decl.log_size(),xmm,
- xmm ? src-x86_64_v2.XMM_OFFSET : src,ptr,idx,off,scale);
- }
-
-
- // General form: "stN [base + idx<<2 + 12],val"
- @Override public void asm(CodeGen code, SB sb) {
- asm_address(code,sb).p(",");
- if( val()==null ) sb.p("#").p(_imm);
- else sb.p(code.reg(val()));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java
deleted file mode 100644
index 29f0033a..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.util.SB;
-
-public class SubFX86 extends MachConcreteNode implements MachNode {
- SubFX86( Node subf) { super(subf); }
- @Override public String op() { return "subf"; }
- @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; }
- @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
- @Override public int twoAddress() { return 1; }
-
- @Override public void encoding( Encoding enc ) {
- X86.sse(enc,0xF2,0x0F5C,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET);
- }
-
- // General form: "subf dst -= src"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this)).p(" -= ").p(code.reg(in(2)));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java
deleted file mode 100644
index 8da34bf5..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java
+++ /dev/null
@@ -1,10 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java
deleted file mode 100644
index e3563713..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFunPtr;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-// Function constants
-public class TFPX86 extends FunPtrNode implements MachNode, RIPRelSize {
- private byte _opLen;
- final String _ext; // External name
- // Pointer to a Simple function
- TFPX86( FunPtrNode fptr ) { this((TypeFunPtr)fptr._type,null); }
- // Spill clone: copy inputs without registering their reverse edges;
- // insertBefore will place and register the clone in its use block.
- private TFPX86( TFPX86 fptr ) { super(fptr); _ext = fptr._ext; }
- // Pointer to an Extern "C" function
- TFPX86( TypeFunPtr fptr, String ext ) { super(fptr,CodeGen.CODE._start,null); _type = fptr; _ext = ext; }
- @Override public String op() { return "ldx"; }
- @Override public String label() { return op(); }
- @Override public boolean isClone() { return true; }
- @Override public TFPX86 copy() { return new TFPX86(this); }
- @Override public RegMask regmap(int i) { return null; }
- @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
- @Override public void encoding( Encoding enc ) {
- if( _ext==null ) enc.relo(this);
- else enc.external(this,_ext);
- _opLen=X86.rip(enc,0,0x8D,true,enc.reg(this));
- }
-
- @Override public byte encSize(int delta) { return _opLen; }
-
- // Delta is from opcode start, X86 expects it from opcode end
- @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- enc.patch4(opStart + opLen - 4, delta - opLen);
- }
-
- // Human-readable form appended to the SB. Things like the encoding,
- // indentation, leading address or block labels not printed here.
- // Just something like "ld4\tR17=[R18+12] // Load array base".
- // General form: "op\tdst=src+src"
- @Override public void asm(CodeGen code, SB sb) {
- sb.p(code.reg(this));
- if( _ext == null ) _type.print(sb.p(" #"));
- else sb.p(_ext);
- }
- @Override public boolean eq(Node n) { return this==n; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java
deleted file mode 100644
index 682597f4..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.TypeMemPtr;
-import com.seaofnodes.simple.util.SB;
-
-public class TMPX86 extends ConstantNode implements MachNode, RIPRelSize{
- private byte _opLen;
- final String _ext;
- TMPX86( ConstantNode con, String ext ) { super(con); _ext = ext; }
- @Override public String op() { return "ldp"; }
- @Override public RegMask regmap(int i) { return null; }
- @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
- @Override public boolean isClone() { return true; }
- @Override public Node copy() { return new TMPX86(this,_ext); }
-
- @Override public void encoding( Encoding enc ) {
- short dst = enc.reg(this);
- // 0 or 1 for REX depending on the dst.
- _opLen = X86.rip(enc,0,0x8D,true,dst);
- if( _ext!=null ) enc.externalData(this,_ext);
- else enc.largeConstant(this,((TypeMemPtr)_con)._obj, _opLen-4, 2/*ELF encoding PC32*/);
- }
-
- // Delta is from opcode start
- @Override public byte encSize(int delta) { return _opLen; }
-
- // Delta is from opcode start
- @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- enc.patch4(opStart + opLen - 4, delta - opLen);
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- _con.print(sb.p(code.reg(this)).p(" = #"));
- }
- @Override public boolean eq(Node n) { return this==n; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java
deleted file mode 100644
index 591a51c3..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.isa.X86;
-
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-
-// unconditional jump
-public class UJmpX86 extends CFGNode implements MachNode, RIPRelSize {
- UJmpX86() { }
- @Override public String op() { return "jmp"; }
- @Override public String label() { return op(); }
- @Override protected String format() { return "jmp "; }
- @Override public RegMask regmap(int i) {return null; }
- @Override public RegMask outregmap() { return null; }
- @Override public Type compute() { throw Utils.TODO(); }
- @Override public Node idealize() { throw Utils.TODO(); }
- @Override public void encoding( Encoding enc ) {
- enc.jump(this,uctrl());
- X86.branch(enc,-1);
- }
-
- // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding
- @Override public byte encSize(int delta) {
- return X86.branchSize(delta,false);
- }
-
- // Delta is from opcode start
- @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- X86.patchBranch(enc.bits(),opStart,opLen,delta,-1);
- }
-
- @Override public void asm(CodeGen code, SB sb) {
- CFGNode target = uctrl();
- //assert target.nOuts() > 1; // Should optimize jmp to empty targets
- sb.p(label(target));
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java
deleted file mode 100644
index 2d989a50..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java
+++ /dev/null
@@ -1,11 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java
deleted file mode 100644
index 86703903..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java
+++ /dev/null
@@ -1,11 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
deleted file mode 100644
index 3dc93bf6..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
+++ /dev/null
@@ -1,596 +0,0 @@
-package com.seaofnodes.simple.node.cpus.x86_64_v2;
-
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.Utils;
-
-public class x86_64_v2 extends Machine {
- public x86_64_v2( CodeGen code ) {}
- // X86-64 V2. Includes e.g. SSE4.2 and POPCNT.
- @Override public String name() { return "x86_64_v2"; }
- @Override public int defaultOpSize() { return 5; }
-
- public static final int RAX = 0, RCX = 1, RDX = 2, RBX = 3, RSP = 4, RBP = 5, RSI = 6, RDI = 7;
- public static final int R08 = 8, R09 = 9, R10 = 10, R11 = 11, R12 = 12, R13 = 13, R14 = 14, R15 = 15;
-
- public static final int XMM0 = 16, XMM1 = 17, XMM2 = 18, XMM3 = 19, XMM4 = 20, XMM5 = 21, XMM6 = 22, XMM7 = 23;
- public static final int XMM8 = 24, XMM9 = 25, XMM10 = 26, XMM11 = 27, XMM12 = 28, XMM13 = 29, XMM14 = 30, XMM15 = 31;
- public static final int FLAGS = 32;
- public static final int MAX_REG = 33;
- public static final int RPC = 33;
-
- // Human-readable name for a register number, e.g. "RAX".
- public static final String[] REGS = new String[]{
- "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi",
- "r8" , "r9" , "r10", "r11", "r12", "r13", "r14", "r15",
- "xmm0", "xmm1", "xmm2" , "xmm3" , "xmm4" , "xmm5" , "xmm6" , "xmm7" ,
- "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15",
- "flags",
- };
- // Hard crash for bad register number, fix yer bugs!
- @Override public String[] regs() { return REGS; }
-
- public static int XMM_OFFSET = 16;
- // General purpose register mask: pointers and ints, not floats
- static final long RD_BITS = 0b1111111111111111L; // All the GPRs
- static RegMask RMASK = new RegMask(RD_BITS);
- // No RSP in the *write* general set.
- static final long WR_BITS = 0b1111111111101111L; // All the GPRs minus RSP
- static RegMask WMASK = new RegMask(WR_BITS);
- // Xmm register mask
- static final long FP_BITS = 0b1111111111111111L << XMM0; // All the XMMs
- static RegMask XMASK = new RegMask(FP_BITS);
- static RegMask FLAGS_MASK = new RegMask(FLAGS);
- static RegMask RPC_MASK = new RegMask(RPC);
- // Divide mask: exclude RDX:RAX
- static RegMask DIV_MASK = new RegMask(RD_BITS & ~(1L<<RAX) & ~(1L<<RDX));
-
- static final long SPILLS = -(1L << MAX_REG);
- static final RegMask SPLIT_MASK = new RegMask(WR_BITS | FP_BITS | SPILLS, -1L );
-
- // Load/store mask; both GPR and FPR
- static RegMask MEM_MASK = new RegMask(WR_BITS | FP_BITS);
-
- public static RegMask RAX_MASK = new RegMask(RAX);
- public static RegMask RCX_MASK = new RegMask(RCX);
- public static RegMask RDX_MASK = new RegMask(RDX);
- public static RegMask RDI_MASK = new RegMask(RDI);
- public static RegMask R08_MASK = new RegMask(R08);
- public static RegMask R09_MASK = new RegMask(R09);
- public static RegMask RSI_MASK = new RegMask(RSI);
-
- public static RegMask XMM0_MASK = new RegMask(XMM0);
-
- // Encoding
- public static int REX = 0x40;
- public static int REX_W = 0x48;
- public static int REX_WR = 0x4C;
- public static int REX_WRB= 0x4D;
- public static int REX_WB = 0x49;
-
- // opcode included here
- // 0F 84 cd JE rel32
- // 0F 85 cd JNE rel32
- // 0F 8F cd JG rel32
- // 0F 8C cd JL rel32
- // 0F 8E cd JLE rel32
- // 0F 8D cd JGE rel32
- static public int jumpop(String op) {
- return switch (op) {
- case "==" -> 0x84;
- case "!=" -> 0x85;
- case ">" -> 0x8F;
- case "<" -> 0x8C;
- case "<=" -> 0x8E;
- case ">=" -> 0x8D;
- default -> throw new IllegalArgumentException("Too many arguments");
- };
- }
-
- // Limit of float args passed in registers
- static RegMask[] XMMS8 = new RegMask[]{
- new RegMask(XMM0), new RegMask(XMM1), new RegMask(XMM2), new RegMask(XMM3),
- new RegMask(XMM4), new RegMask(XMM5), new RegMask(XMM6), new RegMask(XMM7),
- };
-
- // Map from function signature and argument index to register.
- // Used to set input registers to CallNodes, and ParmNode outputs.
- @Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); }
- @Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot, String conv ) { return callInMask(tfp,idx,maxArgSlot,conv); }
- static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot ) {
- return callInMask(tfp,idx,maxArgSlot,CodeGen.CODE._callingConv);
- }
- static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot, String conv ) {
- if( idx==0 ) return RPC_MASK;
- if( idx==1 ) return null;
- if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence
- return switch( conv ) {
- case "SystemV" -> callSys5 (tfp,idx,maxArgSlot);
- case "win64" -> callWin64(tfp,idx,maxArgSlot);
- default -> throw Utils.TODO();
- };
- }
-
- // Maximum stack args used by this signature
- @Override public short maxArgSlot( TypeFunPtr tfp ) {
- return maxArgSlot(tfp,CodeGen.CODE._callingConv);
- }
- @Override public short maxArgSlot( TypeFunPtr tfp, String conv ) {
- return switch( conv ) {
- case "SystemV" -> maxArgSlotSys5 (tfp);
- case "win64" -> maxArgSlotWin64(tfp);
- default -> throw Utils.TODO();
- };
- }
-
- // Win64 - max *4* args in registers of all kinds; after that on stack.
- // Stack args land in increasing memory order with empty/mirror slots for
- // every register.
- //
- // foo( int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, ... )
- //
- // -- Prior Frame --
- // i6
- // f5
- // i4
- // empty mirror, XMM3= f3
- // empty mirror, R08 = i2
- // empty mirror, XMM1= f1
- // empty mirror, RCX = i0
- // -- Caller Frame; 16b align --
- // RPC
- // PAD/ALIGN
- // -- Callee Frame; 16b align --
- static RegMask[] WIN64_CALL = new RegMask[] {
- RCX_MASK,
- RDX_MASK,
- R08_MASK,
- R09_MASK,
- };
-
- static RegMask callWin64(TypeFunPtr tfp, int idx, int maxArgSlot ) {
- // idx 2,3,4,5 passed in registers, with stack slot mirrors.
- // idx >= 6 passed on stack, starting at slot#1 (#0 reserved for RPC).
- int sigidx = idx-2;
- if( hiddenSelf(tfp) ) {
- if( sigidx==0 ) return null;
- sigidx--;
- }
- if( sigidx >= 4 )
- return new RegMask(MAX_REG+maxArgSlot+sigidx);
- return tfp.arg(idx-2) instanceof TypeFloat
- ? XMMS8 [sigidx]
- : WIN64_CALL[sigidx];
- }
- static short maxArgSlotWin64(TypeFunPtr tfp) {
- return (short)Math.max(4,tfp.nargs() - (hiddenSelf(tfp) ? 1 : 0));
- }
- static short maxIncomingArgSlotWin64(TypeFunPtr tfp) {
- return (short)Math.max(0,tfp.nargs() - (hiddenSelf(tfp) ? 1 : 0) - 4);
- }
-
- // Sys5: max 6 GPRs and 8 FPRS filled first. Extra args land in increasing
- // memory order as needed - no mirror space.
- //
- // foo( int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, ... )
- // RDI =i0, RSI =i2, RDX =i4, RCX =i6, R08 =i8, R09 =i10
- // XMM0=f1, XMM1=f3, XMM2=f5, XMM3=f7, XMM4=f9, XMM5=f11, XMM6=f13, XMM7=f15
- //
- // -- Prior Frame --
- // f18
- // i18
- // f17
- // i16
- // i14
- // i12
- // -- Caller Frame; 16b align --
- // RPC
- // PAD/ALIGN
- // -- Callee Frame; 16b align --
- // SystemV(param passing)
- static RegMask[] SYS5_CALL = new RegMask[] {
- RDI_MASK,
- RSI_MASK,
- RDX_MASK,
- RCX_MASK,
- R08_MASK,
- R09_MASK,
- };
-
- static RegMask callSys5(TypeFunPtr tfp, int idx, int maxArgSlot ) {
- // First 6 integers passed in registers: rdi,rsi,rdx,rcx,r08,r09
- // First 8 floats passed in registers: xmm0-xmm7
- int sigidx = idx-2;
- if( hiddenSelf(tfp) ) {
- if( sigidx==0 ) return null;
- sigidx--;
- }
- int icnt=0, fcnt=0; // Count of ints, floats
- for( int i=hiddenSelf(tfp) ? 1 : 0; i<idx-2; i++ ) {
- if( tfp.arg(i) instanceof TypeFloat ) fcnt++;
- else icnt++;
- }
- int nstk = Math.max(icnt-6,0)+Math.max(fcnt-8,0);
- return tfp.arg(idx-2) instanceof TypeFloat
- ? fcnt<8 ? XMMS8 [fcnt] : new RegMask(MAX_REG+maxArgSlot+nstk)
- : icnt<6 ? SYS5_CALL[icnt] : new RegMask(MAX_REG+maxArgSlot+nstk);
- }
- static short maxArgSlotSys5(TypeFunPtr tfp) {
- int icnt=0, fcnt=0; // Count of ints, floats
- for( int i=hiddenSelf(tfp) ? 1 : 0; i<tfp.nargs(); i++ ) {
- if( tfp.arg(i) instanceof TypeFloat ) fcnt++;
- else icnt++;
- }
- int nstk = Math.max(icnt-6,0)+Math.max(fcnt-8,0);
- return (short)nstk;
- }
- private static boolean hiddenSelf(TypeFunPtr tfp) {
- return tfp.nargs() > 0 && tfp.arg(0) == TypePtr.PTR;
- }
-
- static final long SYSTEM5_CALLER_SAVE =
- (1L << RAX) | (1L << RCX) | (1L << RDX) |
- (1L << RDI) | (1L << RSI) |
- (1L << R08) | (1L << R09) | (1L << R10) | (1L << R11) |
- (1L << FLAGS) | // Flags are killed
- // All FP regs are killed
- FP_BITS;
-
- static final long WIN64_CALLER_SAVE =
- (1L<< RAX) | (1L<< RCX) | (1L<< RDX) |
- (1L<< R08) | (1L<< R09) | (1L<< R10) | (1L<< R11) |
- (1L<<FLAGS)| // Flags are killed
- // Only XMM0-XMM5 are killed; XMM6-XMM15 are preserved
- (1L<<XMM0) | (1L<<XMM1) | (1L<<XMM2) | (1L<<XMM3) |
- (1L<<XMM4) | (1L<<XMM5) ;
-
- @Override public long callerSave() {
- return callerSave(CodeGen.CODE._callingConv);
- }
- @Override public long callerSave(String conv) {
- return switch (conv) {
- case "SystemV" -> SYSTEM5_CALLER_SAVE;
- case "win64" -> WIN64_CALLER_SAVE;
- default -> throw new IllegalArgumentException("Unknown calling convention: " + conv);
- };
- }
- @Override public String cCallingConv(String callingConv) {
- return callingConv;
- }
- @Override public long neverSave() { return 1L<<RSP; }
- @Override public RegMask retMask( TypeFunPtr tfp ) {
- return tfp.ret() instanceof TypeFloat ? XMM0_MASK : RAX_MASK;
- }
- @Override public int rpc() { return RPC; }
-
-
- // Create a split op; any register to any register, including stack slots
- @Override public SplitNode split(LRG lrg, String kind, byte round ) {
- return lrg._machDef!=null && lrg._machDef.outregmap().test(FLAGS)
- ? new SplitFlagsX86(lrg,kind,round)
- : new SplitX86(lrg,kind,round);
- }
-
- // Return a MachNode unconditional branch
- @Override public CFGNode jump() {
- return new UJmpX86();
- }
-
- // Instruction selection
- @Override
- public Node instSelect(Node n) {
- return switch (n) {
- case AddNode add -> add(add);
- case AndNode and -> and(and);
- case BoolNode bool -> cmp(bool);
- case CLInitNode init -> new CLInitNode(init);
- case CProjNode c -> new CProjNode(c);
- case CallEndNode cend -> new CallEndMach(cend);
- case CallNode call -> call(call);
- case PtrToIntNode ptr -> new PtrToIntMach(ptr);
- case GuardNode guard -> new GuardMach(guard);
- case ConstantNode con -> con(con);
- case DivNode div -> div.mode()==2 ? new DivFX86(div) : new DivX86(div);
- case EscapeNode esc -> new EscapeNode(esc);
- case FunNode fun -> new FunX86(fun);
- case FunPtrNode fptr -> fptr(fptr);
- case IfNode iff -> jmp(iff);
- case LoadNode ld -> ld(ld);
- case MemMergeNode mem -> new MemMergeNode(mem);
- case MinusNode neg -> {
- if( neg.mode()==2 ) throw Utils.TODO();
- yield new NegX86(neg);
- }
- case MulNode mul -> mul.mode()==2 ? new MulFX86(mul) : mul(mul);
- case NewNode nnn -> new NewX86(nnn);
- case NotNode not -> new NotX86(not);
- case OrNode or -> or(or);
- case ParmNode parm -> new ParmX86(parm);
- case BulkMemPhiNode phi-> new BulkMemPhiNode(phi);
- case MemPhiNode phi -> new MemPhiNode(phi);
- case PhiNode phi -> new PhiNode(phi);
- case ProjNode prj -> prj(prj);
- case ReadOnlyNode read-> new ReadOnlyMach(read);
- case ReturnNode ret -> new RetX86(ret, ret.fun());
- case SarNode sar -> sar(sar);
- case ShlNode shl -> shl(shl);
- case ShrNode shr -> shr(shr);
- case StartCUNode start -> new StartCUNode(start);
- case StopCUNode stop -> new StopCUNode(stop);
- case StartNode start -> new StartNode(start);
- case StopNode stop -> new StopNode(stop);
- case StoreNode st -> st(st);
- case SubNode sub -> sub.mode()==2 ? new SubFX86(sub) : sub(sub);
- case ToFloatNode tfn -> i2f8(tfn);
- case ToIntegerNode cvt -> new F8ToIX86(cvt);
- case XCtrlNode x -> ConstantNode.raw(Type.XCONTROL);
- case XorNode xor -> xor(xor);
-
- case LoopNode loop -> new LoopNode(loop);
- case RegionNode region -> new RegionNode(region);
- default -> throw Utils.TODO();
- };
- }
-
- public static boolean imm8( long imm ) { return -128 <= imm && imm <= 127; }
-
- public static boolean imm32( long imm ) { return (int)imm==imm; }
-
- // Attempt a full LEA-style break down.
- private Node add(AddNode add) {
- if( add.mode()==2 )
- return new AddFX86(add);
- Node lhs = add.in(1);
- Node rhs = add.in(2);
- if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && ld.declaredType().log_size() >= 3)
- return new AddMemX86(add, address(ld), ld.ptr(), idx, off, scale, 0, rhs);
- // Attempt a full LEA-style break down.
- // Returns one of AddX86, AddIX86, LeaX86, or LHS
- if( rhs instanceof ConstantNode off2 && off2._con instanceof TypeInteger toff ) {
- long imm = toff.value();
- assert imm!=0; // Folded in peeps
- if( (int)imm != imm ) // Full 64bit immediate
- return new AddX86(add);
- // Now imm <= 32bits
- if( lhs instanceof AddNode ladd )
- // ((base + (idx << scale)) + off)
- return _lea(add, ladd.in(1), ladd.in(2), (int)imm);
- if( lhs instanceof ShlNode shift )
- // (idx << scale) + off; no base
- return _lea(add, null, shift, (int)imm);
-
- // lhs + rhs1
- return new AddIX86(add, (int)imm);
- }
- return _lea(add, lhs, rhs, 0);
- }
-
-
- private Node _lea(Node add, Node base, Node idx, int off) {
- int scale = 0;
- if( base instanceof ShlNode && !(idx instanceof ShlNode) )
- throw Utils.TODO(); // Bug in canonicalization, should on RHS
- if( idx instanceof ShlNode shift && shift.in(2) instanceof ConstantNode shfcon &&
- shfcon._con instanceof TypeInteger tscale && 0 <= tscale.value() && tscale.value() <= 3 ) {
- idx = shift.in(1);
- scale = ((int) tscale.value());
- }
- // (base + idx) + off
- return off == 0 && scale == 0
- ? new AddX86(add)
- : new LeaX86(add, base, idx, scale, off);
- }
-
-
- private Node and(AndNode and) {
- if( and.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) )
- return new AndIX86(and, (int)ti.value());
- return new AndX86(and);
- }
-
- private Node call(CallNode call) {
- return call.fptr() instanceof FunPtrNode con
- ? new CallX86(call, (TypeFunPtr)con._type)
- : call.fptr() instanceof ConstantNode con && con._type instanceof TypeFunPtr
- ? new CallX86(call, (TypeFunPtr)con._type)
- : new CallRX86(call);
- }
-
- // Because X86 flags, a normal ideal Bool is 2 X86 ops: a "cmp" and at "setz".
- // Ideal If reading from a setz will skip it and use the "cmp" instead.
- private static boolean swap, unsigned;
- private Node cmp( BoolNode bool ) {
- swap = unsigned = false;
- Node cmp = _cmp(bool);
- return new SetX86(cmp, swap ? IfNode.swap(bool.op()) : bool.op(), unsigned);
- }
-
- private Node _cmp(BoolNode bool) {
- // Float variant
- if( bool.isFloat() ) {
- unsigned = true;
- return new CmpFX86(bool);
- }
- Node lhs = bool.in(1);
- Node rhs = bool.in(2);
-
- // Since cmp does not record a BOP, SetX/Jmp need to know if the CMP is swapped.
-
- // Vs memory
- if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && rhs._type.isa(ld.declaredType()) )
- return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(rhs), val, false);
-
- // Operands swap in the encoding directly, no need for Set/Jmp to swap `bop`
- if( rhs instanceof LoadNode ld && ld.nOuts() == 1 && lhs._type.isa(ld.declaredType()) &&
- (!(lhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value())) ||
- bool.op()=="==" || bool.op()=="!=") )
- return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(lhs), val, true );
-
- // Vs immediate
- if( rhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) )
- return new CmpIX86(bool, (int)ti.value(), false);
-
- // Operand swap compare; Set and Jmp need to swap `bop`
- if( lhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) )
- return new CmpIX86(bool, (int)ti.value(), swap=true);
-
- // x vs y
- return new CmpX86(bool);
- }
-
- private Node con( ConstantNode con ) {
- if( !con._con.isConstant() )
- return ConstantNode.raw(con); // Default unknown caller inputs
- String ext = con instanceof ExternNode ext0 ? ext0._extern : null;
- return switch (con._con) {
- case TypeInteger ti -> new IntX86(con,ext);
- case TypeFloat tf -> new FltX86(con,ext);
- case TypeMemPtr tmp -> new TMPX86(con,ext);
- case TypeFunPtr tfp -> new TFPX86(tfp,ext);
- case TypeNil tn -> throw Utils.TODO();
- // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
- case Type t -> t == Type.NIL ? new IntX86(con,null) : ConstantNode.raw(con);
- };
- }
-
- private Node fptr( FunPtrNode con ) {
- return new TFPX86(con);
- }
-
- private Node i2f8(ToFloatNode tfn) {
- assert tfn.in(1)._type instanceof TypeInteger ti;
- return new I2f8X86(tfn);
- }
-
- private Node jmp(IfNode iff) {
- // If/Bool combos will match to a Cmp/Set which sets flags.
- // Most general arith ops will also set flags, which the Jmp needs directly.
- // Loads do not set the flags, and will need an explicit TEST
- String op = "!=";
- if( iff.in(1) instanceof BoolNode bool ) op = swap ? IfNode.swap(bool.op()) : bool.op();
- else if( iff.in(1)==null ) op = "=="; // Never-node cutout
- else iff.setDef(1, new BoolNode.NE(iff.in(1),
- ConstantNode.raw(TypeInteger.ZERO),
- (byte)(iff.in(1)._type instanceof TypeFloat ? 2 : 1)));
- return new JmpX86(iff, op);
- }
-
- private Node ld(LoadNode ld) {
- return new LoadX86(address(ld), ld.ptr(), idx, off, scale);
- }
-
- private Node mul(MulNode mul) {
- if( mul.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) )
- return new MulIX86(mul, (int)ti.value());
- return new MulX86(mul);
- }
-
- private Node or(OrNode or) {
- if( or.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) )
- return new OrIX86(or, (int)ti.value());
- return new OrX86(or);
- }
-
- private Node prj( ProjNode prj ) {
- if( prj.in(0) instanceof CLInitNode ) return new ProjNode(prj);
- TypeInteger ret = cReturn(prj);
- if( ret!=null ) return new ExtendX86(prj,ret);
- return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjX86(prj);
- }
-
- private Node sar(SarNode sar) {
- if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value()))
- return new SarIX86(sar, (int)(ti.value() & 0xff) );
- return new SarX86(sar);
- }
-
- private Node shl(ShlNode shl) {
- if( shl.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value()))
- return new ShlIX86(shl, (int)(ti.value() & 0xff) );
- return new ShlX86(shl);
- }
-
- private Node shr( ShrNode shr ) {
- if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value()) )
- return new ShrIX86(shr, (int)(ti.value() & 0xff) );
- return new ShrX86(shr);
- }
-
- private Node st( StoreNode st ) {
- // Look for "*ptr op= val"
- Node op = st.val();
- if( op instanceof AddNode ) {
- if( op.in(1) instanceof LoadNode ld && stld_match(st,ld) )
- return new MemAddX86(address(st), st.ptr(), idx, off, scale, imm(op.in(2)), val);
- if( op.in(2) instanceof LoadNode ld && stld_match(st,ld) )
- return new MemAddX86(address(st), st.ptr(), idx, off, scale, imm(op.in(1)), val);
- }
- return new StoreX86(address(st), st.ptr(), idx, off, scale, imm(st.val()), val);
- }
- private static boolean stld_match(StoreNode st, LoadNode ld ) {
- return
- ld.in(0) == st.in(0) &&
- ld.mem() == st.mem() &&
- ld.ptr() == st.ptr() &&
- ld.off() == st.off();
- }
-
-
- private Node sub (SubNode sub ){
- return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti
- ? new AddIX86(sub, (int)-ti.value())
- : new SubX86(sub);
- }
-
- private Node xor (XorNode xor){
- if( xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) )
- return new XorIX86(xor, (int)ti.value());
- return new XorX86(xor);
- }
-
- // Gather X86 addressing mode bits prior to constructing. This is a
- // builder pattern, but saving the bits in a *local* *global* here to keep
- // mess contained.
- private static int off, scale, imm;
- private static Node idx, val;
- private <N extends MemOpNode > N address(N mop) {
- off = scale = imm = 0; // Reset
- idx = val = null;
- Node base = mop.ptr();
- // Skip/throw-away a ReadOnly, only used to typecheck
- if(base instanceof ReadOnlyNode read) base = read.in(1);
- assert !(base instanceof AddNode) && base._type instanceof TypeMemPtr; // Base ptr always, not some derived
- if(mop.off() instanceof AddNode add && add.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) {
- off = (int) ti.value();
- assert off == ti.value(); // In 32-bit range
- idx = add.in(1);
- if(idx instanceof ShlNode shift && shift.in(2) instanceof ConstantNode shfcon &&
- shfcon._con instanceof TypeInteger tscale && 0 <= tscale.value() && tscale.value() <= 3) {
- idx = shift.in(1);
- scale = (int) tscale.value();
- }
- } else {
- if(mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti) {
- off = (int) ti.value();
- assert off == ti.value(); // In 32-bit range
- } else {
- idx = mop.off();
- }
- }
- return mop;
- }
-
- private int imm( Node xval ) {
- assert val == null && imm == 0;
- if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti && ti.isConstant() && imm32(ti.value()) ) {
- val = null;
- imm = (int) ti.value();
- assert imm == ti.value(); // In 32-bit range
- } else {
- val = xval;
- }
- return imm;
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java b/chapter-modules/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
deleted file mode 100644
index 58506e9c..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
+++ /dev/null
@@ -1,101 +0,0 @@
-package com.seaofnodes.simple.print;
-
-import com.seaofnodes.print.AssemblyAdapter;
-import com.seaofnodes.print.ASMPrinter.Data;
-import com.seaofnodes.print.ASMPrinter.Pool;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.BAOS;
-import java.util.ArrayList;
-import com.seaofnodes.simple.codegen.Encoding;
-import java.util.HashMap;
-
-/** Machine-specific facts; traversal, columns and byte display live in print/. */
-public final class ASMPrinter extends AssemblyAdapter<Node> {
- private final CodeGen _code;
- private final Encoding _enc;
- private ASMPrinter(CodeGen code) {
- super(new IRPrinter());
- _code=code;
- _enc=code._encoding;
- }
- public static SB print(SB sb, CodeGen code) {
- return sb.p(new com.seaofnodes.print.ASMPrinter<>(new ASMPrinter(code)).print());
- }
- @Override public ArrayList<Node> blocks() {
- var blocks=new ArrayList<Node>();
- var cfg=_enc==null ? _code._cfg : _enc._cfg;
- if( cfg!=null ) for( Node n : cfg ) blocks.add(n);
- return blocks;
- }
- @Override public String op(Node n) { return n instanceof MachNode m ? m.op() : n.label(); }
- @Override public String register(Node n) { return _code.reg(n); }
- @Override public String comment(Node n) { return n.comment(); }
- @Override public String phiName(Node n) { return ((PhiNode)n)._label; }
- @Override public String operands(Node n) {
- SB sb=new SB();
- if( n instanceof MachNode m ) m.asm(_code,sb);
- else if( !(n._type instanceof TypeMem) ) {
- sb.p(n._nid).p(": ");
- for( Node def : n._inputs ) sb.p(def==null ? "___" : Integer.toString(def._nid)).p(' ');
- }
- return sb.toString();
- }
- @Override public boolean labelBlock(Node n) {
- return !(n instanceof FunNode) && !(n instanceof IfNode) && !(n instanceof CallNode) &&
- !(n instanceof CallEndNode) && !(n instanceof CProjNode && ir.input0(n) instanceof CallEndNode);
- }
- @Override public boolean hidden(Node n) {
- if( n instanceof PhiNode || n instanceof ProjNode )
- if( n._type instanceof TypeMem || n._type instanceof TypeRPC ) return true;
- if( postAlloc() && n instanceof CalleeSaveNode ) return true;
- if( n instanceof MemMergeNode ) return true;
- if( n instanceof EscapeNode ) return true;
- return n.getClass()==ConstantNode.class;
- }
- @Override public boolean postAlloc() { return _code._phase.ordinal()>CodeGen.Phase.RegAlloc.ordinal() ||
- (_code._phase==CodeGen.Phase.RegAlloc && _code._regAlloc!=null && _code._regAlloc.done()); }
- @Override public boolean encoded() { return _enc!=null && _code._phase.ordinal()>=CodeGen.Phase.Encoding.ordinal(); }
- @Override public int defaultSize() { return _code._mach==null ? 2 : _code._mach.defaultOpSize(); }
- @Override public boolean littleEndian() { return _code._asmLittle; }
- @Override public boolean prologue(Node n) { return ((FunNode)n)._frameAdjust!=0; }
- @Override public byte[] bytes() { return encoded() ? _enc._bits.buf() : null; }
- @Override public int offset(Node n) { return encoded() ? _enc.opStart(n) : -1; }
- @Override public int size(Node n) { return encoded() ? _enc.opLen(n)&255 : 0; }
- private static ArrayList<Data> entries(Iterable<Encoding.Relo> relos, boolean ro) {
- var data=new ArrayList<Data>();
- for( Encoding.Relo r : relos ) {
- if( r.readOnly()!=ro ) continue;
- int align=1<<r._align;
- int size=r._t instanceof TypeStruct ? (r._structSize+align-1)&-align : align;
- data.add(new Data(r._t,r._t.str(),r._align,size));
- }
- return data;
- }
- @Override public ArrayList<Pool> pools(int codeEnd) {
- var pools=new ArrayList<Pool>();
- if( !encoded() ) return pools;
- int base=(codeEnd+15)&-16;
- if( _enc._cpool.size()>0 )
- pools.add(new Pool("Constant Pool",_enc._cpool.buf(),0,base,objects(true)));
- base=(base+_enc._cpool.size()+15)&-16;
- if( _enc._sdata.size()>0 )
- pools.add(new Pool("Static Data",_enc._sdata.buf(),0,base,objects(false)));
- return pools;
- }
- private ArrayList<Data> objects(boolean ro) {
- var data=new ArrayList<Data>();
- for( var obj : _enc._data.entries() )
- if( !obj.external && obj.readOnly==ro )
- data.add(new Data(obj,obj.symbol+": "+obj.type.str(),obj.alignment,obj.size));
- return data;
- }
- // Retain this diagnostic entry point for checking that pool printing never lays out types.
- static int printConstantPool(int address, SB sb, BAOS bits, HashMap<Node,Encoding.Relo> relos,
- boolean ro, String name) {
- sb.p(com.seaofnodes.print.ASMPrinter.pool(new Pool(name,bits.buf(),0,address,entries(relos.values(),ro))));
- return address+bits.size();
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/print/IRPrinter.java b/chapter-modules/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
deleted file mode 100644
index 531a549a..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.seaofnodes.simple.print;
-
-import com.seaofnodes.print.IRAdapter;
-import com.seaofnodes.simple.node.*;
-import java.util.ArrayList;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.codegen.CodeGen;
-
-/** This chapter's read-only IR facts and the familiar debugger entry points. */
-public final class IRPrinter extends IRAdapter<Node> {
- private static final com.seaofnodes.print.IRPrinter<Node> PRINT =
- new com.seaofnodes.print.IRPrinter<>(new IRPrinter());
- @Override public boolean global(Node n) { return n.isConst(); }
- @Override public String type(Node n) { return n._type==null ? "" : n._type.str(); }
- @Override public String inputMark(Node n, Node def) { return n instanceof MemMergeNode && def instanceof MemMergeNode ? "^" : " "; }
- @Override public Kind kind(Node n) {
- if( n instanceof StartCUNode ) return Kind.UNIT;
- if( n instanceof StopCUNode ) return Kind.UNIT;
- if( n instanceof StartNode ) return Kind.START;
- if( n instanceof StopNode ) return Kind.STOP;
- if( n instanceof FunNode ) return Kind.FUN;
- if( n instanceof LoopNode ) return Kind.LOOP;
- if( n instanceof RegionNode ) return Kind.REGION;
- if( n instanceof ParmNode ) return Kind.PARM;
- if( n instanceof PhiNode ) return Kind.PHI;
- if( n instanceof CallEndNode ) return Kind.CALL_END;
- if( n instanceof CallNode ) return Kind.CALL;
- if( n instanceof ReturnNode ) return Kind.RETURN;
- if( n instanceof ConstantNode ) return Kind.CONSTANT;
- if( n instanceof CProjNode ) return Kind.CPROJ;
- if( n instanceof ProjNode ) return n instanceof CFGNode ? Kind.CPROJ : Kind.PROJ;
- if( n instanceof CFGNode ) return Kind.CTRL;
- if( n instanceof MultiNode ) return Kind.MULTI;
- return Kind.DATA;
- }
- @Override public int index(Node n) {
- if( n instanceof ProjNode p ) return p._idx;
- if( n instanceof CProjNode p ) return p._idx;
- return n._nid;
- }
- @Override public String functionName(Node n) { return ((FunNode)n)._name==null ? "" : ((FunNode)n)._name; }
- @Override public String signature(Node n) {
- FunNode fun=(FunNode)n;
- return (fun._name==null ? "" : fun._name)+" "+fun.sig().str();
- }
-
- public static String prettyPrint(Node n, int depth) { return PRINT.prettyPrint(n,depth); }
- public static SB printLine(Node n, SB sb) { return sb.p(PRINT.line(n)); }
-
- public static String prettyPrint(CodeGen code) {
- var units=new ArrayList<com.seaofnodes.print.IRPrinter.Unit<Node>>();
- for( var cu : code._compunits.values() ) {
- var funs=new ArrayList<Node>();
- for( FunNode fun : code._linker )
- if( fun!=null && fun._compunit==cu ) funs.add(fun);
- units.add(new com.seaofnodes.print.IRPrinter.Unit<>(cu._cname,cu._start,cu._stop,funs));
- }
- return PRINT.program(code._start,code._stop,units);
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter-modules/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java
deleted file mode 100644
index 9ca0ec51..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package com.seaofnodes.simple.print;
-
-import com.seaofnodes.graph.GraphAdapter;
-import com.seaofnodes.graph.GraphSnapshot.*;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.type.TypeMem;
-
-/** Chapter 25's view of the IR, including partially constructed parse-time graphs. */
-public class SimpleGraphAdapter extends GraphAdapter<Node> {
-
- @Override protected boolean show(Node n) {
- if( n != CodeGen.CODE.ZERO && n != CodeGen.CODE.XCTRL ) return true;
- for( int i=0; i<n.nOuts(); i++ )
- if( n.out(i) != null ) return true;
- return false; // The parser's keep is not a graph use.
- }
-
- @Override protected String edgeName(Node n, int i) {
- return n instanceof ScopeNode scope && i < scope._vars.size()
- ? scope.var(i)._name : null;
- }
- @Override protected int edgeJump(Node n, int i) {
- return n instanceof CallEndNode && i>0 && n.in(i) instanceof ReturnNode ret
- ? ref(ret.fun()) : 0;
- }
- @Override protected int projectionIndex(Node n) { return n instanceof Proj p ? p.idx() : -1; }
-
- @Override protected boolean folding(Node n) {
- if( n instanceof FunNode fun ) return fun.folding();
- if( n instanceof ReturnNode ret ) return ret.fun().folding();
- if( n instanceof CallEndNode cend ) return cend.folding();
- if( n instanceof CallNode )
- for( int i=0; i<n.nOuts(); i++ )
- if( n.out(i) instanceof CallEndNode cend && cend.folding() ) return true;
- return false;
- }
-
- @Override protected Kind kind(Node n) {
- if( n instanceof StopNode ) return Kind.STOP;
- if( n instanceof ScopeNode ) return Kind.SCOPE;
- if( n instanceof StartCUNode ) return Kind.UNIT;
- if( n instanceof FunNode ) return Kind.FUN;
- // Start inherits Loop for whole-program analysis, but is not a source loop.
- if( n instanceof StartNode ) return Kind.START;
- if( n instanceof LoopNode ) return Kind.LOOP;
- if( n instanceof RegionNode ) return Kind.REGION;
- if( n instanceof PhiNode ) return Kind.PHI;
- if( n instanceof CFGNode ) return Kind.CTRL;
- return isMem(n) ? Kind.MEM : Kind.DATA;
- }
-
- @Override protected Role role(Node n, int i) {
- if( n instanceof ScopeNode || n instanceof ConstantNode || n instanceof FunPtrNode ||
- n instanceof CtrlNode || n instanceof XCtrlNode )
- return Role.ASSOC;
- if( n instanceof PhiNode )
- return i == 0 ? Role.ASSOC : isMem(n) ? Role.MEM : Role.DATA;
- if( n instanceof Proj )
- return n instanceof CFGNode ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA;
- // StartCU inherits Region, but slot 0 really connects it to Start.
- if( n instanceof StartCUNode && i == 0 ) return Role.CTRL;
- if( n instanceof RegionNode && i == 0 ) return Role.ASSOC;
- // Preserve known slot roles even when an input is not attached/typed yet.
- if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL;
- if( n instanceof MemMergeNode ||
- (i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode)) ||
- (n instanceof EscapeNode && i >= 2) ) return Role.MEM;
- Node def = n.in(i);
- if( def instanceof CFGNode ) return Role.CTRL;
- if( def != null && isMem(def) ) return Role.MEM;
- return Role.DATA;
- }
-
- private boolean isMem(Node n) {
- // These Phi subclasses are memory even before their first type computation.
- return n instanceof MemPhiNode || n instanceof BulkMemPhiNode || n.isMem() || n._type instanceof TypeMem;
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter-modules/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
deleted file mode 100644
index f2ed2f72..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.seaofnodes.simple.print;
-
-import com.seaofnodes.graph.GraphCapture;
-import com.seaofnodes.graph.GraphViewer;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.node.Node;
-import java.util.ArrayList;
-import java.io.IOException;
-
-/** Chapter 25's compilation context; capture and event bookkeeping are shared. */
-public class SimpleGraphObserver extends GraphCapture<Node> {
- private CodeGen _code;
- public SimpleGraphObserver() { super(new SimpleGraphAdapter()); }
- public static void main(String[] args) throws IOException {
- GraphViewer.run(new SimpleGraphObserver());
- }
- @Override protected void compile(String src) {
- _code = new CodeGen(src);
- _code._obs = this;
- _code.driver(CodeGen.Phase.TypeCheck);
- }
- @Override protected String phase() { return _code._phase.name(); }
- @Override protected int pos() { return _code._phase == CodeGen.Phase.Parse ? _code.P.pos() : -1; }
- @Override protected void detach() {
- if( _code != null ) _code._obs = null;
- _code = null;
- }
- @Override protected Node scope() { return _code.P._scope; }
- @Override protected void roots(ArrayList<Node> roots) {
- roots.add(_code._stop);
- if( _code._phase == CodeGen.Phase.Parse ) {
- roots.addAll(_code.P._xScopes);
- }
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/sys.java b/chapter-modules/src/main/java/com/seaofnodes/simple/sys.java
deleted file mode 100644
index 45106770..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/sys.java
+++ /dev/null
@@ -1,25 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/Field.java
deleted file mode 100644
index ec4a588d..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/Field.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package com.seaofnodes.simple.type;
-
-import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.SB;
-import java.util.*;
-
-/**
- * Represents a field in a struct. This is not a Type in the type system.
- */
-public class Field extends Type {
- static final Field[] EMPTY = new Field[0];
-
- // The pair {fieldName,type} uniquely identifies a field.
-
- // Field name
- public String _fname;
- // Type of the field
- public Type _t;
- // Unique memory alias, not sensibly part of a "type" but very convenient here.
- public int _alias;
- // Field is only written in the constructor
- public boolean _final;
- // Native C storage, outside the containing object's layout.
- public boolean _extern;
-
- private static final Ary<Field> FREE = new Ary<>(Field.class);
- private Field(String fname, Type type, int alias, boolean xfinal, boolean external ) { super(TFLD); init(fname,type,alias,xfinal,external); }
- private Field init(String fname, Type type, int alias, boolean xfinal, boolean external ) {
- _fname = fname;
- _t = type;
- _alias = alias;
- _final = xfinal;
- _extern = external;
- return this;
- }
-
- // Return a filled-in Field; either from free list or alloc new.
- private static Field malloc(String fname, Type type, int alias, boolean xfinal, boolean external ) {
- return FREE.isEmpty() ? new Field(fname,type,alias,xfinal,external) : FREE.pop().init(fname,type,alias,xfinal,external);
- }
- // Malloc-from
- public Field malloc( ) { return malloc(_fname,null,_alias,_final,_extern); }
- @Override Field free(Type t) {
- Field f = (Field)t;
- assert !f.isFree() && !f._terned;
- f._t = null;
- f._dual = null;
- f._hash = 0;
- FREE.push(f);
- return this;
- }
- @Override boolean isFree() { return _t ==null; }
-
- // Make and intern with listed fields
- public static Field make( String fname, Type type, int alias, boolean xfinal ) {
- return make(fname,type,alias,xfinal,false);
- }
- public static Field make( String fname, Type type, int alias, boolean xfinal, boolean external ) {
- Field f = malloc(fname,type,alias,xfinal,external);
- Field f2 = f.intern();
- if( f2==f ) return f;
- return VISIT.isEmpty() ? f2.free(f) : f2.delayFree(f);
- }
- public Field makeFrom( Type type ) {
- return type == _t ? this : make(_fname,type,_alias,_final,_extern);
- }
- public Field makeFrom( boolean xfinal ) {
- return xfinal == _final ? this : make(_fname,_t,_alias,xfinal,_extern);
- }
-
- // Cycle-making-breaking
- void setType(Type t) { assert _t ==null; _t = t; }
-
- public static final Field BOT = make(" ",Type.BOTTOM,-999,false);
- public static final Field TEST = make("test",Type.NIL,-2,false);
- public static final Field TEST2 = make("test",Type.NIL,-2,true);
- public static final Field FLT = make("flt ",TypeFloat.F32,-2,false);
- public static final Field EXT = make("test",TypeInteger.I32,-2,false,true);
- public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(TEST2); ts.add(FLT); ts.add(EXT); }
-
- @Override Field xmeet( Type that ) {
- Field fld = (Field)that; // Invariant
- if( this==BOT || fld==BOT ) return BOT;
- if( this==BOT.dual() ) return fld ;
- if( fld ==BOT.dual() ) return this;
- if( _fname!=fld._fname ) { assert !_fname.equals(fld._fname); return BOT; }
- assert _alias==fld._alias;
- if( _extern!=fld._extern ) return BOT;
- return make(_fname, _t.meet(fld._t ), _alias, _final | fld._final,_extern);
- }
-
- @Override
- Field xdual() { return malloc(_fname, _t.dual(),_alias,!_final,_extern); }
-
- @Override Field rdual() {
- if( _dual!=null ) return dual();
- assert !_terned;
- Field d = malloc(_fname,null,_alias,!_final,_extern);
- (_dual = d)._dual = this; // Cross link duals
- d._t = _t._terned ? _t.dual() : _t.rdual();
- return d;
- }
-
- @Override boolean _isConstant() { return _t._isConstant(); }
- @Override boolean _isFinal() { return _final && _t._isFinal(); }
- @Override Field _makeRO() { return _final ? this : make(_fname, _t._makeRO(),_alias,true,_extern); }
- @Override Field _close( String name, HashMap<String, Type> TYPES ) { return makeFrom( _t._close(name, TYPES )); }
-
- @Override Type _upgradeType(HashMap<String,Type> TYPES) {
- return makeFrom(_t._upgradeType(TYPES));
- }
-
- // Override in subclasses
- int hash() { return _fname.hashCode() ^ _t.hashCode() ^ _alias ^ (_final ? 1024 : 0) ^ (_extern ? 2048 : 0); }
-
- private boolean static_eq( Field f ) {
- return _fname.equals(f._fname) && _alias==f._alias && _final==f._final && _extern==f._extern && _t !=null && f._t !=null;
- }
- @Override boolean eq( Type t ) {
- Field f = (Field)t;
- return static_eq(f) && _t ==f._t;
- }
- @Override boolean cycle_eq(Type t) {
- if( this==t ) return true;
- Field f = (Field)t;
- return static_eq(f) && _t.cycle_eq(f._t );
- }
-
- @Override public int nkids() { return 1; }
- @Override public Type at( int idx ) { return _t; }
- @Override public void set( int idx, Type t ) { _t = t; }
- // Tags: final/!final; +alias+name+external-storage flag
-
-
- @Override
- public SB _print( SB sb, BitSet visit, boolean html ) {
- sb.p(_extern ? "extern C " : "").p(_final?"":"!").p(_fname).p(":").p(_alias).p(" : ");
- return _t ==null ? sb.p("---") : _t.print(sb,visit,html);
- }
-
- @Override public String str() { return (_final?"":"!")+_fname; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/Type.java
deleted file mode 100644
index 1979368e..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/Type.java
+++ /dev/null
@@ -1,548 +0,0 @@
-package com.seaofnodes.simple.type;
-
-import com.seaofnodes.simple.util.*;
-import java.util.*;
-
-/**
- * These types are part of a Monotone Analysis Framework,
- * @see <a href="https://www.cse.psu.edu/~gxt29/teaching/cse597s21/slides/08monotoneFramework.pdf">see for example this set of slides</a>.
- * <p>
- * The types form a lattice; @see <a href="https://en.wikipedia.org/wiki/Lattice_(order)">a symmetric complete bounded (ranked) lattice.</a>
- * <p>
- * This wild lattice theory will be needed later to allow us to easily beef up
- * the analysis and optimization of the Simple compiler... but we don't need it
- * now, just know that it is coming along in a later Chapter.
- * <p>g
- * One of the fun things here is that while the theory is deep and subtle, the
- * actual implementation is darn near trivial and is generally really obvious
- * what we're doing with it. Right now, it's just simple integer math to do
- * simple constant folding e.g. 1+2 == 3 stuff.
- */
-
-public class Type /*implements Cloneable*/ {
- // Interning tables
-
- // Main intern table; a collection of all unique types.
- static final HashMap<Type,Type> INTERN = new HashMap<>();
- // Starting intern table; used to rapidly reset the main table between tests
- private static final HashMap<Type,Type> INTERN0= new HashMap<>();
- // Lifetime management during cyclic installs is complex; this is used to
- // track created-but-already-interned objects
- private static final Ary<Type> FREES = new Ary<>(Type.class);
- // "visit" bit management, based solely on _uid
- static final BitSet BITS = new BitSet();
-
- // "visit" bit management. Key varies by kind of visit:
-
- // - "close" an open cyclic type. Used by the Parser, it will hit on
- // same-named structs, even if the structs have different states of
- // completion/fields.
- // - "uid/name" - Either the UID or name works (unlike "close" which must
- // use the name). Used to make cyclic copies with changes, like make
- // read-only where there should be only 1 copy of a name in a cycle.
- // - "pair of uids" - For recursive MEET, where we need to key off two types
- static final HashMap<Object,Type> VISIT = new HashMap<>();
-
- // Every Type has a UID to allow visit bits on cyclic visits
- private static int UID=1;
- public char _uid; // Unique ID for every type
-
- // ----------------------------------------------------------
- // Simple types are implemented fully here. "Simple" means: the code and
- // type hierarchy are simple, not that the Type is conceptually simple.
- static final byte TBOT = 0; // Bottom (ALL)
- static final byte TTOP = 1; // Top (ANY)
- static final byte TCTRL = 2; // Ctrl flow bottom
- static final byte TXCTRL = 3; // Ctrl flow top (mini-lattice: any-xctrl-ctrl-all)
- static final byte TNIL = 4; // low null of all flavors
- static final byte TXNIL = 5; // high or choice null
- static final byte TSIMPLE = 5; // End of the Simple Types
-
- static final byte TSCALAR = 6; // Integer, float, memory pointer or function pointer
- static final byte TPTR = 7; // All nil-able pointer values
- static final byte TINT = 8; // All Integers; see TypeInteger
- static final byte TFLT = 9; // All Floats ; see TypeFloat
- static final byte TCONARY =10; // Constant array
- static final byte TRPC =11; // Return Program Control (Return PC or RPC)
-
- static final byte TCYCLIC =12; // Has internal pointers, needs recursive treatment
- static final byte TMEMPTR =12; // Memory pointer to a struct type
- static final byte TFUNPTR =13; // Function pointer; unique signature and code address (just a bit)
- static final byte TMEM =14; // All memory (alias 0) or A slice of memory - with specific alias
- static final byte TFLD =15; // Named fields in structs
- static final byte TSTRUCT =16; // Structs; tuples with named fields
- static final byte TTUPLE =17; // Tuples; finite collections of unrelated Types, kept in parallel
-
- static final byte TMAX =18;
-
- // Basic RTTI, useful for a lot of fast tests.
- public final byte _type;
- public boolean _terned;
-
- public boolean is_simple() { return _type <= TSIMPLE; }
- public boolean is_nokids() { return _type < TCYCLIC; }
- private static final String[] STRS = new String[]{"Bot","Top","Ctrl","~Ctrl","null","~nil"};
- protected Type(byte type) {
- _type = type; // RTTI
- _uid = (char)UID++; // A unique ID for every type
- if( UID > 32000 )
- type = type;
- assert _uid!=0; // Overflow
- }
-
- public static final Type BOTTOM = new Type( TBOT ).intern(); // ALL
- public static final Type TOP = BOTTOM.dual();
- public static final Type CONTROL = new Type( TCTRL ).intern(); // Ctrl
- public static final Type XCONTROL = CONTROL.dual();
- public static final Type NIL = new Type( TNIL ).intern(); // low null of all flavors
- public static final Type XNIL = NIL.dual();
- public static Type[] gather() {
- ArrayList<Type> ts = new ArrayList<>();
- ts.add(BOTTOM);
- ts.add(CONTROL);
- ts.add(NIL);
- TypeScalar.gather(ts);
- TypeNil.gather(ts);
- TypePtr.gather(ts);
- TypeInteger.gather(ts);
- TypeFloat.gather(ts);
- TypeMemPtr.gather(ts);
- TypeFunPtr.gather(ts);
- TypeMem.gather(ts);
- Field.gather(ts);
- TypeStruct.gather(ts);
- TypeTuple.gather(ts);
- TypeRPC.gather(ts);
- TypeConAry.gather(ts);
-
- int sz = ts.size();
- for( int i = 0; i < sz; i++ )
- ts.add(ts.get(i).dual());
- return ts.toArray(new Type[ts.size()]);
- }
-
- // Is high or on the lattice centerline.
- public boolean isHigh () { return _type==TTOP || _type==TXCTRL || _type==TXNIL; }
- public boolean isHighOrConst() { return isHigh() || isConstant(); }
-
- // ----------------------------------------------------------
-
- // Notes on Type interning: At the moment it is not easy to reset the
- // interned types because we hold static references to several types and
- // these are scattered around. This means the INTERN cache will retain all
- // types from every run of the Parser. For this to work correctly types
- // must be rigorous about defining when they are the same. Also types need
- // to be immutable once defined. The rationale for interning is
- // *correctness* with cyclic type definitions. Simple structural recursive
- // checks go exponential with merely sharing, but with cycles they will
- // stack overflow and crash. Interning means we do not need to have sharing
- // checks with every type compare, only during interning.
-
- // Factory method which interns "this"
- @SuppressWarnings("unchecked")
- public <T extends Type> T intern() {
- //assert check();
- assert !_terned; // Do not ask for already-interned
- T t2 = (T)INTERN.get(this);
- if( t2!=null ) {
- assert t2._dual != null; // Prior is complete with dual
- assert this != t2; // Do not hashcons twice, should not get self back
- return t2; // Return prior
- }
- // Mid recursive type construction
- if( !VISIT.isEmpty() && _type >= TCYCLIC ) {
- // No intern attempt at all
- return (T)this;
- }
- // Not in type table
- _dual = null; // No dual yet
- INTERN.put(this,this); // Put in table without dual
- _terned = true;
- //Util.hash_quality_check_per(INTERN,"INTERN");
- T d = xdual(); // Compute dual without requiring table lookup, and not setting name
- _dual = d;
- if( this==d ) return d; // Self-symmetric? Dual is self
- assert !equals(d); // Self-symmetric is handled by caller
- assert d._dual==null; // Else dual-dual not computed yet
- assert INTERN.get(d)==null;
- d._dual = (T)this;
- INTERN.put(d,d); // Install dual also
- d._terned = true;
- //assert check();
- return (T)this;
- }
-
- // Recursive visit elsewhere, interns with dual already
- <T extends Type> T _intern() {
- //assert check();
- assert !_terned;
- Type t = INTERN.get(this);
- if( t!=null ) return (T)t.delayFree(this).delayFree(_dual);
- assert !INTERN.containsKey( _dual );
- INTERN.put(this,this);
- INTERN.put(_dual,_dual);
- _terned = _dual._terned = true;
- //assert check();
- return (T)this;
- }
-
- private static boolean check() {
- for( Type t : INTERN.keySet() ) {
- Type t2 = INTERN.get(t);
- assert t==t2;
- }
- return true;
- }
-
- public static Type find(int uid) {
- for( Type t : INTERN.keySet() )
- if( t._uid==uid ) return t;
- return null;
- }
-
-
- int _hash; // Hash cache; not-zero when set.
- @Override
- public final int hashCode() {
- if( _hash!=0 ) return _hash;
- _hash = hash();
- if( _hash==0 ) _hash = 0xDEADBEEF; // Bad hash from subclass; use some junk thing
- return _hash;
- }
- // Override in subclasses
- int hash() { return _type; }
-
- @Override
- public final boolean equals( Object o ) {
- if( o==this ) return true;
- if( !(o instanceof Type t)) return false;
- if( _type != t._type ) return false;
- return eq(t);
- }
- // Overridden in subclasses; subclass can assume "this!=t" and java classes are same
- boolean eq(Type t) { return this==t; }
- boolean cycle_eq(Type t) { assert is_nokids(); return _type==t._type && eq(t); }
- // A pair of uids
- int pid( Type that ) {
- return _uid < that._uid ? (_uid<<16 | that._uid) : (that._uid<<16 | _uid);
- }
-
- // At/Set child at 'idx' to t
- public Type at( int idx ) { throw Utils.TODO("Should not reach here: type has no children"); }
- public void set( int idx, Type t ) { throw Utils.TODO("Should not reach here: type has no children"); }
- public int nkids() { return 0; } // Number of kids
-
-
- // Clear and re-insert the basic Type INTERN table
- public static void reset() {
- VISIT.clear();
- FREES.clear();
- if( INTERN0.isEmpty() ) {
- // First time run all <clinit> and collect basic types
- Type t = TypeRPC .BOT; // Force class load, intern
- Type u = TypeTuple.BOT; // Force class load, intern
- Type w = TypePtr .PTR; // Force class load, intern
- INTERN0.putAll(INTERN);
- } else {
- // Later times, reset back to first time
- VISIT.put(0,CONTROL); // Defeat assert
- for( Iterator<Type> it = INTERN.keySet().iterator(); it.hasNext(); ) {
- Type t = it.next();
- if( !INTERN0.containsKey(t) )
- { t._terned=false; t.free(t); }
- it.remove();
- }
- VISIT.clear();
- INTERN.putAll(INTERN0);
- }
- }
-
- // ----------------------------------------------------------
- public final Type meet(Type t) {
- assert _terned && t._terned;
- // Shortcut for the self case
- if( t == this ) return this;
- // Same-type is always safe in the subclasses
- if( _type==t._type ) return xmeet(t);
- // TypeNil vs TypeNil meet
- if( this instanceof TypeNil ptr0 && t instanceof TypeNil ptr1 )
- return ptr0.nmeet(ptr1);
- // Reverse; xmeet 2nd arg is never "is_simple" and never equal to "this".
- if( is_simple() ) return this.xmeet(t );
- if( t.is_simple() ) return t .xmeet(this);
- // TypeConAry meet its element type is OK
- if( this instanceof TypeConAry tcon && t instanceof TypeInteger ti ) return tcon.ymeet(ti);
- if( t instanceof TypeConAry tcon && this instanceof TypeInteger ti ) return tcon.ymeet(ti);
- // Two distinct scalar families meet at the scalar envelope.
- if( this instanceof TypeScalar scalar0 && t instanceof TypeScalar scalar1 )
- return scalar0.smeet(scalar1);
- // Mixing 2 unrelated types
- return Type.BOTTOM;
- }
-
- // Compute meet right now. Overridden in subclasses.
- // Handle cases where 'this.is_simple()' and unequal to 't'.
- // Subclassed xmeet calls can assert that '!t.is_simple()'.
- Type xmeet(Type t) {
- assert is_simple(); // Should be overridden in subclass
- // ANY meet anything is thing; thing meet ALL is ALL
- if( _type==TBOT || t._type==TTOP ) return this;
- if( _type==TTOP || t._type==TBOT ) return t;
-
- // RHS TypeNil vs NIL/XNIL
- if( _type==TNIL || _type==TXNIL ) {
- if( t instanceof TypeNil ptr ) return _type==TNIL ? ptr.meet0() : ptr.meetX();
- if( t instanceof TypeScalar scalar ) {
- if( t._type==TSCALAR ) return t.isHigh() ? this : t;
- return TypeScalar.BOT;
- }
- return t._type==(_type==TNIL ? TXNIL : TNIL) ? TypePtr.PTR : BOTTOM;
- }
-
- // 'this' is only {TCTRL,TXCTRL}
- // Other non-simple RHS things bottom out
- if( !t.is_simple() ) return BOTTOM;
- // If RHS is NIL/XNIL
- if( t._type==TNIL || t._type==TXNIL ) return BOTTOM;
- // Both are {TCTRL,TXCTRL} and unequal to each other
- return _type==TCTRL || t._type==TCTRL ? CONTROL : XCONTROL;
- }
-
-
- // The dual is pre-computed on creation and available "for free" thereafter
- Type _dual;
- public final <T extends Type> T dual() { return (T)_dual; }
-
- <T extends Type> T xdual() {
- return (T)new Type(switch( _type ) {
- case TBOT -> TTOP;
- case TTOP -> TBOT;
- case TCTRL -> TXCTRL;
- case TXCTRL-> TCTRL;
- case TNIL -> TXNIL;
- case TXNIL -> TNIL;
- default -> throw Utils.TODO("Should not reach here: unknown simple type");
- });
- }
-
-
- // ----------------------------------------------------------
- // Our lattice is defined with a MEET and a DUAL.
- // JOIN is dual of meet of both duals.
- public final Type join(Type t) {
- if( this==t ) return this;
- return dual().meet(t.dual()).dual();
- }
-
- // True if this "isa" t; e.g. 17 isa TypeInteger.BOT
- public boolean isa( Type t ) { return meet(t)==t; }
-
- public Type nonZero() { return this==NIL ? TypePtr.XNPTR : TypePtr.NPTR; }
-
- // Make a zero version of this type, 0 for integers and null for pointers.
- public Type makeZero() { return Type.NIL; }
-
- // Cap at limits
- public Type oob() { return isHigh() ? TOP : BOTTOM; }
-
- // ----------------------------------------------------------
-
- // Cyclic types! Flag the start of a cyclic type by putting a sentinel in
- // VISIT (and eventually clearing VISIT when done). Then do a normal
- // recursive descent visit of all types; this will accumulate types without
- // interning them. When done we have to visit the possibly cyclic type and
- // intern the whole cycle, possibly hitting the entire cycle on a prior
- // interned cycle. Any sub-part might also be interned, including whole
- // disjoint cycles.
-
- // E.g. a new type: "A<->B -> C1<->D1" gets created; the "C1<->D1" cycle
- // already exists as "C0<->D0", but the "A<->B" cycle is new. The "A<->B"
- // cycle will get interned - except the pointer to C1 needs to get replaced
- // with C0. The "C1<->D1" cycle gets freed and "B" will point to the
- // "C0<->D0" cycle.
-
- Type recurOpen() { assert VISIT.isEmpty(); VISIT.put(0L,BOTTOM); return this; }
- Type recurClose() { return recurClose(null); }
- Type recurClose(Type[] types) {
- VISIT.remove(0L); // Just ignore the sentinel
- Ary<Type> ts = new Ary<>(Type.class);
- ts.addAll(VISIT.values());
- if( ts.find(this)== -1 ) ts.add(this);
-
- // Pass #1: replace already interned fields
- assert BITS.isEmpty();
- for( int i=0; i<ts._len; i++ )
- if( ts.at(i).tern()._terned )
- ts.del(i--);
- BITS.clear();
-
- // Pass#2: compute recursive duals
- for( Type t : ts )
- t.rdual();
-
- // Pass#3: Recursive install
- for( Type t : ts )
- if( !t._terned && INTERN.get(t)==null )
- t.install();
- // Upgrade the result
- Type rez = INTERN.get(this);
- assert rez!=null;
- if( types!=null )
- for( int i=0; i<types.length; i++ )
- types[i] = INTERN.get(types[i]);
-
- // Pass#4?: Free up any created-but-already interned
- for( Type t : FREES )
- t.rfree();
- FREES.clear();
- VISIT.clear();
- return rez;
- }
-
- // Read-only recursive visits do not *make* cyclic types and so the cleanup
- // is much easier.
- static boolean recurClose(boolean rez) { VISIT.clear(); return rez; }
-
- final Type tern() {
- if( _terned ) return this;
- if( !BITS.get(_uid) ) {
- BITS.set(_uid);
- int nkids = nkids();
- for( int i=0; i<nkids; i++ )
- set(i,at(i).tern());
- }
- Type told = INTERN.get(this);
- return told==null ? this : told.delayFree(this);
- }
-
- // Recursive dual. Visit all types recursively. Each visited type is
- // *new*, and hits in the intern table, or not. If not, we recursively
- // call rdual to get its dual, and cross-link the duals. If hitting, we
- // use the intern type and mark the prior type for freeing.
- Type rdual() { assert !_terned; return xdual(); }
-
- // Recursively free type and children
- final void rfree() {
- if( isFree() ) return;
- int nkids = nkids();
- for( int i=0; i<nkids; i++ )
- if( !at(i)._terned )
- at(i).rfree();
- free(this);
- }
-
- Type free(Type free) {
- return this;
- }
- boolean isFree() { return false; }
-
- final <T extends Type> T delayFree(Type free) {
- assert !free._terned;
- FREES.push(free);
- return (T)this;
- }
-
- final Type install() {
- if( this instanceof TypeStruct ) {
- Type x = _intern(); // Stop the recursion
- if( x!=this ) return x.delayFree(this);
- }
- int nkids = nkids();
- for( int i=0; i<nkids; i++ ) {
- Type kid = at(i);
- if( !kid._terned ) {
- Type ikid = kid.install();
- if( ikid != kid ) {
- set(i,ikid);
- _dual.set(i,ikid.dual());
- }
- }
- }
- return this instanceof TypeStruct || _terned ? this : _intern();
- }
-
- // Strict constant values, things on the lattice centerline.
- // Excludes both high and low values
- public final boolean isConstant() { return recurClose(recurOpen()._isConstant()); }
- boolean _isConstant() { return _type==TNIL; }
-
- // Are all reachable struct Fields are final?
- // Writable array slots are invariant in their element access permissions.
- // Otherwise an alias could insert a readonly pointer into a writable slot.
- public boolean accessISA(Type dst) { return true; }
-
- public final boolean isFinal() { return recurClose(recurOpen()._isFinal()); }
- boolean _isFinal() { assert is_nokids(); return true; }
-
- public final Type makeRO() {
- if( isFinal() ) return this;
- return recurOpen()._makeRO().recurClose();
- }
- Type _makeRO() { return this; }
-
- /** Declared shape used when a value is published into mutable memory. */
- public final Type makeStorage() {
- return recurOpen()._makeStorage().recurClose();
- }
- Type _makeStorage() { assert is_simple(); return BOTTOM; }
-
- // Bulk close-over all recursive types
- public static TypeStruct[] closeOver(TypeStruct[] ts, HashMap<String,Type> TYPES) {
- BOTTOM.recurOpen();
- for( TypeStruct t : ts )
- t._close(null, TYPES);
- for( int i=0; i<ts.length; i++ ) {
- // Parser only tracks the mutable name of arrays, so the closeOver
- // returns that one. However, internal types might have immutable
- // versions.
- String name = ts[i].isAry() ? (ts[i]._name+"!").intern() : ts[i]._name;
- ts[i] = (TypeStruct)VISIT.get(name);
- }
- BOTTOM.recurClose(ts);
- return ts;
- }
-
- Type _close( String name, HashMap<String, Type> TYPES ) { return this; }
- public Type widen() { return this; }
-
- // Replace recursively all TypeBuilders with cyclic TypeStructs
- public Type upgradeType(HashMap<String,Type> TYPES) {
- return recurOpen()._upgradeType(TYPES).recurClose();
- }
- Type _upgradeType(HashMap<String,Type> TYPES) { assert is_nokids(); return this; }
-
-
- // ----------------------------------------------------------
-
- // 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/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeConAry.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
deleted file mode 100644
index 860cefc5..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package com.seaofnodes.simple.type;
-
-import com.seaofnodes.simple.util.BAOS;
-import com.seaofnodes.simple.util.Utils;
-import java.util.ArrayList;
-import java.util.HashMap;
-
-/**
- * Represents a constant array of primitives
- */
-public abstract class TypeConAry<A> extends TypeScalar {
- boolean _any;
- byte _widen;
- // One of byte or int array
- public final A _ary;
-
- TypeConAry( boolean any, byte widen, A ary ) { super(TCONARY); _any = any; _widen = widen; _ary = ary; }
- public static void gather(ArrayList<Type> ts) {
- ts.add(TypeConAryB.ABC);
- ts.add(TypeConAryB.ABCD);
- ts.add(TypeConAryI.I123);
- }
- // Fresh, uninterned type sharing this array; xdual must not intern.
- abstract TypeConAry<A> _make(boolean any, byte widen);
-
- @Override TypeConAry<A> xdual() { return _make( !_any, (byte)(3-_widen) ); }
-
- @Override Type xmeet(Type t) {
- TypeConAry ary = (TypeConAry)t; // Invariant
- // Same base array but different?
- if( _equals(ary) )
- // Return array with larger widen
- return _widen >= ary._widen ? this : ary;
- // Unrelated constant arrays, falls to some int range
- return elem().meet(ary.elem());
- }
-
- Type ymeet( TypeInteger ti ) {
- // if i can isa *each* element, then maybe can keep.
- // no good to i.isa(elem()) because fails the dual
- for( int j=0; j<len(); j++ )
- if( !ti.isa(TypeInteger.make(at8(j),at8(j),ti._widen)) )
- return ti.meet(elem());
- byte widen = (byte)Math.max(_widen,ti._widen);
- return widen==_widen ? this : _make(_any,widen).intern();
- }
-
- private boolean _equals(TypeConAry ary) {
- int len = len();
- if( len != ary.len() ) return false;
- for( int i=0; i<len; i++ )
- if( at8(i) != ary.at8(i) )
- return false;
- return true;
- }
-
- @Override public boolean isHigh() { return this==TOP; }
- @Override boolean _isConstant() { return true; }
- @Override Type _makeStorage() { return this; }
-
- // Meet-over-elements type
- public Type elem() {
- if( _ary==null )
- return _any ? Type.TOP : BOTTOM;
- int len = len();
- long min = Long.MAX_VALUE;
- long max = Long.MIN_VALUE;
- for( int i=0; i<len; i++ ) {
- min = Math.min(min,at8(i));
- max = Math.max(max,at8(i));
- }
- return TypeInteger.make(min,max,_widen);
- }
- public abstract long at8(int idx);
- public abstract int len();
- @Override public abstract int log_size();
-
-
- public void write( BAOS baos ) { throw Utils.TODO("Should not reach here: abstract constant array cannot be written"); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java
deleted file mode 100644
index bddcb1fb..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.seaofnodes.simple.type;
-
-import com.seaofnodes.simple.util.BAOS;
-import java.util.Arrays;
-
-public class TypeConAryB extends TypeConAry<byte[]> {
- private TypeConAryB(boolean any, byte widen, byte[] bs) { super(any,widen,bs); }
- @Override TypeConAryB _make( boolean any, byte widen ) { return new TypeConAryB(any,widen,_ary); }
- public static TypeConAryB make( byte[] bs ) { return new TypeConAryB(false,(byte)0,bs).intern(); }
- public static TypeConAryB make( String s ) { return make(s.getBytes()); }
- static final TypeConAryB ABC = make("abc");
- static final TypeConAryB ABCD = make("abcd");
- @Override public long at8(int idx) { return _ary[idx]; }
- @Override public int len() { return _ary.length; }
- @Override public int log_size() { return 0; }
- @Override public String str() { return "[\""+new String(_ary)+"\"]"; }
- @Override boolean eq(Type t) { return t instanceof TypeConAryB ary && _widen==ary._widen && Arrays.equals(_ary,ary._ary); }
- @Override int hash() { return Arrays.hashCode(_ary); }
-
- @Override public void write( BAOS baos ) { baos.write(_ary); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java
deleted file mode 100644
index b1b061de..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.seaofnodes.simple.type;
-
-import java.util.Arrays;
-
-public class TypeConAryI extends TypeConAry<int[]> {
- private TypeConAryI(boolean any, byte widen, int[] bs) { super(any,widen,bs); }
- public static TypeConAryI make( int[] bs ) { return new TypeConAryI(false,(byte)0,bs).intern(); }
- @Override TypeConAryI _make( boolean any, byte widen ) { return new TypeConAryI(any,widen,_ary); }
- static final TypeConAryI I123 = make(new int[]{1,2,3});
- @Override public long at8(int idx) { return _ary[idx]; }
- @Override public int len() { return _ary.length; }
- @Override public int log_size() { return 2; }
- @Override public String str() { return "[i32]"; }
- @Override boolean eq(Type t) { return t instanceof TypeConAryI ary && _widen==ary._widen && Arrays.equals(_ary,ary._ary); }
- @Override int hash() { return Arrays.hashCode(_ary); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeFloat.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
deleted file mode 100644
index 8638b8c9..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
+++ /dev/null
@@ -1,111 +0,0 @@
-package com.seaofnodes.simple.type;
-
-import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.AryInt;
-import com.seaofnodes.simple.util.Utils;
-import java.util.ArrayList;
-import java.util.HashMap;
-
-/**
- * Float Type
- */
-public class TypeFloat extends TypeScalar {
-
- // - high -64, high -32, con 0, low +32, low +64
- public byte _sz;
-
- /**
- * The constant value or 0
- */
- public double _con;
-
- private static final Ary<TypeFloat> FREE = new Ary<>(TypeFloat.class);
- private TypeFloat(byte sz, double con) { super(TFLT); init(sz,con); }
- private TypeFloat init( byte sz, double con ) { _sz = sz; _con = con; return this; }
- public static TypeFloat malloc(byte sz, double con) { return FREE.isEmpty() ? new TypeFloat(sz,con) : FREE.pop().init(sz,con); }
- private static TypeFloat make(int sz, double con) {
- TypeFloat f = malloc((byte)sz,con);
- TypeFloat t2 = f.intern();
- return t2==f ? f : t2.free(f);
- }
- @Override TypeFloat free(Type t) {
- TypeFloat f = (TypeFloat)t;
- f._hash = 0;
- f._dual=null;
- FREE.push(f);
- return this;
- }
-
- public static TypeFloat constant(double con) { return make(0, con); }
-
- public final static TypeFloat ONE = constant(1.0);
- public final static TypeFloat FZERO = constant(0.0);
- public final static TypeFloat F32 = make(32, 0);
- public final static TypeFloat F64 = make(64, 0);
-
- public static void gather(ArrayList<Type> ts) { ts.add(F64); ts.add(F32); ts.add(constant(3.141592653589793)); }
-
- @Override public String str() {
- return switch( _sz ) {
- case -64 -> "~flt";
- case -32 -> "~f32";
- case 0 -> ""+_con+((float)_con==_con ? "f" : "");
- case 32 -> "f32";
- case 64 -> "flt";
- default -> throw Utils.TODO("Should not reach here: unknown float size");
- };
- }
- private boolean isF32() { return ((float)_con)==_con; }
-
- @Override public boolean isHigh () { return _sz< 0; }
- @Override public int log_size() { return _sz==32 || _sz==-32 ? 2 : 3; }
-
- public double value() { return _con; }
-
- @Override
- public TypeFloat xmeet(Type other) {
- TypeFloat f = (TypeFloat)other;
- // Invariant from caller: 'this' != 'other' and same class (TypeFloat).
- TypeFloat i = (TypeFloat)other; // Contract
- // Larger size in i1, smaller in i0
- TypeFloat i0 = _sz < i._sz ? this : i;
- TypeFloat i1 = _sz < i._sz ? i : this;
-
- if( i1._sz== 64 ) return F64;
- if( i0._sz==-64 ) return i1;
- if( i1._sz== 32 )
- return i0._sz==0 && !i0.isF32() ? F64 : F32;
- if( i1._sz!= 0 ) return i1;
- // i1 is a constant
- if( i0._sz==-32 )
- return i1.isF32() ? i1 : F64;
- // Since both are constants, and are never equals (contract) unequals
- // constants fall to bottom
- return i0.isF32() && i1.isF32() ? F32 : F64;
- }
-
- @Override
- TypeFloat xdual() {
- return _isConstant() ? this : new TypeFloat((byte)-_sz,0); // Constants are a self-dual
- }
-
- @Override boolean _isConstant() { return _sz==0; }
-
- @Override Type _makeStorage() {
- if( _isConstant() ) return isF32() ? F32 : F64;
- return isHigh() ? dual() : this;
- }
-
- @Override public Type makeZero() { return FZERO; }
-
-
- @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/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
deleted file mode 100644
index 301fcc8b..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
+++ /dev/null
@@ -1,313 +0,0 @@
-package com.seaofnodes.simple.type;
-
-import com.seaofnodes.simple.util.*;
-import java.util.*;
-
-/**
- * Represents a Pointer to a function.
- * <p></p>
- * Functions have argument types and a return type, making a function
- * signature. Within a signature there can be many instances of functions, and
- * each function is labeled with a small integer constant. A TFP can represent
- * a single signature and a set of functions; the set might contain only 1
- * function or the zeroth function (the null ptr).
- * <p></p>
- * Functions with different signatures cannot be mixed, and will result in a
- * bottom function type, which can only be null-checked.
- */
-public class TypeFunPtr extends TypeNil {
- // A TypeFunPtr is Signature and a set of functions.
- public Type[] _sig;
- public Type _ret;
- boolean _open; // Extra args are true:scalar BOT, false:scalar TOP
- // Set of functions. Bit zero is used for mutable/immutable safety. Sign
- // bit repeats infinitely.
- public int[] _fidxs;
-
- private static final Ary<TypeFunPtr> FREE = new Ary<>(TypeFunPtr.class);
- private TypeFunPtr(byte nil) { super(TFUNPTR,nil); }
- private TypeFunPtr init(byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) {
- assert sig != null;
- _nil = nil;
- _open = open;
- _sig = sig;
- _ret = ret;
- assert _fidxs == null;
- _fidxs= XInt.intern(fidxs);
- return this;
- }
- static TypeFunPtr malloc( byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) {
- return (FREE.isEmpty() ? new TypeFunPtr(nil) : FREE.pop()).init(nil,open,sig,ret,fidxs);
- }
- @Override TypeFunPtr free(Type t) {
- TypeFunPtr fun = (TypeFunPtr)t;
- assert !fun.isFree() && !fun._terned;
- fun._sig = null;
- fun._ret = null;
- fun._dual = null;
- fun._hash = 0;
- fun._fidxs = null;
- FREE.push(fun);
- return this;
- }
- @Override boolean isFree() { return _sig==null; }
-
- // All fields directly listed
- public static TypeFunPtr make( byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) {
- TypeFunPtr fun = malloc(nil,open,sig,ret,fidxs);
- TypeFunPtr f2 = fun.intern();
- if( f2==fun ) return fun;
- return VISIT.isEmpty() ? f2.free(fun) : f2.delayFree(fun);
- }
- // Trim trailing args which match the default.
- private static TypeFunPtr makeAdjusted( byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) {
- Type def = open ? TypeScalar.BOT : TypeScalar.TOP;
- int nargs = sig.length;
- while( nargs > 0 && sig[nargs-1] == def )
- nargs--;
- if( nargs != sig.length )
- sig = nargs == 0 ? ARG_EMPTY : Arrays.copyOf(sig,nargs);
- return make(nil,open,sig,ret,fidxs);
- }
- public static TypeFunPtr make( boolean nil, Type[] sig, Type ret ) { return makeAdjusted((byte)(nil ? 3 : 2),true,sig,ret, XInt.FULL); }
- public static TypeFunPtr make1( byte nil, boolean open, Type[] sig, Type ret, int fidx ) {
- return makeAdjusted(nil,open,sig,ret,XInt.make(fidx));
- }
-
-
- @Override TypeFunPtr makeFrom( byte nil ) { return nil ==_nil ? this : make( nil,_open,_sig,_ret, _fidxs); }
- public TypeFunPtr makeFrom( Type ret ) { return ret ==_ret ? this : make( _nil,_open,_sig, ret, _fidxs); }
- public TypeFunPtr makeFrom( int fidx ) { return make1(_nil,_open,_sig,_ret,fidx); }
- public TypeFunPtr makeFrom( Type arg, int idx ) {
- // Alter named arg
- Type[] sig = _sig.clone();
- sig[idx] = arg;
- return make(_nil,_open,sig,_ret,_fidxs);
- }
- @Override public Type nonZero() { return makeFrom((byte)(_nil <= 1 ? 1 : 2)); }
-
- public static final Type[] TEMPTY = new Type[0];
- static final Type[] TINT = new Type[]{TypeInteger.BOT};
- static final Type[] TINTMEM = new Type[]{TypeInteger.BOT,TypeFloat.F32};
- static final Type[] TINTINT = new Type[]{TypeInteger.BOT,TypeInteger.BOT};
- public static TypeFunPtr BOT = make((byte)3,true,TEMPTY,Type.BOTTOM, XInt.FULL);
- public static TypeFunPtr TEST = make((byte)2,true,TINTMEM,TypeInteger.BOT, XInt.SET1);
- public static TypeFunPtr TEST0 = make((byte)3,true,TINTMEM,TypeInteger.BOT, XInt.SET3);
- public static TypeFunPtr MAIN = make((byte)3,true,TINT ,Type.BOTTOM, XInt.FULL); // Main can return anything
- public static TypeFunPtr CALLOC= make((byte)3,true,TINTINT,TypeMemPtr .BOT, XInt.FULL);
- public static void gather(ArrayList<Type> ts) {
- ts.add(TEST);
- ts.add(TEST0);
- ts.add(BOT);
- ts.add(MAIN);
- ts.add(CALLOC);
- ts.add(make((byte)2,false,TINT,TypeInteger.I32, XInt.SET1));
- ts.add(make((byte)2,true ,TINT,TypeInteger.BOT, XInt.SET3));
- ts.add(make1((byte)2,true,
- new Type[]{TypeInteger.BOT},
- TypeInteger.BOT, 65));
- ts.add(make1((byte)2,true,
- new Type[]{TypeInteger.BOT, TypeScalar.BOT},
- TypeInteger.BOT, 65));
- // Explicit trailing defaults must agree with the implicit argument tail.
- for( boolean open : new boolean[]{false,true} ) {
- Type def = open ? TypeScalar.BOT : TypeScalar.TOP;
- ts.add(make1((byte)2,open,TINT,TypeInteger.I32,1));
- ts.add(make1((byte)2,open,new Type[]{TypeInteger.BOT,def,def},TypeInteger.I32,1));
- ts.add(make1((byte)2,open,new Type[]{def,def},TypeInteger.I32,1));
- ts.add(make1((byte)2,open,new Type[]{def,TypeInteger.BOT},TypeInteger.I32,1));
- }
- }
-
- private static final Type[] ARG_EMPTY = new Type[0];
- @Override
- Type xmeet(Type t) {
- TypeFunPtr that = (TypeFunPtr) t;
- boolean open = _open | that._open;
- Type def0 = this._open ? TypeScalar.BOT : TypeScalar.TOP;
- Type def1 = that._open ? TypeScalar.BOT : TypeScalar.TOP;
- Type def = open ? TypeScalar.BOT : TypeScalar.TOP;
-
- int nargs = Math.max(_sig.length,that._sig.length);
- Type[] args = new Type[nargs];
-
- for( int i=0; i<nargs; i++ ) {
- Type t0 = i < this._sig.length ? this._sig[i] : def0;
- Type t1 = i < that._sig.length ? that._sig[i] : def1;
- args[i] = t0.meet(t1);
- }
-
- while( nargs>0 && args[nargs-1] == def )
- nargs--;
-
- if( nargs != args.length )
- args = nargs == 0 ? ARG_EMPTY : Arrays.copyOf(args,nargs);
-
- return make(xmeet0(that), open, args, _ret.meet(that._ret), XInt.meet(_fidxs,that._fidxs));
- }
-
- static Type[] xmeet( Type[] ts0, Type[] ts1) {
- if( ts0==ts1 ) return ts0;
- assert ts0.length==ts1.length;
- Type[] ts = new Type[ts0.length];
- for( int i=0; i<ts0.length; i++ )
- ts[i] = ts0[i].meet(ts1[i]);
- return ts;
- }
-
- @Override TypeFunPtr xdual() { return malloc(dual0(), !_open, xdual(_sig), _ret.dual(), XInt.dual(_fidxs)); }
- static Type[] xdual(Type[] ts) {
- Type[] dual = new Type[ts.length];
- for( int i=0; i<ts.length; i++ )
- dual[i] = ts[i].dual();
- return dual;
- }
-
- @Override TypeFunPtr rdual() {
- if( _dual!=null ) return dual();
- assert !_terned;
- Type[] sigs = new Type[_sig.length];
- TypeFunPtr d = malloc(dual0(),!_open,sigs,null, XInt.dual(_fidxs));
- (_dual = d)._dual = this; // Cross link duals
- // Return rdual
- d._ret = _ret._terned ? _ret.dual() : _ret.rdual();
- // Signature rdual
- for( int i=0; i<_sig.length; i++ )
- sigs[i] = _sig[i]._terned ? _sig[i].dual() : _sig[i].rdual();
- return d;
- }
-
- // RHS is NIL; deep-dual when crossing the centerline
- @Override public Type meet0() {
- if( _nil==3 ) return this;
- if( _nil==2 ) return make((byte)3,_open,_sig,_ret,_fidxs);
-
- return make((byte)3,_open,_sig,_ret,_fidxs);
- }
-
- @Override public boolean isHigh() { return _nil <= 1 || (_nil==2 && _fidxs== XInt.EMPTY); }
-
- @Override public boolean _isConstant() { return (_nil==2 && XInt.isConstant(_fidxs)) || (_nil==3 && _fidxs== XInt.EMPTY); }
-
- @Override boolean _isFinal() { return true; }
- @Override TypeFunPtr _makeStorage() { return this; }
-
- @Override TypeFunPtr _close( String name, HashMap<String, Type> TYPES ) {
- Type[] sig = new Type[_sig.length];
- TypeFunPtr fun = malloc(_nil,true,sig,null,_fidxs);
- // Now start the recursion
- fun._ret = _ret._close(name, TYPES );
- for( int i=0; i<sig.length; i++ )
- sig[i] = _sig[i]._close(name, TYPES );
- return fun;
- }
-
- // Replace recursively all TypeBuilders with cyclic TypeStructs
- @Override Type _upgradeType(HashMap<String,Type> TYPES) {
- Type[] sig = new Type[_sig.length];
- TypeFunPtr fun = malloc(_nil,_open,sig,null,_fidxs);
- // Now start the recursion
- fun._ret = _ret._upgradeType(TYPES);
- for( int i=0; i<sig.length; i++ )
- sig[i] = _sig[i]._upgradeType(TYPES);
- return fun;
- }
-
- @Override public int log_size() { return 2; } // (1<<2)==4-byte pointers
-
- public Type arg(int i) { return _sig[i]; }
- public int[] fidxs() { return _fidxs; }
- public Type ret() { return _ret; }
- public int nargs() { return _sig.length; }
- public int fidx() { return XInt.onlyBit(_fidxs); }
- public int nfcns() {
- if( XInt.isHigh(_fidxs) ) return Integer.MAX_VALUE; // Infinite choices
- return XInt.bitCount(_fidxs);
- }
-
-
- // Used to walk over type structures
- @Override public int nkids() { return _sig.length+1; }
- @Override public Type at( int idx ) {
- return idx == _sig.length ? _ret : _sig[idx];
- }
- @Override public void set( int idx, Type t ) {
- if( idx == _sig.length ) _ret = t;
- else _sig[idx] = t;
- }
-
-
- @Override
- int hash() {
- long hash = _ret.hashCode() ^ XInt.hash(_fidxs) ^ super.hash();
- for( Type arg : _sig )
- hash = hash*19 ^ arg.hashCode();
- return Utils.fold(hash);
- }
-
- @Override
- boolean eq(Type t) {
- // Recursive; so use cyclic equals
- if( !VISIT.isEmpty() ) {
- assert TypeStruct.CEQUALS.isEmpty();
- boolean rez = cycle_eq(t);
- TypeStruct.CEQUALS.clear();
- return rez;
- }
- TypeFunPtr fun = (TypeFunPtr)t; // Invariant
- if( !super.eq(fun) || _open != fun._open || _fidxs != fun._fidxs || _sig.length != fun._sig.length ) return false;
- if( _sig == fun._sig && _ret == fun._ret ) return true;
-
- if( !_ret.eq(fun._ret) ) return false;
- for( int i = 0; i < _sig.length; i++ )
- if( !_sig[i].eq(fun._sig[i]) )
- return false;
- return true;
- }
- @Override boolean cycle_eq(Type t) {
- if( t._type != TFUNPTR ) return false;
- TypeFunPtr fun = (TypeFunPtr)t; // Invariant
- if( !super.eq(fun) || _open != fun._open || _fidxs != fun._fidxs || _sig.length != fun._sig.length ) return false;
- if( _sig == fun._sig && _ret == fun._ret ) return true;
-
- // Check to see if we've ever compared this pair of types before;
- // if so, then assume the cycle is equal here.
- int pid = pid(fun);
- if( TypeStruct.CEQUALS.find(pid)!= -1 ) return true;
- TypeStruct.CEQUALS.push(pid);
- if( !_ret.cycle_eq(fun._ret) ) return false;
- for( int i = 0; i < _sig.length; i++ )
- if( !_sig[i].cycle_eq(fun._sig[i]) )
- return false;
- return true;
- }
-
- @Override public String str() {
- if( this==BOT ) return "FunBot";
- if( this==BOT.dual() ) return "FunTop";
- return "{"+printFIDX()+"}";
- }
-
- @Override
- SB _print(SB sb, BitSet visit, boolean html ) {
- if( this==BOT ) return sb.p("FunBot");
- if( this==BOT.dual() ) return sb.p("FunTop");
- sb.p(x()).p("{ ");
- if( _sig!=null )
- for( Type t : _sig )
- sb.p(t==null ? "---" : t.str()).p(" "); // Short form in signature
- if( !_open ) sb.p("^ ");
- _ret.print(sb.p(html ? "&rarr; " : "-> "),visit,html).p(" #");
- // Print fidxs
- return sb.p(printFIDX()).p("}").p(q());
- }
- String printFIDX() {
- String tilde = isHigh() ? "~" : "";
- String fidx = XInt.str(_fidxs);
- return tilde+fidx;
- }
-
- // Usage: for( long fidxs=fidxs(); fidxs!=0; fidxs=nextFIDX(fidxs) ) { int fidx = Long.numberOfTrailingZeros(fidxs); ... }
- //public static long nextFIDX(long fidxs) { return fidxs & (fidxs-1); }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
deleted file mode 100644
index a2cf08b0..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
+++ /dev/null
@@ -1,185 +0,0 @@
-package com.seaofnodes.simple.type;
-
-import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.AryInt;
-import com.seaofnodes.simple.util.Utils;
-import java.util.ArrayList;
-import java.util.HashMap;
-
-/**
- * Integer Type
- */
-public class TypeInteger extends TypeScalar {
- /**
- * Describes an integer *range* - everything from min to max; both min and
- * max are inclusive. If min==max, this is a constant.
- * <br>
- * If min <= max, this is a below center (towards bottom).
- * If min > max, this is an above center (towards top).
- */
- public long _min, _max;
-
- public byte _widen;
-
- private static final Ary<TypeInteger> FREE = new Ary<>(TypeInteger.class);
- private TypeInteger(long min, long max, byte widen) { super(TINT); init(min,max,widen); }
- private TypeInteger init(long min, long max, byte widen) {
- _min = min; _max = max; _widen=widen;
- return this;
- }
- public static TypeInteger malloc(long lo, long hi, byte widen) { return FREE.isEmpty() ? new TypeInteger(lo,hi,widen) : FREE.pop().init(lo,hi,widen); }
- public static TypeInteger make(long lo, long hi) { return make(lo,hi,(byte)0); }
- public static TypeInteger make(long lo, long hi, byte widen) {
- TypeInteger i = malloc(lo,hi,widen);
- TypeInteger t2 = i.intern();
- return t2==i ? i : t2.free(i);
- }
- public TypeInteger makeWide(byte widen) {
- if( widen == _widen ) return this;
- if( _min == Long.MIN_VALUE && _max == Long.MAX_VALUE )
- return TypeInteger.BOT;
- if( _min==_max ) return this; // Leave constants always narrow
- return make(_min,_max,widen);
- }
- @Override TypeInteger free(Type t) {
- TypeInteger i = (TypeInteger)t;
- i._min = i._max = 0;
- i._widen = 0;
- i._hash = 0;
- i._dual = null;
- FREE.push(i);
- return this;
- }
-
- public Type same_but_slightly_wider_than( ) {
- return _widen < 3 ? make(_min,_max, (byte)(_widen+1)) : BOT;
- }
- // Force max widen
- @Override public Type widen() {
- return _widen==3 ? this : make(_min,_max,(byte)3);
- }
- public static TypeInteger constant(long con) { return make(con, con); }
-
- public final static TypeInteger ZERO= make(0,0);
- public final static TypeInteger FALSE=ZERO;
- public final static TypeInteger TRUE= make(1,1);
- public final static TypeInteger TWO = make(2,2); // Shows up in some Simple tests as the starting argument
-
- public final static TypeInteger I1 = make(-1,0,(byte)3);
- public final static TypeInteger I8 = make(-128,127,(byte)3);
- public final static TypeInteger I16 = make(-32768,32767,(byte)3);
- public final static TypeInteger I32 = make(-1L<<31,(1L<<31)-1,(byte)3);
- public final static TypeInteger BOT = make(Long.MIN_VALUE,Long.MAX_VALUE,(byte)3);
- public final static TypeInteger TOP = (TypeInteger)BOT.dual();
-
- public final static TypeInteger U1 = make(0,1,(byte)3);
- public final static TypeInteger BOOL= U1;
- public final static TypeInteger U8 = make(0,255,(byte)3);
- public final static TypeInteger U16 = make(0,65535,(byte)3);
- public final static TypeInteger U32 = make(0,(1L<<32)-1,(byte)3);
-
- public final static TypeInteger FATBOOL = make(0,1,(byte)2);
-
- public static void gather(ArrayList<Type> ts) {
- ts.add(I32); ts.add(BOT); ts.add(U1); ts.add(I1); ts.add(U8); ts.add(ZERO); ts.add(FATBOOL);
- // Ranges before and after excluding zero, at each widening level.
- for( byte widen=0; widen<=3; widen++ ) {
- ts.add(make(0,255,widen));
- ts.add(make(1,255,widen));
- ts.add(make(-255,0,widen));
- ts.add(make(-255,-1,widen));
- }
- }
-
- @Override public String str() {
- if( _isConstant() ) return ""+_min;
- long lo = _min, hi = _max;
- String x = "";
- if( hi < lo ) {
- lo = _max; hi = _min;
- x = "~";
- }
- return x+_str(lo,hi);
- }
- private static String _str(long lo, long hi) {
- if( lo==Long.MIN_VALUE && hi==Long.MAX_VALUE ) return "i64";
- if( lo== 0 && hi== 1 ) return "bool";
- if( lo== -1 && hi== 0 ) return "i1";
- if( lo== -128 && hi== 127 ) return "i8";
- if( lo== -32768 && hi== 32767 ) return "i16";
- if( lo== -1L<<31 && hi==(1L<<31)-1 ) return "i32";
- if( lo== 0 && hi== 255 ) return "u8";
- if( lo== 0 && hi== 65535 ) return "u16";
- if( lo== 0 && hi==(1L<<32)-1 ) return "u32";
- return "["+lo+"-"+hi+"]";
- }
-
- @Override public boolean isHigh () { return _min > _max; }
-
- @Override public int log_size() {
- if( isHigh() ) return 0; // High types are dead, and should never hit code emission.
- if( this==I8 || this==U8 || this==BOOL ) return 0; // 1<<0 == 1 bytes
- if( this==I16 || this==U16 ) return 1; // 1<<1 == 2 bytes
- if( this==I32 || this==U32 ) return 2; // 1<<2 == 4 bytes
- if( this==BOT ) return 3; // 1<<3 == 8 bytes
- if( _isConstant() ) return log(_min); // just const here
- int lo = log(_min);
- int hi = log(_max);
- return Math.max(lo,hi);
- }
-
- private static int log(long x) {
- if( -0xFFL <= x && x <= 0xFFL) return 0;
- if( -0xFFFFL <= x && x <= 0xFFFFL) return 1;
- if(-0xFFFFFFFFL <= x && x <= 0xFFFFFFFFL) return 2;
- return 3;
- }
-
- public long value() { assert _isConstant(); return _min; }
-
- // AND-mask of forced zeros. e.g. unsigned types will return their mask;
- // u8 will return 0xFF. But also a range of 16-18 (0x10-0x12) will return
- // 0x13 - no value in the range {16,17,18} will allow bit 0x04 to be set.
- public long mask() {
- if( isHigh() ) return 0;
- if( isConstant() ) return _min;
- // Those bit positions which differ min to max
- long x = _min ^ _max;
- // Highest '1' bit in the differ set. Since the range is from min to
- // max, all values below here are possible.
- long ff1 = Long.highestOneBit(x);
- // Make a all-1's mask from ff1, and set over the same bits (either min
- // or max is ok).
- long mask = _min | (ff1-1) | ff1;
- return mask;
- }
-
- @Override
- public Type xmeet(Type other) {
- // Invariant from caller: 'this' != 'other' and same class (TypeInteger)
- TypeInteger i = (TypeInteger)other; // Contract
- return make(Math.min(_min,i._min), Math.max(_max,i._max), (byte)Math.max(_widen,i._widen));
- }
-
- @Override TypeInteger xdual() { return malloc(_max,_min,(byte)(3-_widen)); }
-
- @Override public TypeInteger nonZero() {
- if( isHigh() ) return this;
- if( this==ZERO ) return null; // No sane answer
- if( _min==0 ) return make(1,Math.max(_max,1),_widen); // specifically good on BOOL
- if( _max==0 ) return make(_min,-1,_widen);
- return this;
- }
- @Override public Type makeZero() { return ZERO; }
- @Override boolean _isConstant() { return _min == _max; }
- @Override Type _makeStorage() {
- if( _isConstant() ) return BOT;
- return isHigh() ? dual() : this;
- }
-
-
- @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/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeMem.java
deleted file mode 100644
index 4234a5e5..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeMem.java
+++ /dev/null
@@ -1,229 +0,0 @@
-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(int alias) { return make(alias,_t,_one,_clz,_final,_escFs,_escAs); }
- public TypeMem makeFrom(int[] fidxes, int[] aliases) {
- return make(_alias,_t,_one,_clz,_final,fidxes,aliases);
- }
- // Existing escapes plus `t` escapes
- public TypeMem escapes(Type t) {
- return make(_alias,_t,_one,_clz,_final, escapeFIDX(_escFs,t), escapeAlias(_escAs,t));
- }
- // Existing escapes plus `t` escapes
- public TypeMem escapesFrom(Type t) {
- return make(_alias,_t,_one,_clz,_final, escapeFIDX(_escFs,t), escapeAlias(_escAs,t));
- }
- public TypeMem escapesAliases(Type t) {
- return make(_alias,_t,_one,_clz,_final, _escFs, escapeAlias(_escAs,t));
- }
-
- public static final TypeMem TOP = make(1, Type.TOP ,true ,true, true , XInt.EMPTY, XInt.EMPTY);
- public static final TypeMem BOT = make(1, Type.BOTTOM,false,false,true , XInt.FULL , XInt.FULL );
- public static final TypeMem START= make(1,Type.BOTTOM,false,false,false, XInt.EMPTY, XInt.EMPTY);
- public static final TypeMem SELF_MEM = make(1, TypeStruct.BOT);
-
- public static void gather(ArrayList<Type> ts) {
- ts.add(make(2,Type.NIL));
- ts.add(make(2,TypeInteger.ZERO));
- ts.add(BOT);
- ts.add(SELF_MEM);
- ts.add(START);
- ts.add(make(2,TypeInteger.ZERO,true,false,true,XInt.EMPTY,XInt.make(2)));
- ts.add(make(2,TypeInteger.BOT,false,false,false,XInt.EMPTY,XInt.FULL));
- }
-
- @Override
- TypeMem xmeet(Type t) {
- TypeMem that = (TypeMem) t; // Invariant: TypeMem and unequal
- if( this==TOP ) return that;
- if( that==TOP ) return this;
- if( this==BOT ) return BOT;
- if( that==BOT ) return BOT;
- // Treat a alias-1 has either very-high or very-low, according to "_t"
- int alias = _alias==that._alias ? _alias // Same-same
- : this._alias==1 && this._t.isHigh() ? that._alias // this is high-1 alias, so take that
- : that._alias==1 && that._t.isHigh() ? this._alias // that is high-1 alias, so take this
- : 1;
- Type mt = _t.meet(that._t);
- int[] fidxs = XInt.meet( _escFs, that._escFs );
- int[] aliases= XInt.meet( _escAs, that._escAs );
- // Singleton and class facts are kept only when both sides agree.
- // Finality is a low-side fact, and flips on the dual side.
- boolean highAlias1 = (this._alias==1 && this._t.isHigh()) || (that._alias==1 && that._t.isHigh());
- boolean xfinal = highAlias1 ? (_final & that._final) : (_final | that._final);
- return make(alias, mt, _one & that._one, _clz & that._clz, xfinal, fidxs, aliases);
- }
-
- @Override
- Type xdual() { return malloc(_alias,_t.dual(),!_one,!_clz,!_final, XInt.dual(_escFs), XInt.dual(_escAs)); }
-
- @Override TypeMem rdual() {
- if( _dual!=null ) return dual();
- assert !_terned;
- TypeMem d = malloc(_alias,null,!_one,!_clz,!_final, XInt.dual(_escFs), XInt.dual(_escAs));
- (_dual = d)._dual = this; // Cross link duals
- d._t = _t._terned ? _t.dual() : _t.rdual();
- if( d._alias==1 && !d._one ) { assert !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("Should not reach here: memory has no scalar storage size"); }
- @Override boolean _isFinal() { throw Utils.TODO("Should not reach here"); }
- @Override TypeMem _makeStorage() { throw Utils.TODO("Should not reach here"); /*return make(_alias,_t._makeStorage(),_one,_clz,_final,_escFs,_escAs);*/ }
-
- @Override TypeMem _close( String name, HashMap<String, Type> TYPES ) { return malloc(_alias,_t._close(name, TYPES ),_one,_clz,_final,_escFs,_escAs); }
-
- @Override Type _upgradeType(HashMap<String,Type> TYPES) {
- return make(_alias,_t._upgradeType(TYPES),_one,_clz,_final,_escFs,_escAs);
- }
-
- @Override int hash() { return 9876543 + _alias + _t.hashCode() + (_one ? 8196 : 0) + (_clz ? 4098 : 0) + (_final ? 16392 : 0) + XInt.hash(_escFs) + XInt.hash(_escAs); }
-
- @Override boolean eq(Type t) {
- TypeMem that = (TypeMem) t; // Invariant
- return _alias == that._alias && _t == that._t && _one == that._one && _clz == that._clz && _final == that._final && _escFs == that._escFs && _escAs == that._escAs;
- }
-
- @Override boolean cycle_eq(Type t) {
- 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; }
-
-
- @Override public SB _print(SB sb, BitSet visit, boolean html) {
- if( this==BOT ) return sb.p("MemBot");
- if( this==TOP ) return sb.p("MemTop");
- sb.p('#');
- if( !_final ) sb.p('!'); // Mutable memory
- if( _one ) sb.p(_clz ? 'z' : '-');
- if( _alias==0 ) return sb.p(_t._type==TTOP ? "TOP" : "BOT");
- sb.p(_alias).p(":[");
- _t.print(sb,visit,html).p(",");
- XInt.str(sb,_escFs).p(",");
- return XInt.str(sb,_escAs).p("]");
- }
-
- @Override public String str() {
- if( this==BOT ) return "MemBot";
- if( this==TOP ) return "MemTop";
- SB sb = new SB().p('#');
- if( !_final ) sb.p('!'); // Mutable memory
- if( _one ) sb.p(_clz ? 'z' : '-');
- if( _alias==0 ) return sb.p(_t._type==TTOP ? "TOP" : "BOT").toString();
- sb.p(_alias).p(":[").p(_t.str()).p(",");
- XInt.str(sb,_escFs).p(",");
- return XInt.str(sb,_escAs).p("]").toString();
- }
-
-
- // Tracking bulk escaped aliases and fidxs
-
- // Escape all FIDXs from t
- private static int[] escapeFIDX( int[] xs, Type t ) {
- return switch(t) {
- case TypeMemPtr tmp -> escapeFIDX(xs,tmp._obj);
- case TypeStruct ts -> ts._open && !ts.isAry()
- ? XInt.FULL // Open structs can have any fields
- : XInt.meet(xs,ts.fidxs());
- case TypeFunPtr tfp -> XInt.meet(xs,tfp.fidxs());
- case TypeMem mem -> XInt.meet(xs,mem._escFs);
- 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 && !ts.isAry()
- ? 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/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
deleted file mode 100644
index 73ca6190..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
+++ /dev/null
@@ -1,179 +0,0 @@
-package com.seaofnodes.simple.type;
-
-import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.AryInt;
-import com.seaofnodes.simple.util.SB;
-import java.util.*;
-
-/**
- * Represents a Pointer to memory.
- *
- * Null is generic pointer to non-existent memory.
- * *void is a non-Null pointer to all possible refs, both structs and arrays.
- * Pointers can be to specific struct and array types, or a union with Null.
- * The distinguished *$BOT ptr represents union of *void and Null.
- * The distinguished *$TOP ptr represents the dual of *$BOT.
- */
-public class TypeMemPtr extends TypeNil {
- // A TypeMemPtr is pair (obj,nil)
- // where obj is a TypeStruct, possibly TypeStruct.BOT/TOP
- // where nil is an explicit null is allowed or not
-
- // Examples:
- // (Person,false) - a not-nil Person
- // (Person,true ) - a Person or a nil
- // (BOT ,false) - a not-nil void* (unspecified struct)
- // (TOP ,true ) - a nil
-
- public TypeStruct _obj;
- // Deep read-only access through this pointer; independent of slot finality.
- public boolean _ro;
-
- public boolean _one; // Singleton pointer value; the pointer itself is constant.
- public boolean _pub; // Escaped/public pointer. False means constructor-private.
-
- private static final Ary<TypeMemPtr> FREE = new Ary<>(TypeMemPtr.class);
- private TypeMemPtr(byte nil, TypeStruct obj, boolean one, boolean pub) { super(TMEMPTR,nil); init(nil,obj,one,pub); }
- private TypeMemPtr init(byte nil, TypeStruct obj, boolean one, boolean pub) { _nil=nil; _obj=obj; _ro=obj==TypeStruct.BOT; _one=one; _pub=pub; return this; }
- // Return a filled-in TypeMemPtr; either from free list or alloc new.
- static TypeMemPtr malloc(byte nil, TypeStruct obj, boolean one, boolean pub) {
- return FREE.isEmpty() ? new TypeMemPtr(nil,obj,one,pub) : FREE.pop().init(nil,obj,one,pub);
- }
-
- // All fields directly listed
- public static TypeMemPtr make(byte nil, TypeStruct obj, boolean one) {
- return make(nil,obj,one,true);
- }
- public static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean pub) {
- return make(nil,obj,one, pub,obj==TypeStruct.BOT);
- }
- private static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean pub, boolean ro) {
- TypeMemPtr tmp = malloc(nil, obj, one, pub).access(ro);
- TypeMemPtr t2 = tmp.intern();
- if( t2==tmp ) return tmp;
- return VISIT.isEmpty() ? t2.free(tmp) : t2.delayFree(tmp);
- }
- @Override TypeMemPtr free(Type t) {
- TypeMemPtr tmp = (TypeMemPtr)t;
- tmp._nil = -99;
- tmp._obj = null;
- tmp._one = false;
- tmp._pub = true;
- tmp._dual = null;
- tmp._hash = 0;
- FREE.push(tmp);
- return this;
- }
- @Override boolean isFree() { return _obj==null; }
-
- static TypeMemPtr make(byte nil, TypeStruct obj) { return make(nil, obj, false); }
- public static TypeMemPtr make (TypeStruct obj) { return make((byte)2, obj); }
- public static TypeMemPtr makeNullable(TypeStruct obj) { return make((byte)3, obj); }
- public static TypeMemPtr makePrivate (TypeStruct obj) { return make((byte)2, obj, false, false); }
-
- public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj, _one, _pub,_ro); }
- public TypeMemPtr makeNullable() { return makeFrom((byte)3); }
- /** Inferred mutable local: nullable, public and non-singleton. */
- public TypeMemPtr makeVar() { return (TypeMemPtr)makeStorage(); }
- /** Public-memory shape: nullable and non-singleton, preserving the referent. */
- @Override TypeMemPtr _makeStorage() { return make(_nil,_obj._makeStorage(),false,true,_ro); }
- @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one, _pub,_ro); }
- @Override public Type nonZero() { return makeFrom((byte)(_nil <= 1 ? 1 : 2)); }
-
- private TypeMemPtr access(boolean ro) { _ro=ro; return this; }
-
- public TypeMemPtr withAccess(boolean ro) {
- if( _ro==ro ) return this;
- TypeMemPtr ptr = malloc(_nil, _obj, _one, _pub);
- ptr._ro = ro;
- TypeMemPtr res = ptr.intern();
- if( res==ptr ) return ptr;
- return VISIT.isEmpty() ? res.free(ptr) : res.delayFree(ptr);
- }
-
- // Writable slots can load and store references, so their element access
- // must agree in both directions. A read-only outer view can weaken access.
- @Override public boolean accessISA(Type dst) {
- if( !(dst instanceof TypeMemPtr ptr) ) return true;
- if( _ro && !ptr._ro ) return false;
- if( ptr._ro || _obj._fields==null || ptr._obj._fields==null || !_obj.isAry() || !ptr._obj.isAry() ) return true;
- Type a = _obj._fields[1]._t, b = ptr._obj._fields[1]._t;
- return a.accessISA(b) && b.accessISA(a);
- }
-
- // An abstract pointer, pointing to either a Struct or an Array.
- // Can also be null or not, so 4 choices {TOP,BOT} x {nil,not}
- public static final TypeMemPtr BOT = make((byte)3, TypeStruct.BOT);
- public static final TypeMemPtr TOP = BOT.dual();
- public static final TypeMemPtr NOTBOT = make((byte)2,TypeStruct.BOT);
-
- public static final TypeMemPtr TEST= make((byte)2, TypeStruct.TEST);
- public static void gather(ArrayList<Type> ts) { ts.add(NOTBOT); ts.add(BOT); ts.add(TEST); ts.add(TEST.makeRO()); }
-
- @Override
- public TypeNil xmeet(Type t) {
- TypeMemPtr that = (TypeMemPtr) t;
- assert !isFree() && !that.isFree();
- // Can't keep _one if mixing two unequal singletons; the result is not
- // either singleton. Once either side is public, the meet is public.
- boolean one = _one && that._one && (_obj==that._obj || !(_obj.isConAry() || that._obj.isConAry()));
- return make(xmeet0(that), (TypeStruct)_obj.meet(that._obj), one, _pub | that._pub,_ro | that._ro);
- }
-
- @Override
- TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), !_one, !_pub).access(!_ro); }
-
- @Override TypeMemPtr rdual() {
- if( _dual!=null ) return dual();
- assert !_terned;
- TypeMemPtr d = malloc(dual0(), null, !_one, !_pub).access(!_ro);
- (_dual = d)._dual = this; // Cross link duals
- d._obj = _obj._terned ? _obj.dual() : _obj.rdual();
- return d;
- }
-
- // RHS is NIL; do not deep-dual when crossing the center line
- @Override Type meet0() { return _nil==3 ? this : make((byte)3,_obj,false,true,_ro); }
-
- @Override boolean _isConstant() { return _one; }
- @Override boolean _isFinal() { return _ro; }
- @Override TypeMemPtr _makeRO() { return withAccess(true); }
- @Override TypeMemPtr _close( String name, HashMap<String, Type> TYPES ) { return malloc(_nil,_obj._close(name, TYPES ),_one,_pub).access(_ro); }
-
- @Override Type _upgradeType(HashMap<String,Type> TYPES) {
- return makeFrom((TypeStruct)_obj._upgradeType(TYPES));
- }
-
- @Override public int log_size() { return 3; } // (1<<3)==8-byte pointers
-
- @Override int hash() { return (_ro ? 8192 : 0) ^ _obj.hashCode() ^ super.hash() ^ (_one ? 2048 : 0) ^ (_pub ? 4096 : 0); }
-
- @Override boolean eq(Type t) {
- TypeMemPtr ptr = (TypeMemPtr)t; // Invariant
- return _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _obj == ptr._obj;
- }
- @Override boolean cycle_eq(Type t) {
- if( t._type != TMEMPTR ) return false;
- TypeMemPtr ptr = (TypeMemPtr)t; // Invariant
- return _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _obj.cycle_eq(ptr._obj);
- }
-
- @Override public int nkids() { return 1; }
- @Override public Type at( int idx ) { return _obj; }
- @Override public void set( int idx, Type t ) { _obj = (TypeStruct)t; }
-
-
- @Override public String str() {
- if( this==BOT ) return "PtrBot";
- if( this==TOP ) return "PtrTop";
- if( this== NOTBOT) return "*void";
- return x()+"*"+(_obj==null?"---":_obj.str())+q();
- }
-
- @Override SB _print(SB sb, BitSet visit, boolean html ) {
- if( this==BOT ) return sb.p("PtrBot");
- if( this==TOP ) return sb.p("PtrTop");
- if( this== NOTBOT) return sb.p("*void");
- return _obj.print(sb.p(x()).p("*"),visit,html).p(q());
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeNil.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeNil.java
deleted file mode 100644
index 2c1c1d76..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeNil.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package com.seaofnodes.simple.type;
-
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-import java.util.ArrayList;
-
-/**
- * Nil-able Scalar types
- */
-public abstract class TypeNil extends TypeScalar {
- // 0 = high-subclass choice nil
- // 1 = high-subclass no nil
- // 2 = low -subclass no nil
- // 3 = low -subclass also nil
- byte _nil;
-
- TypeNil(byte t, byte nil ) { super(t); _nil = nil; }
-
- public static void gather(ArrayList<Type> ts) { }
-
- abstract TypeNil makeFrom(byte nil);
-
- byte xmeet0(TypeNil that) { return (byte)Math.max(_nil,that._nil); }
-
- byte dual0() { return (byte)(3-_nil); }
-
- // RHS is NIL
- abstract Type meet0();
-
- // RHS is XNIL
- Type meetX() {
- return _nil==0 ? XNIL : (_nil<=2 ? TypePtr.NPTR : TypePtr.PTR);
- }
-
- Type nmeet(TypeNil tn) {
- // Invariants: both are TypeNil subclasses and unequal classes.
- // If this is TypePtr, we went to TypePtr.nmeet and not here.
- // If that is TypePtr, this is not (invariant); reverse and go again.
- if( tn instanceof TypePtr ts ) return ts.nmeet(this);
-
- // Two mismatched TypeNil, no Scalar.
- if( _nil==0 && tn._nil==0 ) return XNIL;
- if( _nil<=2 && tn._nil<=2 ) return TypePtr.NPTR;
- return TypePtr.PTR;
- }
-
- @Override public boolean isHigh () { return _nil <= 1; }
- @Override public boolean isHighOrConst() { return isHigh() || isConstant(); }
-
- public boolean notNull() { return _nil==1 || _nil==2; }
- public boolean nullable() { return _nil==3; }
-
- @Override boolean _isConstant() { return false; }
-
- final String q() { return _nil == 1 || _nil == 2 ? "" : "?"; }
- final String x() { return isHigh() ? "~" : ""; }
-
- int hash() { return _nil<<17; }
-
- @Override boolean eq(Type ptr) { return ptr instanceof TypeNil nil && _nil ==nil._nil; }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypePtr.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypePtr.java
deleted file mode 100644
index 7e9e70e4..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypePtr.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.seaofnodes.simple.type;
-
-import com.seaofnodes.simple.util.AryInt;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.HashMap;
-
-/**
- * Represents a Scalar; a single register-sized value.
- */
-public class TypePtr extends TypeNil {
- private static final String[] STRS = new String[]{"~ptr","~nptr","nptr","ptr"};
-
- private TypePtr(byte nil) { super(TPTR,nil); }
-
- // An abstract pointer, pointing to either a Struct or an Array.
- // Can also be null or not, so 4 choices {TOP,BOT} x {nil,not}
- public static TypePtr XPTR = new TypePtr((byte)0).intern();
- public static TypePtr XNPTR= new TypePtr((byte)1).intern();
- public static TypePtr NPTR = (TypePtr)XNPTR.dual();
- public static TypePtr PTR = (TypePtr)XPTR.dual();
- private static final TypePtr[] PTRS = new TypePtr[]{XPTR,XNPTR,NPTR,PTR};
-
- public static void gather(ArrayList<Type> ts) { ts.add(PTR); ts.add(NPTR); }
-
- TypeNil makeFrom(byte nil) { throw Utils.TODO("Should not reach here: plain pointer nil state is canonical"); }
-
- @Override public TypeNil xmeet(Type t) {
- TypePtr that = (TypePtr) t;
- return PTRS[xmeet0(that)];
- }
-
- @Override TypePtr xdual() { return new TypePtr((byte)(3-_nil)); }
-
- // High scalar loses, low scalar wins
- @Override TypeNil nmeet(TypeNil tn) {
- if( _nil==0 ) return tn; // High scalar loses
- if( _nil==1 ) return tn.makeFrom(xmeet0(tn)); // High scalar loses
- if( _nil==2 ) return tn._nil==3 ? PTR : NPTR; // Low scalar wins
- return PTR; // this
- }
-
-
- // RHS is NIL
- @Override Type meet0() { return isHigh() ? NIL : PTR; }
- // RHS is XNIL
- // 0->xscalar, 1->nscalar, 2->nscalar, 3->scalar
- @Override Type meetX() { return _nil==0 ? XNIL : (_nil==3 ? PTR : NPTR); }
-
- @Override TypePtr _makeStorage() { return PTR; }
-
-
- @Override public String str() { return STRS[_nil]; }
- @Override public SB _print(SB sb, BitSet visit, boolean html) { return sb.p(str()); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeRPC.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeRPC.java
deleted file mode 100644
index 7acc99de..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeRPC.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package com.seaofnodes.simple.type;
-
-import com.seaofnodes.simple.util.Ary;
-import java.util.*;
-
-/**
- * Return Program Control or Return PC or RPC
- */
-public class TypeRPC extends Type {
-
- // A set of CallEndNode IDs (or StopNode); commonly just one. High-bit
- // XInt sets represent complements/infinite sets.
- public int[] _rpcs;
-
- private static final Ary<TypeRPC> FREE = new Ary<>(TypeRPC.class);
- private TypeRPC init(int[] rpcs) { _rpcs = XInt.intern(rpcs); return this; }
- private TypeRPC() { super(TRPC); }
- private static TypeRPC malloc(int[] rpcs) {
- return (FREE.isEmpty() ? new TypeRPC() : FREE.pop()).init(rpcs);
- }
- private static TypeRPC make(int[] rpcs) {
- TypeRPC rpc = malloc(rpcs);
- TypeRPC t2 = rpc.intern();
- return t2==rpc ? rpc : t2.free(rpc);
- }
- @Override TypeRPC free(Type t) {
- TypeRPC rpc = (TypeRPC)t;
- rpc._rpcs = null;
- rpc._dual = null;
- rpc._hash = 0;
- FREE.push(rpc);
- return this;
- }
- @Override boolean isFree() { return _rpcs==null; }
-
- public static TypeRPC constant(int cend) {
- return make(XInt.make(cend));
- }
-
- public int rpc() {
- return XInt.onlyBit(_rpcs);
- }
-
-
- public static final TypeRPC BOT = make(XInt.FULL);
- private static final TypeRPC TEST2 = constant(2);
- private static final TypeRPC TEST3 = constant(2);
- public static final TypeRPC EMPTY = make(XInt.EMPTY);
-
- public static void gather(ArrayList<Type> ts) { ts.add(BOT); ts.add(TEST2); ts.add(TEST3); }
-
- @Override public TypeRPC makeZero() { return EMPTY; }
-
-
- @Override public String str() {
- if( _rpcs == XInt.FULL ) return "$[ALL]";
- if( _rpcs == XInt.EMPTY ) return "$[]";
- return "$"+XInt.str(_rpcs);
- }
-
- @Override
- public TypeRPC xmeet(Type other) {
- TypeRPC rpc = (TypeRPC)other;
- return make(XInt.meet(_rpcs,rpc._rpcs));
- }
-
- @Override TypeRPC xdual() { return malloc(XInt.dual(_rpcs)); }
- @Override Type rdual() {
- TypeRPC d = xdual();
- (_dual = d)._dual = this;
- return d;
- }
-
- @Override boolean _isConstant() { return XInt.isConstant(_rpcs); }
- @Override TypeRPC _makeStorage() { return this; }
-
-
- @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/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeScalar.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeScalar.java
deleted file mode 100644
index e369e6a5..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeScalar.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.seaofnodes.simple.type;
-
-import com.seaofnodes.simple.util.SB;
-
-import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.HashMap;
-
-/**
- * The envelope for values which can live in an ordinary register or memory
- * field: integers, floats, memory pointers, and function pointers.
- *
- * `BOT` means any scalar value and `TOP` means no scalar value. They sit
- * strictly inside the global {@link Type#BOTTOM}/{@link Type#TOP} pair, which
- * also includes non-values such as control and memory.
- */
-public class TypeScalar extends Type {
- protected TypeScalar(byte type) { super(type); }
- private TypeScalar() { this(TSCALAR); }
-
- public static final TypeScalar BOT = new TypeScalar().intern();
- public static final TypeScalar TOP = BOT.dual();
-
- public static void gather(ArrayList<Type> ts) { ts.add(BOT); }
-
- /** Meet two different scalar families. */
- final Type smeet(TypeScalar scalar) {
- if( _type == TSCALAR ) return isHigh() ? scalar : this;
- if( scalar._type == TSCALAR ) return scalar.isHigh() ? this : scalar;
- return BOT;
- }
-
- @Override Type xmeet(Type t) {
- TypeScalar scalar = (TypeScalar)t;
- return isHigh() ? scalar : this;
- }
-
- @Override TypeScalar xdual() { return new TypeScalar(); }
- @Override public boolean isHigh() { return this == TOP; }
- @Override Type _makeStorage() { return BOT; }
-
-
- @Override public String str() { return isHigh() ? "~scalar" : "scalar"; }
- @Override SB _print(SB sb, BitSet visit, boolean html) { return sb.p(str()); }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
deleted file mode 100644
index 67169009..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
+++ /dev/null
@@ -1,640 +0,0 @@
-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 makeInit() {
- Field[] fs = new Field[_fields.length];
- for( int i=0; i<_fields.length; i++ ) {
- if( _fields[i]._extern ) { fs[i] = _fields[i]; continue; }
- Type zero = _fields[i]._t.makeZero();
- fs[i] = _fields[i].makeFrom(_fields[i]._t != Type.BOTTOM && zero.isa(_fields[i]._t) ? zero : Type.TOP);
- }
- return make(_name,_open,_fref,fs);
- }
-
- public TypeStruct add( Field f ) {
- assert find(f._fname)==-1; // No double field names
- Field[] flds = Arrays.copyOf(_fields,_fields.length+1);
- flds[_fields.length] = f;
- return make(_name,_open,_fref,flds);
- }
- public TypeStruct replace( Field f ) {
- Field[] flds = Arrays.copyOf(_fields,_fields.length);
- flds[find(f._fname)] = f;
- return make(_name,_open,_fref,flds);
- }
- public TypeStruct remove( int i ) {
- Field[] flds = new Field[_fields.length-1];
- System.arraycopy(_fields, 0,flds,0,i);
- System.arraycopy(_fields,i+1,flds,i,flds.length-i);
- return make(_name,_open,_fref,flds);
- }
-
- public TypeStruct addOrUpdate(Field fld) {
- int idx = find(fld._fname);
- int len = idx == -1 ? (idx=_fields.length)+1 : _fields.length;
- Field[] flds = Arrays.copyOf(_fields,len);
- flds[idx] = fld;
- return make(_name,_open,_fref,flds);
- }
-
- public final TypeStruct close() {
- return (TypeStruct)recurOpen()._close(_name, null).recurClose();
- }
- @Override TypeStruct _close( String name, HashMap<String, Type> TYPES ) {
- assert (name==null) != (TYPES==null); // One or the other
- String namePlusFinal = isAry() ? (_name+(_fields[1]._final ? "~" : "!")).intern() : _name;
- TypeStruct ts = (TypeStruct)VISIT.get(namePlusFinal);
- if( ts!=null ) return ts;
- TypeStruct base = name==null ? (TypeStruct)TYPES.get(_name) : this;
- assert base != null;
- boolean open = base._fref ? base._open : name != _name && name != null && _open;
- ts = base.recurPre(namePlusFinal, _name, open, base._fref );
- Field[] flds = ts._fields;
- // Now start the recursion
- for( int i=0; i<flds.length; i++ )
- flds[i].setType(base._fields[i]._t._close(name, TYPES));
-
- // Note: this whole complex shenanigans here is because there are
- // actually two flavors of any array (non-/final contents) but I
- // allow the parser to represent both with the same name string.
- if( isAry() && _fields[1]._final && !flds[1]._final ) // This is a final array variant
- flds[1] = flds[1].makeFrom(true); // Add final to the base TYPES non-final array
-
- return ts;
- }
-
- // Needs full recursive treatment; e.g.
- // struct LL { LL? next; int !q; } // Q is mutable field.
- // Stored into a not-mutable, so need that version also:
- // struct LL { LL? next; int q; } // Q is NOT mutable field.
- // Now upgrade:
- // it is not the default version from TYPES.
- // it is fully cyclic
- //
- // Evil question: has no *open* internal fields, so can I assume it is fully correct and never needs upgrade?
- //
- // upgrades are for well-formed types with internal ptrs to early open
- // versions of other types, as happens in all the CoRecur tests.
- // During the co-recur tests, can I make non-default cycles with
- // weaker versions of other early types?
- // Seems plausible...
- @Override Type _upgradeType(HashMap<String,Type> TYPES) {
- TypeStruct base = (TypeStruct)TYPES.get(_name);
- if( this == base ) return this; // Already the good form
- TypeStruct ts = (TypeStruct)VISIT.get(_uid);
- if( ts!=null ) return ts;
- // Main work: replace open with closed
- if( _open )
- return base;
- // Build recursive version
- ts = recurPre(_uid,_name,false,_fref);
- Field[] flds = ts._fields;
- // Now start the recursion
- for( int i=0; i<flds.length; i++ )
- flds[i].setType(_fields[i]._t._upgradeType(TYPES));
- return ts;
- }
-
- static final AryInt CEQUALS = new AryInt();
-
- public static final String TOPNAME = "$TOP", BOTNAME = "$BOT";
- public static final TypeStruct TOP = make("$TOP",false);
- public static final TypeStruct BOT = TOP.dual();
- public static final TypeStruct TEST= make("test",false,Field.TEST);
- private static final TypeStruct ARY = makeAry("[]i64",TypeInteger.U32,-1,TypeInteger.BOT,-2,false);
- private static final TypeStruct STR = makeAry("[]u8" ,TypeInteger.U32,-1,TypeInteger.U8 ,-4,false);
- private static final TypeStruct ABC = makeAry("[]u8",-1,-4,TypeConAryB.ABC);
- // Representative unresolved forms for the exhaustive lattice tests:
- // bare forward ref, forward ref constrained by a field use, and definition.
- private static final TypeStruct FREF0 = forward("%FREF");
- private static final TypeStruct FREF1 = FREF0.add(Field.make("x",Type.BOTTOM,-20,false));
- private static final TypeStruct FDEF1 = make("%FREF",false,Field.make("x",TypeInteger.BOT,-20,false));
-
- // A pair of self-cyclic types
- private static final TypeStruct SINT0 = make("%SINT",false);
- private static final TypeStruct SFLT0 = make("%SFLT",false);
- public static final TypeStruct SINT1 = SINT0.add(Field.make("a", TypeInteger.U32, -1, false)).add(Field.make("s2",TypeMemPtr.make((byte)2,SFLT0),-2, false));
- public static final TypeStruct SFLT1 = SFLT0.add(Field.make("b", TypeFloat .F32, -3, false)).add(Field.make("s1",TypeMemPtr.make((byte)2,SINT1),-4, false));
-
- @Override public boolean isHigh() { return _name==TOPNAME; }
-
- public static void gather(ArrayList<Type> ts) {
- ts.add(BOT);
- ts.add(TEST);
- ts.add(ARY);
- ts.add(STR);
- ts.add(ABC);
- ts.add(FREF0);
- ts.add(FREF1);
- ts.add(FDEF1);
- ts.add(SINT0);
- ts.add(SFLT0);
- ts.add(SINT1);
- ts.add(SFLT1);
- // Break cyclic init: built a struct
- Field fcalloc = Field.make("calloc",TypeFunPtr.CALLOC,-2,true);
- TypeStruct scalloc = make("calloc",false,fcalloc);
- ts.add(scalloc);
-
- }
-
- // Find field index by name
- public int find(String fname) {
- for( int i=0; i<_fields.length; i++ )
- if( _fields[i]._fname==fname )
- return i;
- return -1;
- }
- public Field field(String fname) {
- int idx = find(fname);
- return idx== -1 ? null : _fields[idx];
- }
-
- // Find field index by alias
- public int findAlias( int alias ) {
- for( int i=0; i<_fields.length; i++ )
- if( _fields[i]._alias==alias )
- return i;
- return -1;
- }
-
- @Override
- TypeStruct xmeet(Type t) {
- TypeStruct that = (TypeStruct) t;
- assert !isFree() && !that.isFree();
- if( this==TOP ) return that;
- if( that==TOP ) return this;
- if( this==BOT ) return BOT;
- if( that==BOT ) return BOT;
-
- // if equal, no matters.
- // if short is BOT, chop ; recurPre on short.
- // if short is TOP, copy long; recurPre on long.
- TypeStruct min = _fields.length < that._fields.length ? this : that;
- if( _fields.length != that._fields.length && (!min._open ^ min!=this) )
- return that.xmeet(this);
-
- assert (_name==that._name) == (_name.equals(that._name)); // Strings are interned
- String name =
- that._name==TOPNAME ? _name :
- _name==TOPNAME ? that._name :
- that._name==_name ? _name :
- BOTNAME;
- if( name==BOTNAME ) return BOT;
-
- // Check all other fields are sanely similar; same struct type but
- // different field contents (e.g. field "age" is either 'int' or '18').
- int len = Math.min(_fields.length,that._fields.length);
- for( int i=0; i<len; i++ ) {
- Field f0 = _fields[i], f1 = that._fields[i];
- if( !f0._fname.equals(f1._fname) || f0._alias != f1._alias || f0._extern != f1._extern )
- return BOT;
- }
-
- // See if we get a mid-recursion hit; breaks cycles
- Integer pid = pid(that);
- TypeStruct ts = (TypeStruct)VISIT.get(pid);
- if( ts!=null ) return ts;
- // If not mid-recursion, we are now; flag recursion.
- boolean notRecur = VISIT.isEmpty();
- if( notRecur ) recurOpen();
-
- // Setup and install the type, prior to recursing, so we can find our
- // recursive self again.
- ts = recurPre(pid,name,_open | that._open,_fref & that._fref);
-
- // Recurse all common fields
- Field[] flds = ts._fields;
- for( int i=0; i<len; i++ ) {
- Field f0 = _fields[i], f1 = that._fields[i];
- flds[i].setType(f0._t.meet(f1._t ));
- flds[i]._final = f0._final | f1._final;
- }
-
- // Handle extra fields via `_open`
- for( int i=len; i<_fields.length; i++ )
- flds[i].setType(_fields[i]._t );
-
- // Cleanup post-recursion; intern/install at cycle end
- if( notRecur ) ts = (TypeStruct)ts.recurClose();
- return ts;
- }
-
- @Override
- TypeStruct xdual() {
- Field[] flds = new Field[_fields.length];
- for( int i=0; i<_fields.length; i++ )
- flds[i] = _fields[i].dual();
- return malloc(sdual(),!_open,!_fref,flds);
- }
-
- // Recursive dual
- @Override TypeStruct rdual() {
- if( _dual!=null ) return dual();
- assert !_terned;
- Field[] flds = new Field[_fields.length];
- TypeStruct d = malloc(sdual(),!_open,!_fref,flds);
- (_dual = d)._dual = this; // Cross link duals
- for( int i=0; i<_fields.length; i++ )
- flds[i] = _fields[i]._terned ? _fields[i].dual() : _fields[i].rdual();
- return d;
- }
-
- private String sdual() {
- return
- _name==TOPNAME ? BOTNAME :
- _name==BOTNAME ? TOPNAME :
- _name;
- }
-
- @Override boolean _isConstant() {
- assert !_open; // who is asking about constant open structs? if( _open ) return false; // Infinite BOT fields
- assert !VISIT.containsKey(_uid); //expect constants to not include self-cycles. if( VISIT.containsKey(_uid) ) return true; // Cycles assume constant
- VISIT.put(_uid,this);
- // Check all fields for being constant
- for( Field field : _fields )
- if( !field._isConstant() )
- return false;
- return true;
- }
-
- // All fields are final
- @Override boolean _isFinal() {
- if( _open ) return false; // May have more non-final fields
- if( VISIT.containsKey(_uid) ) // Test: been here before?
- return true; // Cycles assume final
- VISIT.put(_uid,this); // Set: don't do this again
- for( Field fld : _fields )
- if( !fld._isFinal() )
- return false;
- return true;
- }
-
- private TypeStruct recurPre(Object key, String name, boolean open, boolean fref) {
- // Make a clone of original; suitable for hashing so can build
- // e.g. TMPs to clone as part of cycles - but will fail the 'eq' check
- // until the entire cycle is built.
- Field[] flds = new Field[_fields.length];
- for( int i=0; i<flds.length; i++ )
- flds[i] = _fields[i].malloc(); // Blank copy, but can be hashed
- TypeStruct ts = malloc(name, open, fref, flds );
- ts._aliases = aliases();
- VISIT.put(key,ts);
- return ts;
- }
-
- private interface FieldTransform { Type apply(Type t); }
-
- private TypeStruct recurTransform(Object key, String name, boolean open, boolean fref, boolean xfinal, FieldTransform transform) {
- TypeStruct ts = (TypeStruct)VISIT.get(key);
- if( ts!=null ) return ts; // Already visited
- ts = recurPre(key,name,open,fref);
- Field[] flds = ts._fields;
- for( Field fld : flds )
- fld._final |= xfinal;
-
- // Now start the recursion
- for( int i=0; i<flds.length; i++ )
- flds[i].setType(transform.apply(_fields[i]._t));
-
- return ts;
- }
-
- // Make a read-only version
- @Override TypeStruct _makeRO() {
- return recurTransform(_name,_name,_open,_fref,true,Type::_makeRO);
- }
-
- @Override TypeStruct _makeStorage() {
- return recurTransform(_uid,_name,_open,false,false,Type::_makeStorage);
- }
-
- // log_size for a struct is not defined, unless its exactly some power of
- // 2. *Total size* is well-defined, and is available in the offsets.
- @Override public int log_size() { throw Utils.TODO("Should not reach here: structs have no scalar log size"); }
- @Override public int size() { return offset(_fields.length); }
- @Override public int alignment() {
- int align = 0;
- for( Field f : _fields )
- if( !f._extern ) align = Math.max(align, f._t.alignment());
- return align;
- }
-
- // If false, always false.
- // If true , maybe true, need to check recursive fields.
- private boolean static_eq( TypeStruct ts ) {
- assert (_name==ts._name) == (_name.equals(ts._name)); // Strings are interned
- return _name==ts._name && _open==ts._open && _fref==ts._fref && _fields.length==ts._fields.length;
- }
-
- @Override boolean eq(Type t) {
- TypeStruct ts = (TypeStruct)t; // Invariant
-
- // Recursive; so use cyclic equals
- if( !VISIT.isEmpty() ) {
- assert CEQUALS.isEmpty();
- boolean rez = cycle_eq(t);
- CEQUALS.clear();
- return rez;
- }
- // Normal equals
- if( !static_eq(ts) ) return false;
- if( _fields==ts._fields ) return true;
- for( int i = 0; i < _fields.length; i++ )
- if( !_fields[i].eq(ts._fields[i]) )
- return false;
- return true;
- }
-
- @Override boolean cycle_eq(Type t) {
- if( this==t ) return true;
- TypeStruct ts = (TypeStruct)t; // Invariant
- if( !static_eq(ts) ) return false;
- if( _fields==ts._fields ) return true;
- // Check to see if we've ever compared this pair of types before;
- // if so, then assume the cycle is equal here.
- int pid = pid(ts);
- if( CEQUALS.find(pid)!= -1 ) return true;
- CEQUALS.push(pid);
- // Recursively check fields
- for( int i = 0; i < _fields.length; i++ )
- if( !_fields[i].cycle_eq(ts._fields[i]) )
- return false;
- return true;
- }
-
- @Override
- int hash() {
- long hash = _name.hashCode() ^ (_open ? 4 : 0) ^ (_fref ? 8 : 0);
- for( Field f : _fields )
- hash = Utils.rot(hash,13) ^ ((long)f._fname.hashCode() * f._alias);
- return Utils.fold(hash);
- }
-
- @Override public int nkids() { return _fields.length; }
- @Override public Field at( int idx ) { return _fields[idx]; }
- @Override public void set( int idx, Type t ) { _fields[idx] = (Field)t; }
- // Tags 0-4 - closed +#nflds (+name)
- // Tags 5 - closed(+ nflds +name)
- // Tags 6-10- open +#nflds (+name)
- // Tags 11 - open (+ nflds +name)
-
-
- @Override
- SB _print(SB sb, BitSet visit, boolean html ) {
- if( this==BOT ) return sb.p("StructBot");
- if( this==TOP ) return sb.p("StructTop");
- if( isFree() ) return sb.p("FREE:").p(_name);
- if( isAry() && field("[]")._t instanceof TypeConAry con )
- return sb.p(con.str());
- sb.p(_name);
- if( isAry() )
- // Array names deliberately canonicalize mutable and immutable
- // element variants. The verbose debugger print must retain the
- // otherwise invisible lattice coordinate.
- return sb.p(_fields[1]._final ? "[final]" : "[mutable]");
- if( html )
- return sb;
- sb.p(" {");
- for( Field f : _fields )
- (f._t ==null ? sb.p("---") : f._t.print(sb,visit,html)).p(f._final ? " " : " !").p(f._fname).p("; ");
- if( _open && !_fref ) sb.p("... ");
- return sb.p("}");
- }
-
- @Override public String str() {
- if( this==BOT ) return "StructBot";
- if( this==TOP ) return "StructTop";
- return (isFree() ? "FREE:":"")+_name;
- }
-
-
- public boolean isAry() { return find("[]")!=-1; }
- public boolean isConAry() {
- return isAry() && _fields[1]._t instanceof TypeConAry;
- }
-
- public int aryBase() {
- assert isAry();
- if( _offs==null ) _offs = offsets();
- return _offs[1];
- }
- public int aryScale() {
- assert isAry();
- return _fields[1]._t.log_size();
- }
-
-
- // Field offsets as packed byte offsets
- private int[] _offs; // Lazily computed and not part of the type semantics
- public int offset(int idx) {
- if( _offs==null ) _offs = offsets();
- return _offs[idx];
- }
- private int[] offsets() { // Field byte offsets
- assert CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.FinalizeIR.ordinal();
- // Compute a layout for a collection of fields
- assert _fields != null; // No forward refs
-
- // Compute a layout
- _offs = new int[_fields.length+1];
- if( isAry() ) { // Arrays used fixed u32 len, padding to elems, var length
- assert _fields[1]._fname=="[]";
- // Pad out to the element alignment
- int align = _fields[1]._t.alignment();
- _offs[1] = (4 + ((1<<align)-1)) & -(1<<align);
- if( _fields[1]._t instanceof TypeConAry ary ) // Array length is well-defined for constants, not defined for runtime arrays
- _offs[2] = _offs[1]+ary.len();
-
- } else {
- int[] cnts = new int[5]; // Count of fields at log field size
- int flen = _fields.length;
- for( int i=0; i<flen; i++ )
- if( !_fields[i]._extern )
- cnts[_fields[i]._t.log_size()]++; // Log size is 0(byte), 1(i16/u16), 2(i32/f32), 3(i64/dbl)
- int off = 0, idx = 0; // Base common struct fields go here, e.g. Mark/Klass
- // Compute offsets to the start of each power-of-2 aligned fields.
- int[] offs = new int[4];
- for( int i=3; i>=0; i-- ) {
- offs[i] = off;
- off += cnts[i]<<i;
- }
- // Assign offsets to all fields.
- // Really a hidden radix sort.
- for( int i=0; i<flen; i++ ) {
- if( _fields[i]._extern ) { _offs[idx++] = -1; continue; }
- int log = _fields[i]._t.log_size();
- _offs[idx++] = offs[log]; // Field offset
- offs[log] += 1<<log; // Next field offset at same alignment
- cnts[log]--; // Count down, should be all zero at end
- }
- _offs[flen] = off; // Max struct size, no trailing padding
- }
- return _offs;
- }
-
- // Field order with increasing offset
- public int[] layout() {
- offset(0);
- int len = _offs.length;
- int[] is = new int[len];
- for( int i=0; i<len; i++ ) is[i] = i;
- // Stoopid hand rolled bubble sort
- for( int i=0; i<len; i++ )
- for( int j=i+1; j<len; j++ )
- if( _offs[is[j]] < _offs[is[i]] )
- { int tmp = is[i]; is[i] = is[j]; is[j] = tmp; }
- return is;
- }
-
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
deleted file mode 100644
index 0d70bdf2..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
+++ /dev/null
@@ -1,158 +0,0 @@
-package com.seaofnodes.simple.type;
-
-import com.seaofnodes.simple.util.*;
-import java.util.*;
-
-public class TypeTuple extends Type {
-
- public Type[] _types;
-
- private static final Ary<TypeTuple> FREE = new Ary<>(TypeTuple.class);
- private TypeTuple(Type[] types) { super(TTUPLE); _types = types; }
- private TypeTuple init( Type[] types ) { _types = types; return this; }
- private static TypeTuple malloc(Type[] types) {
- return FREE.isEmpty() ? new TypeTuple(types) : FREE.pop().init(types);
- }
- // All fields directly listed
- public static TypeTuple make(Type... types) {
- TypeTuple tt = malloc(types);
- TypeTuple t2 = tt.intern();
- return t2==tt ? tt : t2.free(tt);
- }
- @Override TypeTuple free(Type t) {
- TypeTuple tt = (TypeTuple)t;
- tt._types = null;
- tt._dual = null;
- tt._hash = 0;
- FREE.push(tt);
- return this;
- }
- @Override boolean isFree() { return _types==null; }
-
- public TypeTuple makeFrom(int idx, Type tx ) {
- if( _types[idx]==tx ) return this;
- Type[] types = _types.clone();
- types[idx] = tx;
- return make(types);
- }
- public TypeTuple meetFrom(int idx, Type t ) { return makeFrom(idx,_types[idx].meet(t)); }
-
- public static final TypeTuple BOT = malloc(new Type[0]).intern();
- public static final TypeTuple TOP = BOT.dual();
- // The default state, a triple of CONTROL, Memory, and Value, with bottoms
- // for all. This is the default StopNode type, when new functions are
- // being created and we cannot tell what the future holds. This is the
- // default ReturnNode type, returning what the worst a function can do.
- public static final TypeTuple STATE = make(Type.CONTROL,TypeMem.BOT,Type.BOTTOM);
- public static final TypeTuple STOP_HIGH = make(Type.CONTROL,TypeMem.START,Type.TOP);
-
- public static final TypeTuple TEST = make(TypeInteger.BOT,TypeMemPtr.TEST);
- public static final TypeTuple DEAD_NEW = make(TypeMemPtr.TOP,TypeMem.TOP);
-
- public static final TypeTuple IF_BOTH = make(Type. CONTROL,Type. CONTROL);
- public static final TypeTuple IF_NEITHER = make(Type.XCONTROL,Type.XCONTROL);
- public static final TypeTuple IF_TRUE = make(Type. CONTROL,Type.XCONTROL);
- public static final TypeTuple IF_FALSE = make(Type.XCONTROL,Type. CONTROL);
-
- public static void gather(ArrayList<Type> ts) { ts.add(BOT); ts.add(TEST); ts.add(STATE); ts.add(IF_TRUE); }
-
- @Override
- Type xmeet(Type other) {
- TypeTuple that = (TypeTuple)other; // contract from xmeet
- if( this==TOP ) return that;
- if( that==TOP ) return this;
- if( _types.length != that._types.length )
- return BOT;
- return make(TypeFunPtr.xmeet(_types,that._types));
- }
-
- @Override TypeTuple xdual() {
- if( _types.length==0 ) return malloc(null);
- boolean isCon=true;
- for( Type t : _types )
- if( t._dual!=t )
- // Not a constant
- return malloc(TypeFunPtr.xdual(_types));
- return this;
- }
- @Override TypeTuple rdual() {
- if( _dual!=null ) return dual();
- assert !_terned;
- assert _types.length>0;
- Type[] ts = new Type[_types.length];
- TypeTuple d = malloc(ts);
- (_dual = d)._dual = this; // Cross link duals
- // Collection rdual
- for( int i=0; i<_types.length; i++ )
- ts[i] = _types[i]._terned ? _types[i].dual() : _types[i].rdual();
- return d;
- }
-
- @Override boolean _isConstant() {
- for( Type t : _types )
- if( !t._isConstant() )
- return false;
- return true;
- }
- @Override boolean _isFinal() { throw Utils.TODO("Should not reach here: tuples are not stored object values"); }
-
- @Override TypeMemPtr _makeRO() { throw Utils.TODO("Should not reach here: tuples cannot be made read-only"); }
-
- @Override Type _upgradeType(HashMap<String,Type> TYPES) {
- Type[] ts = new Type[_types.length];
- for( int i=0; i<ts.length; i++ )
- ts[i] = _types[i]._upgradeType(TYPES);
- return make(ts);
- }
-
- @Override public int log_size() { throw Utils.TODO("Should not reach here: tuples have no scalar storage size"); }
- @Override public int alignment() {
- assert isConstant();
- int align = 0;
- for( Type t : _types )
- align = Math.max(align,t.alignment());
- return align;
- }
-
- public Type ctl() { assert _types.length>=3; return _types[0]; }
- public Type ret() { assert _types.length>=3; return _types[2]; }
- public TypeMem mem() { assert _types.length>=3;
- return _types[1] instanceof TypeMem tmem ? tmem : (_types[1].isHigh() ? TypeMem.START : TypeMem.BOT);
- }
-
- @Override public int nkids() { return _types.length; }
- @Override public Type at( int idx ) { return _types[idx]; }
- @Override public void set( int idx, Type t ) { _types[idx] = t; }
-
-
- @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/chapter-modules/src/main/java/com/seaofnodes/simple/type/XInt.java b/chapter-modules/src/main/java/com/seaofnodes/simple/type/XInt.java
deleted file mode 100644
index 126a4e64..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/type/XInt.java
+++ /dev/null
@@ -1,244 +0,0 @@
-package com.seaofnodes.simple.type;
-
-import com.seaofnodes.simple.util.*;
-import java.lang.Integer;
-import java.util.Arrays;
-
-// Stupidly overkill interned immutable int[]'s
-public abstract class XInt {
- private static final Ary<Ary<int[]>> FREES = new Ary(Ary.class);
- private static final Ary<Ary<int[]>> INTERNS = new Ary(Ary.class);
- private static int PROBES, HITS, NEWS;
-
- public static final int[] EMPTY = intern(new int[]{0, 0});
- public static final int[] FULL = intern(new int[]{0,-1});
- public static final int[] SET1 = make(1);
- public static final int[] SET3 = make(SET1, 2);
-
- static int idx(int x) { return (x>>5)+1; }
- static int mask(int x) { return 1<<(x&31); }
-
- // No hash set, still mutable
- public static boolean isMutable(int[] xs) { return xs[0]==0; }
- // Return hash code; must be set hence frozen
- public static int hash(int[] xs) { return xs[0]; }
-
- // High bit set (infinite 1's extension)
- public static boolean isHigh(int[] xs) { return xs[xs.length-1] < 0; }
-
- // Check for a bit
- public static boolean bit(int[] xs, int x) {
- int idx = idx(x);
- if( idx >= xs.length )
- return isHigh(xs);
- return (xs[idx] & mask(x))!=0;
- }
-
- // Number of bits set
- public static int bitCount(int[] xs) {
- assert !isHigh(xs); // Dont ask if infinite
- int cnt=0;
- for( int i=1; i<xs.length; i++ )
- cnt += Integer.bitCount(xs[i]);
- return cnt;
- }
-
- // Largest bit
- public static int max(int[] xs) {
- assert !isHigh(xs);
- return Integer.numberOfTrailingZeros(xs[xs.length-1])+((xs.length-1-1)<<5);
- }
-
- // Has 1 bit set
- public static boolean isConstant(int[] xs) { return !isHigh(xs) && bitCount(xs)==1; }
- // Return the 1 set bit or assert
- public static int onlyBit(int[] xs) {
- assert isConstant(xs);
- for( int i=1; i<xs.length; i++ )
- if( xs[i] != 0 )
- return Integer.numberOfTrailingZeros(xs[i]) + ((i-1)<<5);
- throw Utils.TODO("Should not reach here");
- }
-
- // Subset check: ys is a subset of xs
- public static boolean subset(int[] xs, int[] ys) {
- assert !isMutable(ys);
- if( xs==ys ) return true;
- if( xs==FULL ) return true;
- if( ys==EMPTY ) return true;
- int min = Math.min(xs.length,ys.length);
- for( int i=1; i<min; i++ )
- if( (xs[i] | ys[i])!=xs[i] )
- return false;
- if( xs.length == ys.length )
- return true;
- if( ys.length < xs.length )
- return !isHigh(ys); // If ys is infinite, it'll hit one of the xs extra zeros
- return isHigh(xs);
- }
-
-
- // Construct a set with 1 bit set
- public static int[] make(int x ) { return make(EMPTY,x); }
-
- // Add a bit
- public static int[] make(int[] xs, int x ) {
- assert !isMutable(xs);
- int idx = idx(x);
- if( idx >= xs.length && isHigh(xs) )
- return xs; // Set in the default infinite
- if( idx < xs.length && (mask(x) & xs[idx])!=0 )
- return xs; // Already set
- int len = Math.max(xs.length,idx+1);
- if( (x&31)==31 && idx+1==len )
- len++;
- int[] ys = free(len);
- System.arraycopy(xs,1,ys,1,xs.length-1);
- ys[idx] |= mask(x);
- return intern(ys);
- }
-
- // Interning, and life-cycle management
- // Intern the array
- public static int[] intern(int[] xs) {
- if( !isMutable(xs) ) return xs; // Already interned
- PROBES++;
-
- // Trim trailing 0s or -1s, use the infinite extend bit
- int len = xs.length;
- while( len > 2 &&
- ((xs[len-1]== 0 && xs[len-2]>=0) ||
- (xs[len-1]== -1 && xs[len-2]< 0) ) )
- len--;
- // Replace with trimmed
- if( len < xs.length ) {
- var ys = free(len);
- System.arraycopy(xs,1,ys,1,len-1);
- xs = _free(xs,ys);
- }
-
- // Compute lame hash code
- int hash = 0;
- for( int i=1; i<len; i++ )
- hash += xs[i];
- if( hash == 0 ) hash = 0xCAFEBABE;
- xs[0] = hash; // Set lame hashcode, and mark immutable
-
- // Get length-specific INTERN table
- var xss = INTERNS.atX(len);
- if( xss==null )
- INTERNS.setX( xs.length, xss = new Ary<>(int[].class));
-
- // Probe for a hit
- for( int[] ys : xss )
- if( Arrays.equals(xs,ys) ) // Hit!
- // Free new array and return old array
- { HITS++; return _free(xs,ys); }
-
- // Intern new array and return
- return xss.push(xs);
- }
-
- // Return Ary of free int[]s of fixed size
- private static Ary<int[]> _xss(int len) {
- Ary<int[]> xss = FREES.atX(len);
- if( xss==null )
- FREES.setX(len, xss = new Ary<>(int[].class));
- return xss;
- }
- // Pull free array from free list
- static int[] free(int len) {
- int[] xs = _xss(len).pop();
- if( xs==null )
- { NEWS++; xs = new int[len]; }
- assert isMutable(xs);
- return xs;
- }
- // Return ys. Put xs on free list
- static int[] _free(int[] xs, int[] ys) {
- Arrays.fill(xs,0); // Mutable again
- _xss(xs.length).push(xs);
- return ys;
- }
-
- // Meet: Set union
- public static int[] meet(int[] xs, int[] ys) {
- if( xs==ys ) return xs;
- boolean isX=true, isY=true;
- int min = Math.min(xs.length,ys.length);
- int max = Math.max(xs.length,ys.length);
- for( int i=1; i<max; i++ ) {
- int x = i < xs.length ? xs[i] : isHigh(xs) ? -1 : 0;
- int y = i < ys.length ? ys[i] : isHigh(ys) ? -1 : 0;
- if( (x | y)!=x ) isX=false;
- if( (x | y)!=y ) isY=false;
- }
- if( isX ) return xs;
- if( isY ) return ys;
- // Get a free one
- int[] ms = free(max);
- for( int i=1; i<min; i++ )
- ms[i] = xs[i] | ys[i];
- for( int i=min; i<ms.length; i++ ) {
- int x = i < xs.length ? xs[i] : isHigh(xs) ? -1 : 0;
- int y = i < ys.length ? ys[i] : isHigh(ys) ? -1 : 0;
- ms[i] = x | y;
- }
- return intern(ms);
- }
-
- // Dual: flip all the bits
- public static int[] dual(int[] xs) {
- int[] ds = free(xs.length);
- for( int i=1; i<xs.length; i++ )
- ds[i] = ~xs[i];
- return intern(ds);
- }
-
-
- // 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 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/chapter-modules/src/main/java/com/seaofnodes/simple/util/Ary.java b/chapter-modules/src/main/java/com/seaofnodes/simple/util/Ary.java
deleted file mode 100644
index d67801c2..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/util/Ary.java
+++ /dev/null
@@ -1,229 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/util/AryInt.java b/chapter-modules/src/main/java/com/seaofnodes/simple/util/AryInt.java
deleted file mode 100644
index b498fd78..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/util/AryInt.java
+++ /dev/null
@@ -1,192 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/util/BAOS.java b/chapter-modules/src/main/java/com/seaofnodes/simple/util/BAOS.java
deleted file mode 100644
index 61ce7387..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/util/BAOS.java
+++ /dev/null
@@ -1,162 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/util/IntHashMap.java b/chapter-modules/src/main/java/com/seaofnodes/simple/util/IntHashMap.java
deleted file mode 100644
index b356ec31..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/util/IntHashMap.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package com.seaofnodes.simple.util;
-
-import java.util.Arrays;
-
-// Int-Int Hash Map
-public class IntHashMap {
- private int[] _es;
- private int _size;
- private final int _sentinel;
-
- // Pick a sentinel value; not allowed for keys or values. Sentinel is
- // returned on a miss. Writing sentinel counts as a remove.
- public IntHashMap(int sentinel) {
- _sentinel = sentinel;
- _es = new int[4];
- Arrays.fill(_es,sentinel);
- }
-
- public int size() { return _size; }
- public boolean isEmpty() { return _size==0; }
- public void clear() {
- _size = 0;
- Arrays.fill(_es,_sentinel);
- }
-
- private int cap() { return _es.length>>1; }
-
- public int put( int key, int val ) {
- int slot = probe(key);
- int old = _es[(slot<<1)+1];
- if( old == _sentinel && val != _sentinel ) _size++;
- if( old != _sentinel && val == _sentinel ) _size--;
- // Check cap, resize at 3/4 full
- if( _size > ((cap()>>1) + (cap()>>2)) ) {
- grow();
- slot = probe(key);
- }
-
- _es[ slot<<1 ] = key;
- _es[(slot<<1)+1] = val;
- return old;
- }
-
- public int get( int key ) {
- int slot = probe(key);
- return _es[(slot<<1)+1];
- }
-
- private int probe( int key ) {
- int mask = cap()-1;
- int slot = key & mask;
- while( _es[slot<<1] != _sentinel && _es[slot<<1] != key )
- slot = (slot + (key|1)) & mask;
- return slot;
- }
-
- // Double underlying array size
- private void grow() {
- int[] old = _es;
- _es = new int[_es.length<<1];
- Arrays.fill(_es,_sentinel);
- for( int i=0; i<old.length; i+=2 )
- if( old[i] != _sentinel ) {
- int slot = probe(old[i]);
- _es[ slot<<1 ] = old[i ];
- _es[(slot<<1)+1] = old[i+1];
- }
- }
-
- // Just all the keys
- public int[] keySet() {
- int[] keys = new int[_size];
- int j=0;
- for( int i=0; i<_es.length; i+=2 )
- if( _es[i] != _sentinel )
- keys[j++] = _es[i];
- return keys;
- }
-
- @Override public String toString() {
- SB sb = new SB().p("[ ");
- for( int i=0; i<_es.length; i+=2 )
- if( _es[i] != _sentinel )
- sb.p('(').p(_es[i]).p(',').p(_es[i+1]).p(')').p(',');
- return sb.unchar().p(']').toString();
- }
-}
diff --git a/chapter-modules/src/main/java/com/seaofnodes/simple/util/SB.java b/chapter-modules/src/main/java/com/seaofnodes/simple/util/SB.java
deleted file mode 100644
index cc59f2a6..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/util/SB.java
+++ /dev/null
@@ -1,99 +0,0 @@
-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/chapter-modules/src/main/java/com/seaofnodes/simple/util/Utils.java b/chapter-modules/src/main/java/com/seaofnodes/simple/util/Utils.java
deleted file mode 100644
index 577a60af..00000000
--- a/chapter-modules/src/main/java/com/seaofnodes/simple/util/Utils.java
+++ /dev/null
@@ -1,53 +0,0 @@
-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); }
-}