summaryrefslogtreecommitdiff
path: root/chapter-sccp/src/main/java/com/seaofnodes/simple/node
diff options
context:
space:
mode:
Diffstat (limited to 'chapter-sccp/src/main/java/com/seaofnodes/simple/node')
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/AddFNode.java31
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/AddNode.java169
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/AndNode.java48
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/ArithFNode.java30
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/ArithNode.java98
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/BoolNode.java156
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java240
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/CFGNode.java222
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/CProjNode.java69
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/CallEndMach.java36
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/CallEndNode.java235
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/CallNode.java175
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java23
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/CastMach.java19
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/CastNode.java71
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java41
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/ConstantNode.java59
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/CtrlNode.java16
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/DivFNode.java26
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/DivNode.java28
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/ExternNode.java21
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/FRefNode.java35
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/FunNode.java257
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java12
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/IfNode.java103
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/LoadNode.java263
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/LoopNode.java104
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java27
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/MachNode.java60
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java102
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/MemOpNode.java146
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java49
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/MinusFNode.java31
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/MinusNode.java33
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/MulFNode.java36
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/MulNode.java63
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/MultiNode.java29
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/NeverNode.java23
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/NewNode.java103
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/Node.java693
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/NotNode.java26
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/OrNode.java42
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/ParmNode.java59
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/PhiNode.java310
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/ProjNode.java54
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java19
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java27
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/RegionNode.java174
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/ReturnNode.java163
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java34
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/SarNode.java36
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/ScopeNode.java530
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/ShlNode.java48
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/ShrNode.java32
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/SplitNode.java28
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/StartNode.java49
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/StopNode.java76
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/StoreNode.java183
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/StructNode.java47
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/SubFNode.java27
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/SubNode.java54
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java27
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java23
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java16
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/XorNode.java42
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java21
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java23
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java28
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java22
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java25
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java19
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java32
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java78
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java51
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java33
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java23
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java22
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java41
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java20
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java20
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java31
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java21
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java44
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java22
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java26
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java36
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java34
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java20
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java34
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java20
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java29
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java79
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java20
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java19
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java25
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java38
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java28
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java21
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java25
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java17
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java15
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java16
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java34
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java66
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java21
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java11
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java38
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java20
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java19
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java29
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java57
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java55
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java48
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java20
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java27
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java466
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java43
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java18
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java22
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java21
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java13
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java16
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java95
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java63
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java32
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java20
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java17
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java32
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java19
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java47
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java23
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java24
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java55
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java47
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java31
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java33
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java30
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java80
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java18
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java17
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java21
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java48
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java22
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java13
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java16
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java17
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java15
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java22
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java34
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java21
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java20
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java13
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java17
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java67
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java15
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java17
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java13
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java17
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java50
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java18
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java16
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java49
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java42
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java49
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java14
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java16
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java428
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java33
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java24
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java23
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java24
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java11
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java11
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java11
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java30
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java49
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java24
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java38
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java44
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java28
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java24
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java25
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java29
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java22
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java36
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java31
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java21
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java32
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java40
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java65
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java38
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java44
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java28
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java89
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java24
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java27
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java24
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java20
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java23
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java21
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java11
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java11
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java17
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java15
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java22
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java15
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java32
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java21
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java44
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java32
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java21
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java27
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java21
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java25
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java52
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java48
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java23
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java10
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java53
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java40
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java41
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java11
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java11
-rw-r--r--chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java564
224 files changed, 12183 insertions, 0 deletions
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/AddFNode.java
new file mode 100644
index 00000000..4ad02dbb
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/AddFNode.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+
+import java.util.BitSet;
+
+public class AddFNode extends ArithFNode {
+ public AddFNode(Node lhs, Node rhs) { super(lhs, rhs); }
+
+ @Override public String label() { return "AddF"; }
+
+ @Override public String glabel() { return "+"; }
+
+ @Override double doOp( double x, double y ) { return x+y; }
+
+ @Override
+ public Node idealize() {
+ // Add of 0.
+ Type t2 = in(2)._type;
+ if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==0 )
+ return in(1);
+
+ // Move constants to RHS: con+arg becomes arg+con
+ if ( in(1)._type.isConstant() && !t2.isConstant() )
+ return swap12();
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new AddFNode(lhs,rhs); }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/AddNode.java
new file mode 100644
index 00000000..2bbe1053
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -0,0 +1,169 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.*;
+
+import static com.seaofnodes.simple.Parser.con;
+
+public class AddNode extends ArithNode {
+ public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "Add"; }
+ @Override public String op() { return "+"; }
+
+ @Override long doOp( long x, long 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 () {
+ 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 instanceof TypeInteger )
+ return lhs;
+
+ // Add of same to a multiply by 2
+ if( lhs==rhs )
+ return new ShlNode(null,lhs,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));
+
+ // 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 new AddNode(new AddNode(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))
+ return spine_cmp(lhs,rhs,this) ? swap12() : phiCon(this,true);
+
+ // 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.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 new AddNode(lhs.in(1),new AddNode(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 new AddNode(new AddNode(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)) ?
+ // 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,lphi._minType.meet(op._type),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); }
+ @Override Node copyF() { return new AddFNode(null,null); }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/AndNode.java
new file mode 100644
index 00000000..9957472b
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/AndNode.java
@@ -0,0 +1,48 @@
+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); }
+
+ @Override public String label() { return "And"; }
+ @Override public String op() { return "&"; }
+ @Override long doOp( long x, long y ) { return x & y; }
+ @Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
+ // Sharpen allowed bits if either value is narrowed
+ long mask = x.mask() & y.mask();
+ return mask < 0 ? TypeInteger.BOT : TypeInteger.make(0,mask);
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ Type t1 = lhs._type;
+ Type t2 = rhs._type;
+ if( !(t1 instanceof TypeInteger && t2 instanceof TypeInteger t2i) )
+ return null; // Malformed, e.g. (17 & 3.14)
+
+ // And of -1. We do not check for (-1&x) because this will already
+ // canonicalize to (x&-1). We do not check for zero, because
+ // the compute() call will return a zero already.
+ if( t2i.isConstant() && t2i.value()==-1 )
+ 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-sccp/src/main/java/com/seaofnodes/simple/node/ArithFNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ArithFNode.java
new file mode 100644
index 00000000..dafba626
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ArithFNode.java
@@ -0,0 +1,30 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+
+public abstract class ArithFNode extends Node {
+ public ArithFNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ return p.open().n(in(1)).p(glabel()).n(in(2)).close();
+ }
+
+ abstract double doOp(double x, double y);
+
+ @Override
+ public Type compute() {
+ Type t1 = in(1)._type, t2 = in(2)._type;
+ if( t1.isHigh() || t2.isHigh() )
+ return Type.TOP;
+ 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);
+ }
+
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ArithNode.java
new file mode 100644
index 00000000..54290330
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ArithNode.java
@@ -0,0 +1,98 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+
+public abstract class ArithNode extends Node {
+ // Source location for late reported errors
+ Parser.Lexer _loc;
+
+ public ArithNode( Parser.Lexer loc, Node lhs, Node rhs) { super(null, lhs, rhs); _loc = loc; }
+ 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);
+
+ // Generic airthmetic op math: high returns high; low returns low; 2
+ // constants fold; only 2 non-constants call specialized math.
+ @Override
+ public final Type compute() {
+ Type t1 = in(1)._type, t2 = in(2)._type;
+ if( t1.isHigh() || t2.isHigh() )
+ return Type.TOP;
+ 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;
+ }
+
+ 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))".
+
+ // This check is slightly different from the
+
+ // Expected to be called after other ideal checks are done.
+ @Override public Node idealize() {
+ if( in(1) instanceof PhiNode lhs &&
+ in(2) instanceof PhiNode rhs &&
+ lhs.nIns() >= 2 && !lhs.inProgress() &&
+ lhs.region()==rhs.region() && !(lhs.region() instanceof FunNode) &&
+ lhs.nIns()>2 && // A 1-input Phi will collapse already
+ // Disallow with self-looping phi; these will collapse
+ (lhs.in(2)!=lhs && rhs.in(2)!=rhs) ) {
+ // Profit check: only 1 instance of `this` will remain, all the
+ // others will fold to constants.
+ 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)
+ ? new ConstantNode(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,lhs._minType,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() {
+ if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return null;
+ if( !(in(1)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+op()+"' " + in(1)._type,_loc);
+ if( !(in(2)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+op()+"' " + in(2)._type,_loc);
+ return null;
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/BoolNode.java
new file mode 100644
index 00000000..0d82c5b9
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/BoolNode.java
@@ -0,0 +1,156 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Utils;
+import static com.seaofnodes.simple.type.TypeInteger.*;
+
+abstract public class BoolNode extends Node {
+
+ public BoolNode(Node lhs, Node rhs) {
+ super(null, lhs, rhs);
+ }
+
+ abstract public String op(); // String opcode name
+
+ @Override
+ public String label() { return getClass().getSimpleName(); }
+
+ @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;
+ // 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;
+ if( t1 instanceof TypeInteger i1 &&
+ t2 instanceof TypeInteger i2 )
+ return doOp(i1,i2);
+ 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(); }
+ Node copyF(Node lhs, Node rhs) { return null; }
+ public boolean isFloat() { return false; }
+
+ @Override
+ public Node idealize() {
+ // Compare of same
+ if( in(1)==in(2) )
+ return this instanceof LT ? Parser.ZERO : new ConstantNode(TRUE);
+
+ // 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 in(1)._type instanceof TypeFloat ? new EQF(in(2),in(1)) : 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;
+ }
+
+ public static class EQ extends BoolNode {
+ public EQ(Node lhs, Node rhs) { super(lhs,rhs); }
+ 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;
+ }
+ Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); }
+ Node copyF() { return new EQF(null,null); }
+ }
+
+ public static class NE extends BoolNode {
+ public NE(Node lhs, Node rhs) { super(lhs,rhs); }
+ 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;
+ }
+ Node copy(Node lhs, Node rhs) { return new NE(lhs,rhs); }
+ Node copyF() { throw Utils.TODO(); }
+ }
+
+ public static class LT extends BoolNode {
+ public LT(Node lhs, Node rhs) { super(lhs,rhs); }
+ 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;
+ }
+ Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs); }
+ Node copyF() { return new LTF(null,null); }
+ }
+
+ public static class LE extends BoolNode {
+ public LE(Node lhs, Node rhs) { super(lhs,rhs); }
+ 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;
+ }
+ Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs); }
+ Node copyF() { return new LEF(null,null); }
+ }
+
+ public static class EQF extends EQ {
+ public EQF(Node lhs, Node rhs) { super(lhs,rhs); }
+ @Override boolean doOp(double lhs, double rhs) { return lhs == rhs; }
+ @Override public boolean isFloat() { return true; }
+ @Override Node copy(Node lhs, Node rhs) { return new EQF(lhs,rhs); }
+ }
+ public static class LTF extends LT {
+ public LTF(Node lhs, Node rhs) { super(lhs,rhs); }
+ @Override boolean doOp(double lhs, double rhs) { return lhs < rhs; }
+ @Override public boolean isFloat() { return true; }
+ @Override Node copy(Node lhs, Node rhs) { return new LTF(lhs,rhs); }
+ }
+ public static class LEF extends LE {
+ public LEF(Node lhs, Node rhs) { super(lhs,rhs); }
+ @Override boolean doOp(double lhs, double rhs) { return lhs <= rhs; }
+ @Override public boolean isFloat() { return true; }
+ @Override Node copy(Node lhs, Node rhs) { return new LEF(lhs,rhs); }
+ }
+
+ // 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); }
+ 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-sccp/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
new file mode 100644
index 00000000..7d4f1fb8
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
@@ -0,0 +1,240 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
+import java.util.BitSet;
+
+/**
+ * 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.
+ // Each split clones the exclusions; published sets are never mutated.
+ public final BitSet _aliases;
+
+ public BulkMemPhiNode(String label, Node... inputs) {
+ this(label,EMPTY,inputs);
+ }
+
+ private BulkMemPhiNode(String label, BitSet aliases, Node... inputs) {
+ super(label,TypeMem.BOT,inputs);
+ _aliases = aliases;
+ }
+
+ public boolean isSplit(int alias) { return _aliases.get(alias); }
+
+ public BulkMemPhiNode(BulkMemPhiNode phi) { super(phi); _aliases = phi._aliases; }
+
+ @Override public String label() { return "BulkPhi"+_aliases; }
+
+ @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,CodeGen.CODE.add(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<_outputs.size(); i++ ) {
+ int alias = outputAlias(_outputs.get(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 mmm.in(1)!=null && 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 ProjNode proj -> 0;
+ case ParmNode parm -> 0;
+ case ConstantNode con -> 0;
+ case MemMergeNode mmm -> missingAlias(mmm);
+ case MemOpNode mem -> unsplit(mem._alias);
+ case BulkMemPhiNode bulk -> missingAlias(bulk);
+ default -> throw new AssertionError("Unexpected memory input "+n);
+ };
+ }
+
+ // 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.
+ addDepForwards(use);
+ return switch(use) {
+ case ScopeNode scope -> 0;
+ case MemMergeNode mmm -> missingAlias(mmm);
+ case BulkMemPhiNode bulk -> missingAlias(bulk);
+ case MemOpNode mem -> unsplit(mem._alias);
+ case MemPhiNode phi -> unsplit(phi._alias);
+ default -> {
+ assert use instanceof ReturnNode || use instanceof CallNode || use instanceof ParmNode
+ : "Unexpected bulk-memory user "+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;
+ }
+
+ // Every explicit slot requests that alias, including a partial allocation
+ // input whose slot still points to this bulk Phi.
+ 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);
+
+ 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
+ bphi.setType(TypeMem.BOT);
+ Node bulk = bphi;
+ 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(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);
+ 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.make(alias,Type.BOTTOM));
+ 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()._outputs )
+ 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-sccp/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CFGNode.java
new file mode 100644
index 00000000..6805c1ef
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -0,0 +1,222 @@
+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.Ary;
+import com.seaofnodes.simple.util.Utils;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/** 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;
+ }
+
+ // Find nearest enclosing FunNode
+ public FunNode fun() {
+ CFGNode cfg = this;
+ while( !(cfg instanceof FunNode fun) )
+ 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
+ static class LoopTree {
+ LoopTree _par;
+ final LoopNode _head;
+ int _depth;
+ LoopTree(LoopNode head) { _head = head; }
+ @Override public String toString() { return "LOOP"+_head._nid; }
+ 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(StartNode start, StopNode stop) {
+ // Unlink all linked calls. This can remove RPC constants which
+ // shuffled the StartNode outputs so requires a while loop.
+ boolean done=false;
+ while(!done) {
+ done = true;
+ for( Node use : start._outputs )
+ if( use instanceof FunNode fun )
+ for( Node c : fun._inputs )
+ if( c instanceof CallNode call )
+ { call.unlink_all(); done=false; }
+ }
+
+ _ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this);
+ _bltWalk(2,null,stop, new BitSet());
+ }
+ int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) {
+ // 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 && !skip( usecfg ) )
+ pre = usecfg._bltWalk( pre, use instanceof FunNode fuse ? fuse : fun, stop, post );
+
+ // 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;
+ }
+
+ private 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 String label( CFGNode target ) {
+ return (target instanceof LoopNode ? "LOOP" : "L")+target._nid;
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CProjNode.java
new file mode 100644
index 00000000..c62de840
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CProjNode.java
@@ -0,0 +1,69 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.type.TypeTuple;
+import com.seaofnodes.simple.util.Utils;
+
+public class CProjNode extends CFGNode {
+
+ // 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 Parser.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 && addDep(iff.pred()) instanceof NotNode not )
+ return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True");
+
+ // Copy of some other input
+ return ((MultiNode)ctrl()).pcopy(_idx);
+ }
+
+ // Only called during basic-block layout, inverts a T/F CProj
+ public void invert() {
+ _label = _idx == 0 ? "False" : "True";
+ _idx = 1-_idx;
+ }
+
+ @Override
+ public boolean eq( Node n ) { return _idx == ((CProjNode)n)._idx; }
+
+ @Override
+ int hash() { return _idx; }
+
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CallEndMach.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CallEndMach.java
new file mode 100644
index 00000000..d26ea33b
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CallEndMach.java
@@ -0,0 +1,36 @@
+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 = 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 = 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-sccp/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
new file mode 100644
index 00000000..3321301b
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -0,0 +1,235 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.type.*;
+
+/**
+ * CallEnd
+ */
+public class CallEndNode extends CFGNode implements MultiNode {
+
+ // When set true, this Call/CallEnd/Fun/Return is being trivially inlined
+ private boolean _folding;
+ public boolean folding() { return _folding; }
+ public final TypeRPC _rpc;
+ // Pessimistic analysis may know more than the written signature. Preserve
+ // that summary when local SCCP disconnects the callee's return edges.
+ private Type _retType = Type.BOTTOM;
+
+ public CallEndNode(CallNode call) { super(new Node[]{call}); _rpc = TypeRPC.constant(_nid); }
+ public CallEndNode(CallEndNode cend) { super(cend); _rpc = cend._rpc; _retType = cend._retType; }
+
+ public void freezeResult() {
+ if( _type instanceof TypeTuple tt ) {
+ unlock();
+ _retType = tt.ret();
+ }
+ }
+
+ @Override public boolean eq(Node n) { return _retType==((CallEndNode)n)._retType; }
+ @Override int hash() { return _retType.hashCode(); }
+
+ @Override public String label() { return "CallEnd"; }
+ @Override public boolean blockHead() { return true; }
+
+ public CallNode call() { return (CallNode)in(0); }
+
+ @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.RET.dual();
+ // Mid-fold, just take the one single callers' return type
+ if( _folding ) return in(1)._type;
+ // Grab the TFP and use the functions declared return type.
+ Type ftype = addDep(call.fptr())._type;
+ Type ret = ftype.isHigh() ? Type.TOP : Type.BOTTOM;
+ if( ftype instanceof TypeFunPtr tfp ) {
+ ret = tfp.ret().join(_retType);
+ // Here, if I can figure out I've found *all* callers, then I can meet
+ // across the linked returns and join with the function return type.
+ if( 1+tfp.nfcns() == nIns() ) { // A linked function for every concrete function
+ ret = Type.TOP;
+ for( int i=1; i<nIns(); i++ ) {
+ Type tret = in(i)._type instanceof TypeTuple rtt ? rtt.ret() : in(i)._type;
+ ret = ret.meet(tret);
+ }
+ }
+ }
+ return TypeTuple.make(call._type,TypeMem.BOT,ret);
+ }
+
+ @Override
+ public Node idealize() {
+
+ // Trivial inlining: call site calls a single function; single function
+ // is only called by this call site.
+ if( !_folding && nIns()==2 && in(0) instanceof CallNode call ) {
+ Node fptr = call.fptr();
+ if( fptr.nOuts() == 1 && // Only user is this call
+ fptr instanceof ConstantNode && // We have an immediate call
+ // Function is being called, and its not-null
+ fptr._type instanceof TypeFunPtr tfp && tfp.notNull() &&
+ // Arguments are correct
+ call.err()==null ) {
+ ReturnNode ret = (ReturnNode)in(1);
+ FunNode fun = ret.fun();
+ // Expecting Start, and the Call
+ if( fun.nIns()==3 ) {
+ assert fun.in(1) instanceof StartNode && fun.in(2)==call;
+ // Disallow self-recursive inlining (loop unrolling by another name).
+ // Disallow if still folding other things, as it makes other
+ // dependency checks carry long chains of half-folded calls.
+ // Dead control may end the walk before an enclosing function.
+ CFGNode idom = call;
+ while( idom!=null ) {
+ idom = idom.idom();
+ if( idom instanceof FunNode ) break;
+ if( idom instanceof CallEndNode cend && cend._folding ) break;
+ }
+ // Inline?
+ if( idom instanceof FunNode fun2 && fun2 != fun && !fun2._folding ) {
+ // Trivial inline: rewrite
+ _folding = true;
+ // Rewrite Fun so the normal RegionNode ideal collapses
+ fun._folding = true;
+ fun.setDef(1,Parser.XCTRL); // No default/unknown StartNode caller
+ fun.setDef(2,call.ctrl()); // Bypass the Call;
+ fun.ret().setDef(3,null); // Return is folding also
+ CodeGen.CODE.addAll(fun._outputs);
+ // Repeat defs 1 layer down, for users of Parm (Phis)
+ for( Node parm : fun._outputs )
+ if( parm instanceof ParmNode )
+ CodeGen.CODE.addAll(parm._outputs);
+
+ // Inlining immediately blows all cache idepth fields past the inline point.
+ // Bump the global version number invalidating them en-masse.
+ CodeGen.CODE.invalidateIDepthCaches();
+ return this;
+ } else if( idom!=null ) {
+ addDep(idom);
+ }
+ } else {
+ addDep(fun);
+ }
+ } else { // Function ptr has multiple users (so maybe multiple call sites)
+ addDep(fptr);
+ }
+ }
+
+ return null;
+ }
+
+ // Clone only tiny, straight-line boilerplate, after ordinary peeps settle.
+ // Larger bodies still use the existing single-caller, non-cloning inline.
+ public boolean inlineSmall() {
+ if( isDead() || _folding || nIns()!=2 || !(in(0) instanceof CallNode call) ) return false;
+ Node fptr = call.fptr();
+ if( !(fptr instanceof ConstantNode) ||
+ !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) )
+ { addDep(fptr); return false; }
+ if( call.err()!=null ) return false;
+ ReturnNode ret = (ReturnNode)in(1);
+ FunNode fun = ret.fun();
+ if( fun._folding ) return false;
+ int len = smallBody(fun);
+ if( len==0 ) return false;
+ // All callers consume the three normal return projections.
+ for( Node use : _outputs )
+ if( !((use instanceof ProjNode p && p._idx>=0 && p._idx<3) ||
+ (use instanceof CProjNode cp && cp._idx==0)) ) return false;
+
+ // Substitute control and arguments directly; no new function identity,
+ // parameter Phis, or return address is needed for this straight line.
+ Node[] copy = new Node[len];
+ for( int i=0; i<len; i++ ) {
+ Node n = TINYBODY[i];
+ copy[i] = n==fun ? call.ctrl() :
+ n instanceof ParmNode parm ? (parm._idx==0 ? null : call.arg(parm._idx)) :
+ n==ret ? null : n.copyEmpty();
+ }
+ for( int i=0; i<len; i++ ) {
+ Node n = TINYBODY[i];
+ if( n instanceof StoreNode || n instanceof MemMergeNode ) {
+ for( Node in : n._inputs ) copy[i].addDef(mapped(in,copy));
+ CodeGen.CODE.add(copy[i]);
+ }
+ }
+ Node[] result = new Node[3];
+ for( int i=0; i<3; i++ ) result[i] = mapped(ret.in(i),copy).keep();
+ keep();
+ call.unlink_all();
+ CodeGen.CODE.add(fun);
+ CodeGen.CODE.addAll(fun._outputs);
+ // subsume kills each projection; retain CallEnd until all are replaced.
+ while( nOuts()>1 ) {
+ Node use = out(0)==null ? out(1) : out(0);
+ int idx = use instanceof ProjNode p ? p._idx : ((CProjNode)use)._idx;
+ CodeGen.CODE.addAll(use._outputs);
+ use.subsume(result[idx]);
+ }
+ unkill();
+ for( Node n : result ) n.unkill();
+ CodeGen.CODE.invalidateIDepthCaches();
+ return true;
+ }
+
+ private static Node mapped(Node n, Node[] copy) {
+ for( int i=0; i<copy.length; i++ )
+ if( TINYBODY[i]==n ) return copy[i];
+ return n; // Constants outside the body are shared.
+ }
+
+ // Bounded recognition of 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 while a large body shrinks.
+ // Watch this small prefix, including an unsupported node that may fold.
+ private static final Node[] TINYBODY = new Node[15]; // Tiny worklist
+ private int smallBody(FunNode fun) {
+ ReturnNode ret = fun.ret();
+ for( Node in : ret._inputs )
+ if( in!=null ) addDep(in);
+ if( ret.ctrl()!=fun ) return 0;
+ 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 0;
+ if( n!=fun && !(n instanceof ParmNode) &&
+ !(n instanceof StoreNode) && !(n instanceof MemMergeNode) )
+ return 0;
+ if( n.nOuts()>TINYBODY.length ) return 0;
+ for( Node use : n._outputs ) {
+ if( use==null ) continue;
+ int j=0;
+ while( j<len && TINYBODY[j]!=use ) j++;
+ if( j<len ) continue;
+ if( len==TINYBODY.length ) { addDep(use); return 0; }
+ TINYBODY[len++] = use;
+ }
+ }
+ return len;
+ }
+
+ @Override public Node pcopy(int idx) {
+ return _folding ? in(1).in(idx) : null;
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CallNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CallNode.java
new file mode 100644
index 00000000..e63b3f62
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CallNode.java
@@ -0,0 +1,175 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.IterPeeps;
+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.util.Utils;
+
+/**
+ * Call
+ */
+public class CallNode extends CFGNode {
+
+ // Source location for late reported errors
+ public final Parser.Lexer _loc;
+
+ public CallNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; }
+ public CallNode(CallNode call) { super(call); _loc = call._loc; }
+
+ @Override public String label() { return "Call"; }
+
+ @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() { return false; }
+
+
+ // Find the Call End from the Call
+ public CallEndNode cend() {
+ // Always in use slot 0
+ if( nOuts()>0 && out(0) instanceof CallEndNode cend ) {
+ assert _cend()==cend;
+ return cend;
+ } else {
+ assert _cend()==null;
+ return null;
+ }
+ }
+ private CallEndNode _cend() {
+ CallEndNode cend=null;
+ for( Node n : _outputs )
+ if( n instanceof CallEndNode cend0 )
+ { assert cend == null; cend = cend0; }
+ return cend;
+ }
+
+ // Get the one control following; error to call with more than one e.g. an
+ // IfNode or other multi-way branch.
+ @Override public CFGNode uctrl() { return cend(); }
+
+
+ @Override
+ public Type compute() {
+ return ctrl()._type;
+ }
+
+ @Override
+ public Node idealize() {
+ CallEndNode cend = cend();
+ if( cend==null ) return null; // Still building
+
+ // Link: call calls target function. Linking makes the target FunNode
+ // point to this Call, and all his Parms point to the call arguments;
+ // also the CallEnd points to the Return.
+ Node progress = null;
+ if( fptr()._type instanceof TypeFunPtr tfp && tfp.nargs() == nargs() ) {
+ // If fidxs is negative, then infinite unknown functions
+ long fidxs = tfp.fidxs();
+ if( fidxs > 0 ) {
+ // Wipe out the return which matching in the linker table
+ // Walk the (63 max) bits and link
+ for( ; fidxs!=0; fidxs = TypeFunPtr.nextFIDX(fidxs) ) {
+ int fidx = Long.numberOfTrailingZeros(fidxs);
+ TypeFunPtr tfp0 = tfp.makeFrom(fidx);
+ FunNode fun = CodeGen.CODE.link(tfp0);
+ if( fun!=null && !fun._folding && !linked(fun) )
+ progress = link(fun);
+ }
+ }
+ }
+
+ return progress;
+ }
+
+ // True if Fun is linked to this Call
+ public boolean linked( FunNode fun ) {
+ for( Node n : fun._inputs )
+ if( n == this )
+ return true;
+ return false;
+ }
+
+ // Link so this calls fun
+ public Node link( FunNode fun ) {
+ assert !linked(fun);
+ fun.addDef(this);
+ for( Node use : fun._outputs )
+ if( use instanceof ParmNode parm )
+ parm.addDef(parm._idx==0 ? new ConstantNode(cend()._rpc).peephole() : arg(parm._idx));
+ // Call end points to function return
+ CodeGen.CODE.add(cend()).addDef(fun.ret());
+ return this;
+ }
+
+ // Unlink all linked functions
+ public void unlink_all() {
+ for( int i=0; i<_outputs._len; i++ )
+ if( out(i) instanceof FunNode fun ) {
+ assert linked(fun);
+ int idx = fun._inputs.find(this);
+ for( Node use : fun._outputs )
+ if( use instanceof ParmNode )
+ use.delDef(idx);
+ fun.delDef(idx);
+ cend().delDef(cend()._inputs.find(fun.ret()));
+ assert !linked(fun);
+ i--;
+ }
+ }
+
+ @Override
+ public Parser.ParseException err() {
+ if( !(fptr()._type instanceof TypeFunPtr tfp) )
+ throw Utils.TODO();
+ if( !tfp.notNull() )
+ return Parser.error( "Might be null calling "+tfp, _loc);
+ if( nargs() != tfp.nargs() )
+ return Parser.error( "Expecting "+tfp.nargs()+" arguments, but found "+nargs(), _loc);
+
+ // Check for args
+ for( int i=0; i<tfp.nargs(); i++ )
+ if( !arg(i+2)._type.isa(tfp.arg(i)) || !arg(i+2)._type.accessISA(tfp.arg(i)) ) {
+ cend().addDep(arg(i+2)); // Upgrading arg type may allow inlining
+ return Parser.error( "Argument #" + i + " isa " + arg(i+2)._type + ", but must be a " + tfp.arg(i), _loc );
+ }
+
+ if( tfp.fidxs() < 0 )
+ throw Utils.TODO(); // Infinite unknown TFPs? Should be fairly precise CG
+
+ return null;
+ }
+
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java
new file mode 100644
index 00000000..0afc5827
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CalleeSaveNode.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.type.Type;
+
+public class CalleeSaveNode extends ProjNode implements MachNode {
+ final RegMask _mask;
+ public CalleeSaveNode(FunNode fun, int reg, String label) {
+ super(fun,reg,label);
+ fun._outputs.pop();
+ int i=0;
+ while( fun.out(i) instanceof PhiNode ) i++;
+ fun._outputs.insert(this,i);
+ _mask = new RegMask(reg);
+ }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return _mask; }
+ @Override public void encoding( Encoding enc ) { }
+
+ @Override public Type compute() { return Type.BOTTOM; }
+ @Override public Node idealize() { return null; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CastMach.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CastMach.java
new file mode 100644
index 00000000..e45898f5
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CastMach.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.util.SB;
+
+/**
+ * Cast a pointer to read-only
+ */
+public class CastMach extends CastNode implements MachNode {
+ public CastMach( CastNode 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-sccp/src/main/java/com/seaofnodes/simple/node/CastNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CastNode.java
new file mode 100644
index 00000000..4e93f3e6
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CastNode.java
@@ -0,0 +1,71 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.TypeMemPtr;
+import com.seaofnodes.simple.util.Utils;
+
+// Upcast (join) the input to a t. Used after guard test to lift an input.
+// Can also be used to make a type-assertion if ctrl is null.
+public class CastNode extends Node {
+ public Type _t;
+ public CastNode(Type t, Node ctrl, Node in) {
+ super(ctrl, in);
+ _t = t;
+ setType(compute());
+ }
+
+ public CastNode(CastNode c) {
+ super(c.in(0), c.in(1)); // Call parent constructor
+ this._t = c._t; // Copy the Type field
+ setType(compute()); // Ensure type is recomputed
+ }
+
+ @Override public String label() { return "("+_t.str()+")"; }
+
+
+ @Override public boolean isConst() { return true; }
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ return p.p(label()).n(in(1));
+ }
+
+ @Override
+ public Type compute() {
+ // Cast array to int
+ Type t1 = in(1)._type;
+ if( _t == TypeInteger.BOT && t1 instanceof TypeMemPtr tmp && tmp._obj.isAry() )
+ return _t;
+ // Freeze if the join is high but both inputs are low
+ Type tj = t1.join(_t);
+ if( tj.isHigh() && !t1.isHigh() )
+ return _type==null ? _t : _type;
+
+ return tj;
+ }
+
+ @Override
+ public Node idealize() {
+ return in(1)._type.isa(_t) && in(1)._type.accessISA(_t) ? in(1) : null;
+ }
+
+ @Override
+ public boolean eq(Node n) {
+ CastNode cast = (CastNode)n; // Contract
+ return _t==cast._t;
+ }
+
+ @Override
+ int hash() { return _t.hashCode(); }
+
+ @Override
+ public Parser.ParseException err() {
+ // Has a condition to test, so OK
+ if( in(0) != null ) return null;
+ // No condition to test, so this must optimize away
+ return Parser.error( "Type " + in(1)._type.str() + " is not of declared type " + _t.str(), null );
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
new file mode 100644
index 00000000..afdd29be
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
@@ -0,0 +1,41 @@
+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.TypeInteger;
+import com.seaofnodes.simple.type.TypeStruct;
+import com.seaofnodes.simple.util.SB;
+
+/**
+ * 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 final String _name; // Struct name
+ public final String _fname; // Field name
+ public ConFldOffNode( String name, String fname ) { super(TypeInteger.BOT); _name = name; _fname = fname; }
+
+ @Override public String label() {
+ return _fname == " len" ? "sizeof("+_name+")" : "#"+_fname;
+ }
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ return p.p(label());
+ }
+
+ // Convert field offset to an integer
+ public Node asOffset(TypeStruct ts) {
+ return new ConstantNode(TypeInteger.constant(ts.offset(_fname==" len" ? ts._fields.length : ts.find(_fname)))).peephole();
+ }
+
+
+ @Override public boolean eq(Node n) {
+ ConFldOffNode off = (ConFldOffNode)n; // Invariant
+ return _name.equals(off._name) && _fname.equals(off._fname) && super.eq(n);
+ }
+ @Override int hash() { return super.hash() * _name.hashCode() * _fname.hashCode(); }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
new file mode 100644
index 00000000..7c330175
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
@@ -0,0 +1,59 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.util.SB;
+
+/**
+ * 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 Node {
+ public final Type _con;
+ public ConstantNode( Type type ) {
+ super(new Node[]{CodeGen.CODE._start});
+ _con = _type = type;
+ }
+ public ConstantNode( ConstantNode con ) { super(con); _con = con._type; }
+
+ public static Node make( Type type ) {
+ if( type==Type. CONTROL ) return new CtrlNode();
+ if( type==Type.XCONTROL ) return new XCtrlNode();
+ if( type instanceof TypeFunPtr tfp && tfp.isConstant() && tfp.notNull() ) {
+ FunNode fun = CodeGen.CODE.link(tfp);
+ if( fun!=null && !fun.isDead() ) return new FunPtrNode(tfp,fun.ret());
+ }
+ return new ConstantNode(type);
+ }
+
+ @Override protected String repeatName() {
+ return _con==null || _con.toString().length()<=32 ? null : uniqueName();
+ }
+
+ @Override public String label() { return "Con"; }
+
+ @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; }
+ @Override public boolean eq(Node n) { return _con==((ConstantNode)n)._con; }
+ @Override int hash() { return _con.hashCode(); }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CtrlNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
new file mode 100644
index 00000000..cf630ad4
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
@@ -0,0 +1,16 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.Parser;
+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-sccp/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/DivFNode.java
new file mode 100644
index 00000000..0d037a30
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/DivFNode.java
@@ -0,0 +1,26 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+
+import java.util.BitSet;
+
+public class DivFNode extends ArithFNode {
+ public DivFNode(Node lhs, Node rhs) { super(lhs, rhs); }
+
+ @Override public String label() { return "DivF"; }
+
+ @Override public String glabel() { return "/"; }
+
+ @Override double doOp( double x, double y ) { return x/y; }
+
+ @Override
+ public Node idealize() {
+ // Div of constant
+ if( in(2)._type instanceof TypeFloat f && f.isConstant() )
+ return new MulFNode(in(1),new ConstantNode(TypeFloat.constant(1.0/f.value())).peephole());
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new DivFNode(lhs,rhs); }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/DivNode.java
new file mode 100644
index 00000000..7b5ae16c
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -0,0 +1,28 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import java.util.BitSet;
+
+public class DivNode extends ArithNode {
+ public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "Div"; }
+ @Override public String op() { return "//"; }
+
+ @Override long doOp( long x, long y ) { return y==0 ? 0 : 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 instanceof TypeInteger )
+ return in(1);
+ return super.idealize();
+ }
+
+ @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); }
+ @Override Node copyF() { return new DivFNode(null,null); }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ExternNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ExternNode.java
new file mode 100644
index 00000000..ccbcfc66
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ExternNode.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.util.SB;
+import java.util.BitSet;
+
+/**
+ A constant with external linkage.
+ */
+
+public class ExternNode extends ConstantNode {
+ public final String _extern;
+ public ExternNode(Type t, String ex) { super(t); _extern = ex; }
+
+ @Override public String label() { return "#"+_con+":"+_extern; }
+
+ @Override public boolean eq(Node n) { return this==n; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/FRefNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/FRefNode.java
new file mode 100644
index 00000000..e7333c23
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/FRefNode.java
@@ -0,0 +1,35 @@
+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.
+ */
+public class FRefNode extends ConstantNode {
+ public static final Type FREF_TYPE = Type.BOTTOM;
+ public final Var _n;
+ public FRefNode( Var n ) { super(FREF_TYPE); _n = n; }
+
+ @Override public String label() { return "FRef"+_n; }
+
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ return p.p("FRef_").p(_n);
+ }
+
+ @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 '"+_n._name+"'",_n._loc); }
+
+ @Override public boolean eq(Node n) { return this==n; }
+ @Override int hash() { return _n._idx; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/FunNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/FunNode.java
new file mode 100644
index 00000000..70e1c200
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/FunNode.java
@@ -0,0 +1,257 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.type.TypeTuple;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+import java.util.BitSet;
+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
+ boolean _folding;
+ public boolean folding() { return _folding; }
+
+ private TypeFunPtr _sig; // Initial signature
+ private ReturnNode _ret; // Return pointer
+
+ public String _name; // Debug name
+
+ public FunNode( Parser.Lexer loc, TypeFunPtr sig, String name, Node... nodes ) { super(loc,nodes); _name=name; _sig = sig; }
+ public FunNode( FunNode fun ) {
+ super( fun, fun==null ? null : fun._loc );
+ if( fun!=null ) {
+ _sig = fun.sig();
+ _name = fun._name;
+ } else {
+ _sig = TypeFunPtr.BOT;
+ _name = "";
+ }
+ }
+
+ @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 rpc() {
+ ParmNode rpc = null;
+ for( Node n : _outputs )
+ if( n instanceof ParmNode parm && parm._idx==0 )
+ { assert rpc==null; rpc=parm; }
+ return rpc;
+ }
+
+ // 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; }
+
+ // Direct calls are known; any other use of an address may have unknown callers.
+ public boolean hasPtr() {
+ for( Node use : ret()._outputs )
+ if( use instanceof FunPtrNode )
+ for( Node user : use._outputs )
+ if( !(user instanceof CallNode call) || call.fptr()!=use ||
+ call._inputs.find(use)!=call.nIns()-1 ) return true;
+ return false;
+ }
+
+ // Signature can improve over time
+ public TypeFunPtr sig() { return _sig; }
+ public void setSig( TypeFunPtr sig ) {
+ assert sig.isa(_sig);
+ if( _sig != sig ) {
+ CODE.add(this);
+ _sig = sig;
+ }
+ }
+
+ public void setName( String name ) {
+ if( _name==null ) _name=name;
+ else _name += "."+name;
+ }
+
+ @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 no unknown callers, we are not main and the Start input
+ // 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 main never kills Start
+ Node progress = deadPath(unknownCallers());
+ if( progress!=null ) {
+ if( nIns()==3 && in(2) instanceof CallNode call )
+ CODE.add(call.cend()); // If 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() ) {
+ setSig(_sig.makeFrom(tt.ret()));
+ return this;
+ }
+
+
+ // When can we assume no callers? Or no other callers (except main)?
+ // In a partial compilation, we assume Start gets access to any/all
+ // top-level public structures and recursively what they point to.
+ // This in turn is valid arguments to every callable function.
+ //
+ // In a total compilation, we can start from Start and keep things
+ // more contained.
+
+ // 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 then just a ID on input#1
+ @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 && nIns()==3 ? cfg(2) : (nIns()>1 ? cfg(1) : null); }
+
+ // Always in-progress until we run out of unknown callers
+ public boolean unknownCallers() { return nIns()>=2 && in(1) instanceof StartNode; }
+
+ @Override public boolean inProgress() { return unknownCallers(); }
+
+ // Add a new function exit point.
+ public void addReturn(Node ctrl, Node mem, Node rez) { _ret.addReturn(ctrl,mem,rez); }
+
+ // Build the function body
+ public BitSet body() {
+
+ // Reverse up (stop to start) CFG only, collect bitmap.
+ BitSet cfgs = new BitSet();
+ cfgs.set(_nid);
+ walkUp(ret(),cfgs );
+
+ // Top down (start to stop) all flavors. CFG limit to bitmap.
+ // If data use bottoms out in wrong CFG, returns false - but tries all outputs.
+ // If any output hits an in-CFG use (e.g. phi), then keep node.
+ BitSet body = new BitSet();
+ walkDown(this, cfgs, body, new BitSet());
+ return body;
+ }
+
+ private static void walkUp(CFGNode n, BitSet cfgs) {
+ if( cfgs.get(n._nid) ) return;
+ cfgs.set(n._nid);
+ if( n instanceof RegionNode r )
+ for( int i=1; i<n.nIns(); i++ )
+ walkUp(n.cfg(i),cfgs);
+ else walkUp(n.cfg0(),cfgs);
+ }
+
+ private static boolean walkDown( Node n, BitSet cfgs, BitSet body, BitSet visit ) {
+ if( visit.get(n._nid) ) return body.get(n._nid);
+ visit.set(n._nid);
+
+ if( n instanceof CFGNode && !cfgs.get(n._nid) )
+ return false;
+ if( n.in(0)!=null && !cfgs.get(n.in(0)._nid) )
+ return false;
+ boolean in = n.in(0)!=null || n instanceof CFGNode;
+ for( Node use : n._outputs )
+ in |= walkDown(use,cfgs,body,visit);
+ if( in ) body.set(n._nid);
+ return in;
+ }
+
+ // FunNodes must match signature (equivalent: no 2 FunNodes are ever GVN'able)
+ @Override public boolean eq( Node n ) {
+ return _sig == ((FunNode)n)._sig;
+ }
+
+ @Override public int hash() {
+ return _sig.hashCode();
+ }
+
+ // ------------
+ // MachNode specifics, shared across all CPUs
+
+ // Function may call other functions? X86 requires 16b aligned SP on
+ // outbound calls, but not leaf. Set during an unrelated RA scan.
+ public boolean _hasCalls;
+
+ // Maximum incoming argument slot, based on function type
+ public int _maxArgSlot;
+
+ // Frame adjust, including padding, set during early Encoding
+ public int _frameAdjust;
+
+ public String op() { return _frameAdjust==0 ? "entry" : "subi"; }
+ public void postSelect(CodeGen code) {
+ code.link(this);
+ // Max slot seen past space reserved for incoming arguments
+ _maxArgSlot = code._mach.maxArgSlot(sig());
+ }
+ public RegMask regmap(int i) { return null; }
+ public RegMask outregmap() { return null; }
+ public void encoding( Encoding enc ) { throw Utils.TODO(); }
+ public void asm(CodeGen code, SB sb) {
+ if( _frameAdjust!=0 )
+ sb.p("rsp -= #").p(_frameAdjust);
+ }
+
+ public void computeFrameAdjust(CodeGen code, int maxReg) {
+ // Max slot seen, or 0.
+ int maxSlot = Math.max(maxReg - code._mach.regs().length,_maxArgSlot);
+ // Frame adjust before rounding, can be negative for no spills
+ int size = maxSlot - _maxArgSlot;
+ _frameAdjust = size*8;
+ }
+
+ // Given a high register number and during encoding compute the stack
+ // pointer offset. Pre-RA we lack the fun._frameAdjust. We also need a
+ // FunNode.
+ public int computeStackOffset(CodeGen code, int reg) {
+ String[] regs = code._mach.regs();
+ int stackSlot = reg - regs.length;
+ int slotRotate = stackSlot < _maxArgSlot
+ ? stackSlot + (_frameAdjust>>3)
+ : stackSlot - _maxArgSlot;
+ return slotRotate*8;
+ }
+
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java
new file mode 100644
index 00000000..43242c32
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java
@@ -0,0 +1,12 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.TypeFunPtr;
+
+// A function address keeps its body alive, even without a linked Call.
+public class FunPtrNode extends ConstantNode {
+ public FunPtrNode(TypeFunPtr tfp, ReturnNode ret) {
+ super(tfp);
+ addDef(ret);
+ }
+ public ReturnNode ret() { return (ReturnNode)in(1); }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/IfNode.java
new file mode 100644
index 00000000..c96993e5
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/IfNode.java
@@ -0,0 +1,103 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Utils;
+
+public class IfNode extends CFGNode implements MultiNode {
+
+ public IfNode(Node ctrl, Node pred) {
+ super(ctrl, pred);
+ CodeGen.CODE.add(this); // Because idoms are complex, just add it
+ }
+ public IfNode(IfNode iff) { super(iff); }
+
+ @Override
+ public String label() { return "If"; }
+
+ @Override
+ protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ p.p("if( ");
+ if( in(1)==null ) p.p("never");
+ else p.n(in(1));
+ return p.p(" )");
+ }
+
+ public Node ctrl() { return in(0); }
+ public Node pred() { return in(1); }
+
+ // No one unique control follows
+ @Override public CFGNode uctrl() { return null; }
+
+ @Override
+ public Type compute() {
+ // If the If node is not reachable then neither is any following Proj
+ assert !_inputs.isEmpty();
+ if (ctrl()._type != Type.CONTROL && ctrl()._type != Type.BOTTOM )
+ return TypeTuple.IF_NEITHER;
+ Node pred = pred();
+ Type t = pred._type;
+ // High types mean NEITHER side is reachable.
+ // Wait until the type falls to decide which way to go.
+ if( t.isHigh() )
+ return TypeTuple.IF_NEITHER;
+ // If constant is 0 then false branch is reachable
+ // Else true branch is reachable
+ if( t.isConstant() )
+ return (t==Type.NIL || t==TypeInteger.ZERO || (t instanceof TypeFunPtr tfp && tfp._fidxs==0) ) ? 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 && addDep(iff.pred())==pred() && prior instanceof CProjNode prj ) {
+ setDef(1,con( prj._idx==0 ? 1 : 0 ));
+ return this;
+ }
+ if( dom instanceof RegionNode r )
+ for( Node c : r._inputs )
+ if( c!= null ) addDep(c); // If region loses input, idom changes
+ }
+ 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-sccp/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/LoadNode.java
new file mode 100644
index 00000000..e057ef1c
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -0,0 +1,263 @@
+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.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, Type glb, Node mem, Node ptr, Node off) {
+ super(loc, name, alias, true, glb, 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 CallNode ) 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 tmem = mem()._type;
+ if( !(tmem instanceof TypeMem mem) )
+ return tmem; // No memory yet? Assume TOP/BOT
+ assert !_declaredType.isFRef();
+ // No lifting if ptr might null-check
+ Type tptr = ptr()._type;
+ if( err() != null )
+ return _declaredType;
+ if( !(tptr instanceof TypeMemPtr tmp) )
+ return tptr; // No pointer yet? Assume TOP/BOT
+ // Load field from object
+ Field f = tmp._obj.field(_name);
+ // No field? Open objects might yet get the field when falling;
+ // closed objects with missing field are an error.
+ if( f == null )
+ return tmp.isHigh() ? Type.TOP : _declaredType;
+ Type t = f._t;
+ // Load member of constant array
+ if( t instanceof TypeConAry ary )
+ t = ary.elem(); // TODO: if offset is known, can peek the constant
+ // Lift from declared type and memory input
+ t = t.join(MemMergeNode.contents(mem(),_alias,this));
+ if( ptr()._type.isFinal() || _declaredType.isFinal() )
+ t = t.makeRO(); // Deep final applied
+ // Pinch between declared type
+ t = t.join(_declaredType);
+ return t;
+ }
+
+ @Override
+ public Node idealize() {
+ if( mem() instanceof CastNode cast ) {
+ setDef(1,cast.in(1));
+ return this;
+ }
+ if( mem() instanceof MemMergeNode merge ) {
+ setDef(1,CodeGen.CODE.add(merge.alias(_alias)));
+ return this;
+ }
+ Node ptr = ptr();
+ Node mem = mem();
+
+ Node win = find(mem,ptr,null,null);
+ if( win!=null ) return folded(win);
+
+ // 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
+ return ld;
+
+ 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) ) {
+ PhiNode phi = new PhiNode(_name,_declaredType,memphi.region()).keep();
+ phi.setType(_type); // Backedge searches may return this value Phi.
+ 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));
+ }
+ return CodeGen.CODE.add(phi.unkeep());
+ }
+ }
+
+ return null;
+ }
+
+ // Load a flavored zero from a New
+ private Node zero(NewNode nnn) {
+ TypeStruct ts = nnn._ptr._obj;
+ Type zero = ts._fields[ts.findAlias(_alias)]._t.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);
+ while( mem!=null ) {
+ addDep(mem);
+ if( mem==stop ) return mem;
+ switch( mem ) {
+ case MemMergeNode merge: mem=merge.alias(_alias); break;
+ case CastNode cast: mem=cast.in(1); break;
+ case StoreNode st:
+ addDep(st.ptr());
+ if( _alias==st._alias && ptr==st.ptr() && off()==st.off() ) return st;
+ if( _alias==st._alias && !neverAlias(ptr,st.ptr()) &&
+ !addDep(off())._type.join(addDep(st.off())._type).isHigh() ) return null;
+ mem=st.mem();
+ break;
+ case ProjNode proj:
+ if( !(proj.in(0) instanceof NewNode nnn) ) return null;
+ if( ptr instanceof ProjNode p && p.in(0)==nnn ) return nnn;
+ if( !(ptr instanceof ProjNode p && p.in(0) instanceof NewNode) ) return null;
+ mem=nnn.mem(); // Distinct allocations cannot overlap.
+ break;
+ 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,_declaredType,mp.region()).keep();
+ for( int i=1; i<mp.nIns(); i++ ) phi.addDef(load(mp.in(i),ptr,stop,value));
+ return phi.unkeep().peephole();
+ }
+ return folded(win).peephole();
+ }
+ LoadNode ld=new LoadNode(_loc,_name,_alias,_declaredType,mem,ptr,off());
+ ld.setDef(0,in(0));
+ 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( shf._type==TypeInteger.ZERO )
+ return val;
+ Node shl = new ShlNode(null,val,shf.keep()).peephole();
+ return new SarNode(null,shl,shf.unkeep());
+ }
+
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/LoopNode.java
new file mode 100644
index 00000000..1d493db7
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/LoopNode.java
@@ -0,0 +1,104 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.util.Utils;
+import java.util.BitSet;
+import java.util.HashSet;
+
+public class LoopNode extends RegionNode {
+ public LoopNode( Parser.Lexer loc, Node entry ) { super(loc,null,entry,null); }
+ public LoopNode( LoopNode loop ) { super(loop); }
+
+ public CFGNode entry() { return cfg(1); }
+ public CFGNode back () { return cfg(2); }
+
+ @Override
+ public String label() { return "Loop"; }
+
+ @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(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 = new ConstantNode(Type.TOP).peephole();
+ MemMergeNode memout = new MemMergeNode(new ConstantNode(TypeMem.BOT).peephole());
+ memout.setDef(0,f);
+ for( Node u : _outputs )
+ if( u instanceof BulkMemPhiNode ) memout.setDef(1,u);
+ else if( u instanceof MemPhiNode phi ) memout.alias(phi._alias,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(ScopeNode.MEM0,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());
+ ret._type = null; // This may LIFT the Return type, from [XCtrl,mem,expr] to [Ctrl,mem,expr]
+ ret.setType(ret.compute());
+
+ 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-sccp/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java
new file mode 100644
index 00000000..9c0755b9
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java
@@ -0,0 +1,27 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.Utils;
+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 Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+
+ @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-sccp/src/main/java/com/seaofnodes/simple/node/MachNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MachNode.java
new file mode 100644
index 00000000..c0b3af66
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MachNode.java
@@ -0,0 +1,60 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public interface MachNode {
+
+ // Easy access to the abstract Machine
+ default Machine machine() { return CodeGen.CODE._mach; }
+
+ // Run a post-instruction-selection action
+ default void postSelect( CodeGen code ) { }
+
+ // Register mask allowed on input i. 0 for no register.
+ RegMask regmap( int i );
+ // Register mask allowed as a result. 0 for no register.
+ RegMask outregmap();
+ // Multi-reg-defining machine op; idx comes from Proj.
+ // Sometimes these Projs have no uses, and just exist to kill a register.
+ default RegMask outregmap(int idx) { throw Utils.TODO(); }
+
+ // Set of killed registers
+ default RegMask killmap() { return null; }
+
+ // If this op updates the same register as input 'idx', e.g. many X86 "R1 +=
+ // R2" style ops. This forces the output live range to equal the input live
+ // range, so they both get the same register.
+
+ // This can hold true for projections, if the instruction updates many
+ // registers that it also takes in. Note that ops also use the same
+ // registers for both inputs and outputs but these are pinned and without any
+ // choices (e.g. always RCX for both an input and output) and are better
+ // served with just declaring as a fixed register.
+
+ // Returns either 0 for not-two-adr or the updated input; for projections
+ // this is their Multi's updated input.
+ default int twoAddress( ) { return 0; }
+ // Ok to switch arguments
+ default boolean commutes() { return false; }
+
+ // Instructions cheaper to recreate than to spill, such as loading small constants
+ default boolean isClone() { return false; }
+
+ // Make a clone of a cheap instruction
+ default Node copy() { return null; }
+
+ // Encoding is appended, relocations gathered.
+ void encoding( Encoding enc );
+
+ // Human-readable form appended to the SB. Things like the encoding,
+ // indentation, leading address or block labels not printed here.
+ // Just something like "R17=[R18+12] // Load array base".
+ // General form: "dst=src+src".
+ default void asm(CodeGen code, SB sb) { throw Utils.TODO(); }
+
+ // Human readable opcode; e.g. "ld4" or "call".
+ default String op() { throw Utils.TODO(); }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
new file mode 100644
index 00000000..7a329117
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
@@ -0,0 +1,102 @@
+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 java.util.*;
+
+/** A default slice and explicit alias overrides. A null default means partial
+ * memory: absent aliases are not covered, rather than having unknown contents.
+ */
+public class MemMergeNode extends Node {
+ public MemMergeNode(MemMergeNode mem) { super(mem); }
+
+ public MemMergeNode(Node bulk) { super(null,bulk); _type = TypeMem.BOT; }
+ public MemMergeNode(Node bulk, int alias, Node precise) {
+ this(bulk);
+ alias(alias,precise);
+ }
+ public Node alias(int alias) {
+ Node n = alias < nIns() ? in(alias) : null;
+ assert n!=null || !(in(1) instanceof BulkMemPhiNode bulk) || !bulk.isSplit(alias);
+ n = n==null ? in(1) : n;
+ assert n!=null : "Alias not covered: "+alias;
+ return n;
+ }
+
+ public void alias(int alias, Node mem) {
+ assert alias > 1;
+ while( nIns()<=alias ) addDef(null);
+ setDef(alias,mem);
+ }
+
+ // Scalar contents of one alias. New zeroes the allocated fields;
+ // join it with the incoming contents for all previously allocated objects.
+ // Phis use their cached types, so this query does not recurse around loops.
+ static Type contents(Node mem, int alias, Node dep) {
+ // A loop memory Phi can also be a user of the querying Store.
+ // Its type changing still needs to revisit this query.
+ dep.addDepForwards(mem);
+ if( mem instanceof CastNode cast ) return contents(cast.in(1),alias,dep);
+ if( mem instanceof MemMergeNode merge )
+ return contents(merge.alias(alias),alias,dep);
+ if( mem instanceof ProjNode proj && proj.in(0) instanceof NewNode nnn ) {
+ assert proj._idx==1 && nnn.field(alias)!=null;
+ dep.addDep(nnn);
+ if( dep.addDep(nnn.in(0))._type.isHigh() ) return Type.TOP;
+ return contents(nnn.mem(),alias,dep).meet(nnn.field(alias)._t.makeZero());
+ }
+ // Function parameters and call results may contain arbitrary heap values.
+ // Their bulk memory types do not imply empty or zero-filled storage.
+ if( mem._type==Type.TOP || mem._type==TypeMem.TOP ) return Type.TOP;
+ if( mem._type instanceof TypeMem mt && mt._alias==alias ) return mt._t;
+ return Type.BOTTOM;
+ }
+
+ @Override public String label() { return "MemMerge"; }
+ @Override public boolean isMem() { return true; }
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ p.p("MEM[");
+ for( int i=1; i<nIns(); i++ )
+ if( in(i)!=null ) {
+ p.p(i==1 ? "default:" : i+":");
+ p.n(in(i)).p(" ");
+ }
+ return p.p("]");
+ }
+
+
+ @Override public Type compute() {
+ for( Node n : _inputs )
+ if( n != null && !n._type.isHigh() )
+ return TypeMem.BOT;
+ return TypeMem.TOP;
+ }
+
+ @Override public Node idealize() {
+ boolean progress=false, allDefault=true;
+ for( int i=2; i<nIns(); i++ ) {
+ if( in(i) instanceof CastNode cast ) { setDef(i,cast.in(1)); progress=true; }
+ if( in(i)!=null && in(i)==in(1) ) { setDef(i,null); progress=true; }
+ if( in(i) instanceof MemMergeNode mem ) {
+ setDef(i,CodeGen.CODE.add(mem.alias(i)));
+ progress=true;
+ }
+ if( in(i)!=null ) allDefault=false;
+ }
+ if( allDefault && in(1)!=null ) return in(1);
+
+ if( in(1) instanceof MemMergeNode mem ) {
+ for( int i=2; i<mem.nIns(); i++ )
+ if( mem.in(i)!=null && (i>=nIns() || in(i)==null) )
+ alias(i,mem.in(i));
+ CodeGen.CODE.add(mem.in(1));
+ setDef(1,mem.in(1));
+ return this;
+ }
+ assert BulkMemPhiNode.checkMerge(this);
+ return progress ? this : null;
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
new file mode 100644
index 00000000..e7b2c792
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
@@ -0,0 +1,146 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMemPtr;
+import com.seaofnodes.simple.util.Utils;
+import java.lang.StringBuilder;
+import java.util.BitSet;
+import java.util.ArrayList;
+
+/**
+ * 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 Node {
+
+ // The equivalence alias class
+ public final int _alias;
+
+ // True if load-like, false if store-like.
+ //
+ // Stores produce memory (maybe as part of a tuple with other things),
+ // loads do not.
+ //
+ // Loads might pick up anti-dependences 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;
+
+ // Declared type; not final because it might be a forward-reference
+ // which will be lazily improved when the reference is declared.
+ public Type _declaredType;
+
+ // A debug name, no semantic meaning
+ public final String _name;
+ // Source location for late reported errors
+ public final Parser.Lexer _loc;
+
+ public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node mem, Node ptr, Node off) {
+ super(null, mem, ptr, off);
+ _name = name;
+ _alias = alias;
+ _declaredType = glb;
+ _loc = loc;
+ _isLoad = isLoad;
+ }
+ public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node mem, Node ptr, Node off, Node value) {
+ this(loc,name, alias, isLoad, glb, mem, ptr, off);
+ addDef(value);
+ }
+ public MemOpNode( Node mach, MemOpNode mop ) {
+ super(mach);
+ _name = mop==null ? null : mop._name;
+ _alias = mop==null ? 0 : mop._alias;
+ _loc = mop==null ? null : mop._loc;
+ _isLoad= mop==null ? true : mop._isLoad;
+ _declaredType = mop==null ? Type.BOTTOM : mop._declaredType;
+ if( mop==null )
+ throw Utils.TODO("Load or not");
+ }
+
+ // Used by M2 when translating graph to Simple
+ public MemOpNode( boolean isLoad ) {
+ super((Node)null);
+ _name = null;
+ _alias = 0;
+ _loc = null;
+ _isLoad = isLoad;
+ _declaredType = Type.BOTTOM;
+ }
+
+ static String mlabel(String name) { return "[]".equals(name) ? "ary" : ("#".equals(name) ? "len" : name); }
+ String mlabel() { return mlabel(_name); }
+
+ @Override public boolean isMem() { return !_isLoad; }
+
+ public Node mem() { return in(1); }
+ public Node ptr() { return in(2); }
+ public Node off() { return in(3); }
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return _printMach(p); }
+ protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { throw Utils.TODO(); }
+ public int log_size() { return _declaredType.log_size(); }
+
+ // 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==other._alias && _declaredType==other._declaredType && err()==null;
+ }
+
+ @Override
+ public boolean eq(Node n) {
+ MemOpNode mem = (MemOpNode)n; // Invariant
+ return _alias==mem._alias; // When comparing types error to use "equals"; always use "=="
+ }
+
+ @Override
+ int hash() { return _alias; }
+
+ @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 null;
+ return Parser.error( "Might be null accessing '" + _name + "'",_loc);
+ }
+ // Memory writers which must follow this read. A packaging node is not a
+ // write, but can hide New's input, so follow this alias through aggregates.
+ public ArrayList<Node> antiDeps() {
+ ArrayList<Node> deps = new ArrayList<>();
+ antiDeps(mem(),deps,new BitSet());
+ return deps;
+ }
+
+ private void antiDeps(Node mem, ArrayList<Node> deps, BitSet visit) {
+ if( visit.get(mem._nid) ) return;
+ visit.set(mem._nid);
+ for( Node use : mem._outputs ) {
+ if( use instanceof MemMergeNode merge ) {
+ Node slice = _alias<merge.nIns() ? merge.in(_alias) : null;
+ if( (slice==mem) || (slice==null && merge.in(1)==mem) )
+ antiDeps(merge,deps,visit);
+ } else if( use instanceof CallNode ||
+ (use instanceof MemOpNode st && !st._isLoad && st._alias==_alias) ||
+ (use instanceof NewNode nnn && nnn.field(_alias)!=null) ||
+ (use instanceof MemPhiNode phi && phi._alias==_alias) ||
+ (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) {
+ if( !deps.contains(use) ) deps.add(use);
+ }
+ }
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
new file mode 100644
index 00000000..787333d9
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
@@ -0,0 +1,49 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
+
+/** A Phi for one field alias, parallel to a bulk Phi at the same Region. */
+public class MemPhiNode extends PhiNode {
+ public final int _alias;
+
+ public MemPhiNode(String label, int alias, Node... inputs) {
+ super(label,TypeMem.make(alias,Type.BOTTOM),inputs);
+ assert alias > 1;
+ _alias = alias;
+ }
+
+ public MemPhiNode(MemPhiNode phi) { super(phi); _alias = phi._alias; }
+
+ @Override public String label() { return "MemPhi_"+_alias; }
+
+ @Override public Type compute() {
+ assert BulkMemPhiNode.checkMem(this,_alias);
+ if( !(region() instanceof RegionNode r) )
+ return (region()._type==Type.XCONTROL || region()._type==Type.TOP) ? TypeMem.TOP : _type;
+ if( r.inProgress() ) return r instanceof LoopNode ? _minType : TypeMem.BOT;
+ Type t = Type.TOP;
+ for( int i=1; i<nIns(); i++ )
+ if( addDep(r.in(i))._type!=Type.XCONTROL && r.in(i)._type!=Type.TOP )
+ t = t.meet(MemMergeNode.contents(in(i),_alias,this));
+ return TypeMem.make(_alias,t);
+ }
+
+ @Override public Node idealize() {
+ for( int i=1; i<nIns(); i++ )
+ if( in(i) instanceof MemMergeNode mem ) {
+ setDef(i,CodeGen.CODE.add(mem.alias(_alias)));
+ 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-sccp/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MinusFNode.java
new file mode 100644
index 00000000..74439b8c
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MinusFNode.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple.node;
+
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+
+
+public class MinusFNode extends Node {
+ public MinusFNode(Node in) { super(null, in); }
+
+ @Override public String label() { return "MinusF"; }
+
+ @Override protected String format() { return "(-%1)"; }
+
+ @Override
+ public Type compute() {
+ if( in(1)._type.isHigh() ) return TypeFloat.TOP;
+ if( in(1)._type instanceof TypeFloat i0)
+ return i0.isConstant() ? TypeFloat.constant(-i0.value()) : i0;
+ return TypeFloat.F64;
+ }
+
+ @Override
+ public Node idealize() {
+ // -(-x) is x
+ if( in(1) instanceof MinusFNode minus )
+ return minus.in(1);
+
+ return null;
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MinusNode.java
new file mode 100644
index 00000000..94c69785
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node;
+
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.Utils;
+
+public class MinusNode extends Node {
+ public MinusNode(Node in) { super(null, in); }
+
+ @Override public String label() { return "Minus"; }
+
+ @Override protected String format() { return "(-%1)"; }
+
+ @Override
+ public Type compute() {
+ if( in(1)._type.isHigh() ) return TypeInteger.TOP;
+ if( in(1)._type 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;
+ }
+
+ @Override
+ public Node idealize() {
+ // -(-x) is x
+ if( in(1) instanceof MinusNode minus && addDep(minus.in(1))._type instanceof TypeInteger )
+ return minus.in(1);
+
+ return null;
+ }
+ @Override Node copyF() { return new MinusFNode(null); }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MulFNode.java
new file mode 100644
index 00000000..5c04af43
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MulFNode.java
@@ -0,0 +1,36 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+
+import java.util.BitSet;
+
+public class MulFNode extends ArithFNode {
+ public MulFNode(Node lhs, Node rhs) { super(lhs, rhs); }
+
+ @Override public String label() { return "MulF"; }
+
+ @Override public String glabel() { return "*"; }
+
+ @Override double doOp( double x, double y ) { return x*y; }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ Type t1 = lhs._type;
+ Type t2 = rhs._type;
+
+ // Mul of 1. We do not check for (1*x) because this will already
+ // canonicalize to (x*1)
+ if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==1 )
+ return lhs;
+
+ // Move constants to RHS: con*arg becomes arg*con
+ if ( t1.isConstant() && !t2.isConstant() )
+ return swap12();
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new MulFNode(lhs,rhs); }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MulNode.java
new file mode 100644
index 00000000..6e1dc053
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -0,0 +1,63 @@
+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 MulNode extends ArithNode {
+ public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "Mul"; }
+ @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;
+
+ // 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 instanceof TypeInteger ? lhs : null;
+ if( c==0 ) return Parser.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); }
+ @Override Node copyF() { return new MulFNode(null,null); }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MultiNode.java
new file mode 100644
index 00000000..b4d6d78e
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/MultiNode.java
@@ -0,0 +1,29 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.util.Ary;
+
+
+public interface MultiNode {
+
+ abstract Ary<Node> outs();
+
+ // Find a projection by index
+ default ProjNode proj( int idx ) {
+ for( Node out : outs() )
+ if( out instanceof ProjNode prj && prj._idx==idx )
+ return prj;
+ return null;
+ }
+
+ // Find a projection by index
+ default CProjNode cproj( int idx ) {
+ for( Node out : outs() )
+ if( out instanceof CProjNode prj && prj._idx==idx )
+ return prj;
+ return null;
+ }
+
+ // Return not-null if this projection index is a ideal copy.
+ // Called by ProjNode ideal and used to collapse Multis.
+ default Node pcopy(int idx) { return null; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/NeverNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/NeverNode.java
new file mode 100644
index 00000000..66879b66
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/NeverNode.java
@@ -0,0 +1,23 @@
+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(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-sccp/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/NewNode.java
new file mode 100644
index 00000000..840d2759
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/NewNode.java
@@ -0,0 +1,103 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+
+/** Allocate a zeroed object. Inputs {ctrl, $mem, size};
+ * results {ptr, $mem}.
+ * The memory input and result cover only the aliases in the allocated struct.
+ */
+public class NewNode extends Node implements MultiNode {
+
+ public final TypeMemPtr _ptr;
+
+ public NewNode(TypeMemPtr ptr, Node... nodes) {
+ super(nodes);
+ assert !ptr.nullable();
+ _ptr = ptr;
+ assert nodes.length==3;
+ assert nodes[0]._type==Type.CONTROL || nodes[0]._type==Type.XCONTROL;
+ assert nodes[1]._type instanceof TypeMem;
+ assert nodes[2]._type instanceof TypeInteger || nodes[2]._type==Type.NIL;
+ }
+
+ public Node mem() { return in(1); }
+ public Node size() { return in(2); }
+
+ public Field field(int alias) {
+ for( Field f : _ptr._obj._fields )
+ if( !f._one && f._alias==alias ) return f;
+ return null;
+ }
+
+ public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; }
+
+
+ @Override public String label() { return "new_"+(_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._t.str() : _ptr._obj.str()); }
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ return p.p("new ").p(_ptr._obj.str());
+ }
+
+
+ @Override
+ public TypeTuple compute() {
+ return in(0)._type.isHigh()
+ ? TypeTuple.make(_ptr.makeHigh(),TypeMem.TOP)
+ : TypeTuple.make(_ptr,TypeMem.BOT);
+ }
+
+ @Override
+ public Node idealize() {
+ Node progress=null;
+ for( int i=1; i<nIns(); i++ ) {
+ if( in(i) instanceof CastNode cast )
+ progress = setDef(i,cast.in(1));
+ }
+ return progress == null ? null : this;
+ }
+
+ @Override
+ public boolean eq(Node n) { return this == n; }
+
+ @Override
+ int hash() { return _ptr.hashCode(); }
+
+ // ------------
+ // MachNode specifics, shared across all CPUs
+ public int _arg2Reg, _xslot;
+ private RegMask _arg3Mask;
+ private RegMask _retMask;
+ private RegMask _kills;
+ public void cacheRegs(CodeGen code) {
+ _arg2Reg = code._mach.callArgMask(TypeFunPtr.CALLOC,2,0).firstReg();
+ _arg3Mask = code._mach.callArgMask(TypeFunPtr.CALLOC,3,0);
+ // 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._callerSave.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);
+ _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==2 ? _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("#calloc, ").p(code.reg(size()));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/Node.java
new file mode 100644
index 00000000..414530b4
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -0,0 +1,693 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.BaseNode;
+
+import com.seaofnodes.simple.IterPeeps;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.print.IRPrinter;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+import com.seaofnodes.simple.type.TypeInteger;
+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 abstract String label();
+
+ 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
+ protected <N extends Node> void addUse(N n) { _outputs.add(n); }
+
+ // 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
+ }
+ }
+
+ /**
+ * 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
+ @SuppressWarnings("unchecked")
+ public <N extends Node> N keep() { addUse(null); return (N)this; }
+ // 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);
+ n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type.
+ 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 = CodeGen.CODE._midAssert ? null : CodeGen.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;
+ }
+
+ /**
+ * Try to peephole at this node and return a better replacement Node if
+ * possible. We compute a {@link Type} and then check and replace:
+ * <ul>
+ * <li>if the Type {@link Type#isConstant}, we replace with a {@link ConstantNode}</li>
+ * <li>in a future chapter we will look for a
+ * <a href="https://en.wikipedia.org/wiki/Common_subexpression_elimination">Common Subexpression</a>
+ * to eliminate.</li>
+ * <li>we ask the Node for a better replacement. The "better replacement"
+ * is things like {@code (1+2)} becomes {@code 3} and {@code (1+(x+2))} becomes
+ * {@code (x+(1+2))}. By canonicalizing expressions we fold common addressing
+ * math constants, remove algebraic identities and generally simplify the
+ * code. </li>
+ *
+ * Unlike peephole above, this explicitly returns null for no-change, or not-null
+ * for a better replacement (which can be this).
+ * </ul>
+ */
+ public final Node peepholeOpt( ) {
+ CODE.iterCnt();
+ // Compute initial or improved Type
+ Type old = setType(compute());
+
+ // Replace constant computations from non-constants with a constant
+ // node. If peeps are disabled, still allow high Phis to collapse;
+ // they typically come from dead Regions, and we want the Region to
+ // collapse, which requires the Phis to die first.
+ if( !isConst() && _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";
+ 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.get(idx); }
+
+ /**
+ * 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.
+ */
+ <N extends Node> N addDep( N dep ) { return addDep(dep,false); }
+
+ // Keep an explicit dependency when inspecting an immediate user's shape.
+ <N extends Node> N addDepForwards(N dep) { return addDep(dep,true); }
+
+ private <N extends Node> N addDep(N dep, boolean forwards) {
+ // Running peepholes during the big assert cannot have side effects
+ // like adding dependencies.
+ if( CODE._midAssert ) return dep;
+ var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs;
+ if( obs != null ) obs.dep(this, dep);
+ if( dep._deps==null ) dep._deps = new Ary<>(Node.class);
+ if( dep._deps .find(this) != -1 ) return dep; // Already on list
+ if( !forwards && dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors
+ if( !forwards && dep._outputs.find(this) != -1 ) 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; }
+
+ // Semantic change to the graph (so NOT a peephole), used by the Parser.
+ // If any input is a float, flip to a float-flavored opcode and widen any
+ // non-float input.
+ public final Node widen() {
+ if( !hasFloatInput() ) return this;
+ Node flt = copyF();
+ if( flt==null ) return this;
+ for( int i=1; i<nIns(); i++ )
+ flt.setDef(i, in(i)._type instanceof TypeFloat ? in(i) : new ToFloatNode(in(i)).peephole());
+ kill();
+ return flt;
+ }
+ private boolean hasFloatInput() {
+ for( int i=1; i<nIns(); i++ )
+ if( in(i)._type instanceof TypeFloat )
+ return true;
+ return false;
+ }
+ Node copyF() { return null; }
+
+ // ------------------------------------------------------------------------
+ // 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;
+ return n;
+ }
+
+ // Report any post-optimize errors
+ public Parser.ParseException err() { return null; }
+
+ // Common integer constants
+ public static ConstantNode con(long x) { return (ConstantNode)(new ConstantNode(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;
+ }
+
+ /**
+ * 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-sccp/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/NotNode.java
new file mode 100644
index 00000000..775b069b
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/NotNode.java
@@ -0,0 +1,26 @@
+package com.seaofnodes.simple.node;
+
+
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Utils;
+
+public class NotNode extends Node {
+ public NotNode(Node in) { super(null, in); }
+
+ @Override public String label() { return "Not"; }
+
+ @Override protected String format() { return "(!%1)"; }
+
+ @Override
+ public TypeInteger compute() {
+ Type t0 = in(1)._type;
+ if( t0.isHigh() ) return (TypeInteger)TypeInteger.BOOL.dual();
+ if( t0 == Type.NIL || t0 == TypeInteger.ZERO ) return TypeInteger.TRUE;
+ if( t0 instanceof TypeNil tn && tn.notNull() ) return TypeInteger.FALSE;
+ if( t0 instanceof TypeInteger i && (i._min > 0 || i._max < 0) ) return TypeInteger.FALSE;
+ return TypeInteger.BOOL;
+ }
+
+ @Override
+ public Node idealize() { return null; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/OrNode.java
new file mode 100644
index 00000000..d16dd194
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/OrNode.java
@@ -0,0 +1,42 @@
+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); }
+
+ @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 && 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 OrNode(_loc,lhs,rhs); }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ParmNode.java
new file mode 100644
index 00000000..95934e4b
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ParmNode.java
@@ -0,0 +1,59 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMemPtr;
+
+public class ParmNode extends PhiNode {
+
+ // Argument indices are mapped one-to-one on CallNode inputs
+ public final int _idx; // Argument index
+
+ public ParmNode(String label, int idx, Type declaredType, Node... inputs) {
+ super(label,declaredType,inputs);
+ _idx = idx;
+ }
+ public ParmNode(ParmNode parm) { super(parm, parm._label, parm._minType ); _idx = parm._idx; }
+
+ @Override public String label() { return MemOpNode.mlabel(_label); }
+
+ public FunNode fun() { return (FunNode)in(0); }
+
+ @Override
+ protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ if( "main".equals(fun()._name) && _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 Node idealize() {
+ if( !(region() instanceof FunNode) )
+ return in(1); // Input has collapse to e.g. starting control.
+ // If function is folding, do all possible peeps
+ if( fun()._folding ) return super.idealize();
+
+ // Can upgrade minType even while in-progress
+ if( _minType instanceof TypeMemPtr tmp && _minType.isFRef() ) {
+ TypeMemPtr tmp2 = (TypeMemPtr) Parser.TYPES.get(tmp._obj._name);
+ if( tmp2!=null && tmp2 != _minType ) {
+ _minType = tmp2;
+ return this;
+ }
+ }
+ // 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-sccp/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/PhiNode.java
new file mode 100644
index 00000000..953e009e
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/PhiNode.java
@@ -0,0 +1,310 @@
+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;
+
+public class PhiNode extends Node {
+
+ public final String _label;
+
+ // The Phi type we compute must stay within the domain of the Phi. Example
+ // Int stays Int, Ptr stays Ptr, Control stays Control, Mem stays Mem.
+ Type _minType;
+
+ int lattice_drop;
+
+ public PhiNode(String label, Type minType, Node... inputs) {
+ super(inputs);
+ _label = label;
+ assert minType!=null;
+ _minType = minType;
+ }
+ // Used by ParmNode
+ public PhiNode(PhiNode phi, String label, Type minType) { super(phi); _label = label; _type = _minType = minType; }
+ // Used by instruction Selection
+ public PhiNode(PhiNode phi) { this(phi,phi._label,phi._minType ); }
+ // Used by the infinite-loop exit breaker
+ public PhiNode(RegionNode r, Node sample) {
+ super(new Node[]{r});
+ _label = "";
+ _minType = sample._type;
+ while( nIns() < r.nIns() )
+ addDef(sample);
+ }
+
+ public static PhiNode make(String label, Type type, Node... inputs) {
+ return type instanceof TypeMem ? new BulkMemPhiNode(label,inputs) : new PhiNode(label,type,inputs);
+ }
+
+ @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 _minType instanceof TypeMem; }
+ boolean isRPC() { return false; }
+
+ @Override
+ public Type compute() {
+ if( !(region() instanceof RegionNode r) )
+ return region()._type==Type.XCONTROL || region()._type==Type.TOP ? (_type instanceof TypeMem ? TypeMem.TOP : Type.TOP) : _type;
+ // During parsing Phis have to be computed type pessimistically.
+ if( r.inProgress() || in(nIns()-1)==null )
+ // Loop-Phis must lift to the declared type, because that is how
+ // the Parser keeps precise types until the loop finishes parsing.
+ // Similar, ParmNodes use precise minType until all calls are
+ // linked (post opto).
+ return r instanceof LoopNode || (this instanceof ParmNode) ? _minType : 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);
+ }
+ }
+ Type newt = t.join( _minType );
+
+ // Widen loop Phis so integer ranges cannot grow one step per trip forever.
+ // Calls are opaque during SCCP here. The next chapter also widens at
+ // ParmNode and CallEndNode when solving through recursive calls.
+ // Above-center ranges may still fall to unwidened constants.
+ // Widen only below-center ranges to preserve monotonicity.
+ if( region() instanceof LoopNode && // Only around loops
+ newt instanceof TypeInteger newi &&
+ // Types changed and are falling (the optimistic case, expected to fall forever)
+ newi != _type ) {
+ if( !newi.isHigh() && !newi.isConstant() && (!(_type instanceof TypeInteger oldi) || newi._widen <= oldi._widen) )
+ return newi.same_but_slightly_wider_than(_minType);
+ }
+
+ return newt;
+ }
+
+ @Override
+ public Node idealize() {
+ if( !(region() instanceof RegionNode r ) )
+ return in(1); // Input has collapse to e.g. starting control.
+ // Can upgrade minType even while in-progress
+ if( _minType instanceof TypeMemPtr tmp && _minType.isFRef() ) {
+ TypeMemPtr tmp2 = (TypeMemPtr) Parser.TYPES.get(tmp._obj._name);
+ if( tmp2!=null && tmp2._obj != tmp._obj ) {
+ _minType = tmp.makeFrom(tmp2._obj);
+ return this;
+ }
+ }
+ if( r.inProgress() || r.nIns()<=1 )
+ return null; // Input is in-progress
+
+ // If we have only a single unique input, become it.
+ Node live = singleUniqueInput();
+ if( live != null ) {
+ if( live._type.isa(_type) )
+ return live;
+ // Let the bulk input split this alias before collapsing a precise Phi.
+ if( live instanceof BulkMemPhiNode ) return null;
+ // Keep the Phi upcast
+ return new CastNode(_type,null,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;
+
+ // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...).
+ Node down;
+ if( same_op() && (down=drop_same_op())!=null ) return down;
+
+ // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove.
+ if( nIns()==3 ) {
+ if( in(1) instanceof CastNode cast && addDep(cast.in(1))==in(2) ) return in(2);
+ if( in(2) instanceof CastNode cast && addDep(cast.in(1))==in(1) ) return in(1);
+ }
+ // If merging a null-checked null and the checked value, just use the value.
+ // if( val ) ..; phi(Region,False=0/null,True=val);
+ // then replace with plain val.
+ if( nIns()==3 ) {
+ int nullx = -1;
+ if( in(1)._type == in(1)._type.makeZero() ) nullx = 1;
+ if( in(2)._type == in(2)._type.makeZero() ) nullx = 2;
+ if( nullx != -1 ) {
+ Node val = in(3-nullx);
+ if( val instanceof CastNode cast )
+ val = cast.in(1);
+ Node ridom = r.idom(this);
+ if( ridom instanceof IfNode iff && addDep(iff.pred())==val ) {
+ // Must walk the idom on the null side to make sure we hit False.
+ CFGNode idom = (CFGNode)r.in(nullx);
+ while( idom != null && idom.nIns() > 0 && idom.in(0) != iff ) idom = idom.idom();
+ if( idom instanceof CProjNode proj && proj._idx==1 )
+ return val;
+ } else if( ridom != null ) addDep(ridom);
+ }
+ }
+
+ return null;
+ }
+
+ // Same op on all Phi paths; all ops have only the Phi as a use.
+ // None have a control input.
+ private boolean same_op() {
+ Node op = in(1);
+ if( 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);
+ }
+ // Not accepts both pointers and integers, but a Phi cannot mix them.
+ 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.glb(false),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.
+ // Also, joins
+ @Override public boolean eq( Node n ) {
+ if( inProgress() ) return false;
+ Type min = ((PhiNode)n)._minType;
+ if( _minType==min ) return true;
+ Type mt = min.meet(_minType);
+ if( min!=mt && _minType!=mt ) return false;
+ //// Theory says these 2 Phis CAN be merged/GVNd, but I need to pick the
+ //// most general minType.
+ //_minType = ((PhiNode)n)._minType = mt;
+ //return true;
+ return false;
+ }
+
+ @Override
+ public Parser.ParseException err() {
+ if( _type != Type.BOTTOM ) return null;
+
+ // BOTTOM means we mixed e.g. int and ptr
+ 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;
+
+ // Gather a minimal set of types that "cover" all the rest
+ boolean ti=false, tf=false, tp=false, tn=false;
+ for( int i=1; i<nIns(); i++ ) {
+ Type t = in(i)._type;
+ ti |= t instanceof TypeInteger x;
+ tf |= t instanceof TypeFloat x;
+ tp |= t instanceof TypeMemPtr x;
+ tn |= t==Type.NIL;
+ }
+ return ReturnNode.mixerr(ti,tf,tp,tn, ((RegionNode)region())._loc);
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ProjNode.java
new file mode 100644
index 00000000..92c6c2be
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ProjNode.java
@@ -0,0 +1,54 @@
+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;
+
+
+public class ProjNode extends Node {
+
+ // 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); }
+
+ @Override
+ public boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; }
+
+ @Override
+ int hash() { return _idx; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java
new file mode 100644
index 00000000..a35f5a1d
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ReadOnlyMach.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.util.SB;
+
+/**
+ * Cast a pointer to read-only
+ */
+public class ReadOnlyMach extends ReadOnlyNode implements MachNode {
+ public ReadOnlyMach( ReadOnlyNode n ) { super(n); }
+ @Override public String op() { return label(); }
+ @Override public RegMask regmap(int i) { assert i==1; return RegMask.FULL; }
+ @Override public RegMask outregmap() { return RegMask.FULL; }
+ @Override public int twoAddress( ) { return 1; }
+ @Override public void encoding( Encoding enc ) { }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(in(1))); }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java
new file mode 100644
index 00000000..94c1b6f6
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java
@@ -0,0 +1,27 @@
+package com.seaofnodes.simple.node;
+
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMemPtr;
+
+/**
+ * Cast a pointer to read-only
+ */
+public class ReadOnlyNode extends Node {
+ public ReadOnlyNode( Node n ) { super(null,n); }
+ public ReadOnlyNode( ReadOnlyNode n ) { super(n); }
+ @Override public String label() { return "ReadOnly"; }
+
+ @Override protected String format() { return "(const)%1"; }
+ @Override public Type compute() {
+ Type t = in(1)._type;
+ return t instanceof TypeMemPtr tmp ? tmp.makeRO() : t;
+ }
+
+ @Override public Node idealize() {
+ if( in(1)._type instanceof TypeMemPtr tmp && tmp.isFinal() )
+ return in(1);
+ return null;
+ }
+
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/RegionNode.java
new file mode 100644
index 00000000..59030dee
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/RegionNode.java
@@ -0,0 +1,174 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.*;
+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
+ public String label() { return "Region"; }
+
+ @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,Parser.XCTRL);
+ return this;
+ }
+ }
+
+ return null;
+ }
+
+ Node deadPath(boolean skipStart) {
+ // Delete dead paths into a Region
+ int path = findDeadInput(skipStart ? 2 :1);
+ if( path==0 ) return null;
+
+ // Do not delete the entry path of a loop (ok to remove the back edge
+ // and make the loop a single-entry Region which folds away the Loop).
+ // Folding the entry path confused the loop structure, moving the
+ // backedge to the entry point.
+ if( this instanceof LoopNode loop && loop.entry()==in(path) )
+ return null;
+ return removeDeadPath(path);
+ }
+
+ public Node removeDeadPath(int path) {
+ // Cannot use the obvious output iterator here, because a Phi deleting
+ // an input might recursively delete *itself*. This shuffles the
+ // output array, and we might miss iterating an unrelated Phi. So on
+ // rare occasions we repeat the loop to get all the Phis.
+ int nouts = 0;
+ while( nouts != nOuts() ) {
+ nouts = nOuts();
+ for( int i=0; i<nOuts(); i++ )
+ if( out(i) instanceof PhiNode phi && phi.nIns()==nIns() ) {
+ CodeGen.CODE.addAll(phi.in(path)._outputs);
+ CodeGen.CODE.addAll(phi.delDef(path)._outputs);
+ }
+ }
+ return isDead() ? Parser.XCTRL : delDef(path);
+ }
+
+ private int findDeadInput(int startIdx) {
+ for( int i=startIdx; i<nIns(); i++ )
+ if( in(i)._type==Type.XCONTROL )
+ return i;
+ return 0; // All inputs alive
+ }
+
+ boolean hasPhi() {
+ for( Node phi : _outputs )
+ if( phi instanceof PhiNode )
+ return true;
+ return false;
+ }
+
+ // Is there a value in-between 2 Regions? Difficult to correctly fuse
+ // without extensive testing, so just ignore.
+ boolean hasMidUser(RegionNode r) {
+ for( Node use : r._outputs ) {
+ if( use==this ) continue;
+ if( use instanceof PhiNode ) {
+ for( Node data : use._outputs ) {
+ if( !(data instanceof PhiNode phi2) || phi2.region()!=this )
+ { addDep(use); if( data!=null ) addDep(data); return true; }
+ }
+ } else
+ { addDep(use); return true; } // Control user
+ }
+ return false;
+ }
+
+
+ // Immediate dominator of Region is a little more complicated.
+ @Override public int idepth() {
+ if( 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-sccp/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
new file mode 100644
index 00000000..266657be
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
@@ -0,0 +1,163 @@
+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;
+
+/** Keeps all preceding memory effects and the return value alive: {ctrl, $mem, value, rpc}. */
+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
+ public String label() { return "Return"; }
+
+ @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() {
+ if( inProgress () ) return TypeTuple.RET; // In progress
+ // Inlining can delete the entry while callers still use this return.
+ return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type);
+ }
+
+ @Override public Node idealize() {
+ if( inProgress () ) return null;
+ if( _fun.isDead() ) return null;
+
+ // Upgrade signature based on return type
+ Type ret = expr()._type;
+ TypeFunPtr fcn = _fun.sig();
+ if( ret != fcn.ret() && ret.isa(fcn.ret()) )
+ _fun.setSig(fcn.makeFrom(ret));
+
+ // If dead (cannot be reached; infinite loop), kill the exit values
+ if( ctrl()._type==Type.XCONTROL &&
+ !(mem() instanceof ConstantNode && expr() instanceof ConstantNode) ) {
+ Node top = new ConstantNode(Type.TOP).peephole();
+ setDef(1,top);
+ setDef(2,top);
+ return this;
+ }
+
+ return null;
+ }
+
+ public boolean inProgress() {
+ return ctrl().getClass() == RegionNode.class && ((RegionNode)ctrl()).inProgress();
+ }
+
+ // Remember parsed type kinds for diagnostics; the optimized expression decides validity.
+ private boolean ti=false, tf=false, tp=false, tn=false;
+
+ // Add a return exit to the current parsing function
+ void addReturn( Node ctrl, Node rmem, Node expr ) {
+ assert inProgress();
+
+ // Gather parse-time return types for error reporting
+ Type t = expr._type;
+ ti |= t instanceof TypeInteger x;
+ tf |= t instanceof TypeFloat x;
+ tp |= t instanceof TypeMemPtr x;
+ tn |= t==Type.NIL;
+
+ // Merge path into the One True Return
+ RegionNode r = (RegionNode)ctrl();
+ // Assert that the Phis are in particular outputs; not reordered or shuffled
+ PhiNode mem = (PhiNode)r.out(0); assert mem._minType == TypeMem.BOT;
+ PhiNode rez = (PhiNode)r.out(1); assert rez._minType == Type.BOTTOM;
+ // Pop "inProgress" null off
+ r ._inputs.pop();
+ mem._inputs.pop();
+ rez._inputs.pop();
+ // Add new return point
+ r .addDef(ctrl);
+ mem.addDef(rmem);
+ rez.addDef(expr);
+ // Back to being inProgress
+ r .addDef(null);
+ mem.addDef(null);
+ rez.addDef(null);
+ }
+
+ @Override public Parser.ParseException err() {
+ if( ctrl()._type != Type.CONTROL ) return null; // Exit path is dead
+ return expr()._type==Type.BOTTOM || expr()._type==Type.TOP ? mixerr(ti,tf,tp,tn,_fun._loc) : null;
+ }
+
+ static Parser.ParseException mixerr( boolean ti, boolean tf, boolean tp, boolean tn, Parser.Lexer loc ) {
+ if( !ti && !tf && !tp && !tn )
+ // Hit when the function never returns. e.g. `f = { -> return f() };`
+ return Parser.error("No defined return type", loc);
+ // Rather ugly way to print conflicting return types
+ int cnt = (ti?1:0) + (tf?1:0) + (tp||tn?1:0);
+ if( cnt==1 ) return null; // Uniform return type, something else errored and will report
+ SB sb = new SB().p("No common type amongst ");
+ if( ti ) sb.p("int and ");
+ if( tf ) sb.p("f64 and ");
+ if( tp || tn ) sb.p("reference and ");
+ return Parser.error(sb.unchar(5).toString(),loc);
+ }
+
+
+ // ------------
+ // MachNode specifics, shared across all CPUs
+ public String op() {
+ return _fun._frameAdjust > 0 ? "addi" : "ret";
+ }
+ // Correct Nodes outside the normal edges
+ public void postSelect(CodeGen code) {
+ FunNode fun = (FunNode)rpc().in(0);
+ _fun = fun;
+ 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("rsp += #").p(frameAdjust).p("\nret");
+ // Post code-gen, just print the "ret"
+ if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() )
+ // 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),fun())).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-sccp/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java
new file mode 100644
index 00000000..10a0e4aa
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java
@@ -0,0 +1,34 @@
+package com.seaofnodes.simple.node;
+
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+
+
+public class RoundF32Node extends Node {
+ public RoundF32Node(Node lhs) { super(null, lhs); }
+
+ @Override public String label() { return "RoundF32"; }
+
+ @Override protected String format() { return "((f32)%1)"; }
+
+ @Override
+ public Type compute() {
+ if (in(1)._type instanceof TypeFloat i0 && i0.isConstant() )
+ return TypeFloat.constant((float)i0.value());
+ if( in(1)._type==TypeFloat.F64 ) return TypeFloat.F32;
+ return in(1)._type;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Type t1 = lhs._type;
+
+ // RoundF32 of float
+ if( t1 instanceof TypeFloat tf && tf._sz==32 )
+ return lhs;
+
+ return null;
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SarNode.java
new file mode 100644
index 00000000..429be229
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SarNode.java
@@ -0,0 +1,36 @@
+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); }
+
+ @Override public String label() { return "Sar"; }
+ @Override public String op() { return ">>"; }
+ @Override long doOp( long x, long y ) { return x >> y; }
+ @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) {
+ if( y.isConstant() ) {
+ int log = (int)y.value();
+ return TypeInteger.make(-1L<<(63-log),(1L<<(63-log))-1);
+ }
+ return TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ Type t2 = rhs._type;
+
+ // Sar of 0.
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && 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-sccp/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
new file mode 100644
index 00000000..307fc0f9
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -0,0 +1,530 @@
+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.type.*;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.AryInt;
+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 variable records.
+ */
+public class ScopeNode extends Node {
+
+ /**
+ * 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";
+
+ public Node in(Var v) { return in(v._idx); }
+
+ @Override public Type compute() { return Type.BOTTOM; }
+ @Override public Node idealize() { return null; }
+
+ // All active/live variables in all nested scopes, all run together
+ public final Ary<Var> _vars;
+
+ // Lexical scope is typed:
+ public static class Kind {
+ // Basic block scoping
+ public static class Block extends Kind { }
+ // Function scope
+ public static class Func extends Kind { }
+ // Struct definition, mapping types to fields
+ public static class Define extends Kind { public Define(TypeMemPtr tmp) { _tmp=tmp; } public final TypeMemPtr _tmp; }
+ // Struct allocation, mapping values to fields
+ public static class Alloc extends Kind { public Alloc (TypeMemPtr tmp) { _tmp=tmp; } public final TypeMemPtr _tmp; }
+
+ public int _lexSize; // Number of Vars in this scope
+ }
+ public final Ary<Kind> _kinds;
+ // Lexical scope nesting depth
+ public int depth() { return _kinds._len; }
+
+ // Extra guards; tested predicates and casted results
+ private final Ary<Node> _guards;
+
+ // A new ScopeNode
+ public ScopeNode() {
+ _type = Type.BOTTOM;
+ _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 = _vars.at(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(lookup(MEM0)); }
+ 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) { update(MEM0,n); return n; }
+
+ public void push( Kind kind ) {
+ kind._lexSize = _vars.size();
+ _kinds.push(kind);
+ }
+
+ // Pop a lexical scope
+ public void pop() {
+ promote(); // Promote forward references to the next outer scope
+ 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() {
+ Kind kind = _kinds.last();
+ int n = kind._lexSize;
+ for( int i=n; i<nIns(); i++ ) {
+ Var v = var(i);
+ if( !v.isFRef() ) continue;
+ if( depth()==1 )
+ throw Parser.error("Undefined name '" + v._name + "'",v._loc);
+ _vars .swap(n,i);
+ _inputs.swap(n,i);
+ v._idx = n;
+ kind._lexSize = ++n;
+ }
+ }
+
+
+ // Allocation fields live in the scope immediately outside the constructor
+ // block. Nested blocks retain that permission; surrounding bindings do not.
+ public boolean canInit(Var v) {
+ for( int i=_kinds._len-1; i>0; i-- )
+ if( _kinds.at(i) instanceof Kind.Alloc )
+ return v._idx >= _kinds.at(i-1)._lexSize && v._idx < _kinds.at(i)._lexSize;
+ return false;
+ }
+
+ public boolean inConstructor() { return _kinds.last() instanceof Kind.Define; }
+ public boolean inAllocation () { return _kinds.last() instanceof Kind.Alloc ; }
+ public boolean inFunction () { return _kinds.last() instanceof Kind.Func ; }
+
+ public Kind kind( Var v ) {
+ for( int i=depth()-1; i>=0; i-- )
+ if( v._idx >= _kinds.at(i)._lexSize )
+ return _kinds.at(i);
+ throw Utils.TODO();
+ }
+
+ // Return the kind for the defining scope, or a function scope if found
+ // first. Block scopes means the variable can be r/w directly in the
+ // scope, Function scopes require final constants (no capture), and
+ // Constructor scopes mean an instance variable reference.
+
+ // Walk up-lexical scope looking for defining lexical Kind.
+ // - Skip any amount of nested block scopes
+ // - Walking out of a function requires v be a final constant (for now) -
+ // and we can stop walking.
+
+ // - Walking out of a method and (skipping Blocks) into the matching struct
+ // is OK, and this is a instance var load.
+
+ // - Walking out of a method and into the wrong struct is an error;
+ // including nested structs containing (no nested classes yet)
+
+ // Returns error, forward-ref, ok containing struct, ok containing function/block.
+
+ public String outOfFunction( Var v ) {
+ //if( v==null ) return null; // Prolly forward reference
+ //int i; for( i=_lexSize._len-1; i>=0 && v._idx<_lexSize.at(i); i-- )
+ // if( _kinds.at(i)=="{->}" ) return "{->}";
+ //return _kinds.at(i);
+ throw Utils.TODO();
+ }
+
+
+ // 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.charAt(0)!='$' ; // Later scopes do not define memory
+ if( depth() > 0 )
+ for( int i=_vars.size()-1; i>=_kinds.last()._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
+ }
+ }
+ Var v = new Var(nIns(),name,declaredType,xfinal,loc,init==Parser.XCTRL);
+ _vars.add(v);
+ // Creating a forward reference
+ if( init==Parser.XCTRL )
+ init = new FRefNode(v).init();
+ addDef(init);
+ return true;
+ }
+
+ /**
+ * 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).peephole());
+ 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 binding, with the same lazy loop Phi as scalar names.
+ for( int i=1; i<nIns(); i++ )
+ dup.addDef(loop && !_vars.at(i)._final ? this : in(i));
+ return dup;
+ }
+
+ /**
+ * Merges the names whose node bindings differ, by creating Phi node for such names
+ * The names could occur at all stack levels, but a given name can only differ in the
+ * innermost stack level where the name is bound.
+ *
+ * @param that The ScopeNode to be merged into this
+ * @return A new node representing the merge point
+ */
+ public RegionNode mergeScopes(ScopeNode that, Parser.Lexer loc) {
+ RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep());
+ _merge(that,r);
+ that.kill(); // Kill merged scope
+ CodeGen.CODE.add(r);
+ return r.unkeep();
+ }
+
+ private void _merge(ScopeNode that, RegionNode r) {
+ for( int i = 1; i < nIns(); 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.
+ public void balanceIf( ScopeNode scope ) {
+ for( int i = nIns(); i < scope.nIns(); i++ ) {
+ Var n = scope.var(i);
+ if( n.isFRef() ) { // RHS has forward refs
+ _vars.add(n); // Copy to LHS
+ addDef(scope.in(i));
+ } else
+ throw Parser.error("Cannot define a '"+n._name+"' on one arm of an if",n._loc);
+ }
+ }
+
+
+ // peephole the backedge scope into this loop head scope
+ // We set the second input to the phi from the back edge (i.e. loop body)
+ public void endLoop(ScopeNode back, ScopeNode exit ) {
+ Node ctrl = ctrl();
+ assert ctrl instanceof LoopNode loop && loop.inProgress();
+ ctrl.setDef(2,back.ctrl());
+
+ _endLoop(this,back,exit);
+ back.kill(); // Loop backedge is dead
+ // Now one-time do a useless-phi removal
+ _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( _vars.at(i)._final ) continue; // Final vars did not get modified in the loop
+ if( _vars.at(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi
+ if( back.in(i) != scope ) {
+ PhiNode phi = (PhiNode)in(i);
+ 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,in(i));
+ }
+ }
+
+
+ // Now one-time do a useless-phi removal
+ void _useless( ) {
+ for( int i=1; i<nIns(); i++ ) {
+ if( in(i) instanceof PhiNode phi ) {
+ // Do an eager useless-phi removal
+ Node in = phi.peephole();
+ CodeGen.CODE.addAll(phi._outputs);
+ phi.moveDepsToWorklist();
+ if( in != phi ) {
+ if( !phi.iskeep() ) // Keeping phi around for parser elsewhere
+ phi.subsume(in);
+ setDef(i,in); // Set the update back into Scope
+ }
+ }
+ }
+ }
+
+ // Up-casting: using the results of an If to improve a value.
+ // E.g. "if( ptr ) ptr.field;" is legal because ptr is known not-null.
+ public void addGuards( Node ctrl, Node pred, boolean invert ) {
+ assert ctrl instanceof CFGNode;
+ _guards.add(ctrl); // Marker 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( Node ctrl, Node pred, boolean invert ) {
+ if( pred==null || pred.isDead() )
+ return;
+ if( !availableAt(pred,(CFGNode)ctrl,new BitSet()) )
+ 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();
+ }
+ // A false || or true && proves both operands. Only guard values
+ // available at this control, not ones confined to the RHS branch.
+ 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());
+ // This is a zero/null test.
+ // Compute the positive test type.
+ Type tnz = pred._type.nonZero();
+ if( tnz!=null )
+ _addGuard(tnz,ctrl,pred);
+
+ // Compute the negative test type.
+ if( pred instanceof NotNode not ) {
+ Node npred = not.in(1);
+ Type tzero = npred._type.makeZero();
+ _addGuard(tzero,ctrl,npred);
+ }
+ }
+
+ // A Phi's value is available at its Region. Other data nodes require all
+ // their inputs, including pinned control, to dominate the proposed guard.
+ private static boolean availableAt(Node n, CFGNode ctrl, BitSet visit) {
+ if( n==null || n instanceof ConstantNode || visit.get(n._nid) ) return true;
+ visit.set(n._nid);
+ if( n instanceof PhiNode phi ) n = phi.region();
+ if( n instanceof CFGNode cfg ) {
+ for( CFGNode dom=ctrl; dom!=null; dom=dom.idom() )
+ if( dom==cfg ) return true;
+ return false;
+ }
+ for( Node def : n._inputs )
+ if( !availableAt(def,ctrl,visit) ) return false;
+ return true;
+ }
+
+ 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(Type guard, Node ctrl, Node pred) {
+ Type tcast = guard.join(pred._type);
+ if( tcast != pred._type && !tcast.isHigh() ) {
+ Node cast = new CastNode(tcast,ctrl,pred.keep()).peephole().keep();
+ _guards.add(pred);
+ _guards.add(cast);
+ replace(pred,cast);
+ }
+ }
+
+
+ // Remove matching pred/cast pairs from this guarded region.
+ public ScopeNode 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
+ }
+ return this;
+ }
+
+ // If we find a guarded instance of pred, replace with the upcasted version
+ public Node upcastGuard( Node pred ) {
+ // If finding an instanceof pred, replace with cast.
+ // Otherwise, just pred itself.
+ int i = _guards._len;
+ while( i > 0 ) {
+ Node cast = _guards.at(--i);
+ if( cast instanceof CFGNode ) continue; // Marker between guard sets
+ Node xpred = _guards.at(--i);
+ if( xpred == pred )
+ return cast;
+ }
+ return pred;
+ }
+
+ // Kill guards also
+ @Override public void kill() {
+ for( Node n : _guards )
+ if( !(n instanceof CFGNode) )
+ n.unkill();
+ _guards.clear();
+ // Can have lazy uses remaining
+ if( isUnused() )
+ super.kill();
+ }
+
+
+ private void replace( Node old, Node cast ) {
+ assert old!=null && old!=cast;
+ for( int i=0; i<nIns(); i++ )
+ if( in(i)==old )
+ setDef(i,cast);
+ }
+
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ShlNode.java
new file mode 100644
index 00000000..e2951784
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ShlNode.java
@@ -0,0 +1,48 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+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); }
+
+ @Override public String label() { return "Shl"; }
+ @Override public String op() { return "<<"; }
+ @Override long doOp( long x, long y ) { return x << y; }
+ @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) {
+ if( x == TypeInteger.ZERO )
+ return TypeInteger.ZERO;
+ if( y.isConstant() ) {
+ int shf = (int)y.value();
+ // If no overflow, shift endpoints
+ if( !(((x._min<<shf)>>shf) != x._min ||
+ ((x._max<<shf)>>shf) != x._max ) )
+ return TypeInteger.make(x._min<<shf,x._max<<shf);
+ }
+ return TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+
+ if( rhs._type instanceof TypeInteger shl && shl.isConstant() ) {
+ // Shl of 0.
+ if( (shl.value()&63)==0 && 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(), Parser.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-sccp/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ShrNode.java
new file mode 100644
index 00000000..0023431f
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ShrNode.java
@@ -0,0 +1,32 @@
+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); }
+
+ @Override public String label() { return "Shr"; }
+ @Override public String op() { return ">>>"; }
+ @Override long doOp( long x, long y ) { return x >>> y; }
+ @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) {
+ return x == TypeInteger.ZERO ? x : TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ Type t2 = rhs._type;
+
+ // Shr of 0.
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && 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-sccp/src/main/java/com/seaofnodes/simple/node/SplitNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SplitNode.java
new file mode 100644
index 00000000..a7fe6c5f
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SplitNode.java
@@ -0,0 +1,28 @@
+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.util.SB;
+
+public abstract class SplitNode extends MachConcreteNode {
+ public final String _kind; // Kind of split
+ public final byte _round;
+ public SplitNode(String kind, byte round, Node[] nodes) { super(nodes); _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();
+ }
+ @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-sccp/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StartNode.java
new file mode 100644
index 00000000..e13d35c0
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StartNode.java
@@ -0,0 +1,49 @@
+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 java.util.HashSet;
+import static com.seaofnodes.simple.util.Utils.TODO;
+
+/** Start supplies control, whole memory, and the argument, in that order. */
+public class StartNode extends LoopNode implements MultiNode {
+
+ final Type _arg;
+
+ public StartNode(Type arg) { super(null,null); _arg = arg; _type = compute(); }
+ public StartNode(StartNode start) { super(start); _arg = start==null ? null : start._arg; }
+
+ @Override public String label() { return "Start"; }
+
+ @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, its "main"
+ @Override public CFGNode uctrl() {
+ // Find "main", it's the start.
+ CFGNode C = null;
+ for( Node use : _outputs )
+ if( use instanceof FunNode fun && fun.sig().isa(CodeGen.CODE._main) )
+ { assert C==null; C = fun; }
+ return C;
+ }
+
+
+ @Override public TypeTuple compute() {
+ return TypeTuple.make(Type.CONTROL,TypeMem.BOT,_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; }
+
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StopNode.java
new file mode 100644
index 00000000..9f7a925b
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StopNode.java
@@ -0,0 +1,76 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.Utils;
+
+public class StopNode extends CFGNode {
+
+ public final String _src;
+
+ public StopNode(String src) {
+ super();
+ _src = src;
+ _type = compute();
+ }
+ public StopNode(StopNode stop) { super(stop); _src = stop==null ? null : stop._src; }
+
+ @Override
+ public String label() {
+ return "Stop";
+ }
+
+ @Override
+ protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ // For the sake of many old tests, and single value prints as "return val"
+ ReturnNode ret1 = ret();
+ if( ret1!=null ) return p.n(ret1);
+ p.p("Stop[ ");
+ for( Node ret : _inputs )
+ if( ret!=null ) {
+ String name = ((ReturnNode)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() {
+ Node ret1 = this;
+ for( Node ret : _inputs ) {
+ String name = ((ReturnNode)ret).fun()._name;
+ if( name==null || !name.startsWith("sys.") )
+ ret1 = ret1==this ? ((ReturnNode)ret) : null;
+ }
+ return ret1==this ? null : (ReturnNode)ret1;
+ }
+
+ @Override
+ public Type compute() {
+ return Type.BOTTOM;
+ }
+
+ @Override
+ public Node idealize() {
+ int len = nIns();
+ for( int i=0; i<nIns(); i++ )
+ if( addDep(((ReturnNode)in(i)).fun()).isDead() )
+ delDef(i--);
+ if( len != nIns() ) return this;
+ return null;
+ }
+
+ @Override public int idepth() {
+ if( validIDepth() ) return _idepth;
+ int d=0;
+ for( Node ret : _inputs )
+ d = Math.max(d,((ReturnNode)ret).idepth()+1);
+ return cacheIDepth(d);
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StoreNode.java
new file mode 100644
index 00000000..47f12d82
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -0,0 +1,183 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.type.*;
+
+/**
+ * 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 {
+
+ private final boolean _init; // Initializing writes are allowed to write null
+
+ /**
+ * @param name The struct field we are assigning to
+ * @param mem The memory alias node - this is updated after a Store
+ * @param ptr The ptr to the struct base where we will store a value
+ * @param off The offset inside the struct base
+ * @param value Value to be stored
+ */
+ public StoreNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off, Node value, boolean init) {
+ super(loc, name, alias, false, glb, mem, ptr, off, value);
+ _init = init;
+ }
+
+ // Debugger label
+ @Override boolean canDrop(MemOpNode other, Node dep) {
+ return super.canDrop(other,dep) && _init==((StoreNode)other)._init;
+ }
+
+ @Override public String label() { return "st_"+mlabel(); }
+ @Override public boolean isMem() { return true; }
+
+ public Node val() { return in(4); }
+
+ @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;
+ assert !_declaredType.isFRef();
+ Type t = val.join(_declaredType).meet(MemMergeNode.contents(mem(),_alias,this));
+ return TypeMem.make(_alias,t);
+ }
+
+ @Override
+ public Node idealize() {
+ if( mem() instanceof MemMergeNode merge ) {
+ setDef(1,CodeGen.CODE.add(merge.alias(_alias)));
+ return this;
+ }
+
+
+ if( mem() instanceof CastNode cast ) {
+ setDef(1,cast.in(1));
+ return this;
+ }
+
+ // Simple store-after-store on same address. Should pick up the
+ // required init-store being stomped by a first user store.
+ if( mem() instanceof StoreNode st &&
+ ptr()==st.ptr() && _alias==st._alias && // Must check same object
+ off()==st.off() && // And same offset
+ 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.equals(st._name); // Equiv class aliasing is perfect
+ setDef(1,st.mem());
+ return this;
+ }
+
+ // Value is automatically truncated by narrow store
+ if( val() instanceof AndNode and && and.in(2)._type.isConstant() ) {
+ int log = _declaredType.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( 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;
+ }
+
+ // 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 ) 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,_declaredType,mem,ptr(),off()).peephole();
+ Node value = CodeGen.CODE.add(new PhiNode(_name,_declaredType,loop,init,val()).peephole());
+ Node sink = new StoreNode(_loc,_name,_alias,_declaredType,mem,ptr(),off(),value,_init);
+ // 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;
+ if( ptr()._type == Type.TOP )
+ return null; // This means we have an error input, report elsewhere
+ TypeMemPtr tmp = (TypeMemPtr)ptr()._type;
+ if( (tmp._ro || tmp._obj.field(_name)._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; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StructNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StructNode.java
new file mode 100644
index 00000000..78b20e9f
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/StructNode.java
@@ -0,0 +1,47 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.SB;
+
+/**
+ * Build a compound object
+ */
+public class StructNode extends Node {
+
+ public final TypeStruct _ts;
+ public StructNode(TypeStruct ts) { _ts=ts; assert !ts._open; }
+
+ @Override public String label() { return _ts==null ? "STRUCT?" : _ts.str(); }
+
+ @Override
+ protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ if( _ts==null ) return p.p("STRUCT?");
+ p.p(_ts._name).p(" {");
+ for( int i=0; i<nIns(); i++ ) {
+ p.p(_ts._fields[i]._fname).p(":");
+ p.p(in(i)==null ? Type.BOTTOM : in(i)._type);
+ p.p("; ");
+ }
+ return p.unchar("; ").p("}");
+ }
+
+ @Override
+ public TypeStruct compute() {
+ if( _ts==null ) return TypeStruct.BOT;
+ Field[] fs = new Field[_ts._fields.length];
+ for( int i=0; i<fs.length; i++ )
+ fs[i] = _ts._fields[i].makeFrom(in(i)==null ? Type.TOP : in(i)._type);
+ return TypeStruct.make(_ts._name,false,fs);
+ }
+
+ @Override
+ public Node idealize() { return null; }
+
+ @Override
+ public boolean eq(Node n) { return _ts == ((StructNode)n)._ts; }
+
+ @Override
+ int hash() { return _ts==null ? 0 : _ts.hashCode(); }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SubFNode.java
new file mode 100644
index 00000000..3fc4f7ca
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SubFNode.java
@@ -0,0 +1,27 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+
+import java.util.BitSet;
+
+public class SubFNode extends ArithFNode {
+ public SubFNode(Node lhs, Node rhs) { super(lhs, rhs); }
+
+ @Override public String label() { return "SubF"; }
+
+ @Override public String glabel() { return "-"; }
+
+ @Override double doOp( double x, double y ) { return x-y; }
+
+ @Override
+ public Node idealize() {
+ // Sub of 0.
+ Type t2 = in(2)._type;
+ if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==0 )
+ return in(1);
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new SubFNode(lhs,rhs); }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SubNode.java
new file mode 100644
index 00000000..2c4fed39
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -0,0 +1,54 @@
+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 {
+ public SubNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "Sub"; }
+ @Override public String op() { return "-"; }
+
+ @Override long doOp( long x, long 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 for type checking.
+ if( 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); }
+ @Override Node copyF() { return new SubFNode(null,null); }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java
new file mode 100644
index 00000000..840a95ec
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java
@@ -0,0 +1,27 @@
+package com.seaofnodes.simple.node;
+
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+import com.seaofnodes.simple.type.TypeInteger;
+
+
+public class ToFloatNode extends Node {
+ public ToFloatNode(Node lhs) { super(null, lhs); }
+
+ @Override public String label() { return "ToFloat"; }
+
+ @Override protected String format() { return "(flt)%1"; }
+
+ @Override
+ public Type compute() {
+ if( in(1)._type.isHigh() ) return TypeFloat.F64.dual();
+ if( in(1)._type == Type.NIL ) return TypeFloat.FZERO;
+ if( in(1)._type instanceof TypeInteger i0 && i0.isConstant() )
+ return TypeFloat.constant(i0.value());
+ return TypeFloat.F64;
+ }
+
+ @Override public Node idealize() { return null; }
+ @Override Node copy(Node lhs, Node rhs) { return new ToFloatNode(lhs); }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java
new file mode 100644
index 00000000..df740202
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java
@@ -0,0 +1,23 @@
+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-sccp/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
new file mode 100644
index 00000000..477b9af8
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
@@ -0,0 +1,16 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+
+public class XCtrlNode extends CFGNode {
+ public XCtrlNode() { super(new Node[]{CodeGen.CODE._start}); }
+ @Override public String label() { return "Xctrl"; }
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("Xctrl"); }
+ @Override public boolean isConst() { return true; }
+ @Override public Type compute() { return Type.XCONTROL; }
+ @Override public Node idealize() { return null; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/XorNode.java
new file mode 100644
index 00000000..75cea98e
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/XorNode.java
@@ -0,0 +1,42 @@
+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); }
+
+ @Override public String label() { return "Xor"; }
+ @Override public String op() { return "^"; }
+ @Override long doOp( long x, long y ) { return x ^ y; }
+ @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) {
+ return TypeInteger.BOT;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ Type t1 = lhs._type;
+ Type t2 = rhs._type;
+
+ // Xor of 0. We do not check for (0^x) because this will already
+ // canonicalize to (x^0)
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && 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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java
new file mode 100644
index 00000000..76e47ca2
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddARM.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class AddARM extends MachConcreteNode implements MachNode {
+ AddARM( Node add) { super(add); }
+ @Override public String op() { return "add"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ // ADD (shifted register)
+ @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_ADD); }
+ // General form: "rd = rs1 + rs2"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java
new file mode 100644
index 00000000..67a10b48
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddFARM.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class AddFARM extends MachConcreteNode implements MachNode {
+ AddFARM(Node addf) { super(addf); }
+ @Override public String op() { return "addf"; }
+ @Override public RegMask regmap(int i) { return arm.DMASK; }
+ @Override public RegMask outregmap() { return arm.DMASK; }
+
+ //FADD (scalar)
+ @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_OP_ADD);}
+
+ // Default on double precision for now(64 bits)
+ // General form: "addf rd = src1 + src2
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java
new file mode 100644
index 00000000..4c635d07
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AddIARM.java
@@ -0,0 +1,28 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class AddIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ AddIARM(Node add, int imm) {
+ super(add);
+ _inputs.pop();
+ _imm = imm;
+ }
+ @Override public String op() {
+ return _imm == 1 ? "inc" : (_imm == -1 ? "dec" : "addi");
+ }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ //ADD (immediate)
+ @Override public void encoding( Encoding enc ) {
+ arm.imm_inst(enc,this, in(1), Arm64.OPI_ADD,_imm);
+ }
+ // General form: "addi rd = rs1 + imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + #").p(_imm);
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java
new file mode 100644
index 00000000..1a0365ad
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndARM.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.SB;
+
+public class AndARM extends MachConcreteNode implements MachNode {
+ AndARM(Node and) { super(and); }
+ @Override public String op() { return "and"; }
+ @Override public String glabel() { return "&"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ // AND (shifted register)
+ @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_AND); }
+ // General form: #rd = rs1 & rs2
+ @Override public void asm(CodeGen code, SB sb){
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java
new file mode 100644
index 00000000..6292e98e
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AndIARM.java
@@ -0,0 +1,25 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class AndIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ AndIARM(Node and, int imm) {
+ super(and);
+ _inputs.pop();
+ _imm = imm;
+ }
+ @Override public String op() { return "andi"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ arm.imm_inst_n(enc,this, in(1), Arm64.OPI_AND,_imm);
+ }
+ // General form: "andi rd = rs1 & imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & #").p(Arm64.decodeImm12(_imm));
+ }
+} \ No newline at end of file
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java
new file mode 100644
index 00000000..77f14d4c
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrARM.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// Arithmetic Shift Right (register)
+public class AsrARM extends MachConcreteNode implements MachNode {
+ AsrARM(Node asr) {super(asr);}
+ @Override public String op() { return "sar"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.DMASK; }
+ @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,Arm64.OP_ASR); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java
new file mode 100644
index 00000000..7cb42eec
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/AsrIARM.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.SB;
+
+public class AsrIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ AsrIARM(Node asr, int imm) {
+ super(asr);
+ _inputs.pop();
+ _imm = imm;
+ }
+ @Override public String op() { return "asri"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short rd = enc.reg(this);
+ short rn = enc.reg(in(1));
+ assert _imm > 0;
+ enc.add4(Arm64.imm_shift(Arm64.OPI_ASR,_imm, 0b111111, rn, rd));
+ }
+ // General form: "asri rd = rs1 >> imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> #").p(_imm);
+ }
+} \ No newline at end of file
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java
new file mode 100644
index 00000000..127f17ff
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java
@@ -0,0 +1,78 @@
+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;
+ BranchARM(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 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)) );
+ } 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)));
+ } 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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java
new file mode 100644
index 00000000..e1717cb3
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java
@@ -0,0 +1,51 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.util.SB;
+
+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;
+ FunNode fun = CodeGen.CODE.link(tfp);
+ _name = fun==null ? ((ExternNode)call.fptr())._extern : fun._name; // Can be null for extern calls
+ }
+
+ @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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java
new file mode 100644
index 00000000..1949ae46
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java
@@ -0,0 +1,33 @@
+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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java
new file mode 100644
index 00000000..1bd9c372
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpARM.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class CmpARM extends MachConcreteNode implements MachNode {
+ CmpARM( Node cmp ) { super(cmp); }
+ @Override public String op() { return "cmp"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.FLAGS_MASK; }
+
+ // SUBS (shifted register)
+ @Override public void encoding( Encoding enc ) { arm.r_reg_subs(enc,this,Arm64.OP_CMP); }
+
+ // General form: "cmp rs1, rs2"
+ @Override public void asm(CodeGen code, SB sb) {
+ String dst = code.reg(this);
+ if( dst!="flags" ) sb.p(dst).p(" = ");
+ sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java
new file mode 100644
index 00000000..67ffc27b
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpFARM.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// compare instruction on float regs input(D-registers)
+public class CmpFARM extends MachConcreteNode implements MachNode{
+ CmpFARM(Node cmp) {super(cmp);}
+ @Override public String op() { return "cmpf"; }
+ @Override public RegMask regmap(int i) { return arm.DMASK; }
+ @Override public RegMask outregmap() { return arm.FLAGS_MASK; }
+
+ // FCMP
+ @Override public void encoding( Encoding enc ) { arm.f_cmp(enc,this); }
+ // General form: "cmp d0, d1"
+ @Override public void asm(CodeGen code, SB sb) {
+ String dst = code.reg(this);
+ if( dst!="flags" ) sb.p(dst).p(" = ");
+ sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java
new file mode 100644
index 00000000..cd4dd9cf
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/CmpIARM.java
@@ -0,0 +1,41 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// Compare with immediate.
+// CMP (immediate) - SUBS
+public class CmpIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ final String _bop;
+ CmpIARM(BoolNode bool, int imm) {
+ super(bool);
+ _inputs.pop();
+ _bop = bool.op();
+ _imm = imm;
+ }
+ CmpIARM( CmpIARM cmp ) {
+ super((Node)null);
+ addDef(null);
+ addDef(cmp.in(1));
+ _bop = cmp._bop;
+ _imm = cmp._imm;
+ }
+ @Override public String op() { return _imm==0 ? "test" : "cmp"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.FLAGS_MASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public Node copy() { return new CmpIARM(this); }
+
+ @Override public void encoding( Encoding enc ) { arm.imm_inst_subs(enc,in(1),in(1), Arm64.OPI_CMP,_imm); }
+
+ // General form: "cmp rs1, 1"
+ @Override public void asm(CodeGen code, SB sb) {
+ String dst = code.reg(this);
+ if( dst!="flags" ) sb.p(dst).p(" = ");
+ sb.p(code.reg(in(1)));
+ if( _imm != 0 ) sb.p(", #").p(_imm);
+ }
+ }
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java
new file mode 100644
index 00000000..e94b6491
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivARM.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class DivARM extends MachConcreteNode implements MachNode {
+ DivARM( Node div ) { super(div); }
+ @Override public String op() { return "div"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ // SDIV
+ @Override public void encoding( Encoding enc ) { arm.madd(enc,this,Arm64.OP_DIV,3); }
+ // General form: "div dst /= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java
new file mode 100644
index 00000000..68ad6047
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/DivFARM.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class DivFARM extends MachConcreteNode implements MachNode {
+ DivFARM( Node divf) { super(divf); }
+ @Override public String op() { return "divf"; }
+ @Override public RegMask regmap(int i) { return arm.DMASK; }
+ @Override public RegMask outregmap() { return arm.DMASK; }
+
+ // FDIV (scalar)
+ @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_DIV); }
+ // General form: "VDIF = dst /= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java
new file mode 100644
index 00000000..814a82c2
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ExtendARM.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+// Convert a narrow C return register to Simple's full-width integer value.
+public class ExtendARM extends MachConcreteNode {
+ final int _bits;
+ final boolean _signed;
+ ExtendARM(ProjNode prj, TypeInteger ret) {
+ super(prj);
+ _inputs.set(0,null);
+ _inputs.add(new ProjARM(prj));
+ _bits = 8 << ret.log_size();
+ _signed = ret._min < 0;
+ }
+ @Override public String op() { return (_signed ? "sext" : "zext")+_bits; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding(Encoding enc) {
+ short dst = enc.reg(this), src = enc.reg(in(1));
+ // SBFM/UBFM aliases: sign/zero extend the low byte, short or int.
+ enc.add4(Arm64.imm_shift(_signed ? Arm64.OPI_ASR : Arm64.OPI_LSR,0,_bits-1,src,dst));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java
new file mode 100644
index 00000000..31dcfc36
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class F8ToIARM extends MachConcreteNode implements MachNode {
+ F8ToIARM(Node f8toi) { super(f8toi); }
+ @Override public String op() { return "cvti"; }
+ @Override public RegMask regmap(int i) { return arm.DMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ enc.add4(Arm64.floatToInteger(enc.reg(in(1))-arm.D_OFFSET,enc.reg(this)));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1)));
+ }
+
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java
new file mode 100644
index 00000000..dc21e075
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java
@@ -0,0 +1,44 @@
+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.TypeFloat;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+//FMOV (scalar, immediate)
+//Floating-point move immediate.
+public class FloatARM extends ConstantNode implements MachNode, RIPRelSize {
+ FloatARM(ConstantNode con) { super(con); }
+ @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 FloatARM copy() { return new FloatARM(this); }
+
+ @Override public void encoding( Encoding enc ) {
+ 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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java
new file mode 100644
index 00000000..17cafcde
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/FunARM.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.FunNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class FunARM extends FunNode implements MachNode {
+ FunARM(FunNode fun) { super(fun); }
+ @Override public void computeFrameAdjust(CodeGen code, int maxReg) {
+ super.computeFrameAdjust(code,maxReg);
+ _frameAdjust = (_frameAdjust+15) & -16; // Keep SP aligned, including leaf spills.
+ }
+ @Override public void encoding( Encoding enc ) {
+ int sz = _frameAdjust;
+ if( sz == 0 ) return; // Skip if no frame adjust
+ if( sz >= 1L<<12 ) throw Utils.TODO();
+ enc.add4(Arm64.imm_inst(Arm64.OPI_SUB, sz, arm.RSP, arm.RSP));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java
new file mode 100644
index 00000000..b1c30afc
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/I2F8ARM.java
@@ -0,0 +1,26 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class I2F8ARM extends MachConcreteNode implements MachNode {
+ I2F8ARM(Node i2f8) { super(i2f8); }
+ @Override public String op() { return "cvt"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.DMASK; }
+ @Override public void encoding( Encoding enc ) {
+ // SCVTF
+ short self = (short)(enc.reg(this )-arm.D_OFFSET);
+ short reg1 = enc.reg(in(1));
+ int body = Arm64.float_cast(Arm64.OP_FLOAT_C, 1, reg1, self);
+ enc.add4(body);
+ }
+
+ // General form: "i2f8 (flt)int_value"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1)));
+ }
+
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java
new file mode 100644
index 00000000..7a9e74c6
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java
@@ -0,0 +1,36 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.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;
+
+// Integer constants
+public class IntARM extends ConstantNode implements MachNode {
+ IntARM( ConstantNode con ) { super(con); }
+
+ @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); }
+
+ @Override public void encoding( Encoding enc ) {
+ 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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
new file mode 100644
index 00000000..8fc22a67
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
@@ -0,0 +1,34 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.LoadNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.SB;
+
+// Load memory addressing on ARM
+// Support imm, reg(direct), or reg+off(indirect) addressing
+// Base = base - base pointer, offset is added to base
+// idx = null
+// off = off - offset added to base
+
+public class LoadARM extends MemOpARM {
+ LoadARM(LoadNode ld, Node base, Node idx, int off) {
+ super(ld, base, idx, off, 0);
+ }
+ @Override public String op() { return "ld"+_sz; }
+ @Override public RegMask outregmap() { return size() >= 4 ? arm.MEM_MASK : arm.WMASK; }
+
+ private static final int[] OP_LOADS = new int[]{ Arm64.OP_LOAD_IMM_8, Arm64.OP_LOAD_IMM_16, Arm64.OP_LOAD_IMM_32, Arm64.OP_LOAD_IMM_64, };
+
+ private static final int[] OP_LOAD_RS = new int[]{ Arm64.OP_LOAD_R_8 , Arm64.OP_LOAD_R_16 , Arm64.OP_LOAD_R_32 , Arm64.OP_LOAD_R_64, };
+
+ // ldr(immediate - unsigned offset) | ldr(register)
+ @Override public void encoding( Encoding enc ) {
+ ldst_encode(enc, OP_LOADS[Integer.numberOfTrailingZeros(size())], OP_LOAD_RS[_declaredType.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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java
new file mode 100644
index 00000000..954cfac2
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslARM.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// Logical Shift Left (register)
+public class LslARM extends MachConcreteNode implements MachNode {
+ LslARM(Node asr) {super(asr);}
+ @Override public String op() { return "shr"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,Arm64.OP_LSL); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java
new file mode 100644
index 00000000..b32153ab
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LslIARM.java
@@ -0,0 +1,34 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.SB;
+
+public class LslIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ LslIARM(Node lsl, int imm) {
+ super(lsl);
+ _inputs.pop();
+ _imm = imm;
+ }
+ @Override public String op() { return "lsli"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short rd = enc.reg(this);
+ short rn = enc.reg(in(1));
+ assert _imm > 0;
+ // UBFM <Xd>, <Xn>, #(-<shift> MOD 64), #(63-<shift>)
+ // immr must be (-<shift> MOD 64) = 64 - shift
+ enc.add4(Arm64.imm_shift(Arm64.OPI_LSL, 64 - _imm, (64 - _imm) - 1, rn, rd));
+ }
+ // General form: "lsli rd = rs1 << imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << #").p(_imm);
+ }
+} \ No newline at end of file
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java
new file mode 100644
index 00000000..91be34e3
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrARM.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// Logical Shift Right (register)
+public class LsrARM extends MachConcreteNode implements MachNode {
+ LsrARM(Node asr) {super(asr);}
+ @Override public String op() { return "shr"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,Arm64.OP_LSR); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java
new file mode 100644
index 00000000..243ce968
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/LsrIARM.java
@@ -0,0 +1,29 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// logical right shift immediate
+public class LsrIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ LsrIARM(Node lsr, int imm) {
+ super(lsr);
+ _inputs.pop();
+ _imm = imm;
+ }
+ @Override public String op() { return "lsri"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short rd = enc.reg(this);
+ short rn = enc.reg(in(1));
+ assert _imm > 0;
+ enc.add4(Arm64.imm_shift(Arm64.OPI_LSR,_imm, 0b111111, rn,rd));
+ }
+ // General form: "lsri rd = rs1 >>> imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> #").p(_imm);
+ }
+} \ No newline at end of file
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java
new file mode 100644
index 00000000..dfbe5e06
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java
@@ -0,0 +1,79 @@
+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 com.seaofnodes.simple.util.Utils;
+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 char _sz = (char)('0'+(1<<_declaredType.log_size()));
+ 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;
+ }
+
+ // 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 Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+
+ int size() { return 1<<_declaredType.log_size(); }
+
+ // 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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java
new file mode 100644
index 00000000..f598cefb
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulARM.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// mulh signed multiply instruction(no-imm form)
+public class MulARM extends MachConcreteNode implements MachNode {
+ MulARM(Node mul) {super(mul);}
+ @Override public String op() { return "mul"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ @Override public void encoding( Encoding enc ) { arm.madd(enc,this,Arm64.OP_MUL,31); }
+ // General form: "rd = rs1 * rs2"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java
new file mode 100644
index 00000000..a4f81fd4
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/MulFARM.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class MulFARM extends MachConcreteNode implements MachNode{
+ MulFARM(Node mulf) {super(mulf);}
+ @Override public String op() { return "mulf"; }
+ @Override public RegMask regmap(int i) { return arm.DMASK; }
+ @Override public RegMask outregmap() { return arm.DMASK; }
+ @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_MUL); }
+ // Default on double precision for now(64 bits)
+ // General form: "VMUL.f32 rd = src1 * src2
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java
new file mode 100644
index 00000000..881b41f9
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NegARM.java
@@ -0,0 +1,25 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+public class NegARM extends MachConcreteNode implements MachNode {
+ NegARM(Node sub) {
+ super(sub);
+ //_inputs.pop();
+ }
+ @Override public String op() { return "neg"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ // reverse subtract with immediate 0
+ arm.imm_inst(enc,this, in(1), 0b00100110, 0);
+ }
+ // General form: "subi rd = rs1 - imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = -").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java
new file mode 100644
index 00000000..7110df62
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java
@@ -0,0 +1,38 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.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("ldi x0=#1\n");
+ sb.p("call #calloc");
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
new file mode 100644
index 00000000..f8e450f6
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/NotARM.java
@@ -0,0 +1,28 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.util.SB;
+
+public class NotARM extends MachConcreteNode implements MachNode{
+ NotARM(NotNode not) {super(not);}
+ @Override public String op() { return "not"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public RegMask killmap() { return arm.FLAGS_MASK; }
+ @Override public void encoding( Encoding enc ) {
+ // subs xzr, rs, #0
+ // cset rd, eq // Set rd to 1 if rs == 0 (equal), else 0
+ // subtracting zero from rs will just yield rs, it sets the zero flag and then it's used in cset
+ short self = enc.reg(this );
+ short reg1 = enc.reg(in(1));
+ int subs = Arm64.imm_inst(Arm64.OP_SUBS, 0, reg1, 31);
+ enc.add4(subs);
+ int cset = Arm64.cset(Arm64.OP_CSET, 31, Arm64.COND.EQ, 31, self);
+ enc.add4(cset);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java
new file mode 100644
index 00000000..5b7264ec
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrARM.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// Not using the ORRS variant.
+public class OrARM extends MachConcreteNode implements MachNode {
+ OrARM(Node and) { super(and); }
+ @Override public String op() { return "or"; }
+ @Override public String glabel() { return "|"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_OR); }
+ // General form: #rd = rs1 & rs2
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java
new file mode 100644
index 00000000..86d07bee
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/OrIARM.java
@@ -0,0 +1,25 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class OrIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ OrIARM(Node or, int imm) {
+ super(or);
+ _inputs.pop();
+ _imm = imm;
+ }
+ @Override public String op() { return "ori"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ arm.imm_inst_n(enc, this, in(1), Arm64.OPI_OR, _imm);
+ }
+ // General form: "ori rd = rs1 | imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | #").p(_imm);
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java
new file mode 100644
index 00000000..b419fe0a
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ParmARM.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.ParmNode;
+
+public class ParmARM extends ParmNode implements MachNode {
+ final RegMask _rmask;
+ ParmARM(ParmNode parm) {
+ super(parm);
+ _rmask = arm.callInMask(fun().sig(),_idx,0);
+ }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return _rmask; }
+ @Override public void encoding( Encoding enc ) { }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java
new file mode 100644
index 00000000..4b7d6f13
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/ProjARM.java
@@ -0,0 +1,15 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.ProjNode;
+
+public class ProjARM extends ProjNode implements MachNode {
+ ProjARM( ProjNode p ) { super(p); }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() {
+ return ((MachNode)in(0)).outregmap(_idx);
+ }
+ @Override public void encoding( Encoding enc ) { }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java
new file mode 100644
index 00000000..a7ed5073
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/RetARM.java
@@ -0,0 +1,16 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class RetARM extends ReturnNode implements MachNode {
+ RetARM(ReturnNode ret, FunNode fun) { super(ret, fun); fun.setRet(this); }
+ @Override public void encoding( Encoding enc ) {
+ int frameAdjust = ((FunARM)fun())._frameAdjust;
+ if( frameAdjust > 0 )
+ enc.add4(Arm64.imm_inst(Arm64.OPI_ADD, frameAdjust, arm.RSP, arm.RSP));
+ enc.add4(Arm64.ret(Arm64.OP_RET));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java
new file mode 100644
index 00000000..afbbd3bf
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java
@@ -0,0 +1,34 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+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 <,<=,==
+ SetARM(Node cmp, String bop) {
+ super(cmp);
+ _inputs.setLen(1); // Pop the cmp inputs
+ // Replace with the matched cmp
+ _inputs.push(cmp);
+ _bop = bop;
+ }
+ @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), 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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java
new file mode 100644
index 00000000..f93f0f24
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java
@@ -0,0 +1,66 @@
+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(String kind, byte round) { super(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 = enc._fun.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 = enc._fun.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));
+ }
+ }
+
+ // General form: "mov dst = src"
+ @Override public void asm(CodeGen code, SB sb) {
+ FunNode fun = code._encoding==null ? null : code._encoding._fun;
+ sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java
new file mode 100644
index 00000000..3e045a6f
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+
+// NZCV transfers require a GPR. Further splits can spill that GPR to the stack.
+public class SplitFlagsARM extends SplitARM {
+ private static final RegMask MASK = new RegMask(arm.WR_BITS,1L);
+ SplitFlagsARM(String kind,byte round) { super(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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java
new file mode 100644
index 00000000..46eff4ca
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.codegen.*;
+
+// Keep a saved NZCV value in GPRs and stack slots through subsequent spills.
+public class SplitGPRARM extends SplitARM {
+ private static final RegMask MASK = new RegMask(arm.WR_BITS,-2L);
+ SplitGPRARM(String kind,byte round) { super(kind,round); }
+ @Override public RegMask regmap(int i) { return MASK; }
+ @Override public RegMask outregmap() { return MASK; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java
new file mode 100644
index 00000000..2cc54f76
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java
@@ -0,0 +1,38 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.node.StoreNode;
+import com.seaofnodes.simple.util.SB;
+
+// Store memory addressing on ARM
+// Support imm, reg(direct), or reg+off(indirect) addressing
+// base - base pointer, offset is added to base
+// null - index never allowed (no [reg+reg] mode)
+// off - offset added to base
+// imm - immediate value to store, only if val is null
+// val - value to store or null
+
+//e.g s.cs[0] = 67; // C
+// base = s.cs, off = 4, imm = 67, val = null
+public class StoreARM extends MemOpARM {
+ StoreARM(StoreNode st, Node base, Node idx, int off, Node val) {
+ super(st, base, idx, off, 0, val);
+ }
+ @Override public String op() { return "st"+_sz; }
+ @Override public RegMask outregmap() { return null; }
+
+ private static final int[] OP_STORES = new int[]{ Arm64.OP_STORE_IMM_8, Arm64.OP_STORE_IMM_16, Arm64.OP_STORE_IMM_32, Arm64.OP_STORE_IMM_64, };
+
+ private static final int[] OP_STORE_RS = new int[]{ Arm64.OP_STORE_R_8, Arm64.OP_STORE_R_16, Arm64.OP_STORE_R_32, Arm64.OP_STORE_R_64, };
+
+ @Override public void encoding( Encoding enc ) {
+ ldst_encode(enc, OP_STORES[Integer.numberOfTrailingZeros(size())], OP_STORE_RS[_declaredType.log_size()], 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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java
new file mode 100644
index 00000000..fd5cd4de
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubARM.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class SubARM extends MachConcreteNode implements MachNode {
+ SubARM(Node sub) { super(sub); }
+ @Override public String op() { return "sub"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_SUB); }
+
+ // General form: "sub # rd = rs1 - rs2"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java
new file mode 100644
index 00000000..1a751434
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubFARM.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class SubFARM extends MachConcreteNode implements MachNode {
+ SubFARM(Node subf) {super(subf);}
+ @Override public String op() { return "subf"; }
+ @Override public RegMask regmap(int i) { return arm.DMASK; }
+ @Override public RegMask outregmap() { return arm.DMASK; }
+ @Override public void encoding( Encoding enc ) { arm.f_scalar(enc,this,Arm64.OPF_SUB); }
+ // Default on double precision for now(64 bits)
+ // General form: "vsub.f32 rd = src1 + src2
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java
new file mode 100644
index 00000000..b2c0b2ed
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/SubIARM.java
@@ -0,0 +1,29 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+public class SubIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ SubIARM(Node sub, int imm) {
+ super(sub);
+ _inputs.pop();
+ _imm = imm;
+ }
+ @Override public String op() { return _imm == -1 ? "dec" : "subi"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ arm.imm_inst(enc,this, in(1), Arm64.OPI_SUB,_imm);
+ }
+
+ // General form: "subi rd = rs1 - imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - #").p(_imm);
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java
new file mode 100644
index 00000000..98a56089
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java
@@ -0,0 +1,57 @@
+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.TypeFunPtr;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class TFPARM extends ConstantNode implements MachNode, RIPRelSize {
+ TFPARM( ConstantNode con ) { super(con); }
+ @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(this); }
+ @Override public void encoding( Encoding enc ) {
+ enc.relo(this);
+ short self = enc.reg(this);
+ // adrp x0, 0
+ int adrp = Arm64.adrp(1,0, Arm64.OP_ADRP, 0,self);
+ // add x0, x0, 0
+ enc.add4(adrp);
+ arm.imm_inst(enc,Arm64.OPI_ADD,0, 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 ) {
+ // ARM encoding delta is from PC & 0xFFF
+ int target = opStart+delta;
+ int base = opStart & ~0xFFF;
+ delta = target-base;
+ int adrp_delta = delta >> 12;
+ // patch upper 20 bits via adrp
+ enc.patch4(opStart, Arm64.adrp(1, adrp_delta & 0b11, 0b10000, adrp_delta >> 2, rpc));
+ // low 12 bits via add
+ enc.patch4(opStart+4, Arm64.imm_inst_l(Arm64.OPI_ADD, delta & 0xfff, 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);
+ _con.print(sb.p(reg).p(" #"));
+ }
+ @Override public boolean eq(Node n) { return this==n; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java
new file mode 100644
index 00000000..d1cafa96
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java
@@ -0,0 +1,55 @@
+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 {
+ TMPARM( ConstantNode con ) { super(con); }
+ @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); }
+ @Override public void encoding( Encoding enc ) {
+ 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 encoding delta is from PC & 0xFFF
+ int target = opStart+delta;
+ int base = opStart & ~0xFFF;
+ delta = target-base;
+ int adrp_delta = delta >> 12;
+ // patch upper 20 bits via adrp
+ enc.patch4(opStart, Arm64.adrp(1, adrp_delta & 0b11, 0b10000, adrp_delta >> 2, dst));
+ // low 12 bits via add
+ enc.patch4(opStart+4, Arm64.imm_inst_l(Arm64.OPI_ADD, delta & 0xfff, 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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java
new file mode 100644
index 00000000..aecd02bb
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/UJmpARM.java
@@ -0,0 +1,48 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// unconditional jump
+public class UJmpARM extends CFGNode implements MachNode, RIPRelSize {
+ UJmpARM() { }
+ @Override public String op() { return "jmp"; }
+ @Override public String label() { return op(); }
+ @Override protected String format() { return "jmp "; }
+ @Override public RegMask regmap(int i) {return null; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public Type compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+ @Override public void encoding( Encoding enc ) {
+ enc.jump(this,uctrl());
+ int body = Arm64.b(Arm64.OP_UJMP, 0);
+ enc.add4(body);
+ }
+
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) {
+ if( -(1<<26) <= delta && delta < (1<<26) ) return 4;
+ // 2 word encoding needs a tmp register, must teach RA
+ throw Utils.TODO();
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ if( opLen==4 ) {
+ enc.patch4(opStart,Arm64.b(Arm64.OP_UJMP, delta));
+ } else {
+ throw Utils.TODO();
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ CFGNode target = uctrl();
+ //assert target.nOuts() > 1; // Should optimize jmp to empty targets
+ sb.p(label(target));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java
new file mode 100644
index 00000000..d56397a3
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorARM.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class XorARM extends MachConcreteNode implements MachNode{
+ XorARM(Node xor) {super(xor);}
+ @Override public String op() { return "xor"; }
+ @Override public String glabel() { return "^"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+ @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,Arm64.OP_XOR); }
+ // General form: "rd = x1 ^ x2"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java
new file mode 100644
index 00000000..12143ccd
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/XorIARM.java
@@ -0,0 +1,27 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.util.SB;
+
+public class XorIARM extends MachConcreteNode implements MachNode {
+ final int _imm;
+ XorIARM(Node xor, int imm) {
+ super(xor);
+ _inputs.pop();
+ _imm = imm;
+ }
+
+ @Override public String op() { return "xori"; }
+ @Override public RegMask regmap(int i) { return arm.RMASK; }
+ @Override public RegMask outregmap() { return arm.WMASK; }
+
+ // General form: "xori rd = rs1 ^ imm"
+ @Override public void encoding( Encoding enc ) { arm.imm_inst_n(enc,this, in(1), Arm64.OPI_XOR,_imm); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ #").p(_imm);
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
new file mode 100644
index 00000000..fae98a99
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -0,0 +1,466 @@
+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 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
+ // Count floats in signature up to index
+ int fcnt=0;
+ for( int i=2; i<idx; i++ )
+ if( tfp.arg(i-2) 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( idx-2-fcnt < cargs.length )
+ return cargs[idx-2-fcnt];
+ }
+ // Pass on stack slot (8 and higher)
+ if( maxArgSlot>0 ) throw Utils.TODO();
+ return new RegMask(MAX_REG + 1 + (idx - 2));
+ }
+
+ // 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=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 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(String kind, byte round, LRG lrg) {
+ 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(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(kind,round);
+ return new SplitARM(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 AddFNode addf -> new AddFARM(addf);
+ case AddNode add -> add(add);
+ case AndNode and -> and(and);
+ case BoolNode bool -> cmp(bool);
+ case CallNode call -> call(call);
+ case CastNode cast -> new CastMach(cast);
+ case CallEndNode cend-> new CallEndMach(cend);
+ case CProjNode c -> new CProjNode(c);
+ case ConstantNode con-> con(con);
+ case DivFNode divf -> new DivFARM(divf);
+ case DivNode div -> new DivARM(div);
+ case FunNode fun -> new FunARM(fun);
+ case IfNode iff -> jmp(iff);
+ case LoadNode ld -> ld(ld);
+ case MemMergeNode mem-> new MemMergeNode(mem);
+ case MinusNode neg -> new NegARM(neg);
+ case MulFNode mulf -> new MulFARM(mulf);
+ case MulNode 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 StartNode start -> new StartNode(start);
+ case StopNode stop -> new StopNode(stop);
+ case StoreNode st -> st(st);
+ case SubFNode subf -> new SubFARM(subf);
+ case SubNode 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());
+ }
+ 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 = "!=";
+ if( iff.in(1) instanceof BoolNode bool ) op = bool.op();
+ else if( iff.in(1)==null ) op = "=="; // Never-node cutout
+ else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO)));
+ return new BranchARM(iff, op);
+ }
+
+ private Node add(AddNode 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 new ConstantNode( con ); // Default unknown caller inputs
+ return switch( con._con ) {
+ case TypeInteger ti -> new IntARM(con);
+ case TypeFloat tf -> new FloatARM(con);
+ case TypeFunPtr tfp -> new TFPARM(con);
+ case TypeMemPtr tmp -> new TMPARM(con);
+ case TypeNil tn -> throw Utils.TODO();
+ // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
+ case Type t -> t==Type.NIL ? new IntARM(con) : new ConstantNode(con);
+ };
+ }
+
+ private Node call(CallNode call){
+ return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp
+ ? new CallARM(call, tfp)
+ : 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 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;
+ }
+
+ private Node prj(ProjNode prj) {
+ TypeInteger ret = cReturn(prj);
+ return ret==null ? new ProjARM(prj) : new ExtendARM(prj,ret);
+ }
+
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java
new file mode 100644
index 00000000..14581111
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java
@@ -0,0 +1,43 @@
+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.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// Add upper 20bits to PC. Immediate comes from the relocation info.
+public class AUIPC extends ConstantNode implements MachNode, RIPRelSize {
+ AUIPC( Type tfp ) { super(tfp); }
+ @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(_con); }
+ @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+#");
+ if( _con == null ) sb.p("---");
+ else _con.print(sb);
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java
new file mode 100644
index 00000000..d87e49b9
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddFRISC.java
@@ -0,0 +1,18 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// fadd.d
+public class AddFRISC extends MachConcreteNode implements MachNode {
+ AddFRISC(Node addf) {super(addf);}
+ @Override public String op() { return "addf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; }
+ @Override public RegMask outregmap() { return riscv.FMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,RiscV.RM.RNE,1); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java
new file mode 100644
index 00000000..8012b61e
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddIRISC.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.Utils;
+
+public class AddIRISC extends ImmRISC {
+ // Used to inst-selection as a direct match against an ideal Add/Sub
+ public AddIRISC( Node add, int imm12, boolean pop ) { super(add,imm12,pop); }
+ @Override public String op() { return "addi"; }
+ @Override public String glabel() { return "+"; }
+ @Override int opcode() { return RiscV.OP_IMM; }
+ @Override int func3() {return 0;}
+ @Override public AddIRISC copy() {
+ // Clone the AddI, using the same inputs-only code used during inst select.
+ // Output edges are missing.
+ AddIRISC add = new AddIRISC(in(1),_imm12,false);
+ // Copys happen when output edges should be valid, so correct missing output edges.
+ in(1)._outputs.add(add);
+ return add;
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java
new file mode 100644
index 00000000..7010916f
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AddRISC.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class AddRISC extends MachConcreteNode implements MachNode {
+ public AddRISC( Node add ) { super(add); }
+ AddRISC( Node base, Node off ) {
+ super(new Node[3]);
+ _inputs.set(1,base);
+ _inputs.set(2,off );
+ }
+ @Override public String op() { return "add"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0,0); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java
new file mode 100644
index 00000000..9fd65e7d
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndIRISC.java
@@ -0,0 +1,13 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.Utils;
+
+public class AndIRISC extends ImmRISC {
+ public AndIRISC( Node and, int imm) { super(and,imm); }
+ @Override public String op() { return "andi"; }
+ @Override public String glabel() { return "&"; }
+ @Override int opcode() { return RiscV.OP_IMM; }
+ @Override int func3() {return 7;}
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java
new file mode 100644
index 00000000..1845985e
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AndRISC.java
@@ -0,0 +1,16 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class AndRISC extends MachConcreteNode implements MachNode {
+ public AndRISC(Node and) { super(and); }
+ @Override public String op() { return "and"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0b111,0); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" & ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java
new file mode 100644
index 00000000..a9d03da5
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/BranchRISC.java
@@ -0,0 +1,95 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// Conditional branch such as: BEQ
+public class BranchRISC extends IfNode implements MachNode, RIPRelSize {
+ String _bop;
+ // label is obtained implicitly
+ public BranchRISC( IfNode iff, String bop, Node n1, Node n2 ) {
+ super(iff);
+ _bop = bop;
+ _inputs.setX(1,n1);
+ _inputs.setX(2,n2);
+ }
+
+ @Override public String op() { return "b"+_bop; }
+ @Override public String label() { return op(); }
+ @Override public String comment() { return "L"+cproj(1)._nid; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public void negate() {
+ _bop = negate(_bop);
+ // Cannot encode ">" or "<=", so flip these
+ if( _bop.equals(">") || _bop.equals("<=") ) {
+ swap12(); // Cannot negate the test, so swap the operands
+ _bop = swap(_bop); // Swap test to match
+ }
+ }
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ p.p("if( ");
+ if( in(1)==null ) p.p("0");
+ else p.n(in(1));
+ p.p(_bop);
+ if( in(2)==null ) p.p("0");
+ else p.n(in(2));
+ return p.p(" )");
+ }
+
+ @Override public void encoding( Encoding enc ) {
+ if( in(1)==null && _bop=="!=" ) return; // Inverted never-node, no code
+ enc.jump(this,cproj(0));
+ if( in(1)==null ) // Never node
+ enc.add4(RiscV.j_type(RiscV.OP_JAL, 0, 0));
+ else {
+ // Todo: relocs (for offset - immf)
+ short src1 = enc.reg(in(1));
+ short src2 = in(2)==null ? (short)riscv.ZERO : enc.reg(in(2));
+ enc.add4(RiscV.b_type(RiscV.OP_BRANCH, RiscV.jumpop(_bop), src1, src2, 0));
+ }
+ }
+
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) {
+ if( in(1)==null && _bop=="!=" ) return 0; // Inverted never-node, no code
+ if( -4*1024 <= delta && delta < 4*1024 ) return 4;
+ // 2 word encoding needs a tmp register, must teach RA
+ throw Utils.TODO();
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ assert !( in(1)==null && _bop=="!=" ); // Inverted never-node, no code no patch
+ if( in(1)==null ) { // Never node
+ enc.patch4(opStart,RiscV.j_type(RiscV.OP_JAL, 0, delta));
+ } else {
+ short src1 = enc.reg(in(1));
+ short src2 = in(2)==null ? (short)riscv.ZERO : enc.reg(in(2));
+ if( opLen==4 ) {
+ enc.patch4(opStart,RiscV.b_type(RiscV.OP_BRANCH, RiscV.jumpop(_bop), src1, src2, delta));
+ } else {
+ throw Utils.TODO();
+ }
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ if( in(1)==null && _bop=="!=" ) {
+ sb.p("never");
+ return;
+ }
+ String src1 = in(1)==null ? "#0" : code.reg(in(1));
+ String src2 = in(2)==null ? "#0" : code.reg(in(2));
+ sb.p(src1).p(" ").p(_bop).p(" ").p(src2).p(" ");
+ CFGNode prj = cproj(0).uctrlSkipEmpty();
+ if( !prj.blockHead() ) prj = prj.cfg0();
+ sb.p(label(prj));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java
new file mode 100644
index 00000000..f0ad8e88
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java
@@ -0,0 +1,63 @@
+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;
+ FunNode fun = CodeGen.CODE.link(tfp);
+ _name = fun==null ? ((ExternNode)call.fptr())._extern : fun._name; // Can be null for extern calls
+ }
+
+ @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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java
new file mode 100644
index 00000000..5eee596d
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.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 null; }
+
+ @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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java
new file mode 100644
index 00000000..8eb4fb3b
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivFRISC.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// fdiv.d
+public class DivFRISC extends MachConcreteNode implements MachNode{
+ DivFRISC(Node divf) {super(divf);}
+ @Override public String op() { return "divf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; }
+ @Override public RegMask outregmap() { return riscv.FMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,RiscV.RM.RUP,13); }
+ // Default on double precision for now(64 bits)
+ // General form: "fdiv.d rd = src1 / src2
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java
new file mode 100644
index 00000000..515ffa47
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/DivRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class DivRISC extends MachConcreteNode implements MachNode{
+ public DivRISC(Node div) {super(div);}
+ @Override public String op() { return "div"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,4,1); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2)));
+ }
+
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java
new file mode 100644
index 00000000..5bd3ec1c
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ExtendRISC.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+// Convert a narrow C return register to Simple's full-width integer value.
+public class ExtendRISC extends MachConcreteNode {
+ final int _bits;
+ final boolean _signed;
+ ExtendRISC(ProjNode prj, TypeInteger ret) {
+ super(prj);
+ _inputs.set(0,null);
+ _inputs.add(new ProjRISC(prj));
+ _bits = 8 << ret.log_size();
+ _signed = ret._min < 0;
+ }
+ @Override public String op() { return (_signed ? "sext" : "zext")+_bits; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding(Encoding enc) {
+ short dst = enc.reg(this), src = enc.reg(in(1));
+ int shift = 64-_bits;
+ enc.add4(RiscV.i_type(RiscV.OP_IMM,dst,1,src,shift));
+ enc.add4(RiscV.i_type(RiscV.OP_IMM,dst,5,dst,shift | (_signed ? 0x400 : 0)));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java
new file mode 100644
index 00000000..a56ad216
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class F8ToIRISC extends MachConcreteNode implements MachNode {
+ F8ToIRISC(Node f8toi) {super(f8toi);}
+ @Override public String op() { return "cvti"; }
+ @Override public RegMask regmap(int i) { assert i==1; return riscv.FMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ RiscV.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-riscv.F_OFFSET);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java
new file mode 100644
index 00000000..51ddc9db
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java
@@ -0,0 +1,47 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.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 {
+ FltRISC(ConstantNode con) { super(con); }
+ @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); }
+
+ @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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java
new file mode 100644
index 00000000..d3d64b06
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FunRISC.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.FunNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class FunRISC extends FunNode implements MachNode {
+ FunRISC(FunNode fun) {super(fun);}
+ @Override public void computeFrameAdjust(CodeGen code, int maxReg) {
+ super.computeFrameAdjust(code,maxReg);
+ if( _hasCalls ) // If non-leaf, pad to 16b
+ _frameAdjust = ((_frameAdjust+8) & -16);
+ }
+ @Override public void encoding( Encoding enc ) {
+ int sz = _frameAdjust;
+ if( sz == 0 ) return; // Skip if no frame adjust
+ if( sz >= 1L<<12 ) throw Utils.TODO();
+ enc.add4(RiscV.i_type(RiscV.OP_IMM, riscv.RSP, 0, riscv.RSP, -sz & 0xFFF));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java
new file mode 100644
index 00000000..1b53ab0f
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/I2F8RISC.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// fcvt.d.w
+// Converts a 32-bit signed integer, in integer register rs1 into a double-precision floating-point number in floating-point register rd.
+public class I2F8RISC extends MachConcreteNode implements MachNode {
+ I2F8RISC(Node i2f8) {super(i2f8);}
+ @Override public String op() { return "cvtf"; }
+ @Override public RegMask regmap(int i) { assert i==1; return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.FMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short dst = (short)(enc.reg(this )-riscv.F_OFFSET);
+ short src1 = enc.reg(in(1));
+ int body = RiscV.r_type(RiscV.OP_FP,dst,RiscV.RM.RNE.ordinal(),src1,0,0x69);
+ enc.add4(body);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java
new file mode 100644
index 00000000..ee247c3e
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ImmRISC.java
@@ -0,0 +1,55 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+abstract public class ImmRISC extends MachConcreteNode implements MachNode {
+ public final int _imm12;
+ ImmRISC( Node add, int imm12, boolean pop ) {
+ super(add);
+ _imm12 = imm12;
+ // If pop, we are moving from an ideal Node to MachNode; the ideal Node
+ // inputs got copied here, but we are absorbing the immediate into the
+ // MachNode and so do not want the constant input.
+ if( pop ) _inputs.pop();
+ // if NOT pop, we are the 2nd-half of a 2-part expansion, reset the
+ // input edges to no-control
+ else { _inputs.set(0,null); _inputs.setX(1,add); }
+ }
+ ImmRISC( Node add, int imm12 ) { this(add,imm12,true); }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public boolean isClone() {
+ // Allow cloning of the 2nd half of a 2-part constant; this might
+ // trigger cloning the first part also.
+ return in(1) instanceof LUI || in(1) instanceof AUIPC;
+ }
+
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ int imm12 = (_imm12<<20)>>20; // Sign extend 12 bits
+ p.p("( ").n(in(1));
+ return p.p(String.format(" %s #%d )",glabel(),imm12));
+ }
+
+ abstract int opcode();
+ abstract int func3();
+
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this );
+ short src = enc.reg(in(1));
+ enc.add4(RiscV.i_type(opcode(), dst, func3(), src, _imm12 & 0xFFF));
+ }
+
+ // General form: "addi rd = rs1 + imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ int imm12 = (_imm12<<20)>>20; // Sign extend 12 bits
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ").p(glabel()).p(" #").p(imm12);
+ if( in(1) instanceof LUI lui )
+ sb.p(" // #").hex4((int)(((TypeInteger)lui._con).value()) + imm12);
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java
new file mode 100644
index 00000000..23ac8481
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java
@@ -0,0 +1,47 @@
+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;
+
+// Special instruction for loading 8 byte constants from the constant pool.
+
+public class Int8RISC extends ConstantNode implements MachNode, RIPRelSize {
+ public Int8RISC(ConstantNode con) { super(con); }
+
+ @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); }
+
+ @Override public byte encSize(int delta) { return 8; }
+
+ @Override public void encoding( Encoding enc ) {
+ 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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java
new file mode 100644
index 00000000..9088abbd
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java
@@ -0,0 +1,31 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.ConstantNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+// 12-bit integer constant. Larger constants are made up in the instruction
+// selection by adding with a LUI.
+public class IntRISC extends ConstantNode implements MachNode {
+ public 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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java
new file mode 100644
index 00000000..aedd8b47
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LUI.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.ConstantNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+// Load upper 20bits.
+public class LUI extends ConstantNode implements MachNode {
+ public LUI( int imm20 ) {
+ super(TypeInteger.constant(imm20));
+ assert (imm20 & 0xFFF)==0;
+ }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public LUI copy() { return new LUI((int)((TypeInteger)_con).value()); }
+ @Override public String op() { return "lui"; }
+ @Override public void encoding( Encoding enc ) {
+ long x = ((TypeInteger)_con).value();
+ int imm20 = (int)(x>>12) & 0xFFFFF;
+ short dst = enc.reg(this);
+ int lui = RiscV.u_type(RiscV.OP_LUI, dst, imm20);
+ enc.add4(lui);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ String reg = code.reg(this);
+ sb.p(reg).p(" = #").hex4((int)(((TypeInteger)_con).value()));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java
new file mode 100644
index 00000000..2e3dfd8d
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java
@@ -0,0 +1,30 @@
+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;
+
+// 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 public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(",");
+ asm_address(code,sb);
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java
new file mode 100644
index 00000000..adf21848
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java
@@ -0,0 +1,80 @@
+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;
+ 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;
+ _sz = (char)('0'+(1<<_declaredType.log_size()));
+ }
+
+ @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 Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+
+ // func3 is based on load/store size and extend
+ int func3() {
+ int func3 = -1;
+ // no unsigned flavour for store, so both signed and unsigned trigger the same
+ if(this instanceof StoreRISC) {
+ if( _declaredType == TypeInteger. I8 || _declaredType == TypeInteger.U8 || _declaredType == TypeInteger.BOOL) func3=0; // SB
+ if( _declaredType == TypeInteger.I16 || _declaredType == TypeInteger.U16 ) func3=1; // SH
+ if( _declaredType == TypeInteger.I32 || _declaredType == TypeInteger.U32 ) func3=2; // SW
+ if( _declaredType instanceof TypeMemPtr) func3=3; // SD
+ if( _declaredType == TypeInteger.BOT ) func3=3; // SD
+ if( func3 == -1 ) throw Utils.TODO();
+ return func3;
+ }
+ if( _declaredType == TypeInteger. I8 ) func3=0; // LB
+ if( _declaredType == TypeInteger.I16 ) func3=1; // LH
+ if( _declaredType == TypeInteger.I32 ) func3=2; // LW
+ if( _declaredType == TypeInteger.BOT ) func3=3; // LD
+ if( _declaredType == TypeInteger. U8 ) func3=4; // LBU
+ if( _declaredType == TypeInteger.BOOL) func3=4; // LBU
+ if( _declaredType == TypeInteger.U16 ) func3=5; // LHU
+ if( _declaredType == TypeInteger.U32 ) func3=6; // LWU
+ if( _declaredType instanceof TypeInteger ti ) {
+ if( -128 <= ti._min && ti._max < 128 ) func3 = 0; // LB
+ }
+
+ // float
+ if( _declaredType == TypeFloat.F32) func3 = 2; // fLW fSW
+ if( _declaredType == TypeFloat.F64) func3 = 3; // fLD fSD
+
+ if( _declaredType instanceof TypeMemPtr) func3 = 3; // 8 byte pointers (pick ld)
+ if( func3 == -1 ) throw Utils.TODO();
+ return 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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java
new file mode 100644
index 00000000..dacec2b5
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulFRISC.java
@@ -0,0 +1,18 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// fmul.d
+public class MulFRISC extends MachConcreteNode implements MachNode {
+ MulFRISC(Node mulf) {super(mulf);}
+ @Override public String op() { return "mulf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; }
+ @Override public RegMask outregmap() { return riscv.FMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,RiscV.RM.RNE,9); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java
new file mode 100644
index 00000000..9cb9ace7
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MulRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// mulh signed multiply instruction(no-imm form)
+public class MulRISC extends MachConcreteNode implements MachNode{
+ public MulRISC(Node mul) {super(mul);}
+ @Override public String op() { return "mul"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0,1); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java
new file mode 100644
index 00000000..3070c56c
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NegRISC.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class NegRISC extends MachConcreteNode implements MachNode {
+ public NegRISC( Node neg ) { super(neg); }
+ @Override public String op() { return "neg"; }
+ @Override public RegMask regmap(int i) { assert i==1; return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this );
+ short src = enc.reg(in(1));
+ RiscV.r_type(RiscV.OP,dst,0,0,src,0x20);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = -").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java
new file mode 100644
index 00000000..965a3c7c
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java
@@ -0,0 +1,48 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.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("ldi a1=#1\n");
+ sb.p("auipc a2=#calloc\n");
+ sb.p("call a2+#calloc, a0");
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
new file mode 100644
index 00000000..6f499db1
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NotRISC.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.NotNode;
+import com.seaofnodes.simple.util.SB;
+
+public class NotRISC extends MachConcreteNode implements MachNode {
+ public NotRISC(NotNode not) { super(not); }
+ @Override public String op() { return "not"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this );
+ short src = enc.reg(in(1));
+ // SEQZ is unsigned: negative nonzero inputs must also produce false.
+ enc.add4(RiscV.i_type(RiscV.OP_IMM, dst, 3, src, 1));
+ }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java
new file mode 100644
index 00000000..937595a3
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrIRISC.java
@@ -0,0 +1,13 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.Utils;
+
+public class OrIRISC extends ImmRISC {
+ public OrIRISC( Node and, int imm) { super(and,imm); }
+ @Override int opcode() { return RiscV.OP_IMM; }
+ @Override int func3() { return 6; }
+ @Override public String glabel() { return "|"; }
+ @Override public String op() { return "ori"; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java
new file mode 100644
index 00000000..a310a1f4
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/OrRISC.java
@@ -0,0 +1,16 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class OrRISC extends MachConcreteNode implements MachNode {
+ public OrRISC(Node or) { super(or); }
+ @Override public String op() { return "or"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,6,0); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" | ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java
new file mode 100644
index 00000000..0f8db4e5
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ParmRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.ParmNode;
+
+public class ParmRISC extends ParmNode implements MachNode {
+ final RegMask _rmask;
+ ParmRISC(ParmNode parm) {
+ super(parm);
+ _rmask = riscv.callInMask(fun().sig(),_idx,0);
+ }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return _rmask; }
+ @Override public void encoding( Encoding enc ) { }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java
new file mode 100644
index 00000000..2f9ac469
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/ProjRISC.java
@@ -0,0 +1,15 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.ProjNode;
+
+public class ProjRISC extends ProjNode implements MachNode {
+ ProjRISC(ProjNode p) { super(p); }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() {
+ return ((MachNode)in(0)).outregmap(_idx);
+ }
+ @Override public void encoding( Encoding enc ) { }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java
new file mode 100644
index 00000000..33143c6b
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/RetRISC.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.FunNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.ReturnNode;
+import com.seaofnodes.simple.util.Utils;
+
+public class RetRISC extends ReturnNode implements MachNode {
+ public RetRISC(ReturnNode ret, FunNode fun) { super(ret, fun); fun.setRet(this); }
+ @Override public void encoding( Encoding enc ) {
+ int sz = fun()._frameAdjust;
+ if( sz >= 1L<<12 ) throw Utils.TODO();
+ if( sz != 0 )
+ enc.add4(RiscV.i_type(RiscV.OP_IMM, riscv.RSP, 0, riscv.RSP, sz & 0xFFF));
+ short rpc = enc.reg(rpc());
+ enc.add4(RiscV.i_type(RiscV.OP_JALR, riscv.ZERO, 0, rpc, 0));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java
new file mode 100644
index 00000000..d037dd5c
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetFRISC.java
@@ -0,0 +1,34 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.BoolNode;
+import com.seaofnodes.simple.node.MachConcreteNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.util.SB;
+
+// corresponds to feq.d = equal
+// fle.d = less than or equal
+// flt.d = less than
+public class SetFRISC extends MachConcreteNode implements MachNode {
+ final String _bop; // One of <,<=,==
+ SetFRISC( BoolNode bool ) {
+ super(bool);
+ assert bool.isFloat();
+ _bop = bool.op();
+ }
+ @Override public String op() { return "set"+_bop; }
+ @Override public RegMask regmap(int i) { return riscv.FMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this );
+ short src1 = (short)(enc.reg(in(1))-riscv.F_OFFSET);
+ short src2 = (short)(enc.reg(in(2))-riscv.F_OFFSET);
+ int body = RiscV.r_type(RiscV.OP_FP,dst,RiscV.fsetop(_bop),src1,src2,0b1010001/*FCMP*/);
+ enc.add4(body);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ").p(_bop).p(" ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
new file mode 100644
index 00000000..9bc86565
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetIRISC.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.node.Node;
+
+// corresponds to slti,sltiu
+public class SetIRISC extends ImmRISC {
+ final boolean _unsigned; // slti vs sltiu
+ public SetIRISC( Node src, int imm12, boolean unsigned ) {
+ this(src,imm12,unsigned,true);
+ }
+ public SetIRISC( Node src, int imm12, boolean unsigned, boolean pop ) {
+ super(src,imm12,pop);
+ if( !pop ) _inputs.setLen(2);
+ _unsigned = unsigned;
+ }
+ @Override public String op() { return "slt" + (_unsigned ? "u":"") + "i"; }
+ @Override public String glabel() { return "<"+ (_unsigned ? "u":""); }
+ @Override int opcode() { return 0b0010011; }
+ @Override int func3() { return _unsigned ? 0b011 : 0b010; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java
new file mode 100644
index 00000000..848cc59f
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SetRISC.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// corresponds to slt sltu
+public class SetRISC extends MachConcreteNode implements MachNode {
+ final boolean _unsigned;
+ public SetRISC( Node cmp, boolean unsigned ) { super(cmp); _unsigned=unsigned; }
+ @Override public String op() { return "slt" + (_unsigned ? "u":""); }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) {
+ riscv.r_type(enc,this,_unsigned ? 0b011 : 0b010,0);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" < ").p(code.reg(in(2)));
+ }
+} \ No newline at end of file
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java
new file mode 100644
index 00000000..1425caa2
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllIRISC.java
@@ -0,0 +1,13 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.Utils;
+
+public class SllIRISC extends ImmRISC {
+ public SllIRISC( Node and, int imm) { super(and,imm); }
+ @Override int opcode() { return RiscV.OP_IMM; }
+ @Override int func3() { return 1; }
+ @Override public String glabel() { return "<<"; }
+ @Override public String op() { return "slli"; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java
new file mode 100644
index 00000000..5a4d4cc6
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SllRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// Shift left logical
+public class SllRISC extends MachConcreteNode implements MachNode {
+ public SllRISC(Node sll) { super(sll); }
+ @Override public String op() { return "shl"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,1,0); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" << ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java
new file mode 100644
index 00000000..8a16f6b7
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java
@@ -0,0 +1,67 @@
+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( String kind, byte round ) { super(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 = enc._fun.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 = enc._fun.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));
+ }
+ }
+
+ // General form: "mov dst = src"
+ @Override public void asm(CodeGen code, SB sb) {
+ FunNode fun = code._encoding==null ? null : code._encoding._fun;
+ sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java
new file mode 100644
index 00000000..b316b303
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraIRISC.java
@@ -0,0 +1,15 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.Utils;
+
+public class SraIRISC extends ImmRISC {
+ public SraIRISC( Node and, int imm) {
+ super(and,imm | (0x20 << 5));
+ }
+ @Override int opcode() { return RiscV.OP_IMM; }
+ @Override int func3() { return 5;}
+ @Override public String glabel() { return ">>"; }
+ @Override public String op() { return "srai"; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java
new file mode 100644
index 00000000..e2e34415
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SraRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// Right Shift Arithmetic
+public class SraRISC extends MachConcreteNode implements MachNode {
+ public SraRISC(Node sra) { super(sra); }
+ @Override public String op() { return "sar"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0b101,0b00100000); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >> ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java
new file mode 100644
index 00000000..d0ea6b18
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlIRISC.java
@@ -0,0 +1,13 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.Utils;
+
+public class SrlIRISC extends ImmRISC {
+ public SrlIRISC( Node and, int imm, boolean pop) { super(and,imm,pop); }
+ @Override int opcode() { return RiscV.OP_IMM; }
+ @Override int func3() { return 5; }
+ @Override public String glabel() { return ">>>"; }
+ @Override public String op() { return "srli"; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java
new file mode 100644
index 00000000..1eccc1c8
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SrlRISC.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// Right Shift Logical
+public class SrlRISC extends MachConcreteNode implements MachNode {
+ public SrlRISC(Node srli) { super(srli); }
+ @Override public String op() { return "shr"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,7,0); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" >>> ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
new file mode 100644
index 00000000..48cb7c78
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
@@ -0,0 +1,50 @@
+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.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 _sz >= '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));
+ }
+
+ @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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java
new file mode 100644
index 00000000..044820cf
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubFRISC.java
@@ -0,0 +1,18 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// fsub.d
+public class SubFRISC extends MachConcreteNode implements MachNode {
+ SubFRISC(Node subf) {super(subf);}
+ @Override public String op() { return "subf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.FMASK; }
+ @Override public RegMask outregmap() { return riscv.FMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.rf_type(enc,this,RiscV.RM.RNE,5); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" + ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java
new file mode 100644
index 00000000..2adfce23
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SubRISC.java
@@ -0,0 +1,16 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class SubRISC extends MachConcreteNode implements MachNode {
+ public SubRISC( Node sub ) { super(sub); }
+ @Override public String op() { return "sub"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,0,0x20); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" - ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java
new file mode 100644
index 00000000..e57f6e09
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java
@@ -0,0 +1,49 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.ConstantNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class TFPRISC extends ConstantNode implements MachNode, RIPRelSize {
+ TFPRISC(ConstantNode con) { super(con); }
+ @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(this); }
+ @Override public void encoding( Encoding enc ) {
+ enc.relo(this);
+ // TODO: 1 op encoding, plus a TODO if it does not fit
+ short dst = enc.reg(this);
+ TypeFunPtr tfp = (TypeFunPtr)_con;
+ // 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 ) {
+ // ADDI sign-extends its low 12 bits, so round the AUIPC high part.
+ enc.patch4(opStart ,RiscV.u_type(RiscV.OP_AUIPC, rpc, (delta+0x800)>>12));
+ enc.patch4(opStart+4,RiscV.i_type(RiscV.OP_IMM, rpc, 0, rpc, delta & 0xFFF));
+ } 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) {
+ _con.print(sb.p(code.reg(this)).p(" #"));
+ }
+ @Override public boolean eq(Node n) { return this==n; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java
new file mode 100644
index 00000000..7bc2db7e
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java
@@ -0,0 +1,42 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.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 {
+ TMPRISC(ConstantNode con) { super(con); }
+ @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); }
+ @Override public void encoding( Encoding enc ) {
+ 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);
+ // Round the high part because ADDI's low 12 bits are signed.
+ enc.patch4(opStart , RiscV.u_type(RiscV.OP_AUIPC, dst, (delta+0x800)>>12));
+ // ADDI dst,dst,#low12_constant_pool
+ enc.patch4(opStart+4, RiscV.i_type(RiscV.OP_IMM, dst, 0, dst, delta & 0xFFF));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ _con.print(sb.p(code.reg(this)).p(" #"));
+ }
+ @Override public boolean eq(Node n) { return this==n; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java
new file mode 100644
index 00000000..448d9ec3
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/UJmpRISC.java
@@ -0,0 +1,49 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// unconditional jump
+public class UJmpRISC extends CFGNode implements MachNode, RIPRelSize {
+ @Override public String op() { return "jmp"; }
+ @Override public String label() { return op(); }
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("jmp "); }
+ @Override public RegMask regmap(int i) {return null; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public Type compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+ @Override public void encoding( Encoding enc ) {
+ // Short form +/-4K: beq r0,r0,imm12
+ // Long form: auipc rX,imm20/32; jal r0,[rX+imm12/32]
+ enc.jump(this,uctrl());
+ enc.add4(RiscV.j_type(RiscV.OP_JAL, 0, 0));
+ }
+
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) {
+ if( -(1L<<20) <= delta && delta < (1L<<20) ) return 4;
+ // 2 word encoding needs a tmp register, must teach RA
+ throw Utils.TODO();
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ if( opLen==4 ) {
+ enc.patch4(opStart,RiscV.j_type(RiscV.OP_JAL, 0, delta));
+ } else {
+ throw Utils.TODO();
+ }
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ CFGNode target = uctrl();
+ //assert target.nOuts() > 1; // Should optimize jmp to empty targets
+ sb.p(label(target));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java
new file mode 100644
index 00000000..f4323cae
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorIRISC.java
@@ -0,0 +1,14 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.isa.RiscV;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.Utils;
+
+public class XorIRISC extends ImmRISC {
+ public XorIRISC( Node and, int imm) { super(and,imm); }
+ public XorIRISC( Node and, int imm, boolean pop) { super(and,imm,pop); }
+ @Override int opcode() { return RiscV.OP_IMM; }
+ @Override int func3() { return 4; }
+ @Override public String glabel() { return "^"; }
+ @Override public String op() { return "xori"; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java
new file mode 100644
index 00000000..413a7c3b
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/XorRISC.java
@@ -0,0 +1,16 @@
+package com.seaofnodes.simple.node.cpus.riscv;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class XorRISC extends MachConcreteNode implements MachNode {
+ public XorRISC(Node or) { super(or); }
+ @Override public String op() { return "xor"; }
+ @Override public RegMask regmap(int i) { return riscv.RMASK; }
+ @Override public RegMask outregmap() { return riscv.WMASK; }
+ @Override public void encoding( Encoding enc ) { riscv.r_type(enc,this,4,0); }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" ^ ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
new file mode 100644
index 00000000..7c994624
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -0,0 +1,428 @@
+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";}
+ //@Override public int maxReg() { return MAX_REG; }
+
+ // 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 arguments calling conv
+ 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
+ // Count floats in signature up to index
+ int fcnt=0;
+ for( int i=2; i<idx; i++ )
+ if( tfp.arg(i-2) 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( idx-2-fcnt < cargs.length )
+ return cargs[idx-2-fcnt];
+ }
+ // Pass on stack slot(8 and higher)
+ if( maxArgSlot>0 ) throw Utils.TODO();
+ return new RegMask(MAX_REG + 1 + (idx - 2));
+ }
+
+ @Override public short maxArgSlot( TypeFunPtr tfp ) {
+ int icnt=0, fcnt=0; // Count of ints, floats
+ for( int i=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;
+ }
+
+ 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(String kind, byte round, LRG lrg) { return new SplitRISC(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 AddFNode addf -> addf(addf);
+ case AddNode add -> add(add);
+ case AndNode and -> and(and);
+ case BoolNode bool -> cmp(bool);
+ case CallNode call -> call(call);
+ case CastNode cast -> new CastMach(cast);
+ case CallEndNode cend -> new CallEndMach(cend);
+ case CProjNode c -> new CProjNode(c);
+ case ConstantNode con -> con(con);
+ case DivFNode divf -> new DivFRISC(divf);
+ case DivNode div -> new DivRISC(div);
+ case FunNode fun -> new FunRISC(fun);
+ case IfNode iff -> jmp(iff);
+ case LoadNode ld -> ld(ld);
+ case MemMergeNode mem -> new MemMergeNode(mem);
+ case MinusNode neg -> new NegRISC(neg);
+ case MulFNode mulf -> new MulFRISC(mulf);
+ case MulNode 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 StartNode start -> new StartNode(start);
+ case StopNode stop -> new StopNode(stop);
+ case StoreNode st -> st(st);
+ case SubFNode subf -> new SubFRISC(subf);
+ case SubNode 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 addf(AddFNode addf) {
+ return new AddFRISC(addf);
+ }
+
+ private Node add(AddNode 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 ConstantNode con && con._con instanceof TypeFunPtr tfp
+ ? new CallRISC(call, tfp)
+ : 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 new ConstantNode( con ); // Default unknown caller inputs
+ return switch( con._con ) {
+ case TypeInteger ti -> {
+ 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);
+ }
+ // Load from constant pool
+ case TypeFloat tf -> new FltRISC(con);
+ case TypeFunPtr tfp -> new TFPRISC(con);
+ case TypeMemPtr tmp -> new TMPRISC(con);
+ case TypeNil tn -> throw Utils.TODO();
+ // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
+ case Type t -> t==Type.NIL ? new IntRISC(con) : new ConstantNode(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) {
+ TypeInteger ret = cReturn(prj);
+ if( ret!=null ) return new ExtendRISC(prj,ret);
+ return 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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java
new file mode 100644
index 00000000..974f690f
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class AddFMemX86 extends MemOpX86 {
+ AddFMemX86( AddFNode 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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java
new file mode 100644
index 00000000..c7d0cf9c
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFX86.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class AddFX86 extends MachConcreteNode implements MachNode {
+ AddFX86( Node addf ) { super(addf); }
+ @Override public String op() { return "addf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+ @Override public int twoAddress() { return 1; }
+ @Override public boolean commutes() { return true; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.sse(enc,0xF2,0x0F58,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET);
+ }
+
+ // General form: "addf dst += src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" += ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java
new file mode 100644
index 00000000..0e652a93
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddIX86.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class AddIX86 extends ImmX86 {
+ AddIX86( Node add, int imm ) { super(add,imm); }
+ @Override public String op() {
+ return _imm == 1 ? "inc" : (_imm == -1 ? "dec" : "addi");
+ }
+ @Override public String glabel() { return "+"; }
+ @Override int opcode() { return 0x81; }
+ @Override int mod() { return 0; }
+ @Override public final void encoding( Encoding enc ) {
+ if( _imm== 1 || _imm== -1 ) {
+ short dst = enc.reg(this); // Also src1
+ X86.unary(enc,0xFF,_imm==1 ? 0 : 1,dst);
+ } else super.encoding(enc);
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java
new file mode 100644
index 00000000..ed65b0a8
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddMemX86.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class AddMemX86 extends MemOpX86 {
+ AddMemX86( AddNode add, LoadNode ld , Node base, Node idx, int off, int scale, int imm, Node val ) {
+ super(add,ld, base, idx, off, scale, imm, val );
+ }
+ @Override public String op() { return "add"+_sz; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public int twoAddress() { return 4; }
+ @Override public void encoding( Encoding enc ) {
+ X86.memory(enc,0,0x03,true,enc.reg(this),enc.reg(ptr()),enc.reg(idx()),_off,_scale);
+ }
+
+ // General form: "add dst += [base + idx<<2 + 12]"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" += ");
+ asm_address(code,sb);
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java
new file mode 100644
index 00000000..84543e8f
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class AddX86 extends RegX86 {
+ AddX86( Node add ) { super(add); }
+ @Override public String op() { return "add"; }
+ @Override public String glabel() { return "+"; }
+ @Override int opcode() { return 0x03; }
+ @Override public boolean commutes() { return true; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java
new file mode 100644
index 00000000..3c1b567c
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndIX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class AndIX86 extends ImmX86 {
+ AndIX86( Node add, int imm ) { super(add,imm); }
+ @Override public String op() { return "andi"; }
+ @Override public String glabel() { return "&"; }
+ @Override int opcode() { return 0x81; }
+ @Override int mod() { return 4; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java
new file mode 100644
index 00000000..dedbce45
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AndX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class AndX86 extends RegX86 {
+ AndX86( Node add ) { super(add); }
+ @Override public String op() { return "and"; }
+ @Override public String glabel() { return "&"; }
+ @Override int opcode() { return 0x23; }
+ @Override public boolean commutes() { return true; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java
new file mode 100644
index 00000000..f97163eb
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java
@@ -0,0 +1,30 @@
+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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java
new file mode 100644
index 00000000..d1a43b83
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java
@@ -0,0 +1,49 @@
+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;
+ CallX86( CallNode call, TypeFunPtr tfp ) {
+ super(call);
+ _inputs.pop(); // Pop constant target
+ assert tfp.isConstant();
+ _tfp = tfp;
+ FunNode fun = CodeGen.CODE.link(tfp);
+ _name = fun==null ? ((ExternNode)call.fptr())._extern : fun._name; // Can be null for extern calls
+ }
+ @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 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 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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java
new file mode 100644
index 00000000..5ef7a476
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpFX86.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class CmpFX86 extends MachConcreteNode implements MachNode {
+ CmpFX86( Node cmp ) { super(cmp); }
+ @Override public String op() { return "cmpf"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.XMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.sse(enc,0x66,0x0F2E,false,enc.reg(in(1))-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET);
+ }
+
+ // General form: "cmp src1,src2"
+ @Override public void asm(CodeGen code, SB sb) {
+ String dst = code.reg(this);
+ if( dst!="flags" ) sb.p(dst).p(" = ");
+ sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java
new file mode 100644
index 00000000..151f8ca8
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpIX86.java
@@ -0,0 +1,38 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+// Compare immediate. Sets flags.
+public class CmpIX86 extends MachConcreteNode implements MachNode {
+ final int _imm;
+ CmpIX86( Node cmp, int imm, boolean swap ) {
+ super(cmp);
+ _inputs.del(swap ? 1 : 2);
+ _imm = imm;
+ }
+ CmpIX86( CmpIX86 cmp ) {
+ super((Node)null);
+ addDef(null);
+ addDef(cmp.in(1));
+ _imm = cmp._imm;
+ }
+
+ @Override public String op() { return _imm==0 ? "test" : "cmp"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; }
+ @Override public boolean isClone() { return true; }
+ @Override public Node copy() { return new CmpIX86(this); }
+
+ @Override public final void encoding( Encoding enc ) {
+ X86.imm(enc,0x81,7,enc.reg(in(1)),_imm);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ String dst = code.reg(this);
+ if( dst!="flags" ) sb.p(dst).p(" = ");
+ sb.p(code.reg(in(1)));
+ if( _imm != 0 ) sb.p(", #").p(_imm);
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java
new file mode 100644
index 00000000..7b7d98d5
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java
@@ -0,0 +1,44 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+import com.seaofnodes.simple.util.SB;
+
+public class CmpMemX86 extends MemOpX86 {
+ final boolean _swap; // Op switched LHS, RHS
+ CmpMemX86( BoolNode bool, LoadNode ld, Node base, Node idx, int off, int scale, int imm, Node val, boolean swap ) {
+ super(bool,ld, base, idx, off, scale, imm, val );
+ _swap = swap;
+ assert !_swap || val() != null || bool.op()=="==" || bool.op()=="!="; // Cannot swap with immediate
+ }
+ @Override public String op() { return ((val()==null && _imm==0) ? "test" : "cmp") + _sz; }
+ @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; }
+ @Override public void encoding( Encoding enc ) {
+ short ptr=enc.reg(ptr()), idx=enc.reg(idx()), src=enc.reg(val());
+ if( src==-1 ) {
+ assert !_swap;
+ X86.cmpImm(enc,_declaredType.log_size(),_imm,ptr,idx,_off,_scale);
+ } else encVal(enc,_declaredType,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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java
new file mode 100644
index 00000000..7faea17b
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpX86.java
@@ -0,0 +1,28 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class CmpX86 extends MachConcreteNode implements MachNode {
+ CmpX86( Node add ) { super(add); }
+ @Override public String op() { return "cmp"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.FLAGS_MASK; }
+ // This one is marginal: cloning a Cmp[reg,reg] means stretching the
+ // lifetimes of 2 normal registers in exchange for shortening a flags
+ // register lifetime. Spilling flags is (probably) relatively expensive
+ // compared to some normal registers - and those normal registers might not
+ // be spilling!
+ @Override public boolean isClone() { return true; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.reg(enc,0x3B,enc.reg(in(1)),enc.reg(in(2)));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ String dst = code.reg(this);
+ if( dst!="flags" ) sb.p(dst).p(" = ");
+ sb.p(code.reg(in(1))).p(", ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java
new file mode 100644
index 00000000..7bbb5102
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivFX86.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class DivFX86 extends MachConcreteNode implements MachNode {
+ DivFX86( Node divf) { super(divf); }
+ @Override public String op() { return "divf"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.XMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+ @Override public int twoAddress() { return 1; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.sse(enc,0xF2,0x0F5E,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET);
+ }
+
+
+ // General form: "divf dst /= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" /= ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java
new file mode 100644
index 00000000..9f375a80
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/DivX86.java
@@ -0,0 +1,25 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class DivX86 extends MachConcreteNode implements MachNode {
+ DivX86( Node div ) { super(div); }
+ @Override public String op() { return "div"; }
+ @Override public RegMask regmap(int i) {
+ return (i==1) ? x86_64_v2.RAX_MASK : x86_64_v2.DIV_MASK;
+ }
+ @Override public RegMask outregmap() { return x86_64_v2.RAX_MASK; }
+ @Override public RegMask killmap() { return x86_64_v2.RDX_MASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.divide(enc,enc.reg(in(2)));
+ }
+ // General form: "div dst /= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" / ").p(code.reg(in(2)));
+ }
+ @Override public String comment() { return "kill rdx"; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java
new file mode 100644
index 00000000..1a6ae123
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ExtendX86.java
@@ -0,0 +1,29 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+// Convert a narrow C return register to Simple's full-width integer value.
+public class ExtendX86 extends MachConcreteNode {
+ final int _bits;
+ final boolean _signed;
+ ExtendX86(ProjNode prj, TypeInteger ret) {
+ super(prj);
+ _inputs.set(0,null);
+ _inputs.add(new ProjX86(prj));
+ _bits = 8 << ret.log_size();
+ _signed = ret._min < 0;
+ }
+ @Override public String op() { return (_signed ? "sext" : "zext")+_bits; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public void encoding(Encoding enc) {
+ X86.extend(enc,_bits,_signed,enc.reg(this),enc.reg(in(1)));
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java
new file mode 100644
index 00000000..a79a1017
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class F8ToIX86 extends MachConcreteNode implements MachNode {
+ F8ToIX86(Node f8toi ) { super(f8toi); }
+ @Override public String op() { return "cvti"; }
+ @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.XMASK; }
+ @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-x86_64_v2.XMM_OFFSET);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java
new file mode 100644
index 00000000..da9bdce7
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java
@@ -0,0 +1,36 @@
+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 FltX86 extends ConstantNode implements MachNode, RIPRelSize{
+ private byte _encSize;
+ FltX86(ConstantNode con ) { super(con); }
+ @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); }
+
+ @Override public void encoding( Encoding enc ) {
+ _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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java
new file mode 100644
index 00000000..c7a20e4d
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java
@@ -0,0 +1,31 @@
+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);
+ // 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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java
new file mode 100644
index 00000000..ee357901
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/I2f8X86.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class I2f8X86 extends MachConcreteNode implements MachNode {
+ I2f8X86(Node i2f8 ) { super(i2f8); }
+ @Override public String op() { return "cvtf"; }
+ @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.WMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.sse(enc,0xF2,0x0F2A,true,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(1)));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p("(flt)").p(code.reg(in(1)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java
new file mode 100644
index 00000000..5a4ff4b2
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ImmX86.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.SB;
+
+public abstract class ImmX86 extends MachConcreteNode implements MachNode {
+ final int _imm;
+ ImmX86( Node add, int imm ) {
+ super(add);
+ _inputs.pop(); // Pop constant input off
+ _imm = imm;
+ }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public int twoAddress() { return 1; }
+ @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; }
+
+ abstract int opcode();
+ abstract int mod();
+
+ @Override public void encoding( Encoding enc ) {
+ X86.imm(enc,opcode(),mod(),enc.reg(this),_imm);
+ }
+
+ // General form: "addi dst += #imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm);
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java
new file mode 100644
index 00000000..656b1543
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java
@@ -0,0 +1,40 @@
+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;
+
+// Integer constants
+public class IntX86 extends ConstantNode implements MachNode {
+ IntX86( ConstantNode con ) { super(con); }
+ @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); }
+ @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 ) {
+ 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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java
new file mode 100644
index 00000000..10cee059
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/JmpX86.java
@@ -0,0 +1,65 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// Jump on flags, uses flags
+public class JmpX86 extends IfNode implements MachNode, RIPRelSize {
+ String _bop;
+ JmpX86( IfNode iff, String bop ) {
+ super(iff);
+ _bop = bop;
+ }
+ @Override public String op() { return "j"+_bop; }
+ @Override public String label() { return op(); }
+ @Override public void postSelect(CodeGen code) {
+ Node set = in(1);
+ if( set==null ) return; // Never-node cutout
+ Node cmp = set.in(1);
+ // Bypass an expected Set and just reference the cmp directly
+ if( set instanceof SetX86 setx && (cmp instanceof CmpX86 || cmp instanceof CmpIX86 || cmp instanceof CmpMemX86 || cmp instanceof CmpFX86) ) {
+ _inputs.set( 1, cmp );
+ _bop = setx._bop;
+ } else
+ throw Utils.TODO();
+ }
+ @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.FLAGS_MASK; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public void negate() { _bop = negate(_bop); }
+
+ @Override public void encoding( Encoding enc ) {
+ if( in(1)==null && _bop=="!=" ) return;
+ X86.branch(enc,in(1)==null ? -1 : x86_64_v2.jumpop(_bop)&15);
+ enc.jump(this,cproj(0));
+ }
+
+ // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding
+ @Override public byte encSize(int delta) {
+ if( in(1)==null && _bop=="!=" ) return 0;
+ return X86.branchSize(delta,in(1)!=null);
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ assert !(in(1)==null && _bop=="!=");
+ X86.patchBranch(enc.bits(),opStart,opLen,delta,in(1)==null ? -1 : x86_64_v2.jumpop(_bop)&15);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ if( in(1)!=null ) { // Never-node
+ String src = code.reg(in(1));
+ if( src!="flags" ) sb.p(src).p(" ");
+ } else if( _bop=="!=" ) {
+ sb.p("never");
+ return;
+ }
+ CFGNode prj = cproj(0).uctrlSkipEmpty(); // 0 is True is jump target
+ if( !prj.blockHead() ) prj = prj.cfg0();
+ sb.p(label(prj));
+ }
+
+ @Override public String comment() { return "L"+cproj(1)._nid; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java
new file mode 100644
index 00000000..4b00e4dd
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LeaX86.java
@@ -0,0 +1,38 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class LeaX86 extends MachConcreteNode implements MachNode {
+ final int _scale;
+ final int _offset;
+ LeaX86( Node add, Node base, Node idx, int scale, int offset ) {
+ super(add);
+ assert scale==0 || scale==1 || scale==2 || scale==3;
+ _inputs.pop();
+ _inputs.pop();
+ _inputs.push(base);
+ _inputs.push(idx);
+ _scale = scale;
+ _offset = offset;
+ }
+
+ @Override public String op() { return "lea"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.lea(enc,enc.reg(this),enc.reg(in(1)),enc.reg(in(2)),_offset,_scale);
+ }
+
+ // General form: "lea dst = base + 4*idx + 12"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ");
+ if( in(1) != null )
+ sb.p(code.reg(in(1))).p(" + ");
+ sb.p(code.reg(in(2))).p("<<").p(_scale);
+ if( _offset!=0 ) sb.p(" + #").p(_offset);
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java
new file mode 100644
index 00000000..d4f1413e
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java
@@ -0,0 +1,44 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.LoadNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+public class LoadX86 extends MemOpX86 {
+ LoadX86( LoadNode ld, Node base, Node idx, int off, int scale ) {
+ super(ld,ld, base, idx, off, scale, 0);
+ }
+ @Override public String op() { return "ld"+_sz; }
+ @Override public RegMask outregmap() { return x86_64_v2.MEM_MASK; }
+ @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, _declaredType, 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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java
new file mode 100644
index 00000000..2e18a058
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemAddX86.java
@@ -0,0 +1,28 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class MemAddX86 extends MemOpX86 {
+ MemAddX86( StoreNode st, Node base, Node idx, int off, int scale, int imm, Node val ) {
+ super(st, st, base, idx, off, scale, imm, val );
+ }
+ @Override public String op() {
+ return (_imm == 1 ? "inc" : (_imm == -1 ? "dec" : "add")) + _sz;
+ }
+ // Register mask allowed as a result. 0 for no register.
+ @Override public RegMask outregmap() { return null; }
+ @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; }
+ @Override public void encoding( Encoding enc ) {
+ X86.addMem(enc,enc.reg(val()),_imm,enc.reg(ptr()),enc.reg(idx()),_off,_scale);
+ }
+ // General form: "add [base + idx<<2 + 12] += src"
+ @Override public void asm(CodeGen code, SB sb) {
+ asm_address(code,sb);
+ if( val()==null ) {
+ if( _imm != 1 && _imm != -1 ) sb.p(" += #").p(_imm);
+ } else sb.p(" += ").p(code.reg(val()));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
new file mode 100644
index 00000000..309e62d9
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
@@ -0,0 +1,89 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+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;
+ _sz = (char)('0'+(1<<_declaredType.log_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 Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+
+ // Register mask allowed on input i.
+ @Override public RegMask regmap(int i) {
+ if( i==1 ) return null; // Memory
+ if( i==2 ) return x86_64_v2.RMASK; // base in GPR
+ if( i==3 ) return x86_64_v2.RMASK; // index in GPR
+ if( i==4 ) return _sz >= '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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java
new file mode 100644
index 00000000..74616db7
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulFX86.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class MulFX86 extends MachConcreteNode implements MachNode {
+ MulFX86( Node mulf) { super(mulf); }
+ @Override public String op() { return "mulf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+ @Override public int twoAddress() { return 1; }
+ @Override public boolean commutes() { return true; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.sse(enc,0xF2,0x0F59,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET);
+ }
+
+ // General form: "mulf dst *= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" *= ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java
new file mode 100644
index 00000000..5dcb253d
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java
@@ -0,0 +1,27 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class MulIX86 extends MachConcreteNode implements MachNode {
+ final int _imm;
+ MulIX86( Node add, int imm ) {
+ super(add);
+ _inputs.pop(); // Pop the constant input
+ _imm = imm; // Record the constant
+ }
+ @Override public String op() { return "muli"; }
+ @Override public String glabel() { return "*"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ // Rare 3-address form
+ @Override public final void encoding( Encoding enc ) {
+ X86.imul(enc,enc.reg(this),enc.reg(in(1)),_imm);
+ }
+ // General form: "addi dst += #imm"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * #").p(_imm);
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java
new file mode 100644
index 00000000..6f3fcdeb
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulX86.java
@@ -0,0 +1,24 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class MulX86 extends MachConcreteNode implements MachNode {
+ MulX86( Node mul ) { super(mul); }
+ @Override public String op() { return "mul"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public int twoAddress() { return 1; }
+ @Override public boolean commutes() { return true; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.reg(enc,0x0FAF,enc.reg(this),enc.reg(in(2)));
+ }
+
+ // General form: "mul dst *= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java
new file mode 100644
index 00000000..86610f6d
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NegX86.java
@@ -0,0 +1,20 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class NegX86 extends MachConcreteNode implements MachNode {
+ NegX86(MinusNode not) { super(not); }
+ @Override public String op() { return "neg"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.RMASK; }
+ @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; }
+ @Override public int twoAddress() { return 1; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.unary(enc,0xF7,3,enc.reg(this));
+ }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java
new file mode 100644
index 00000000..1cfc1c42
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.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("ldi rcx = #1\n");
+ sb.p("call #calloc");
+ }
+
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java
new file mode 100644
index 00000000..1e4e7c26
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class NotX86 extends MachConcreteNode implements MachNode {
+ NotX86(NotNode not) { super(not); }
+ @Override public String op() { return "not"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.RMASK; }
+ @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ assert !(in(1) instanceof NotNode); // Cleared out by peeps
+ assert !(in(1) instanceof BoolNode);
+ X86.not(enc,enc.reg(this),enc.reg(in(1)));
+ }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java
new file mode 100644
index 00000000..15f4824d
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrIX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class OrIX86 extends ImmX86 {
+ OrIX86( Node add, int imm ) { super(add,imm); }
+ @Override public String op() { return "ori"; }
+ @Override public String glabel() { return "|"; }
+ @Override int opcode() { return 0x81; }
+ @Override int mod() { return 1; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java
new file mode 100644
index 00000000..bc8f0ea8
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/OrX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class OrX86 extends RegX86 {
+ OrX86( Node add ) { super(add); }
+ @Override public String op() { return "or"; }
+ @Override public String glabel() { return "|"; }
+ @Override int opcode() { return 0x0B; }
+ @Override public boolean commutes() { return true; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java
new file mode 100644
index 00000000..5cfdd804
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ParmX86.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.ParmNode;
+
+public class ParmX86 extends ParmNode implements MachNode {
+ final RegMask _rmask;
+ ParmX86( ParmNode parm ) {
+ super(parm);
+ _rmask = x86_64_v2.callInMask(fun().sig(),_idx,1/*RPC*/);
+ }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() { return _rmask; }
+ @Override public void encoding( Encoding enc ) { }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java
new file mode 100644
index 00000000..046c7b4c
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ProjX86.java
@@ -0,0 +1,15 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.ProjNode;
+
+public class ProjX86 extends ProjNode implements MachNode {
+ ProjX86( ProjNode p ) { super(p); }
+ @Override public RegMask regmap(int i) { return null; }
+ @Override public RegMask outregmap() {
+ return ((MachNode)in(0)).outregmap(_idx);
+ }
+ @Override public void encoding( Encoding enc ) {}
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java
new file mode 100644
index 00000000..06755143
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RegX86.java
@@ -0,0 +1,22 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public abstract class RegX86 extends MachConcreteNode {
+ RegX86( Node add ) { super(add); }
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; }
+ @Override public int twoAddress() { return 1; }
+ abstract int opcode();
+ @Override public void encoding( Encoding enc ) {
+ X86.reg(enc,opcode(),enc.reg(in(1)),enc.reg(in(2)));
+ }
+ // General form: "add dst += src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" ").p(glabel()).p("= ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java
new file mode 100644
index 00000000..e4467bb4
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/RetX86.java
@@ -0,0 +1,15 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+
+public class RetX86 extends ReturnNode implements MachNode {
+ RetX86( ReturnNode ret, FunNode fun ) { super(ret, fun); fun.setRet(this); }
+ @Override public void encoding( Encoding enc ) {
+ int sz = fun()._frameAdjust;
+ if( sz!=0 ) X86.imm(enc,0x81,0,x86_64_v2.RSP,sz);
+ X86.ret(enc);
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java
new file mode 100644
index 00000000..cf6275dc
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarIX86.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.SB;
+
+// Arithmetic Right Shift
+public class SarIX86 extends MachConcreteNode implements MachNode {
+ final int _imm;
+ SarIX86( Node sari, int imm ) { super(sari); assert x86_64_v2.imm8(imm); _inputs.pop(); _imm = imm; }
+ @Override public String op() { return "sari"; }
+ @Override public String glabel() { return ">>"; }
+ int opcode() { return 0xC1; }
+ int mod() { return 7; }
+
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public int twoAddress() { return 1; }
+
+ @Override public void encoding(Encoding enc) {
+ X86.shift(enc,mod(),enc.reg(this),_imm);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm);
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java
new file mode 100644
index 00000000..92493712
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SarX86.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.Utils;
+
+public class SarX86 extends RegX86 {
+ SarX86( Node add ) { super(add); }
+ @Override public String op() { return "sar"; }
+ @Override public String glabel() { return ">>"; }
+ @Override public RegMask regmap(int i) {
+ if (i == 1) return x86_64_v2.WMASK;
+ if (i == 2) return x86_64_v2.RCX_MASK;
+ throw Utils.TODO();
+ }
+ @Override int opcode() { return 0xD3; }
+ @Override public void encoding( Encoding enc ) {
+ X86.unary(enc,opcode(),7,enc.reg(this));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java
new file mode 100644
index 00000000..864c1b55
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SetX86.java
@@ -0,0 +1,44 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// Corresponds to the x86 instruction "sete && setne".
+// Use result of comparison without jump.
+public class SetX86 extends MachConcreteNode implements MachNode {
+ final String _bop; // One of <,<=,==
+ final boolean _unsigned;
+ // Constructor expects input is an X86 and not an Ideal node.
+ SetX86( Node cmp, String bop, boolean unsigned ) {
+ super(cmp);
+ _inputs.setLen(1); // Pop the cmp inputs
+ // Replace with the matched cmp
+ _inputs.push(cmp);
+ _bop = bop;
+ _unsigned = unsigned;
+ }
+ @Override public String op() { return "set"+_bop; }
+ @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.FLAGS_MASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+
+ @Override public void encoding( Encoding enc ) {
+ int op=switch (_bop) {
+ case "==" -> 0x94; // SETE
+ case "<" -> _unsigned ? 0x92 : 0x9C; // SETB /SETL
+ case "<=" -> _unsigned ? 0x96 : 0x9E; // SETBE/SETLE
+ case ">=" -> _unsigned ? 0x93 : 0x9D; // SETAE/SETGE
+ case ">" -> _unsigned ? 0x97 : 0x9F; // SETA /SETG
+ default -> throw Utils.TODO();
+ };
+ X86.set(enc,op&15,enc.reg(this));
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this));
+ String src = code.reg(in(1));
+ if( src!="flags" ) sb.p(" = ").p(src);
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java
new file mode 100644
index 00000000..1e62243e
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlIX86.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.SB;
+
+public class ShlIX86 extends MachConcreteNode implements MachNode {
+ final int _imm;
+ ShlIX86( Node shli, int imm ) {
+ super(shli);
+ assert x86_64_v2.imm8(imm);
+ _inputs.pop(); // Pop constant input off
+ _imm = imm;
+ }
+ public int opcode() { return 0xC1; }
+ public int mod() { return 4; }
+
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public int twoAddress() { return 1; }
+
+ @Override public String op() { return "shli"; }
+ @Override public String glabel() { return "<<"; }
+ @Override public void encoding(Encoding enc) {
+ X86.shift(enc,mod(),enc.reg(this),_imm);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm);
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java
new file mode 100644
index 00000000..768d97e7
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShlX86.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.Utils;
+
+public class ShlX86 extends RegX86 {
+ ShlX86( Node add ) { super(add); }
+ @Override public String op() { return "shl"; }
+ @Override public String glabel() { return "<<"; }
+ @Override public RegMask regmap(int i) {
+ if (i == 1) return x86_64_v2.WMASK;
+ if (i == 2) return x86_64_v2.RCX_MASK;
+ throw Utils.TODO();
+ }
+ @Override int opcode() { return 0xD3; }
+ @Override public final void encoding( Encoding enc ) {
+ X86.unary(enc,opcode(),4,enc.reg(this));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java
new file mode 100644
index 00000000..8a0d1b58
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrIX86.java
@@ -0,0 +1,27 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.SB;
+
+public class ShrIX86 extends MachConcreteNode implements MachNode {
+ final int _imm;
+ ShrIX86( Node shri, int imm ) { super(shri); assert x86_64_v2.imm8(imm); _inputs.pop(); _imm = imm; }
+ @Override public String op() { return "shri"; }
+ @Override public String glabel() { return ">>>"; }
+ int opcode() { return 0xC1; }
+ int mod() { return 5; }
+
+ @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.WMASK; }
+ @Override public int twoAddress() { return 1; }
+
+ @Override public void encoding(Encoding enc) {
+ X86.shift(enc,mod(),enc.reg(this),_imm);
+ }
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" ").p(glabel()).p("= #").p(_imm);
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java
new file mode 100644
index 00000000..1ba20fee
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/ShrX86.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.util.Utils;
+
+public class ShrX86 extends RegX86 {
+ ShrX86( Node add ) { super(add); }
+ @Override public String op() { return "shr"; }
+ @Override public String glabel() { return ">>>"; }
+ @Override public RegMask regmap(int i) {
+ if (i == 1) return x86_64_v2.WMASK;
+ if (i == 2) return x86_64_v2.RCX_MASK;
+ throw Utils.TODO();
+ }
+ @Override int opcode() { return 0xD3; }
+ @Override public final void encoding( Encoding enc ) {
+ X86.unary(enc,opcode(),5,enc.reg(this));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java
new file mode 100644
index 00000000..4650b535
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java
@@ -0,0 +1,25 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+
+// Flag snapshots can use GPRs or stack slots, but never XMM registers.
+public class SplitFlagsX86 extends SplitX86 {
+ private static final RegMask MASK = new RegMask(x86_64_v2.WR_BITS | (1L<<x86_64_v2.FLAGS) | x86_64_v2.SPILLS,-1L);
+ SplitFlagsX86(String kind,byte round) { super(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,enc._fun.computeStackOffset(enc._code,reg),toFlags);
+ else
+ X86.flags(enc,reg,toFlags);
+ } else super.encoding(enc);
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
new file mode 100644
index 00000000..4e9eda7d
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
@@ -0,0 +1,52 @@
+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( String kind, byte round ) { super(kind,round, new Node[2]); }
+ @Override public String op() { return "mov"; }
+ @Override public RegMask regmap(int i) { return x86_64_v2.SPLIT_MASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.SPLIT_MASK; }
+
+ // Ordinary GPR, XMM, and stack moves. Flags use SplitFlagsX86.
+ @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 = enc._fun.computeStackOffset(enc._code,dst);
+ if( src >= x86_64_v2.MAX_REG ) {
+ // Rare stack-stack move. push [RSP+soff]; pop [RSP+doff]
+ int soff = enc._fun.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 = enc._fun.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);
+ }
+
+ // General form: "mov dst = src"
+ @Override public void asm(CodeGen code, SB sb) {
+ FunNode fun = code._encoding==null ? null : code._encoding._fun;
+ sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java
new file mode 100644
index 00000000..c83dfd44
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java
@@ -0,0 +1,48 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.ConstantNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.node.StoreNode;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+import com.seaofnodes.simple.util.SB;
+
+public class StoreX86 extends MemOpX86 {
+ StoreX86( StoreNode st, Node base, Node idx, int off, int scale, int imm, Node val ) {
+ super(st,st, base, idx, off, scale, imm, val);
+ }
+ @Override public String op() { return "st"+_sz; }
+ @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) {
+ Node val = val();
+ p.p(".").p(_name).p("=");
+ if( val==null ) p.p(_imm);
+ else p.n(val);
+ return p.p(";");
+ }
+ // Register mask allowed as a result. 0 for no register.
+ @Override public RegMask outregmap() { return null; }
+ @Override public void encoding( Encoding enc ) {
+ short ptr=enc.reg(ptr()), idx=enc.reg(idx()), src=enc.reg(val());
+ if( src==-1 ) X86.storeImm(enc,_declaredType.log_size(),_imm,ptr,idx,_off,_scale);
+ else encVal(enc,_declaredType,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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java
new file mode 100644
index 00000000..29f0033a
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubFX86.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.util.SB;
+
+public class SubFX86 extends MachConcreteNode implements MachNode {
+ SubFX86( Node subf) { super(subf); }
+ @Override public String op() { return "subf"; }
+ @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.XMASK; }
+ @Override public int twoAddress() { return 1; }
+
+ @Override public void encoding( Encoding enc ) {
+ X86.sse(enc,0xF2,0x0F5C,false,enc.reg(this)-x86_64_v2.XMM_OFFSET,enc.reg(in(2))-x86_64_v2.XMM_OFFSET);
+ }
+
+ // General form: "subf dst -= src"
+ @Override public void asm(CodeGen code, SB sb) {
+ sb.p(code.reg(this)).p(" -= ").p(code.reg(in(2)));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java
new file mode 100644
index 00000000..8da34bf5
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SubX86.java
@@ -0,0 +1,10 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class SubX86 extends RegX86 {
+ SubX86( Node add ) { super(add); }
+ @Override public String op() { return "sub"; }
+ @Override public String glabel() { return "-"; }
+ @Override int opcode() { return 0x2B; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java
new file mode 100644
index 00000000..fff2fb04
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java
@@ -0,0 +1,53 @@
+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.cpus.riscv.riscv;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.util.SB;
+
+// Function constants
+public class TFPX86 extends ConstantNode implements MachNode, RIPRelSize {
+ TFPX86( ConstantNode con ) { super(con); }
+ @Override public String op() {
+ return _con == Type.NIL ? "xor" : "ldx";
+ }
+ @Override public boolean isClone() { return true; }
+ @Override public Node copy() { return new TFPX86(this); }
+ @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 ? x86_64_v2.FLAGS_MASK : null;
+ }
+
+ @Override public void encoding( Encoding enc ) {
+ if( _con==Type.NIL ) { X86.constant(enc,enc.reg(this),0); return; }
+ enc.relo(this);
+ X86.rip(enc,0,0x8D,true,enc.reg(this));
+ }
+
+ @Override public byte encSize(int delta) {
+ return 7;
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ enc.patch4(opStart + 3, delta - 7);
+ }
+
+ // 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 )
+ sb.p(reg).p(",").p(reg);
+ else
+ _con.print(sb.p(reg).p(" = #"));
+ }
+ @Override public boolean eq(Node n) { return this==n; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java
new file mode 100644
index 00000000..c78ac6de
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java
@@ -0,0 +1,40 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.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{
+ TMPX86(ConstantNode con ) { super(con); }
+ @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); }
+
+ @Override public void encoding( Encoding enc ) {
+ short dst = enc.reg(this);
+ X86.rexF(dst, 0, 0, true, enc);
+ enc.add1(0x8D).add1(X86.modrm(X86.MOD.INDIRECT, dst, 0b101)).add4(0);
+ int opLen = 1/*rexF*/+1/*opcode 0x8D*/+1/*modrm*/+4/*pc rel offset*/;
+ // Base of constant array
+ TypeMemPtr tmp = (TypeMemPtr)_con;
+ int baseOff = tmp._obj.aryBase();
+ enc.largeConstant(this,tmp._obj,opLen-baseOff,2/*ELF encoding PC32*/);
+ }
+
+ // Delta is from opcode start
+ @Override public byte encSize(int delta) { return (byte)7; }
+
+ // 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-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java
new file mode 100644
index 00000000..591a51c3
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/UJmpX86.java
@@ -0,0 +1,41 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+
+// unconditional jump
+public class UJmpX86 extends CFGNode implements MachNode, RIPRelSize {
+ UJmpX86() { }
+ @Override public String op() { return "jmp"; }
+ @Override public String label() { return op(); }
+ @Override protected String format() { return "jmp "; }
+ @Override public RegMask regmap(int i) {return null; }
+ @Override public RegMask outregmap() { return null; }
+ @Override public Type compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
+ @Override public void encoding( Encoding enc ) {
+ enc.jump(this,uctrl());
+ X86.branch(enc,-1);
+ }
+
+ // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding
+ @Override public byte encSize(int delta) {
+ return X86.branchSize(delta,false);
+ }
+
+ // Delta is from opcode start
+ @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
+ X86.patchBranch(enc.bits(),opStart,opLen,delta,-1);
+ }
+
+ @Override public void asm(CodeGen code, SB sb) {
+ CFGNode target = uctrl();
+ //assert target.nOuts() > 1; // Should optimize jmp to empty targets
+ sb.p(label(target));
+ }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java
new file mode 100644
index 00000000..2d989a50
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorIX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class XorIX86 extends ImmX86 {
+ XorIX86( Node add, int imm ) { super(add,imm); }
+ @Override public String op() { return "xori"; }
+ @Override public String glabel() { return "^"; }
+ @Override int opcode() { return 0x81; }
+ @Override int mod() { return 6; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java
new file mode 100644
index 00000000..86703903
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/XorX86.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.simple.node.Node;
+
+public class XorX86 extends RegX86 {
+ XorX86( Node add ) { super(add); }
+ @Override public String op() { return "xor"; }
+ @Override public String glabel() { return "^"; }
+ @Override int opcode() { return 0x33; }
+ @Override public boolean commutes() { return true; }
+}
diff --git a/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
new file mode 100644
index 00000000..fcae17f8
--- /dev/null
+++ b/chapter-sccp/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
@@ -0,0 +1,564 @@
+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); }
+ 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
+ return switch( CodeGen.CODE._callingConv ) {
+ 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 switch( CodeGen.CODE._callingConv ) {
+ 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).
+ if( idx >= 6 )
+ return new RegMask(MAX_REG+maxArgSlot+(idx-2));
+ return tfp.arg(idx-2) instanceof TypeFloat
+ ? XMMS8 [idx-2]
+ : WIN64_CALL[idx-2];
+ }
+ static short maxArgSlotWin64(TypeFunPtr tfp) {
+ return (short)tfp.nargs();
+ }
+
+ // 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 icnt=0, fcnt=0; // Count of ints, floats
+ for( int i=2; i<idx; i++ ) {
+ if( tfp.arg(i-2) 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=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;
+ }
+
+ 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 switch (CodeGen.CODE._callingConv) {
+ case "SystemV" -> SYSTEM5_CALLER_SAVE;
+ case "Win64" -> WIN64_CALLER_SAVE;
+ default -> throw new IllegalArgumentException("Unknown calling convention: " + CodeGen.CODE._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(String kind, byte round, LRG lrg) {
+ return lrg._machDef!=null && lrg._machDef.outregmap().test(FLAGS)
+ ? new SplitFlagsX86(kind,round)
+ : new SplitX86(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 AddFNode addf -> addf(addf);
+ case AddNode add -> add(add);
+ case AndNode and -> and(and);
+ case BoolNode bool -> cmp(bool);
+ case CProjNode c -> new CProjNode(c);
+ case CallEndNode cend -> new CallEndMach(cend);
+ case CallNode call -> call(call);
+ case CastNode cast -> new CastMach(cast);
+ case ConstantNode con -> con(con);
+ case DivFNode divf -> new DivFX86(divf);
+ case DivNode div -> new DivX86(div);
+ case FunNode fun -> new FunX86(fun);
+ case IfNode iff -> jmp(iff);
+ case LoadNode ld -> ld(ld);
+ case MemMergeNode mem -> new MemMergeNode(mem);
+ case MinusNode neg -> new NegX86(neg);
+ case MulFNode mulf -> new MulFX86(mulf);
+ case MulNode 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 StartNode start -> new StartNode(start);
+ case StopNode stop -> new StopNode(stop);
+ case StoreNode st -> st(st);
+ case SubFNode subf -> new SubFX86(subf);
+ case SubNode sub -> sub(sub);
+ case ToFloatNode tfn -> i2f8(tfn);
+ case ToIntegerNode cvt -> new F8ToIX86(cvt);
+ case XCtrlNode x -> new ConstantNode(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) {
+ 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);
+
+ if( rhs instanceof LoadNode ld && ld.nOuts()==1 && ld._declaredType.log_size()>=3 )
+ return new AddMemX86(add,address(ld),ld.ptr(),idx,off,scale, 0,lhs);
+
+ // 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 addf(AddFNode addf) {
+ if(addf.in(1) instanceof LoadNode ld && ld.nOuts() == 1)
+ return new AddFMemX86(addf, address(ld), ld.ptr(), idx, off, scale, addf.in(2));
+
+// if(addf.in(2) instanceof LoadNode ld && ld.nOuts() == 1)
+// throw Utils.TODO(); // Swap load sides
+
+ return new AddFX86(addf);
+ }
+
+
+ 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 ConstantNode con && con._con instanceof TypeFunPtr tfp
+ ? new CallX86(call, tfp)
+ : new CallRX86(call);
+ }
+
+ // Because X86 flags, a normal ideal Bool is 2 X86 ops: a "cmp" and at "setz".
+ // Ideal If reading from a setz will skip it and use the "cmp" instead.
+ private static boolean swap, unsigned;
+ private Node cmp( BoolNode bool ) {
+ swap = unsigned = false;
+ Node cmp = _cmp(bool);
+ return new SetX86(cmp, swap ? IfNode.swap(bool.op()) : bool.op(), unsigned);
+ }
+
+ private Node _cmp(BoolNode bool) {
+ // Float variant
+ if( bool.isFloat() ) {
+ unsigned = true;
+ return new CmpFX86(bool);
+ }
+ Node lhs = bool.in(1);
+ Node rhs = bool.in(2);
+
+ // Since cmp does not record a BOP, SetX/Jmp need to know if the CMP is swapped.
+
+ // Vs memory
+ if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && rhs._type.isa(ld._declaredType) )
+ return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(rhs), val, false);
+
+ // Operands swap in the encoding directly, no need for Set/Jmp to swap `bop`
+ if( rhs instanceof LoadNode ld && ld.nOuts() == 1 && lhs._type.isa(ld._declaredType) &&
+ (val!=null || bool.op()=="==" || bool.op()=="!=") )
+ return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(lhs), val, true );
+
+ // Vs immediate
+ if( rhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) )
+ return new CmpIX86(bool, (int)ti.value(), false);
+
+ // Operand swap compare; Set and Jmp need to swap `bop`
+ if( lhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) )
+ return new CmpIX86(bool, (int)ti.value(), swap=true);
+
+ // x vs y
+ return new CmpX86(bool);
+ }
+
+ private Node con( ConstantNode con ) {
+ if( !con._con.isConstant() )
+ return new ConstantNode(con); // Default unknown caller inputs
+ return switch (con._con) {
+ case TypeInteger ti -> new IntX86(con);
+ case TypeFloat tf -> new FltX86(con);
+ case TypeFunPtr tfp -> new TFPX86(con);
+ case TypeMemPtr tmp -> new TMPX86(con);
+ case TypeNil tn -> throw Utils.TODO();
+ // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
+ case Type t -> t == Type.NIL ? new IntX86(con) : new ConstantNode(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), new ConstantNode(TypeInteger.ZERO)));
+ 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 ) {
+ TypeInteger ret = cReturn(prj);
+ if( ret!=null ) return new ExtendX86(prj,ret);
+ return new ProjX86(prj);
+ }
+
+ private Node sar(SarNode sar) {
+ if( sar.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value()))
+ return new SarIX86(sar, (int)(ti.value() & 0xff) );
+ return new SarX86(sar);
+ }
+
+ private Node shl(ShlNode shl) {
+ if( shl.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value()))
+ return new ShlIX86(shl, (int)(ti.value() & 0xff) );
+ return new ShlX86(shl);
+ }
+
+ private Node shr( ShrNode shr ) {
+ if( shr.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm8(ti.value()) )
+ return new ShrIX86(shr, (int)(ti.value() & 0xff) );
+ return new ShrX86(shr);
+ }
+
+ private Node st( StoreNode st ) {
+ // Look for "*ptr op= val"
+ Node op = st.val();
+ if( op instanceof AddNode ) {
+ if( op.in(1) instanceof LoadNode ld && stld_match(st,ld) )
+ return new MemAddX86(address(st), st.ptr(), idx, off, scale, imm(op.in(2)), val);
+ if( op.in(2) instanceof LoadNode ld && stld_match(st,ld) )
+ return new MemAddX86(address(st), st.ptr(), idx, off, scale, imm(op.in(1)), val);
+ }
+ return new StoreX86(address(st), st.ptr(), idx, off, scale, imm(st.val()), val);
+ }
+ private static boolean stld_match(StoreNode st, LoadNode ld ) {
+ return
+ ld.in(0) == st.in(0) &&
+ ld.mem() == st.mem() &&
+ ld.ptr() == st.ptr() &&
+ ld.off() == st.off();
+ }
+
+
+ private Node sub (SubNode sub ){
+ return sub.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti
+ ? new AddIX86(sub, (int)-ti.value())
+ : new SubX86(sub);
+ }
+
+ private Node xor (XorNode xor){
+ if( xor.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value()) )
+ return new XorIX86(xor, (int)ti.value());
+ return new XorX86(xor);
+ }
+
+ // Gather X86 addressing mode bits prior to constructing. This is a
+ // builder pattern, but saving the bits in a *local* *global* here to keep
+ // mess contained.
+ private static int off, scale, imm;
+ private static Node idx, val;
+ private <N extends MemOpNode > N address(N mop) {
+ off = scale = imm = 0; // Reset
+ idx = val = null;
+ Node base = mop.ptr();
+ // Skip/throw-away a ReadOnly, only used to typecheck
+ if(base instanceof ReadOnlyNode read) base = read.in(1);
+ assert !(base instanceof AddNode) && base._type instanceof TypeMemPtr; // Base ptr always, not some derived
+ if(mop.off() instanceof AddNode add && add.in(2) instanceof ConstantNode con && con._con instanceof TypeInteger ti) {
+ off = (int) ti.value();
+ assert off == ti.value(); // In 32-bit range
+ idx = add.in(1);
+ if(idx instanceof ShlNode shift && shift.in(2) instanceof ConstantNode shfcon &&
+ shfcon._con instanceof TypeInteger tscale && 0 <= tscale.value() && tscale.value() <= 3) {
+ idx = shift.in(1);
+ scale = (int) tscale.value();
+ }
+ } else {
+ if(mop.off() instanceof ConstantNode con && con._con instanceof TypeInteger ti) {
+ off = (int) ti.value();
+ assert off == ti.value(); // In 32-bit range
+ } else {
+ idx = mop.off();
+ }
+ }
+ return mop;
+ }
+
+ private int imm( Node xval ) {
+ assert val == null && imm == 0;
+ if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti) {
+ val = null;
+ imm = (int) ti.value();
+ assert imm == ti.value(); // In 32-bit range
+ } else {
+ val = xval;
+ }
+ return imm;
+ }
+
+}