From 0f46dc92a5c3363fe41b53cd47a456615c330577 Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Wed, 26 Mar 2025 15:09:33 +0000 Subject: [PATCH] Add Sea of Nodes IR implementation based on chapter 21 in Simple project --- README.md | 2 +- pom.xml | 1 + seaofnodes/README.md | 6 + seaofnodes/pom.xml | 31 + .../ezlang/compiler/Ary.java | 228 ++++ .../ezlang/compiler/Compiler.java | 983 ++++++++++++++++++ .../ezlang/compiler/IterPeeps.java | 197 ++++ .../ezlang/compiler/SB.java | 99 ++ .../ezlang/compiler/Utils.java | 53 + .../ezlang/compiler/Var.java | 52 + .../ezlang/compiler/codegen/BuildLRG.java | 91 ++ .../ezlang/compiler/codegen/Coalesce.java | 119 +++ .../ezlang/compiler/codegen/CodeGen.java | 389 +++++++ .../ezlang/compiler/codegen/ElfExpr.java | 87 ++ .../ezlang/compiler/codegen/ElfFile.java | 387 +++++++ .../ezlang/compiler/codegen/Encoding.java | 418 ++++++++ .../compiler/codegen/GlobalCodeMotion.java | 281 +++++ .../ezlang/compiler/codegen/IFG.java | 526 ++++++++++ .../ezlang/compiler/codegen/LRG.java | 235 +++++ .../compiler/codegen/ListScheduler.java | 258 +++++ .../ezlang/compiler/codegen/Machine.java | 48 + .../ezlang/compiler/codegen/RIPRelSize.java | 14 + .../ezlang/compiler/codegen/RegAlloc.java | 526 ++++++++++ .../ezlang/compiler/codegen/RegMask.java | 142 +++ .../ezlang/compiler/nodes/AddFNode.java | 44 + .../ezlang/compiler/nodes/AddNode.java | 200 ++++ .../ezlang/compiler/nodes/AndNode.java | 58 ++ .../ezlang/compiler/nodes/BoolNode.java | 135 +++ .../ezlang/compiler/nodes/CFGNode.java | 181 ++++ .../ezlang/compiler/nodes/CProjNode.java | 66 ++ .../ezlang/compiler/nodes/CallEndNode.java | 100 ++ .../ezlang/compiler/nodes/CallNode.java | 165 +++ .../ezlang/compiler/nodes/CalleeSaveNode.java | 26 + .../ezlang/compiler/nodes/CastNode.java | 64 ++ .../ezlang/compiler/nodes/ConstantNode.java | 69 ++ .../ezlang/compiler/nodes/CtrlNode.java | 14 + .../ezlang/compiler/nodes/DivFNode.java | 41 + .../ezlang/compiler/nodes/DivNode.java | 46 + .../ezlang/compiler/nodes/FRefNode.java | 37 + .../ezlang/compiler/nodes/FunNode.java | 199 ++++ .../ezlang/compiler/nodes/IfNode.java | 82 ++ .../ezlang/compiler/nodes/LoadNode.java | 209 ++++ .../ezlang/compiler/nodes/LogicalNode.java | 28 + .../ezlang/compiler/nodes/LoopNode.java | 80 ++ .../compiler/nodes/MachConcreteNode.java | 27 + .../ezlang/compiler/nodes/MachNode.java | 62 ++ .../ezlang/compiler/nodes/MemMergeNode.java | 155 +++ .../ezlang/compiler/nodes/MemOpNode.java | 78 ++ .../ezlang/compiler/nodes/MinusFNode.java | 36 + .../ezlang/compiler/nodes/MinusNode.java | 39 + .../ezlang/compiler/nodes/MulFNode.java | 51 + .../ezlang/compiler/nodes/MulNode.java | 89 ++ .../ezlang/compiler/nodes/MultiNode.java | 29 + .../ezlang/compiler/nodes/NeverNode.java | 20 + .../ezlang/compiler/nodes/NewNode.java | 83 ++ .../ezlang/compiler/nodes/Node.java | 721 +++++++++++++ .../ezlang/compiler/nodes/NotNode.java | 31 + .../ezlang/compiler/nodes/OrNode.java | 53 + .../ezlang/compiler/nodes/ParmNode.java | 47 + .../ezlang/compiler/nodes/PhiNode.java | 205 ++++ .../ezlang/compiler/nodes/ProjNode.java | 54 + .../ezlang/compiler/nodes/ReadOnlyNode.java | 30 + .../ezlang/compiler/nodes/RegionNode.java | 123 +++ .../ezlang/compiler/nodes/ReturnNode.java | 131 +++ .../ezlang/compiler/nodes/RoundF32Node.java | 38 + .../ezlang/compiler/nodes/SarNode.java | 48 + .../ezlang/compiler/nodes/ScopeNode.java | 429 ++++++++ .../ezlang/compiler/nodes/ShlNode.java | 52 + .../ezlang/compiler/nodes/ShrNode.java | 45 + .../ezlang/compiler/nodes/SplitNode.java | 22 + .../ezlang/compiler/nodes/StartNode.java | 57 + .../ezlang/compiler/nodes/StopNode.java | 63 ++ .../ezlang/compiler/nodes/StoreNode.java | 115 ++ .../ezlang/compiler/nodes/StructNode.java | 46 + .../ezlang/compiler/nodes/SubFNode.java | 44 + .../ezlang/compiler/nodes/SubNode.java | 58 ++ .../ezlang/compiler/nodes/ToFloatNode.java | 34 + .../ezlang/compiler/nodes/XCtrlNode.java | 14 + .../ezlang/compiler/nodes/XorNode.java | 52 + .../compiler/nodes/cpus/arm/AddARM.java | 20 + .../compiler/nodes/cpus/arm/AddFARM.java | 22 + .../compiler/nodes/cpus/arm/AddIARM.java | 27 + .../compiler/nodes/cpus/arm/AndARM.java | 21 + .../compiler/nodes/cpus/arm/AndIARM.java | 24 + .../compiler/nodes/cpus/arm/AsrARM.java | 18 + .../compiler/nodes/cpus/arm/AsrIARM.java | 31 + .../compiler/nodes/cpus/arm/BranchARM.java | 65 ++ .../compiler/nodes/cpus/arm/CallARM.java | 47 + .../compiler/nodes/cpus/arm/CallEndARM.java | 24 + .../compiler/nodes/cpus/arm/CallRRARM.java | 32 + .../compiler/nodes/cpus/arm/CmpARM.java | 22 + .../compiler/nodes/cpus/arm/CmpFARM.java | 22 + .../compiler/nodes/cpus/arm/CmpIARM.java | 40 + .../compiler/nodes/cpus/arm/DivARM.java | 19 + .../compiler/nodes/cpus/arm/DivFARM.java | 19 + .../compiler/nodes/cpus/arm/FloatARM.java | 45 + .../compiler/nodes/cpus/arm/FunARM.java | 36 + .../compiler/nodes/cpus/arm/I2F8ARM.java | 25 + .../compiler/nodes/cpus/arm/IntARM.java | 66 ++ .../compiler/nodes/cpus/arm/LoadARM.java | 33 + .../compiler/nodes/cpus/arm/LslARM.java | 19 + .../compiler/nodes/cpus/arm/LslIARM.java | 33 + .../compiler/nodes/cpus/arm/LsrARM.java | 19 + .../compiler/nodes/cpus/arm/LsrIARM.java | 29 + .../compiler/nodes/cpus/arm/MemOpARM.java | 67 ++ .../compiler/nodes/cpus/arm/MulARM.java | 19 + .../compiler/nodes/cpus/arm/MulFARM.java | 18 + .../compiler/nodes/cpus/arm/NewARM.java | 24 + .../compiler/nodes/cpus/arm/NotARM.java | 28 + .../ezlang/compiler/nodes/cpus/arm/OrARM.java | 20 + .../compiler/nodes/cpus/arm/OrIARM.java | 24 + .../compiler/nodes/cpus/arm/ParmARM.java | 17 + .../compiler/nodes/cpus/arm/ProjARM.java | 15 + .../compiler/nodes/cpus/arm/RetARM.java | 43 + .../compiler/nodes/cpus/arm/SetARM.java | 33 + .../compiler/nodes/cpus/arm/SplitARM.java | 54 + .../compiler/nodes/cpus/arm/StoreARM.java | 37 + .../compiler/nodes/cpus/arm/SubARM.java | 19 + .../compiler/nodes/cpus/arm/SubFARM.java | 18 + .../compiler/nodes/cpus/arm/SubIARM.java | 27 + .../compiler/nodes/cpus/arm/TFPARM.java | 52 + .../compiler/nodes/cpus/arm/UJmpARM.java | 48 + .../compiler/nodes/cpus/arm/XorARM.java | 19 + .../compiler/nodes/cpus/arm/XorIARM.java | 27 + .../ezlang/compiler/nodes/cpus/arm/arm.java | 662 ++++++++++++ .../compiler/nodes/cpus/riscv/AUIPC.java | 41 + .../compiler/nodes/cpus/riscv/AddFRISC.java | 17 + .../compiler/nodes/cpus/riscv/AddIRISC.java | 21 + .../compiler/nodes/cpus/riscv/AddRISC.java | 21 + .../compiler/nodes/cpus/riscv/AndIRISC.java | 12 + .../compiler/nodes/cpus/riscv/AndRISC.java | 16 + .../compiler/nodes/cpus/riscv/BranchRISC.java | 76 ++ .../nodes/cpus/riscv/CallEndRISC.java | 23 + .../compiler/nodes/cpus/riscv/CallRISC.java | 62 ++ .../compiler/nodes/cpus/riscv/CallRRISC.java | 31 + .../compiler/nodes/cpus/riscv/CastRISC.java | 18 + .../compiler/nodes/cpus/riscv/DivFRISC.java | 19 + .../compiler/nodes/cpus/riscv/DivRISC.java | 17 + .../compiler/nodes/cpus/riscv/FltRISC.java | 44 + .../compiler/nodes/cpus/riscv/FunRISC.java | 36 + .../compiler/nodes/cpus/riscv/I2F8RISC.java | 23 + .../compiler/nodes/cpus/riscv/ImmRISC.java | 54 + .../compiler/nodes/cpus/riscv/IntRISC.java | 29 + .../ezlang/compiler/nodes/cpus/riscv/LUI.java | 32 + .../compiler/nodes/cpus/riscv/LoadRISC.java | 28 + .../compiler/nodes/cpus/riscv/MemOpRISC.java | 76 ++ .../compiler/nodes/cpus/riscv/MulFRISC.java | 17 + .../compiler/nodes/cpus/riscv/MulRISC.java | 17 + .../compiler/nodes/cpus/riscv/NewRISC.java | 34 + .../compiler/nodes/cpus/riscv/NotRISC.java | 16 + .../compiler/nodes/cpus/riscv/OrIRISC.java | 12 + .../compiler/nodes/cpus/riscv/OrRISC.java | 16 + .../compiler/nodes/cpus/riscv/ParmRISC.java | 17 + .../compiler/nodes/cpus/riscv/ProjRISC.java | 15 + .../compiler/nodes/cpus/riscv/RetRISC.java | 50 + .../compiler/nodes/cpus/riscv/SetFRISC.java | 33 + .../compiler/nodes/cpus/riscv/SetIRISC.java | 17 + .../compiler/nodes/cpus/riscv/SetRISC.java | 17 + .../compiler/nodes/cpus/riscv/SllIRISC.java | 12 + .../compiler/nodes/cpus/riscv/SllRISC.java | 17 + .../compiler/nodes/cpus/riscv/SplitRISC.java | 55 + .../compiler/nodes/cpus/riscv/SraIRISC.java | 12 + .../compiler/nodes/cpus/riscv/SraRISC.java | 17 + .../compiler/nodes/cpus/riscv/SrlIRISC.java | 12 + .../compiler/nodes/cpus/riscv/SrlRISC.java | 17 + .../compiler/nodes/cpus/riscv/StoreRISC.java | 46 + .../compiler/nodes/cpus/riscv/SubFRISC.java | 17 + .../compiler/nodes/cpus/riscv/SubRISC.java | 16 + .../compiler/nodes/cpus/riscv/TFPRISC.java | 54 + .../compiler/nodes/cpus/riscv/UJmpRISC.java | 49 + .../compiler/nodes/cpus/riscv/XorIRISC.java | 12 + .../compiler/nodes/cpus/riscv/XorRISC.java | 16 + .../compiler/nodes/cpus/riscv/riscv.java | 557 ++++++++++ .../nodes/cpus/x86_64_v2/AddFMemX86.java | 44 + .../nodes/cpus/x86_64_v2/AddFX86.java | 33 + .../nodes/cpus/x86_64_v2/AddIX86.java | 13 + .../nodes/cpus/x86_64_v2/AddMemX86.java | 35 + .../compiler/nodes/cpus/x86_64_v2/AddX86.java | 11 + .../nodes/cpus/x86_64_v2/AndIX86.java | 11 + .../compiler/nodes/cpus/x86_64_v2/AndX86.java | 11 + .../nodes/cpus/x86_64_v2/CallEndX86.java | 23 + .../nodes/cpus/x86_64_v2/CallRX86.java | 33 + .../nodes/cpus/x86_64_v2/CallX86.java | 46 + .../nodes/cpus/x86_64_v2/CastX86.java | 16 + .../nodes/cpus/x86_64_v2/CmpFX86.java | 32 + .../nodes/cpus/x86_64_v2/CmpIX86.java | 49 + .../nodes/cpus/x86_64_v2/CmpMemX86.java | 43 + .../compiler/nodes/cpus/x86_64_v2/CmpX86.java | 18 + .../nodes/cpus/x86_64_v2/DivFX86.java | 34 + .../compiler/nodes/cpus/x86_64_v2/DivX86.java | 33 + .../compiler/nodes/cpus/x86_64_v2/FltX86.java | 47 + .../compiler/nodes/cpus/x86_64_v2/FunX86.java | 44 + .../nodes/cpus/x86_64_v2/I2f8X86.java | 31 + .../compiler/nodes/cpus/x86_64_v2/ImmX86.java | 37 + .../compiler/nodes/cpus/x86_64_v2/IntX86.java | 76 ++ .../compiler/nodes/cpus/x86_64_v2/JmpX86.java | 69 ++ .../compiler/nodes/cpus/x86_64_v2/LeaX86.java | 55 + .../nodes/cpus/x86_64_v2/LoadX86.java | 80 ++ .../nodes/cpus/x86_64_v2/MemAddX86.java | 39 + .../nodes/cpus/x86_64_v2/MemOpX86.java | 85 ++ .../nodes/cpus/x86_64_v2/MulFX86.java | 32 + .../nodes/cpus/x86_64_v2/MulIX86.java | 11 + .../compiler/nodes/cpus/x86_64_v2/MulX86.java | 29 + .../compiler/nodes/cpus/x86_64_v2/NewX86.java | 30 + .../compiler/nodes/cpus/x86_64_v2/NotX86.java | 39 + .../compiler/nodes/cpus/x86_64_v2/OrIX86.java | 11 + .../compiler/nodes/cpus/x86_64_v2/OrX86.java | 11 + .../nodes/cpus/x86_64_v2/ParmX86.java | 17 + .../nodes/cpus/x86_64_v2/ProjX86.java | 15 + .../compiler/nodes/cpus/x86_64_v2/RegX86.java | 25 + .../compiler/nodes/cpus/x86_64_v2/RetX86.java | 41 + .../nodes/cpus/x86_64_v2/SarIX86.java | 12 + .../compiler/nodes/cpus/x86_64_v2/SarX86.java | 24 + .../compiler/nodes/cpus/x86_64_v2/SetX86.java | 50 + .../nodes/cpus/x86_64_v2/ShlIX86.java | 11 + .../compiler/nodes/cpus/x86_64_v2/ShlX86.java | 24 + .../nodes/cpus/x86_64_v2/ShrIX86.java | 11 + .../compiler/nodes/cpus/x86_64_v2/ShrX86.java | 24 + .../nodes/cpus/x86_64_v2/SplitX86.java | 99 ++ .../nodes/cpus/x86_64_v2/StoreX86.java | 63 ++ .../nodes/cpus/x86_64_v2/SubFX86.java | 31 + .../compiler/nodes/cpus/x86_64_v2/SubX86.java | 10 + .../compiler/nodes/cpus/x86_64_v2/TFPX86.java | 66 ++ .../nodes/cpus/x86_64_v2/UJmpX86.java | 53 + .../nodes/cpus/x86_64_v2/XorIX86.java | 11 + .../compiler/nodes/cpus/x86_64_v2/XorX86.java | 11 + .../nodes/cpus/x86_64_v2/x86_64_v2.java | 678 ++++++++++++ .../ezlang/compiler/print/ASMPrinter.java | 254 +++++ .../compiler/print/GraphVisualizer.java | 287 +++++ .../ezlang/compiler/print/IRPrinter.java | 327 ++++++ .../ezlang/compiler/sontypes/Field.java | 74 ++ .../ezlang/compiler/sontypes/SONType.java | 235 +++++ .../compiler/sontypes/SONTypeFloat.java | 94 ++ .../compiler/sontypes/SONTypeFunPtr.java | 106 ++ .../compiler/sontypes/SONTypeInteger.java | 127 +++ .../ezlang/compiler/sontypes/SONTypeMem.java | 72 ++ .../compiler/sontypes/SONTypeMemPtr.java | 107 ++ .../ezlang/compiler/sontypes/SONTypeNil.java | 60 ++ .../ezlang/compiler/sontypes/SONTypePtr.java | 56 + .../ezlang/compiler/sontypes/SONTypeRPC.java | 97 ++ .../compiler/sontypes/SONTypeStruct.java | 259 +++++ .../compiler/sontypes/SONTypeTuple.java | 99 ++ .../ezlang/compiler/Main.java | 19 + .../ezlang/compiler/Simple.java | 304 ++++++ .../ezlang/compiler/TestSONTypes.java | 221 ++++ .../ezlang/types/Type.java | 1 + 246 files changed, 19378 insertions(+), 1 deletion(-) create mode 100644 seaofnodes/README.md create mode 100644 seaofnodes/pom.xml create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Ary.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Compiler.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/IterPeeps.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/SB.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Utils.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Var.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/BuildLRG.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/Coalesce.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/CodeGen.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/ElfExpr.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/ElfFile.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/Encoding.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/GlobalCodeMotion.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/IFG.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/LRG.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/ListScheduler.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/Machine.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RIPRelSize.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RegAlloc.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RegMask.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/AddFNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/AddNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/AndNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/BoolNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CFGNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CProjNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CallEndNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CallNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CalleeSaveNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CastNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ConstantNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CtrlNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/DivFNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/DivNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/FRefNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/FunNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/IfNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/LoadNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/LogicalNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/LoopNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/MachConcreteNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/MachNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/MemMergeNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/MemOpNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/MinusFNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/MinusNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/MulFNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/MulNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/MultiNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/NeverNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/NewNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/Node.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/NotNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/OrNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ParmNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/PhiNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ProjNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ReadOnlyNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/RegionNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ReturnNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/RoundF32Node.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/SarNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ScopeNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ShlNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ShrNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/SplitNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/StartNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/StopNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/StoreNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/StructNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/SubFNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/SubNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ToFloatNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/XCtrlNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/XorNode.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/AddARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/AddFARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/AddIARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/AndARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/AndIARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/AsrARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/AsrIARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/BranchARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/CallARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/CallEndARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/CallRRARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/CmpARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/CmpFARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/CmpIARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/DivARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/DivFARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/FloatARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/FunARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/I2F8ARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/IntARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LoadARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LslARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LslIARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LsrARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LsrIARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/MemOpARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/MulARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/MulFARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/NewARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/NotARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/OrARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/OrIARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/ParmARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/ProjARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/RetARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/SetARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/SplitARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/StoreARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/SubARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/SubFARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/SubIARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/TFPARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/UJmpARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/XorARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/XorIARM.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/arm.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AUIPC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AddFRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AddIRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AddRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AndIRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AndRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/BranchRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/CallEndRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/CallRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/CallRRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/CastRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/DivFRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/DivRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/FltRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/FunRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/I2F8RISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/ImmRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/IntRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/LUI.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/LoadRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/MemOpRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/MulFRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/MulRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/NewRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/NotRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/OrIRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/OrRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/ParmRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/ProjRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/RetRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SetFRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SetIRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SetRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SllIRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SllRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SplitRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SraIRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SraRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SrlIRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SrlRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/StoreRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SubFRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SubRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/TFPRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/UJmpRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/XorIRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/XorRISC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/riscv.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddFMemX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddFX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddIX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddMemX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AndIX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AndX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/CallEndX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/CallRX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/CallX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/CastX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/CmpFX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/CmpIX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/CmpMemX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/CmpX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/DivFX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/DivX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/FltX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/FunX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/I2f8X86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ImmX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/IntX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/JmpX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/LeaX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/LoadX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MemAddX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MemOpX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MulFX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MulIX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MulX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/NewX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/NotX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/OrIX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/OrX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ParmX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ProjX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/RegX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/RetX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SarIX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SarX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SetX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShlIX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShlX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShrIX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShrX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SplitX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/StoreX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SubFX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SubX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/TFPX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/UJmpX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/XorIX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/XorX86.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/x86_64_v2.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/print/ASMPrinter.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/print/GraphVisualizer.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/print/IRPrinter.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/Field.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONType.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeFloat.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeFunPtr.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeInteger.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeMem.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeMemPtr.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeNil.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypePtr.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeRPC.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeStruct.java create mode 100644 seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeTuple.java create mode 100644 seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/Main.java create mode 100644 seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/Simple.java create mode 100644 seaofnodes/src/test/java/com/compilerprogramming/ezlang/compiler/TestSONTypes.java diff --git a/README.md b/README.md index 913da06..d9783b2 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ The project is under development and subject to change. At this point in time, w * [stackvm](./stackvm/README.md) - a compiler that generates IR for a stack based virtual machine * [registervm](./registervm/README.md) - a compiler that generates a so called three-address IR and an interpreter that can execute the IR * [optvm](./optvm/README.md) - an optimizing compiler (WIP) that supports SSA. -* seaofnodes - a compiler that will generate Sea of Nodes IR (planned) +* [seaofnodes](./seaofnodes/README.md) - a compiler that will generate Sea of Nodes IR, using SoN backend from [Simple](https://github.com/SeaOfNodes/Simple). ## How can you contribute? diff --git a/pom.xml b/pom.xml index 4ea1d08..f2eb179 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,7 @@ stackvm registervm optvm + seaofnodes diff --git a/seaofnodes/README.md b/seaofnodes/README.md new file mode 100644 index 0000000..668c18e --- /dev/null +++ b/seaofnodes/README.md @@ -0,0 +1,6 @@ +## Sea of Nodes IR + +This module uses the [Simple](https://github.com/SeaOfNodes/Simple) project as a Sea of Nodes +backend. We translate from the EeZee AST to Sea of Nodes IR. + +For copyright info on Simple project please see above website. \ No newline at end of file diff --git a/seaofnodes/pom.xml b/seaofnodes/pom.xml new file mode 100644 index 0000000..e2fef93 --- /dev/null +++ b/seaofnodes/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + com.compilerprogramming.ezlang + compilercraft + 1.0 + + seaofnodes + jar + SeaOfNodes + + + com.compilerprogramming.ezlang + parser + 1.0 + + + com.compilerprogramming.ezlang + types + 1.0 + + + com.compilerprogramming.ezlang + semantic + 1.0 + + + \ No newline at end of file diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Ary.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Ary.java new file mode 100644 index 0000000..9a217e0 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Ary.java @@ -0,0 +1,228 @@ +package com.compilerprogramming.ezlang.compiler; + +import java.lang.StringBuilder; +import java.lang.reflect.Array; +import java.util.*; + +// ArrayList with saner syntax +public class Ary extends AbstractList implements List { + public E[] _es; + public int _len; + + public Ary(E[] es) { this(es,es.length); } + public Ary(E[] es, int len) { if( es.length==0 ) es=Arrays.copyOf(es,1); _es=es; _len=len; } + @SuppressWarnings("unchecked") + public Ary(Class clazz) { this((E[]) Array.newInstance(clazz, 1),0); } + + /** @return list is empty */ + public boolean isEmpty() { return _len==0; } + /** @return active list length */ + public int len() { return _len; } + public int size() { return len(); } + + /** @param i element index + * @return element being returned; throws if OOB + * @exception AIOOBE if !(0 <= i < _len) + */ + public E get( int i ) { return at(i); } + public E at( int i ) { + range_check(i); + return _es[i]; + } + public E atX( int i ) { + if( i >= _len ) return null; + return _es[i]; + } + + /** @return last element */ + public E last( ) { return at(_len-1); } + + /** Add element in amortized constant time + * @param e Element to add at end of list + **/ + public E push( E e ) { + if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1)); + return (_es[_len++] = e); + } + /** @return remove and return last element */ + public E pop( ) { + return _len==0 ? null : _es[--_len]; + } + @Override public E removeLast() { return pop(); } + + + /** Add element in amortized constant time + * @param e Element to add at end of list + * @return true */ + public boolean add( E e ) { + if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1)); + _es[_len++] = e; + return true; + } + + /** Fast, constant-time, element removal. Does not preserve order + * @param i element to be removed + * @return element removed */ + public E del( int i ) { + range_check(i); + E tmp = _es[i]; + _es[i]=_es[--_len]; + return tmp; + } + + /** Slow, linear-time, element removal. Preserves order. + * @param i element to be removed + * @return element removed */ + @Override public E remove( int i ) { + range_check(i); + E e = _es[i]; + System.arraycopy(_es,i+1,_es,i,(--_len)-i); + return e; + } + + /** Slow, linear-time, element insertion. Preserves order. + * @param i where to insert + * @return element inserted */ + public E insert( E e, int i ) { + setLen(_len+1); + System.arraycopy(_es,i,_es,i+1,_len-i); + _es[i] = e; + return e; + } + + + /** Remove all elements */ + public void clear( ) { _len=0; } + + /** Set existing element + * @param i element to set + * @param e value to set + * @return old value + * @exception AIOOBE if !(0 <= i < _len) + */ + public E set( int i, E e ) { + range_check(i); + E old = _es[i]; + _es[i] = e; + return old; + } + + /** Set existing element + * @param i element to set + * @param e value to set + * @return old value + * @exception AIOOBE if !(0 <= i < _len) + */ + public E setX( int i, E e ) { + if( i >= _len ) { + while( i >= _es.length ) _es = Arrays.copyOf( _es, _es.length << 1 ); + _len = i+1; + } + E old = _es[i]; + _es[i] = e; + return old; + } + + public Ary setLen( int len ) { + if( len > _len ) + while( len>= _es.length ) _es = Arrays.copyOf(_es,_es.length<<1); + _len = len; + while( _es.length > (len<<1) ) // Shrink if hugely too large + _es = Arrays.copyOf(_es,_es.length>>1); + Arrays.fill(_es,len,_es.length,null); + return this; + } + + public E swap( int i, int j ) { + range_check(i); + range_check(j); + E tmp = _es[i]; + if( i==j ) return tmp; + _es[i] = _es[j]; + _es[j] = tmp; + return tmp; + } + + + /** @param c Collection to be added */ + public boolean addAll( Collection c ) { + if( c==null ) return false; + for( E e : c ) add(e); + return true; + } + public Ary addAll( Iterable it ) { for( E e : it ) add(e); return this; } + + /** @param es Array to be added */ + public Ary addAll( F[] es ) { + if( es==null || es.length==0 ) return this; + while( _len+es.length > _es.length ) _es = Arrays.copyOf(_es,_es.length<<1); + System.arraycopy(es,0,_es,_len,es.length); + _len += es.length; + return this; + } + + /** @param c Collection to be added */ + public Ary addAll( Ary c ) { + if( c==null || c._len==0 ) return this; + while( _len+c._len > _es.length ) _es = Arrays.copyOf(_es,_es.length<<1); + System.arraycopy(c._es,0,_es,_len,c._len); + _len += c._len; + return this; + } + + /** @return compact array version */ + public E[] asAry() { return Arrays.copyOf(_es,_len); } + + /** Find the first matching element using ==, or -1 if none. Note that + * most del calls shuffle the list, so the first element might be random. + * @param e Element to find + * @return index of first matching element, or -1 if none */ + public int find( E e ) { + for( int i=0; i<_len; i++ ) if( _es[i]==e ) return i; + return -1; + } + + + /** @return an iterator */ + @Override public Iterator iterator() { return new Iter(); } + private class Iter implements Iterator { + int _i=0; + @Override public boolean hasNext() { return _i<_len; } + @Override public E next() { return _es[_i++]; } + } + + @Override public String toString() { + StringBuilder sb = new StringBuilder().append('{'); + for( int i=0; i<_len; i++ ) { + if( i>0 ) sb.append(','); + if( _es[i] != null ) sb.append(_es[i].toString()); + } + return sb.append('}').toString(); + } + + private void range_check( int i ) { + if( i < 0 || i>=_len ) + throw new ArrayIndexOutOfBoundsException(""+i+" >= "+_len); + } + + // Note that the hashCode() and equals() are not invariant to changes in the + // underlying array. If the hashCode() is used (e.g., inserting into a + // HashMap) and the then the array changes, the hashCode() will change also. + @Override public boolean equals( Object o ) { + if( this==o ) return true; + if( !(o instanceof Ary ary) ) return false; + if( _len != ary._len ) return false; + if( _es == ary._es ) return true; + for( int i=0; i<_len; i++ ) + if( !(_es[i]==null ? (ary._es[i] == null) : _es[i].equals(ary._es[i])) ) + return false; + return true; + } + @Override public int hashCode( ) { + int sum=_len; + for( int i=0; i<_len; i++ ) + sum += _es[i]==null ? 0 : _es[i].hashCode(); + return sum; + } + +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Compiler.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Compiler.java new file mode 100644 index 0000000..4798830 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Compiler.java @@ -0,0 +1,983 @@ +package com.compilerprogramming.ezlang.compiler; + +import com.compilerprogramming.ezlang.compiler.codegen.CodeGen; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import com.compilerprogramming.ezlang.compiler.print.GraphVisualizer; +import com.compilerprogramming.ezlang.compiler.sontypes.*; +import com.compilerprogramming.ezlang.exceptions.CompilerException; +import com.compilerprogramming.ezlang.lexer.Lexer; +import com.compilerprogramming.ezlang.parser.AST; +import com.compilerprogramming.ezlang.parser.Parser; +import com.compilerprogramming.ezlang.semantic.SemaAssignTypes; +import com.compilerprogramming.ezlang.semantic.SemaDefineTypes; +import com.compilerprogramming.ezlang.types.Scope; +import com.compilerprogramming.ezlang.types.Symbol; +import com.compilerprogramming.ezlang.types.Type; +import com.compilerprogramming.ezlang.types.TypeDictionary; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; + +public class Compiler { + + public static ConstantNode ZERO; // Very common node, cached here + public static ConstantNode NIL; // Very common node, cached here + public static XCtrlNode XCTRL; // Very common node, cached here + + TypeDictionary typeDictionary; + + // Compile driver + public CodeGen _code; + + /** + * Current ScopeNode - ScopeNodes change as we parse code, but at any point of time + * there is one current ScopeNode. The reason the current ScopeNode can change is to do with how + * we handle branching. + *

+ * Each ScopeNode contains a stack of lexical scopes, each scope is a symbol table that binds + * variable names to Nodes. The top of this stack represents current scope. + *

+ * We keep a list of all ScopeNodes so that we can show them in graphs + */ + public ScopeNode _scope; + + /** + * We clone ScopeNodes when control flows branch; it is useful to have + * a list of all active ScopeNodes for purposes of visualization of the SoN graph + */ + public final Stack _xScopes = new Stack<>(); + + ScopeNode _continueScope; + ScopeNode _breakScope; // Merge all the while-breaks here + FunNode _fun; // Current function being parsed + + + // Mapping from a type name to a Type. The string name matches + // `type.str()` call. No TypeMemPtrs are in here, because Simple does not + // have C-style '*ptr' references. + public static HashMap TYPES = new HashMap<>(); + + private ArrayList ctorStack = new ArrayList<>(); + + public Compiler(CodeGen codeGen, SONTypeInteger arg) { + this._code = codeGen; + } + + public void parse() { + this.typeDictionary = createAST(_code._src); + Map types = new HashMap<>(); + populateDefaultTypes(types); + populateTypes(types); + TYPES.putAll(types); + ZERO = con(SONTypeInteger.ZERO).keep(); + NIL = con(SONType.NIL).keep(); + XCTRL= new XCtrlNode().peephole().keep(); + processFunctions(); + } + + public TypeDictionary createAST(String src) { + Parser parser = new Parser(); + var program = parser.parse(new Lexer(src)); + var typeDict = new TypeDictionary(); + var sema = new SemaDefineTypes(typeDict); + sema.analyze(program); + var sema2 = new SemaAssignTypes(typeDict); + sema2.analyze(program); + return typeDict; + } + + + private void populateDefaultTypes(Map types) { + types.put(typeDictionary.INT.name(), SONTypeInteger.BOT); + for (SONType t: SONType.gather()) { + types.put(t.str(), t); + } + } + + private void populateTypes(Map structTypes) { + // First process struct types + for (var symbol: typeDictionary.getLocalSymbols()) { + if (symbol instanceof Symbol.TypeSymbol typeSymbol) { + if (typeSymbol.type instanceof Type.TypeStruct typeStruct) { + createSONStructType(structTypes,typeSymbol.name, typeStruct); + } + else if (typeSymbol.type instanceof Type.TypeArray typeArray) { + getSONType(structTypes, typeArray); + } + } + } + // Next process function types + for (var symbol: typeDictionary.getLocalSymbols()) { + if (symbol instanceof Symbol.FunctionTypeSymbol functionTypeSymbol) { + createFunctionType(structTypes, functionTypeSymbol); + } + } + } + + private void processFunctions() { + for (var symbol: typeDictionary.getLocalSymbols()) { + if (symbol instanceof Symbol.FunctionTypeSymbol functionTypeSymbol) { + generateFunction(functionTypeSymbol); + } + } + } + + private void createFunctionType(Map structTypes, Symbol.FunctionTypeSymbol functionSymbol) { + Type.TypeFunction functionType = (Type.TypeFunction) functionSymbol.type; + Ary params = new Ary<>(SONType.class); + for (var symbol: functionType.args) { + if (symbol instanceof Symbol.ParameterSymbol parameterSymbol) { + SONType paramType = getSONType(structTypes, parameterSymbol.type); + params.push(paramType); + } + } + var retType = getSONType(structTypes, functionType.returnType); + SONTypeFunPtr tfp = _code.makeFun2(new SONTypeTuple(params.asAry()),retType); + structTypes.put(functionSymbol.name, tfp); + } + + private void createSONStructType(Map structTypes, String typeName, Type.TypeStruct typeStruct) { + Ary fs = new Ary<>(Field.class); + for (int i = 0; i < typeStruct.numFields(); i++) { + String name = typeStruct.getFieldName(i); + Type type = typeStruct.getField(name); // FIXME + fs.push(new Field(name,getSONType(structTypes,type),_code.getALIAS(),false)); + } + SONType fref = structTypes.get(typeName); + if (fref != null) { + if (fref instanceof SONTypeStruct ts) { + ts._fields = fs.asAry(); + } + else throw new CompilerException(""); + } + else { + structTypes.put(typeName, new SONTypeStruct(typeName, fs.asAry())); + } +// SONTypeStruct ts = SONTypeStruct.make(typeName, fs.asAry()); +// TYPES.put(typeName, SONTypeMemPtr.make(ts)); + } + + private String getSONTypeName(Type type) { + if (type instanceof Type.TypeFunction typeFunction) { + return typeFunction.name; + } + else if (type instanceof Type.TypeArray typeArray) { + return typeArray.name(); + } + else if (type instanceof Type.TypeStruct typeStruct) { + return "*" + typeStruct.name; + } + else if (type instanceof Type.TypeInteger || + type instanceof Type.TypeVoid) { + return SONTypeInteger.BOT.str(); + } + else if (type instanceof Type.TypeNull) { + return SONTypeNil.NIL.str(); + } + else if (type instanceof Type.TypeNullable typeNullable) { + return getSONTypeName(typeNullable.baseType)+"?"; + } + else throw new CompilerException("Not yet implemented " + type.name()); + } + + private SONType getSONType(Map structTypes, Type type) { + String tname = getSONTypeName(type); + SONType t = structTypes.get(tname); + if (t != null) return t; + if (type instanceof Type.TypeStruct) { + SONType existing = structTypes.get(type.name); + SONTypeStruct ts; + if (existing instanceof SONTypeStruct tsExisting) + ts = tsExisting; + else { + ts = new SONTypeStruct(type.name, new Field[0]); + structTypes.put(ts.str(), ts); + } + SONTypeMemPtr ptr = new SONTypeMemPtr((byte)2,ts); + if (type.name().equals(ts.str())) { + structTypes.put(ptr.str(), ptr); + return ptr; + } + else throw new CompilerException("Unexpected error"); + } + else if (type instanceof Type.TypeArray typeArray) { + SONType elementType = getSONType(structTypes,typeArray.getElementType()); + SONTypeStruct ts = null; + SONType existing = structTypes.get(typeArray.name()); + if (existing instanceof SONTypeStruct tsExisting) + ts = tsExisting; + else { + ts = SONTypeStruct.makeArray(SONTypeInteger.U32, _code.getALIAS(), elementType, _code.getALIAS()); + structTypes.put(ts.str(), ts); + } + SONTypeMemPtr ptr = new SONTypeMemPtr((byte)2,ts); + structTypes.put(typeArray.name(), ptr); // Array type name is not same as ptr str() + return ptr; + } + else if (type instanceof Type.TypeNullable typeNullable) { + SONType baseType = getSONType(structTypes,typeNullable.baseType); + SONTypeMemPtr ptr = null; + if (baseType instanceof SONTypeMemPtr ptr1) { + if (ptr1.nullable()) + ptr = ptr1; + else + ptr = new SONTypeMemPtr((byte)3,ptr1._obj); + } + else + ptr = new SONTypeMemPtr((byte)2,(SONTypeStruct) baseType); + structTypes.put(ptr.str(), ptr); + return ptr; + } + else if (type instanceof Type.TypeVoid) { + return structTypes.get("Int"); // Only allowed in return types + } + throw new CompilerException("Count not find type " + type.name()); + } + + private Node ctrl() { return _scope.ctrl(); } + private N ctrl(N n) { return _scope.ctrl(n); } + + // TODO because first two slots are MEM and RPC + private int REGNUM = 2; + private void setVarIds(Scope scope, ScopeNode scopeNode, FunNode fun) { + for (Symbol symbol: scope.getLocalSymbols()) { + if (symbol instanceof Symbol.VarSymbol varSymbol) { + varSymbol.regNumber = REGNUM++; + String sonTypeName = getSONTypeName(varSymbol.type); + SONType sonType = TYPES.get(sonTypeName); + if (sonType == null) throw new CompilerException("Unknown SON Type "+sonTypeName); + Node init = null; + + if (varSymbol instanceof Symbol.ParameterSymbol) { + init = new ParmNode(makeVarName(varSymbol),varSymbol.regNumber,sonType,fun,con(sonType)).peephole(); + } + scopeNode.define(makeVarName(varSymbol), sonType, false, init); + } + } + for (Scope childScope: scope.children) { + setVarIds(childScope, scopeNode, fun); + } + } + + private void generateFunction(Symbol.FunctionTypeSymbol functionTypeSymbol) { + _scope = new ScopeNode(); + _scope.define(ScopeNode.CTRL, SONType.CONTROL , false, null); + _scope.define(ScopeNode.MEM0, SONTypeMem.BOT , false, null); + + ctrl(XCTRL); + _scope.mem(new MemMergeNode(false)); + + var funType = (SONTypeFunPtr) TYPES.get(functionTypeSymbol.name); + if (funType == null) throw new CompilerException("Function " + functionTypeSymbol.name + " not found"); + + // Parse whole program, as-if function header "{ int arg -> body }" + generateFunctionBody(functionTypeSymbol,funType); + _code.link(funType)._name = functionTypeSymbol.name; + + // Clean up and reset + //_xScopes.pop(); + _scope.kill(); +// for( StructNode init : INITS.values() ) +// init.unkeep().kill(); +// INITS.clear(); + _code._stop.peephole(); +// if( show ) showGraph(); + showGraph(); + } + + /** + * Dumps out the node graph + * @return {@code null} + */ + Node showGraph() { + System.out.println(new GraphVisualizer().generateDotOutput(_code._stop,_scope,_xScopes)); + return null; + } + + /** + * Parses a function body, assuming the header is parsed. + */ + private ReturnNode generateFunctionBody(Symbol.FunctionTypeSymbol functionTypeSymbol,SONTypeFunPtr sig) { + // Stack parser state on the local Java stack, and unstack it later + Node oldctrl = ctrl().keep(); + Node oldmem = _scope.mem().keep(); + FunNode oldfun = _fun; + ScopeNode breakScope = _breakScope; _breakScope = null; + ScopeNode continueScope = _continueScope; _continueScope = null; + + FunNode fun = _fun = (FunNode)peep(new FunNode(sig,null,_code._start)); + // Once the function header is available, install in linker table - + // allowing recursive functions. Linker matches on declared args and + // exact fidx, and ignores the return (because the fidx will only match + // the exact single function). + _code.link(fun); + + Node rpc = new ParmNode("$rpc",0,SONTypeRPC.BOT,fun,con(SONTypeRPC.BOT)).peephole(); + + // Build a multi-exit return point for all function returns + RegionNode r = new RegionNode(null,null).init(); + assert r.inProgress(); + PhiNode rmem = new PhiNode(ScopeNode.MEM0,SONTypeMem.BOT,r,null).init(); + PhiNode rrez = new PhiNode(ScopeNode.ARG0,SONType.BOTTOM,r,null).init(); + ReturnNode ret = new ReturnNode(r, rmem, rrez, rpc, fun).init(); + fun.setRet(ret); + assert ret.inProgress(); + _code._stop.addDef(ret); + + // Pre-call the function from Start, with worse-case arguments. This + // represents all the future, yet-to-be-parsed functions calls and + // external calls. + _scope.push(ScopeNode.Kind.Function); + ctrl(fun); // Scope control from function + // Private mem alias tracking per function + MemMergeNode mem = new MemMergeNode(true); + mem.addDef(null); // Alias#0 + mem.addDef(new ParmNode(ScopeNode.MEM0,1,SONTypeMem.BOT,fun,con(SONTypeMem.BOT)).peephole()); // All aliases + _scope.mem(mem); + // All args, "as-if" called externally + AST.FuncDecl funcDecl = (AST.FuncDecl) functionTypeSymbol.functionDecl; + setVarIds(funcDecl.scope,_scope,fun); + + // Parse the body + Node last = compileStatement(funcDecl.block); + + // Last expression is the return + if( ctrl()._type==SONType.CONTROL ) + fun.addReturn(ctrl(), _scope.mem().merge(), last); + + // Pop off the inProgress node on the multi-exit Region merge + assert r.inProgress(); + r ._inputs.pop(); + rmem._inputs.pop(); + rrez._inputs.pop(); + assert !r.inProgress(); + + // Force peeps, which have been avoided due to inProgress + ret.setDef(1,rmem.peephole()); + ret.setDef(2,rrez.peephole()); + ret.setDef(0,r.peephole()); + ret = (ReturnNode)ret.peephole(); + + // Function scope ends + _scope.pop(); + _fun = oldfun; + _breakScope = breakScope; + _continueScope = continueScope; + // Reset control and memory to pre-function parsing days + ctrl(oldctrl.unkeep()); + _scope.mem(oldmem.unkeep()); + + return ret; + } + + private Node compileExpr(AST.Expr expr) { + switch (expr) { + case AST.LiteralExpr constantExpr -> { + return compileConstantExpr(constantExpr); + } + case AST.BinaryExpr binaryExpr -> { + return compileBinaryExpr(binaryExpr); + } + case AST.UnaryExpr unaryExpr -> { + return compileUnaryExpr(unaryExpr); + } + case AST.NameExpr symbolExpr -> { + return compileSymbolExpr(symbolExpr); + } + case AST.NewExpr newExpr -> { + return compileNewExpr(newExpr); + } + case AST.InitExpr initExpr -> { + return compileInitExpr(initExpr); + } + case AST.ArrayLoadExpr arrayLoadExpr -> { + return compileArrayIndexExpr(arrayLoadExpr); + } + case AST.ArrayStoreExpr arrayStoreExpr -> { + return compileArrayStoreExpr(arrayStoreExpr); + } + case AST.GetFieldExpr getFieldExpr -> { + return compileFieldExpr(getFieldExpr); + } + case AST.SetFieldExpr setFieldExpr -> { + return compileSetFieldExpr(setFieldExpr); + } + case AST.CallExpr callExpr -> { + return compileCallExpr(callExpr); + } + default -> throw new IllegalStateException("Unexpected value: " + expr); + } + } + + private Node compileFieldExpr(AST.GetFieldExpr getFieldExpr) { + Node objPtr = compileExpr(getFieldExpr.object).keep(); + // Sanity check expr for being a reference + if( !(objPtr._type instanceof SONTypeMemPtr ptr) ) { + throw new CompilerException("Unexpected type " + objPtr._type.str()); + } + String name = getFieldExpr.fieldName; + SONTypeStruct base = ptr._obj; + int fidx = base.find(name); + if( fidx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); + Field f = base._fields[fidx]; // Field from field index + SONType tf = f._type; + // Field offset; fixed for structs + Node off = con(base.offset(fidx)).keep(); + Node load = new LoadNode(name, f._alias, tf, memAlias(f._alias), objPtr, off); + // Arrays include control, as a proxy for a safety range check + // Structs don't need this; they only need a NPE check which is + // done via the type system. + load = peep(load); + objPtr.unkeep(); + off.unkeep(); + return load; + } + + private Node compileSetFieldExpr(AST.SetFieldExpr setFieldExpr) { + Node objPtr; + if (setFieldExpr instanceof AST.InitFieldExpr) + objPtr = ctorStack.getLast(); + else + objPtr = compileExpr(setFieldExpr.object); + // Sanity check expr for being a reference + if( !(objPtr._type instanceof SONTypeMemPtr ptr) ) { + throw new CompilerException("Unexpected type " + objPtr._type.str()); + } + Node val = compileExpr(setFieldExpr.value).keep(); + String name = setFieldExpr.fieldName; + SONTypeStruct base = ptr._obj; + int fidx = base.find(name); + if( fidx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); + Field f = base._fields[fidx]; // Field from field index + SONType tf = f._type; + Node mem = memAlias(f._alias); + Node st = new StoreNode(f._fname,f._alias,tf,mem,objPtr,con(base.offset(fidx)),val.unkeep(),true).peephole(); + memAlias(f._alias,st); + return objPtr; + } + + private Node compileArrayIndexExpr(AST.ArrayLoadExpr arrayLoadExpr) { + Node objPtr = compileExpr(arrayLoadExpr.array).keep(); + // Sanity check expr for being a reference + if( !(objPtr._type instanceof SONTypeMemPtr ptr) ) { + throw new CompilerException("Unexpected type " + objPtr._type.str()); + } + String name = "[]"; + SONTypeStruct base = ptr._obj; + Node index = compileExpr(arrayLoadExpr.expr).keep(); + Node off = peep(new AddNode(con(base.aryBase()),peep(new ShlNode(index.unkeep(),con(base.aryScale()))))).keep(); + int fidx = base.find(name); + if( fidx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); + Field f = base._fields[fidx]; // Field from field index + SONType tf = f._type; + Node load = new LoadNode(name, f._alias, tf, memAlias(f._alias), objPtr, off); + // Arrays include control, as a proxy for a safety range check + // Structs don't need this; they only need a NPE check which is + // done via the type system. + load.setDef(0,ctrl()); + load = peep(load); + objPtr.unkeep(); + off.unkeep(); + return load; + } + + private Node compileArrayStoreExpr(AST.ArrayStoreExpr arrayStoreExpr) { + Node objPtr; + if (arrayStoreExpr instanceof AST.ArrayInitExpr) + objPtr = ctorStack.getLast(); + else + objPtr = compileExpr(arrayStoreExpr.array); + // Sanity check expr for being a reference + if( !(objPtr._type instanceof SONTypeMemPtr ptr) ) { + throw new CompilerException("Unexpected type " + objPtr._type.str()); + } + String name = "[]"; + SONTypeStruct base = ptr._obj; + Node index = compileExpr(arrayStoreExpr.expr).keep(); + Node off = peep(new AddNode(con(base.aryBase()),peep(new ShlNode(index.unkeep(),con(base.aryScale()))))).keep(); + Node val = compileExpr(arrayStoreExpr.value).keep(); + int fidx = base.find(name); + if( fidx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); + Field f = base._fields[fidx]; // Field from field index + SONType tf = f._type; + Node mem = memAlias(f._alias); + Node st = new StoreNode(f._fname,f._alias,tf,mem,objPtr,off.unkeep(),val.unkeep(),true).peephole(); + memAlias(f._alias,st); + return objPtr; + } + + private Node compileInitExpr(AST.InitExpr initExpr) { + Node objPtr = compileExpr(initExpr.newExpr).keep(); + ctorStack.add(objPtr); + // Sanity check expr for being a reference + if( !(objPtr._type instanceof SONTypeMemPtr ptr) ) { + throw new CompilerException("Unexpected type " + objPtr._type.str()); + } + if (initExpr.initExprList != null && !initExpr.initExprList.isEmpty()) { + for (AST.Expr expr : initExpr.initExprList) { + compileExpr(expr); + } + } + ctorStack.removeLast(); + return objPtr.unkeep(); + } + + private Node newArray(SONTypeStruct ary, Node len) { + int base = ary.aryBase (); + int scale= ary.aryScale(); + Node size = peep(new AddNode(con(base),peep(new ShlNode(len.keep(),con(scale))))); + return newStruct(ary,size); + } + /** + * Return a NewNode initialized memory. + * @param obj is the declared type, with GLB fields + */ + private Node newStruct( SONTypeStruct obj, Node size) { + Field[] fs = obj._fields; + if( fs==null ) + throw error("Unknown struct type '" + obj._name + "'"); + int len = fs.length; + Node[] ns = new Node[2+len]; + ns[0] = ctrl(); // Control in slot 0 + // Total allocated length in bytes + ns[1] = size; + // Memory aliases for every field + for( int i = 0; i < len; i++ ) + ns[2+i] = memAlias(fs[i]._alias); + // FIXME make + Node nnn = new NewNode(SONTypeMemPtr.make(obj), ns).peephole().keep(); + for( int i = 0; i < len; i++ ) + memAlias(fs[i]._alias, new ProjNode(nnn,i+2,memName(fs[i]._alias)).peephole()); + return new ProjNode(nnn.unkeep(),1,obj._name).peephole(); + } + + private Node compileNewExpr(AST.NewExpr newExpr) { + Type type = newExpr.type; + if (type instanceof Type.TypeArray typeArray) { + SONTypeMemPtr tarray = (SONTypeMemPtr) TYPES.get(getSONTypeName(typeArray)); + return newArray(tarray._obj,ZERO); + } + else if (type instanceof Type.TypeStruct typeStruct) { + SONTypeStruct tstruct = (SONTypeStruct) TYPES.get(typeStruct.name()); + return newStruct(tstruct,con(tstruct.offset(tstruct._fields.length))); + } + else + throw new CompilerException("Unexpected type: " + type); + } + + + private Node compileUnaryExpr(AST.UnaryExpr unaryExpr) { + String opCode = unaryExpr.op.str; + switch (opCode) { + case "-": return peep(new MinusNode(compileExpr(unaryExpr.expr)).widen()); + // Maybe below we should explicitly set Int + case "!": return peep(new NotNode(compileExpr(unaryExpr.expr))); + default: throw new CompilerException("Invalid unary op"); + } + } + + private Node compileBinaryExpr(AST.BinaryExpr binaryExpr) { + String opCode = binaryExpr.op.str; + int idx=0; + boolean negate = false; + var lhs = compileExpr(binaryExpr.expr1); + switch (opCode) { + case "&&": + case "||": + throw new CompilerException("Not yet implemented"); + case "==": + idx=2; lhs = new BoolNode.EQ(lhs, null); + break; + case "!=": + idx=2; lhs = new BoolNode.EQ(lhs, null); negate=true; + break; + case "<=": + idx=2; lhs = new BoolNode.LE(lhs, null); + break; + case "<": + idx=2; lhs = new BoolNode.LT(lhs, null); + break; + case ">=": + idx=1; lhs = new BoolNode.LE(null, lhs); + break; + case ">": + idx=1; lhs = new BoolNode.LT(null, lhs); + break; + case "+": + idx=2; lhs = new AddNode(lhs,null); + break; + case "-": + idx=2; lhs = new SubNode(lhs,null); + break; + case "*": + idx=2; lhs = new MulNode(lhs,null); + break; + case "/": + idx=2; lhs = new DivNode(lhs,null); + break; + default: + throw new CompilerException("Not yet implemented"); + } + lhs.setDef(idx,compileExpr(binaryExpr.expr2)); + lhs = peep(lhs.widen()); + if( negate ) // Extra negate for != + lhs = peep(new NotNode(lhs)); + return lhs; + } + + private Node compileConstantExpr(AST.LiteralExpr constantExpr) { + if (constantExpr.type instanceof Type.TypeInteger) + return con(constantExpr.value.num.intValue()); + else if (constantExpr.type instanceof Type.TypeNull) + return NIL; + else throw new CompilerException("Invalid constant type"); + } + + private Node compileSymbolExpr(AST.NameExpr symbolExpr) { + if (symbolExpr.type instanceof Type.TypeFunction functionType) + return con(TYPES.get(functionType.name)); + else { + Symbol.VarSymbol varSymbol = (Symbol.VarSymbol) symbolExpr.symbol; + Var v = _scope.lookup(makeVarName(varSymbol)); + if (v == null) throw new CompilerException("Unknown variable name: " + varSymbol.name); + Node n = _scope.in(v._idx); + if (n == null) throw new CompilerException("Variable " + varSymbol.name + " not defined"); + return n; + } + } + + /** + * Parse function call arguments; caller will parse the surrounding "()" + *

+     *   ( arg* )
+     * 
+ */ + private Node compileCallExpr(AST.CallExpr callExpr) { + Node expr = compileExpr(callExpr.callee); + if( expr._type == SONType.NIL ) + throw error("Calling a null function pointer"); + if( !(expr instanceof FRefNode) && !expr._type.isa(SONTypeFunPtr.BOT) ) + throw error("Expected a function but got "+expr._type.glb().str()); + expr.keep(); // Keep while parsing args + + Ary args = new Ary(Node.class); + args.push(null); // Space for ctrl,mem + args.push(null); + for (AST.Expr e: callExpr.args) { + Node arg = compileExpr(e); + args.push(arg.keep()); + } + // Control & memory after parsing args + args.set(0,ctrl().keep()); + args.set(1,_scope.mem().merge().keep()); + args.push(expr); // Function pointer + // Unkeep them all + for( Node arg : args ) + arg.unkeep(); + // Dead into the call? Skip all the node gen + if( ctrl()._type == SONType.XCONTROL ) { + for( Node arg : args ) + if( arg.isUnused() ) + arg.kill(); + return con(SONType.TOP); + } + + // Into the call + CallNode call = (CallNode)new CallNode(args.asAry()).peephole(); + + // Post-call setup + CallEndNode cend = (CallEndNode)new CallEndNode(call).peephole(); + // Control from CallEnd + ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole()); + // Memory from CallEnd + MemMergeNode mem = new MemMergeNode(true); + mem.addDef(null); // Alias#0 + mem.addDef(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); + _scope.mem(mem); + // Call result + return new ProjNode(cend,2,"#2").peephole(); + } + + private String makeVarName(Symbol.VarSymbol varSymbol) { + return varSymbol.name + "$" + varSymbol.regNumber; + } + + private Node compileStatement(AST.Stmt statement) { + switch (statement) { + case AST.BlockStmt blockStmt -> { + return compileBlock(blockStmt); + } + case AST.VarStmt letStmt -> { + return compileLet(letStmt); + } + case AST.VarDeclStmt varDeclStmt -> {} + case AST.IfElseStmt ifElseStmt -> { + return compileIf(ifElseStmt); + } + case AST.WhileStmt whileStmt -> { + return compileWhile(whileStmt); + } + case AST.ContinueStmt continueStmt -> { + return compileContinue(continueStmt); + } + case AST.BreakStmt breakStmt -> { + return compileBreak(breakStmt); + } + case AST.ReturnStmt returnStmt -> { + return compileReturn(returnStmt); + } + case AST.AssignStmt assignStmt -> { + return compileAssign(assignStmt); + } + case AST.ExprStmt exprStmt -> { + return compileExprStmt(exprStmt); + } + default -> throw new IllegalStateException("Unexpected value: " + statement); + } + throw new CompilerException("Not yet implemented"); + } + + private ScopeNode jumpTo(ScopeNode toScope) { + ScopeNode cur = _scope.dup(); + ctrl(XCTRL); // Kill current scope + // Prune nested lexical scopes that have depth > than the loop head + // We use _breakScope as a proxy for the loop head scope to obtain the depth + while( cur._lexSize.size() > _breakScope._lexSize.size() ) + cur.pop(); + // If this is a continue then first time the target is null + // So we just use the pruned current scope as the base for the + // "continue" + if( toScope == null ) + return cur; + // toScope is either the break scope, or a scope that was created here + assert toScope._lexSize.size() <= _breakScope._lexSize.size(); + toScope.ctrl(toScope.mergeScopes(cur).peephole()); + return toScope; + } + private void checkLoopActive() { if (_breakScope == null) throw error("No active loop for a break or continue"); } + + private Node compileBreak(AST.BreakStmt breakStmt) { + checkLoopActive(); + // At the time of the break, and loop-exit conditions are only valid if + // they are ALSO valid at the break. It is the intersection of + // conditions here, not the union. + _breakScope.removeGuards(_breakScope.ctrl()); + _breakScope = jumpTo(_breakScope ); + _breakScope.addGuards(_breakScope.ctrl(), null, false); + return ZERO; + } + + private Node compileContinue(AST.ContinueStmt continueStmt) { + checkLoopActive(); _continueScope = jumpTo( _continueScope ); return ZERO; + } + + private Node compileWhile(AST.WhileStmt whileStmt) { + var savedContinueScope = _continueScope; + var savedBreakScope = _breakScope; + + // Loop region has two control inputs, the first is the entry + // point, and second is back edge that is set after loop is parsed + // (see end_loop() call below). Note that the absence of back edge is + // used as an indicator to switch off peepholes of the region and + // associated phis; see {@code inProgress()}. + + ctrl(new LoopNode(ctrl()).peephole()); // Note we set back edge to null here + + // At loop head, we clone the current Scope (this includes all + // names in every nesting level within the Scope). + // We create phis eagerly for all the names we find, see dup(). + + // Save the current scope as the loop head + ScopeNode head = _scope.keep(); + // Clone the head Scope to create a new Scope for the body. + // Create phis eagerly as part of cloning + _xScopes.push(_scope = _scope.dup(true)); // The true argument triggers creating phis + + // Parse predicate + var pred = compileExpr(whileStmt.condition); + + // IfNode takes current control and predicate + Node ifNode = new IfNode(ctrl(), pred.keep()).peephole(); + // Setup projection nodes + Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); + Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); + + // Clone the body Scope to create the break/exit Scope which accounts for any + // side effects in the predicate. The break/exit Scope will be the final + // scope after the loop, and its control input is the False branch of + // the loop predicate. Note that body Scope is still our current scope. + ctrl(ifF); + _xScopes.push(_breakScope = _scope.dup()); + _breakScope.addGuards(ifF,pred,true); // Up-cast predicate + + // No continues yet + _continueScope = null; + + // Parse the true side, which corresponds to loop body + // Our current scope is the body Scope + ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection + _scope.addGuards(ifT,pred.unkeep(),false); // Up-cast predicate + compileStatement(whileStmt.stmt); // Parse loop body + _scope.removeGuards(ifT); + + // Merge the loop bottom into other continue statements + if (_continueScope != null) { + _continueScope = jumpTo(_continueScope); + _scope.kill(); + _scope = _continueScope; + } + + + // The true branch loops back, so whatever is current _scope.ctrl gets + // added to head loop as input. endLoop() updates the head scope, and + // goes through all the phis that were created earlier. For each phi, + // it sets the second input to the corresponding input from the back + // edge. If the phi is redundant, it is replaced by its sole input. + var exit = _breakScope; + head.endLoop(_scope, exit); + head.unkeep().kill(); + + _xScopes.pop(); // Cleanup + _xScopes.pop(); // Cleanup + + _continueScope = savedContinueScope; + _breakScope = savedBreakScope; + + // At exit the false control is the current control, and + // the scope is the exit scope after the exit test. + _xScopes.pop(); + _xScopes.push(exit); + _scope = exit; + return ZERO; + } + + private Node compileIf(AST.IfElseStmt ifElseStmt) { + var pred = compileExpr(ifElseStmt.condition).keep(); + pred.keep(); + + // IfNode takes current control and predicate + Node ifNode = new IfNode(ctrl(), pred).peephole(); + // Setup projection nodes + Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); + Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole().keep(); + // In if true branch, the ifT proj node becomes the ctrl + // But first clone the scope and set it as current + ScopeNode fScope = _scope.dup(); // Duplicate current scope + _xScopes.push(fScope); // For graph visualization we need all scopes + + // Parse the true side + ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection + _scope.addGuards(ifT,pred,false); // Up-cast predicate + Node lhs = compileStatement(ifElseStmt.ifStmt).keep(); // Parse true-side + _scope.removeGuards(ifT); + + // See if a one-sided def was made: "if(pred) int x = 1;" and throw. + // See if any forward-refs were made, and copy them to the other side: + // "pred ? n*fact(n-1) : 1" + fScope.balanceIf(_scope); + + ScopeNode tScope = _scope; + + // Parse the false side + _scope = fScope; // Restore scope, then parse else block if any + ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection + // Up-cast predicate, even if not else clause, because predicate can + // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;` + _scope.addGuards(ifF,pred,true); + boolean doRHS = ifElseStmt.elseStmt != null; + Node rhs = (doRHS + ? compileStatement(ifElseStmt.elseStmt) + : con(lhs._type.makeZero())).keep(); + _scope.removeGuards(ifF); + if( doRHS ) + fScope = _scope; + pred.unkeep(); + + _scope = tScope; + _xScopes.pop(); // Discard pushed from graph display + // See if a one-sided def was made: "if(pred) int x = 1;" and throw. + // See if any forward-refs were made, and copy them to the other side: + // "pred ? n*fact(n-1) : 1" + tScope.balanceIf(fScope); + + // Merge results + RegionNode r = ctrl(tScope.mergeScopes(fScope)); + Node ret = peep(new PhiNode("",lhs._type.meet(rhs._type),r,lhs.unkeep(),rhs.unkeep())); + r.peephole(); + return ret; + } + + private Node compileExprStmt(AST.ExprStmt exprStmt) { + return compileExpr(exprStmt.expr); + } + + private Node compileAssign(AST.AssignStmt assignStmt) { + if (!(assignStmt.lhs instanceof AST.NameExpr nameExpr)) + throw new CompilerException("Assignment requires a nameExpr"); + String name = nameExpr.name; + if (!(nameExpr.symbol instanceof Symbol.VarSymbol varSymbol)) + throw new CompilerException("Assignment requires a variable name"); + String scopedName = makeVarName(varSymbol); + // Parse rhs + Node expr = compileExpr(assignStmt.rhs); + Var def = _scope.lookup(scopedName); + if( def==null ) + throw error("Undefined name '" + name + "'"); + // Update + _scope.update(scopedName,expr); + return expr; + } + + private Node compileLet(AST.VarStmt letStmt) { + String name = letStmt.varName; + String scopedName = makeVarName(letStmt.symbol); + // Parse rhs + Node expr = compileExpr(letStmt.expr); + Var def = _scope.lookup(scopedName); + if( def==null ) + throw error("Undefined name '" + name + "'"); + // Update + _scope.update(scopedName,expr); + return expr; + } + + private Node compileReturn(AST.ReturnStmt returnStmt) { + var expr = compileExpr(returnStmt.expr); + // Need default memory, since it can be lazy, need to force + // a non-lazy Phi + _fun.addReturn(ctrl(), _scope.mem().merge(), expr); + ctrl(XCTRL); // Kill control + return expr; + } + + private Node compileBlock(AST.BlockStmt block) { + Node last = ZERO; + for (AST.Stmt stmt: block.stmtList) { + last = compileStatement(stmt); + } + return last; + } + + public static Node con(long con ) { return con==0 ? ZERO : con(SONTypeInteger.constant(con)); } + public static ConstantNode con( SONType t ) { return (ConstantNode)new ConstantNode(t).peephole(); } + public Node peep( Node n ) { + // Peephole, then improve with lexically scoped guards + return _scope.upcastGuard(n.peephole()); + } + + // We set up memory aliases by inserting special vars in the scope these + // variables are prefixed by $ so they cannot be referenced in Simple code. + // Using vars has the benefit that all the existing machinery of scoping + // and phis work as expected + private Node memAlias(int alias ) { return _scope.mem(alias ); } + private void memAlias(int alias, Node st) { _scope.mem(alias, st); } + public static String memName(int alias) { return ("$"+alias).intern(); } + + CompilerException errorSyntax(String syntax) { return _errorSyntax("expected "+syntax); } + private CompilerException _errorSyntax(String msg) { + return error("Syntax error, "+msg); + } + public static CompilerException error(String msg) { return new CompilerException(msg); } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/IterPeeps.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/IterPeeps.java new file mode 100644 index 0000000..f58ae9f --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/IterPeeps.java @@ -0,0 +1,197 @@ +package com.compilerprogramming.ezlang.compiler; + +import com.compilerprogramming.ezlang.compiler.codegen.CodeGen; +import com.compilerprogramming.ezlang.compiler.nodes.*; +//import com.seaofnodes.simple.print.JSViewer; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Random; + +/** + * The IterPeeps runs after parsing. It iterates the peepholes to a fixed point + * so that no more peepholes apply. This should be linear because peepholes rarely + * (never?) increase code size. The graph should monotonically reduce in some + * dimension, which is usually size. It might also reduce in e.g. number of + * MulNodes or Load/Store nodes, swapping out more "expensive" Nodes for cheaper + * ones. + *
+ * The theoretical overall worklist is mindless just grabbing the next thing and + * doing it. If the graph changes, put the neighbors on the worklist. + *
+ * Lather, Rinse, Repeat until the worklist runs dry. + *

+ * The main issues we have to deal with: + * + *

    + *
  • Nodes have uses; replacing some set of Nodes with another requires more graph + * reworking. Not rocket science, but it can be fiddly. Its helpful to have a + * small set of graph munging utilities, and the strong invariant that the graph + * is stable and correct between peepholes. In our case `Node.subsume` does + * most of the munging, building on our prior stable Node utilities.
  • + * + *
  • Changing a Node also changes the graph "neighborhood". The neighbors need to + * be checked to see if THEY can also peephole, and so on. After any peephole + * or graph update we put a Nodes uses and defs on the worklist.
  • + * + *
  • Our strong invariant is that for all Nodes, either they are on the worklist + * OR no peephole applies. This invariant is easy to check, although expensive. + * Basically the normal "iterate peepholes to a fixed point" is linear, and this + * check is linear at each peephole step... so quadratic overall. Its a useful + * assert, but one we can disable once the overall algorithm is stable - and + * then turn it back on again when some new set of peepholes is misbehaving. + * The code for this is turned on in `IterPeeps.iterate` as `assert + * progressOnList(stop);`
  • + *
+ */ +public class IterPeeps { + + private final WorkList _work; + + public IterPeeps( long seed ) { _work = new WorkList<>(seed); } + + public N add( N n ) { return (N)_work.push(n); } + + public void addAll( Ary ary ) { _work.addAll(ary); } + + /** + * Iterate peepholes to a fixed point + */ + public void iterate( CodeGen code ) { + assert progressOnList(code); + int cnt=0; + + Node n; + while( (n=_work.pop()) != null ) { + if( n.isDead() ) continue; + cnt++; // Useful for debugging, searching which peephole broke things + Node x = n.peepholeOpt(); + if( x != null ) { + if( x.isDead() ) continue; + // peepholeOpt can return brand-new nodes, needing an initial type set + if( x._type==null ) x.setType(x.compute()); + // Changes require neighbors onto the worklist + if( x != n || !(x instanceof ConstantNode) ) { + // All outputs of n (changing node) not x (prior existing node). + for( Node z : n._outputs ) _work.push(z); + // Everybody gets a free "go again" in case they didn't get + // made in their final form. + _work.push(x); + // If the result is not self, revisit all inputs (because + // there's a new user), and replace in the graph. + if( x != n ) { + for( Node z : n. _inputs ) _work.push(z); + n.subsume(x); + } + } + // If there are distant neighbors, move to worklist + n.moveDepsToWorklist(); + //JSViewer.show(); // Show again + assert progressOnList(code); // Very expensive assert + } + if( n.isUnused() && !(n instanceof StopNode) ) + n.kill(); // Just plain dead + } + + } + + // Visit ALL nodes and confirm the invariant: + // Either you are on the _work worklist OR running `iter()` makes no progress. + + // This invariant ensures that no progress is missed, i.e., when the + // worklist is empty we have indeed done all that can be done. To help + // with debugging, the {@code assert} is broken out in a place where it is easy to + // stop if a change is found. + + // Also, the normal usage of `iter()` may attempt peepholes with distance + // neighbors and these should fail, but will then try to add dependencies + // {@link #Node.addDep} which is a side effect in an assert. The {@link + // #midAssert} is used to stop this side effect. + private boolean progressOnList(CodeGen code) { + code._midAssert = true; + Node changed = code._stop.walk( n -> { + Node m = n; + if( n.compute().isa(n._type) && (!n.iskeep() || n._nid<=6) ) { // Types must be forwards, even if on worklist + if( _work.on(n) ) return null; + m = n.peepholeOpt(); + if( m==null ) return null; + } + System.err.println("BREAK HERE FOR BUG"); + return m; + }); + code._midAssert = false; + return changed==null; + } + + /** + * Classic WorkList, with a fast add/remove, dup removal, random pull. + * The Node's nid is used to check membership in the worklist. + */ + public static class WorkList { + + private Node[] _es; + private int _len; + private final BitSet _on; // Bit set if Node._nid is on WorkList + private final Random _R; // For randomizing pull from the WorkList + private final long _seed; + + /* Useful stat - how many nodes are processed in the post parse iterative opt */ + private long _totalWork = 0; + + public WorkList() { this(123); } + WorkList(long seed) { + _es = new Node[1]; + _len=0; + _on = new BitSet(); + _seed = seed; + _R = new Random(); + _R.setSeed(_seed); + } + + /** + * Pushes a Node on the WorkList, ensuring no duplicates + * If Node is null it will not be added. + */ + public E push( E x ) { + if( x==null ) return null; + int idx = x._nid; + if( !_on.get(idx) ) { + _on.set(idx); + if( _len==_es.length ) + _es = Arrays.copyOf(_es,_len<<1); + _es[_len++] = x; + _totalWork++; + } + return x; + } + + public void addAll( Ary ary ) { + for( E n : ary ) + push(n); + } + + /** + * True if Node is on the WorkList + */ + boolean on( E x ) { return _on.get(x._nid); } + boolean isEmpty() { return _len==0; } + + /** + * Removes a random Node from the WorkList; null if WorkList is empty + */ + public E pop() { + if( _len == 0 ) return null; + int idx = _R.nextInt(_len); + E x = (E)_es[idx]; + _es[idx] = _es[--_len]; // Compress array + _on.clear(x._nid); + return x; + } + + public void clear() { + _len = 0; + _on.clear(); + _R.setSeed(_seed); + _totalWork = 0; + } + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/SB.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/SB.java new file mode 100644 index 0000000..0ed652d --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/SB.java @@ -0,0 +1,99 @@ +package com.compilerprogramming.ezlang.compiler; + +/** Tight/tiny StringBuilder wrapper. + * Short short names on purpose; so they don't obscure the printing. + * Can't believe this wasn't done long, long ago. */ +public final class SB { + private final StringBuilder _sb; + private int _indent = 0; + public SB( ) { _sb = new StringBuilder( ); } + public SB(String s) { _sb = new StringBuilder(s); } + public SB p( String s ) { _sb.append(s); return this; } + public SB p( float s ) { + if( Float.isNaN(s) ) + _sb.append( "Float.NaN"); + else if( Float.isInfinite(s) ) { + _sb.append(s > 0 ? "Float.POSITIVE_INFINITY" : "Float.NEGATIVE_INFINITY"); + } else _sb.append(s); + return this; + } + public SB p( double s ) { + if( Double.isNaN(s) ) + _sb.append("Double.NaN"); + else if( Double.isInfinite(s) ) { + _sb.append(s > 0 ? "Double.POSITIVE_INFINITY" : "Double.NEGATIVE_INFINITY"); + } else _sb.append(s); + return this; + } + public SB p( char s ) { _sb.append(s); return this; } + public SB p( int s ) { _sb.append(s); return this; } + public SB p( long s ) { _sb.append(s); return this; } + public SB p( boolean s) { _sb.append(s); return this; } + // 1 byte, 2 hex digits, 8 bits + public SB hex1(int s) { + int digit = (s>>4) & 0xf; + _sb.append((char)((digit <= 9 ? '0' : ('A'-10))+digit)); + digit = s & 0xf; + _sb.append((char)((digit <= 9 ? '0' : ('A'-10))+digit)); + return this; + } + // 2 bytes, 4 hex digits, 16 bits, Big Endian + public SB hex2(int s) { return hex1(s>> 8).hex1(s); } + // 4 bytes, 8 hex digits, 32 bits, Big Endian + public SB hex4(int s) { return hex2(s>>16).hex2(s); } + // 8 bytes, 16 hex digits, 64 bits, Big Endian + public SB hex8(long s) { return hex4((int)(s>>32)).hex4((int)s); } + + // Fixed width field + public SB fix( int sz, String s ) { + for( int i=0; i void del(ArrayList array, int i) { + E last = array.removeLast(); + if (i < array.size()) array.set(i, last); + } + + /** + * Search a list for an element by reference + * + * @param ary List to search in + * @param x Object to be searched + * @return >= 0 on success, -1 on failure + */ + public static int find( ArrayList ary, E x ) { + for( int i=0; i= 0 on success, -1 on failure + */ + public static int find( E[] ary, E x ) { + for( int i=0; i>>n); } + + // Fold a 64bit hash into 32 bits + public static int fold( long x ) { return (int)((x>>32) ^ x); } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Var.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Var.java new file mode 100644 index 0000000..6016939 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/Var.java @@ -0,0 +1,52 @@ +package com.compilerprogramming.ezlang.compiler; + +import com.compilerprogramming.ezlang.compiler.sontypes.*; + + +/** + * The tracked fields are now complex enough to deserve a array-of-structs layout + */ +public class Var { + + public final String _name; // Declared name + public int _idx; // index in containing scope + private SONType _type; // Declared type + public boolean _final; // Final field + public boolean _fref; // Forward ref + + public Var(int idx, String name, SONType type, boolean xfinal) { + this(idx,name,type,xfinal,false); + } + public Var(int idx, String name, SONType type, boolean xfinal, boolean fref) { + _idx = idx; + _name = name; + _type = type; + _final = xfinal; + _fref = fref; + } + public SONType type() { + if( !_type.isFRef() ) return _type; + // Update self to no longer use the forward ref type + SONType def = Compiler.TYPES.get(((SONTypeMemPtr)_type)._obj._name); + return (_type=_type.meet(def)); + } + public SONType lazyGLB() { + SONType t = type(); + return t instanceof SONTypeMemPtr ? t : t.glb(); + } + + // Forward reference variables (not types) must be BOTTOM and + // distinct from inferred variables + public boolean isFRef() { return _fref; } + + public void defFRef(SONType type, boolean xfinal) { + assert isFRef() && xfinal; + _type = type; + _final = true; + _fref = false; + } + + @Override public String toString() { + return _type.toString()+(_final ? " ": " !")+_name; + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/BuildLRG.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/BuildLRG.java new file mode 100644 index 0000000..99c3fbd --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/BuildLRG.java @@ -0,0 +1,91 @@ +package com.compilerprogramming.ezlang.compiler.codegen; + +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeMem; + +abstract public class BuildLRG { + // Compute live ranges in a single forwards pass. Every def is a new live + // range except live ranges are joined at Phi. Also, gather the + // intersection of register masks in each live range. Example: "free + // always zero" register forces register zero, and calling conventions + // typically force registers. + + // Sets FAILED to the set of hard-conflicts (means no need for an IFG, + // since it will not color, just split the conflicted ranges now). Returns + // true if no hard-conflicts, although we still might not color. + public static boolean run(int round, RegAlloc alloc) { + for( Node bb : alloc._code._cfg ) + for( Node n : bb.outs() ) { + if( n instanceof PhiNode phi && !(phi._type instanceof SONTypeMem) ) { + // All Phi inputs end up with the same LRG. + // Pass 1: find any pre-existing LRG, to avoid make-then-Union a LRG + LRG lrg = alloc.lrg(phi); + if( lrg == null ) + for( int i=1; i=0; lrg=ifg.nextSetBit(lrg+1) ) { + LRG lrg1 = alloc._LRGS[lrg]; + lrg0.addNeighbor(lrg1); + lrg1.addNeighbor(lrg0); + } + } + } + + // Walk all the splits, looking for coalesce chances + boolean progress = false; + for( CFGNode bb : alloc._code._cfg ) { + outer: + for( int j=0; j (v2._adj==null ? 0 : v2._adj._len) ) + { v1 = v2; v2 = alloc.lrg(n); } + int v2len = v2._adj==null ? 0 : v2._adj._len; + // See that they do not conflict (coalescing would make a self-conflict) + if( v1._adj!=null && v2._adj!=null && v1._mask.overlap(v2._mask) ) { + // Walk the smaller neighbors, and add to the larger. + // Check for direct conflicts along the way. + for( LRG v3 : v1._adj ) { + if( v3==v2 ) { + // Direct conflict, skip this LRG pair + v2._adj.setLen(v2len); + continue outer; + } + // Add missing LRGs from V1 to V2 + if( v2._adj.find(v3)== -1 ) + v2._adj.push(v3); + } + } + // Most constrained mask + RegMask mask = v1._mask==v2._mask ? v1._mask : v1._mask.copy().and(v2._mask); + // Check for capacity + if( (v2._adj==null ? 0 : v2._adj._len) >= mask.size() ) { + // Fails capacity, will not be trivial colorable + v2._adj.setLen(v2len); + continue; + } + + // Union lrgs. + progress = true; + + // Since `n` is dying, remove from the LRGS BEFORE doing + // the union; this will let the union code keep the + // matching bits from the other side, preserving some + // info for biased coloring + if( ov1. _machDef==n ) ov1. _machDef = null; + if( ov1._splitDef==n ) ov1._splitDef = null; + if( ov2. _machUse==n ) ov2. _machUse = null; + if( ov2._splitUse==n ) ov2._splitUse = null; + // Keep larger adjacency list. + Ary larger = v2._adj; + v2._adj = null; + + // Union lrgs. Swaps again, based on small lrg + LRG vWin = v1.union(v2); + vWin._adj = larger; + LRG vLose = v1.leader() ? v2 : v1; + + // LRGs below v2len were always in v2, but maybe not in v1. + // LRGs above v2len are in v1 but were not in v2. + + // Walk v2's neighbors, replacing vLose with vWin. + if( larger != null ) + for( LRG vn : larger ) { + int idx = vn._adj.find(vLose); + if( idx != -1 ) + vn._adj.del(idx); + if( vn._adj.find(vWin) == -1 ) + vn._adj.add(vWin); + } + } else { + + // Since `n` is dying, remove from the LRGS + if( v1. _machDef==n ) v1. _machDef=null; + if( v1. _machUse==n ) v1. _machUse=null; + if( v1._splitDef==n ) v1._splitDef=null; + if( v1._splitUse==n ) v1._splitUse=null; + } + + // Remove junk split + n.removeSplit(); + j--; + } + } + + if( progress ) + alloc.unify(); + + return true; + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/CodeGen.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/CodeGen.java new file mode 100644 index 0000000..522cfd3 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/CodeGen.java @@ -0,0 +1,389 @@ +package com.compilerprogramming.ezlang.compiler.codegen; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import com.compilerprogramming.ezlang.compiler.print.*; +import com.compilerprogramming.ezlang.compiler.sontypes.*; +import com.compilerprogramming.ezlang.exceptions.CompilerException; + +import java.io.IOException; +import java.util.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; + +public class CodeGen { + // Last created CodeGen as a global; used all over to avoid passing about a + // "context". + public static CodeGen CODE; + + public enum Phase { + Parse, // Parse ASCII text into Sea-of-Nodes IR + Opto, // Run ideal optimizations + TypeCheck, // Last check for bad programs + InstSelect, // Convert to target hardware nodes + Schedule, // Global schedule (code motion) nodes + LocalSched, // Local schedule + RegAlloc, // Register allocation + Encoding, // Encoding + Export, // Export + } + public Phase _phase; + + // --------------------------- + // Compilation source code + public final String _src; + // Compile-time known initial argument type + public final SONTypeInteger _arg; + + // --------------------------- + public CodeGen( String src ) { this(src, SONTypeInteger.BOT, 123L ); } + public CodeGen( String src, SONTypeInteger arg, long workListSeed ) { + CODE = this; + _phase = null; + _callingConv = null; + _start = new StartNode(arg); + _stop = new StopNode(src); + _src = src; + _arg = arg; + _iter = new IterPeeps(workListSeed); + P = new Compiler(this,arg); + } + + + // --------------------------- + /** + * A counter, for unique node id generation. Starting with value 1, to + * avoid bugs confusing node ID 0 with uninitialized values. + */ + public int _uid = 1; + public int UID() { return _uid; } + public int getUID() { + return _uid++; + } + + // Next available memory alias number + private int _alias = 2; // 0 is for control, 1 for memory + public int getALIAS() { return _alias++; } + + + // Popular visit bitset, declared here, so it gets reused all over + public final BitSet _visit = new BitSet(); + public BitSet visit() { assert _visit.isEmpty(); return _visit; } + + // Start and stop; end points of the generated IR + public StartNode _start; + public StopNode _stop; + + // Global Value Numbering. Hash over opcode and inputs; hits in this table + // are structurally equal. + public final HashMap _gvn = new HashMap<>(); + + // Compute "function indices": FIDX. + // Each new request at the same signature gets a new FIDX. + private final HashMap FIDXS = new HashMap<>(); + public SONTypeFunPtr makeFun( SONTypeTuple sig, SONType ret ) { + Integer i = FIDXS.get(sig); + int fidx = i==null ? 0 : i; + FIDXS.put(sig,fidx+1); // Track count per sig + assert fidx<64; // TODO: need a larger FIDX space + return SONTypeFunPtr.make((byte)2,sig,ret, 1L< _linker = new HashMap<>(); + + // Parser object + public final Compiler P; + + // Parse ASCII text into Sea-of-Nodes IR + public int _tParse; + public CodeGen parse() { + assert _phase == null; + _phase = Phase.Parse; + long t0 = System.currentTimeMillis(); + P.parse(); + _tParse = (int)(System.currentTimeMillis() - t0); + return this; + } + + + // --------------------------- + // Iterator peepholes. + public final IterPeeps _iter; + // Stop tracking deps while assertin + public boolean _midAssert; + // Statistics on peepholes + public int _iter_cnt, _iter_nop_cnt; + // Stat gathering + public void iterCnt() { if( !_midAssert ) _iter_cnt++; } + public void iterNop() { if( !_midAssert ) _iter_nop_cnt++; } + + // Run ideal optimizations + public int _tOpto; + public CodeGen opto() { + assert _phase == Phase.Parse; + _phase = Phase.Opto; + long t0 = System.currentTimeMillis(); + + // Pessimistic peephole optimization on a worklist + _iter.iterate(this); + _tOpto = (int)(System.currentTimeMillis() - t0); + + // TODO: + // Optimistic + // TODO: + // loop unroll, peel, RCE, etc + return this; + } + public N add( N n ) { return (N)_iter.add(n); } + public void addAll( Ary ary ) { _iter.addAll(ary); } + + + // --------------------------- + // Last check for bad programs + public int _tTypeCheck; + public CodeGen typeCheck() { + // Demand phase Opto for cleaning up dead control flow at least, + // required for the following GCM. + assert _phase.ordinal() <= Phase.Opto.ordinal(); + _phase = Phase.TypeCheck; + long t0 = System.currentTimeMillis(); + + CompilerException err = _stop.walk( Node::err ); + _tTypeCheck = (int)(System.currentTimeMillis() - t0); + if( err != null ) + throw err; + return this; + } + + + // --------------------------- + // Code generation CPU target + public Machine _mach; + // + public String _callingConv; + + // Convert to target hardware nodes + public int _tInsSel; + public CodeGen instSelect( String base, String cpu, String callingConv ) { + assert _phase.ordinal() <= Phase.TypeCheck.ordinal(); + _phase = Phase.InstSelect; + + _callingConv = callingConv; + + // Not timing the class load... + // Look for CPU in fixed named place: + // com.compilerprogramming.ezlang.compiler.node.cpus."cpu"."cpu.class" + String clzFile = base+"."+cpu+"."+cpu; + try { _mach = ((Class) Class.forName( clzFile )).getDeclaredConstructor(new Class[]{CodeGen.class}).newInstance(this); } + catch( Exception e ) { throw new RuntimeException(e); } + + // Convert to machine ops + long t0 = System.currentTimeMillis(); + _uid = 1; // All new machine nodes reset numbering + var map = new IdentityHashMap(); + _instSelect( _stop, map ); + _stop = ( StopNode)map.get(_stop ); + _start = (StartNode)map.get(_start); + _instOuts(_stop,visit()); + _visit.clear(); + _tInsSel = (int)(System.currentTimeMillis() - t0); + return this; + } + + // Walk all ideal nodes, recursively mapping ideal to machine nodes, then + // make a machine node for "this". + private Node _instSelect( Node n, IdentityHashMap map ) { + if( n==null ) return null; + Node x = map.get(n); + if( x !=null ) return x; // Been there, done that + + // If n is a MachNode already, then its part of a multi-node expansion. + // It does not need instruction selection (already selected!) + // but it does need its inputs walked. + if( n instanceof MachNode ) { + for( int i=0; i < n.nIns(); i++ ) + n._inputs.set(i, _instSelect(n.in(i),map) ); + return n; + } + + // Produce a machine node from n; map it to flag as done so stops cycles. + map.put(n, x=_mach.instSelect(n) ); + // Walk machine op and replace inputs with mapped inputs + for( int i=0; i < x.nIns(); i++ ) + x._inputs.set(i, _instSelect(x.in(i),map) ); + if( x instanceof MachNode mach ) + mach.postSelect(this); // Post selection action + + // Updates forward edges only. + n._outputs.clear(); + return x; + } + + // Walk all machine Nodes, and set their output edges + private void _instOuts( Node n, BitSet visit ) { + if( visit.get(n._nid) ) return; + visit.set(n._nid); + for( Node in : n._inputs ) + if( in!=null ) { + in._outputs.push(n); + _instOuts(in,visit); + } + } + + + // --------------------------- + // Control Flow Graph in RPO order. + public int _tGCM; + public Ary _cfg = new Ary<>(CFGNode.class); + + // Global schedule (code motion) nodes + public CodeGen GCM() { return GCM(false); } + public CodeGen GCM( boolean show) { + assert _phase.ordinal() <= Phase.InstSelect.ordinal(); + _phase = Phase.Schedule; + long t0 = System.currentTimeMillis(); + + // Build the loop tree, fix never-exit loops + _start.buildLoopTree(_stop); + GlobalCodeMotion.buildCFG(this); + _tGCM = (int)(System.currentTimeMillis() - t0); + if( show ) + System.out.println(new GraphVisualizer().generateDotOutput(_stop,null,null)); + return this; + } + + // --------------------------- + // Local (basic block) scheduler phase, a classic list scheduler + public int _tLocal; + public CodeGen localSched() { + assert _phase == Phase.Schedule; + _phase = Phase.LocalSched; + long t0 = System.currentTimeMillis(); + ListScheduler.sched(this); + _tLocal = (int)(System.currentTimeMillis() - t0); + return this; + } + + + // --------------------------- + // Register Allocation + public int _tRegAlloc; + public RegAlloc _regAlloc; + public CodeGen regAlloc() { + assert _phase == Phase.LocalSched; + _phase = Phase.RegAlloc; + long t0 = System.currentTimeMillis(); + _regAlloc = new RegAlloc(this); + _regAlloc.regAlloc(); + _tRegAlloc = (int)(System.currentTimeMillis() - t0); + return this; + } + + public String reg(Node n) { + if( _phase.ordinal() >= Phase.RegAlloc.ordinal() ) { + String s = _regAlloc.reg(n); + if( s!=null ) return s; + } + return "N"+ n._nid; + } + + // --------------------------- + // Encoding + public int _tEncode; + public Encoding _encoding; + public CodeGen encode() { + assert _phase == Phase.RegAlloc; + _phase = Phase.Encoding; + long t0 = System.currentTimeMillis(); + _encoding = new Encoding(this); + _encoding.encode(); + _tEncode = (int)(System.currentTimeMillis() - t0); + return this; + } + // Encoded binary, no relocation info + public byte[] binary() { return _encoding.bits(); } + + // --------------------------- + // Exporting to external formats + public CodeGen exportELF(String fname) throws IOException { + assert _phase == Phase.Encoding; + _phase = Phase.Export; + ElfFile obj = new ElfFile(this); + obj.export(fname); + return this; + } + + // --------------------------- + SB asm(SB sb) { return ASMPrinter.print(sb,this); } + public String asm() { return asm(new SB()).toString(); } + + + // Testing shortcuts + public Node ctrl() { return _stop.ret().ctrl(); } + public Node expr() { return _stop.ret().expr(); } + public String print() { return _stop.print(); } + + // Debugging helper + @Override public String toString() { + return _phase.ordinal() > Phase.Schedule.ordinal() + ? IRPrinter._prettyPrint( this ) + : _stop.p(9999); + } + + // Debugging helper + public Node f(int idx) { return _stop.find(idx); } + + public static void print_as_hex(Encoding enc) { + for (byte b : enc._bits.toByteArray()) { + System.out.print(String.format("%02X", b)); + } + System.out.println(); + } + + public void print_as_hex() { + for (byte b : _encoding._bits.toByteArray()) { + System.out.print(String.format("%02X", b)); + } + System.out.println(); + } + + //// Debug purposes for now +// public CodeGen printENCODING() { +// ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); +// +// for(Node bb : CodeGen.CODE._cfg) { +// for(Node n: bb.outs()) { +// if(n instanceof MachNode) { +// ((MachNode) n).encoding(E); +// } +// } +// } +// +// print_as_hex(outputStream); +// // Get the raw bytes from the output stream +// return this; +// } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/ElfExpr.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/ElfExpr.java new file mode 100644 index 0000000..f24dc4f --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/ElfExpr.java @@ -0,0 +1,87 @@ +package com.compilerprogramming.ezlang.compiler.codegen; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class ElfExpr { + public static void main(String[] args) { + try (FileOutputStream fos = new FileOutputStream("hello.o")) { + ByteBuffer buffer = ByteBuffer.allocate(512); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + // === ELF HEADER (64 bytes) === + buffer.put(new byte[]{ + 0x7F, 'E', 'L', 'F', // ELF Magic + 2, 1, 1, 0, // 64-bit, little-endian, version + 0, 0, 0, 0, 0, 0, 0, // ABI padding + 2, 0, // Type: Executable (ET_EXEC) + 0x3E, 0x00, // Machine: x86-64 + 1, 0, 0, 0, // ELF version + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Entry point + 0, 0, 0, 0, 0, 0, 0, 0, // Program header offset + 64, 0, 0, 0, 0, 0, 0, 0, // Section header offset + 0, 0, 0, 0, // Flags + 64, 0, // ELF header size + 0, 0, // Program header entry size + 0, 0, // Number of program header entries + 40, 0, // Section header entry size + 2, 0, // Number of section headers + 1, 0 // Section header string table index + }); + + // === .TEXT SECTION (Executable Code) === + byte[] textSection = { + // sys_write (write(1, message, 13)) + (byte) 0x48, (byte) 0xC7, (byte) 0xC0, 0x01, 0x00, 0x00, 0x00, // mov rax, 1 + (byte) 0x48, (byte) 0xC7, (byte) 0xC7, 0x01, 0x00, 0x00, 0x00, // mov rdi, 1 + (byte) 0x48, (byte) 0xBE, (byte)0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rsi, message + (byte) 0x48, (byte) 0xC7, (byte) 0xC2, 0x0D, 0x00, 0x00, 0x00, // mov rdx, 13 + (byte) 0x0F, (byte) 0x05, // syscall + + // sys_exit (exit(0)) + (byte) 0x48, (byte) 0xC7, (byte) 0xC0, 0x3C, 0x00, 0x00, 0x00, // mov rax, 60 + (byte) 0x48, (byte) 0x31, (byte) 0xFF, // xor rdi, rdi + (byte) 0x0F, (byte) 0x05 // syscall + }; + + // Append section data + buffer.position(256); + buffer.put(textSection); + + // === STRING MESSAGE (.data Section) === + byte[] message = "Hello, World!\n".getBytes(); + buffer.position(400); + buffer.put(message); + + // === SECTION HEADERS === + buffer.position(64); + + // Null Section (required as first entry) + buffer.put(new byte[40]); + + // .text Section + buffer.putInt(1); // Offset in section string table (".text") + buffer.putInt(1); // Type: SHT_PROGBITS + buffer.putLong(6); // Flags: Executable | Allocatable + buffer.putLong(0); // Virtual address + buffer.putLong(256); // Offset to section data + buffer.putLong(textSection.length); // Section size + buffer.putInt(0); // Link + buffer.putInt(0); // Info + buffer.putLong(4); // Address alignment + buffer.putLong(0); // No extra entries + + // === SECTION HEADER STRING TABLE === + buffer.position(500); + buffer.put(".text".getBytes()); + buffer.put((byte) 0x00); // Null terminator + + fos.write(buffer.array()); + System.out.println("ELF object file 'hello.o' created."); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/ElfFile.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/ElfFile.java new file mode 100644 index 0000000..eee43a1 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/ElfFile.java @@ -0,0 +1,387 @@ +package com.compilerprogramming.ezlang.compiler.codegen; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import com.compilerprogramming.ezlang.compiler.sontypes.*; + +import java.util.Map; +import java.util.HashMap; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.io.IOException; +import java.io.FileOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.BufferedOutputStream; + +public class ElfFile { + + final CodeGen _code; + Ary
_sections = new Ary<>(Section.class); + + ElfFile( CodeGen code ) { _code = code; } + + public static int writeCString(DataSection strtab, String name) { + // place name in the string table + int pos = strtab._contents.size(); + byte[] name_bytes = name.getBytes(); + strtab._contents.write(name_bytes, 0, name_bytes.length); + // the strings are null terminated + strtab._contents.write(0); + return pos; + } + + public static void write4( ByteArrayOutputStream bits, int op ) { + bits.write(op ); + bits.write(op>> 8); + bits.write(op>>16); + bits.write(op>>24); + } + public static void write8( ByteArrayOutputStream bits, long i64 ) { + write4(bits, (int) i64 ); + write4(bits, (int)(i64>>32)); + } + + public static final int SYMBOL_SIZE = 4+1+1+2+8+8; + + public static final int SYM_BIND_LOCAL = 0; + public static final int SYM_BIND_GLOBAL = 1; + public static final int SYM_BIND_WEAK = 2; + + public static final int SYM_TYPE_NOTYPE = 0; + public static final int SYM_TYPE_OBJECT = 1; + public static final int SYM_TYPE_FUNC = 2; + public static final int SYM_TYPE_SECTION = 3; + + public static class Symbol { + String _name; + int _name_pos; + + // index in the symbol table + int _index; + + // which section is it in + int _parent; + + // depends on the symbol type + // TODO + int _value, _size; + + // top 4bits are the "bind", bottom 4 are "type" + int _info; + + Symbol(String name, int parent, int bind, int type) { + _name = name; + _parent = parent; + _info = (bind << 4) | (type & 0xF); + } + + void writeHeader(ByteBuffer out) { + out.putInt(_name_pos); // name + out.put((byte) _info); // info + out.put((byte) 0); // other + out.putShort((short) _parent); // shndx + out.putLong(_value); // value + out.putLong(_size); // size + } + } + + public static final int SHF_WRITE = 1; + public static final int SHF_ALLOC = 2; + public static final int SHF_EXECINSTR = 4; + public static final int SHF_STRINGS = 32; + public static final int SHF_INFO_LINK = 64; + + public static final int SECTION_HDR_SIZE = 64; + abstract public static class Section { + String _name; + int _index; + + int _type, _flags; + int _link, _info; + + // where's the name in the string table + int _name_pos; + int _file_offset; + + Section(String name, int type) { + _name = name; + _type = type; + } + + void writeHeader(ByteBuffer out) { + out.putInt(_name_pos); // name + out.putInt(_type); // type + out.putLong(_flags); // flags + out.putLong(0); // addr + out.putLong(_file_offset); // offset + out.putLong(size()); // size + out.putInt(_link); // link + out.putInt(_info); // info + out.putLong(1); // addralign + if (_type == 2) { + out.putLong(SYMBOL_SIZE);// entsize + } else if (_type == 4) { + out.putLong(24); // entsize + } else { + out.putLong(0); // entsize + } + } + + abstract void write(ByteBuffer out); + abstract int size(); + } + + public static class SymbolSection extends Section { + Ary _symbols = new Ary<>(Symbol.class); + Ary _loc = new Ary<>(Symbol.class); + + SymbolSection(String name, int type) { + super(name, type); + } + + @Override + void writeHeader(ByteBuffer out) { + // points to string table section + _link = 1; + + // number of non-local symbols + _info = _loc.len() + 1; + + super.writeHeader(out); + } + + void push(Symbol s) { + if ((s._info >> 4) != SYM_BIND_LOCAL) { + _symbols.push(s); + } else { + _loc.push(s); + } + } + + @Override + void write(ByteBuffer out) { + for( int i = 0; i < SYMBOL_SIZE/4; i++ ) { + out.putInt(0); + } + int num=1; + for( Symbol s : _loc ) { + s._index = num++; + s.writeHeader(out); + } + for( Symbol s : _symbols ) { + s._index = num++; + s.writeHeader(out); + } + } + + @Override + int size() { + return (1 + _symbols.len() + _loc.len()) * SYMBOL_SIZE; + } + } + + public static class DataSection extends Section { + ByteArrayOutputStream _contents; + + DataSection(String name, int type) { + super(name, type); + _contents = new ByteArrayOutputStream(); + } + + DataSection(String name, int type, ByteArrayOutputStream contents) { + super(name, type); + _contents = contents; + } + + @Override + void write(ByteBuffer out) { + out.put(_contents.toByteArray()); + } + + @Override + int size() { + return _contents.size(); + } + } + + private void pushSection(Section s) { + _sections.push(s); + // ELF is effectively "1-based", due to the NULL section + s._index = _sections._len; + } + + private final HashMap _funcs = new HashMap<>(); + private void encodeFunctions(SymbolSection symbols, DataSection text) { + int func_start = 0; + for( int i=0; i<_code._cfg._len; i++ ) { + Node bb = _code._cfg.at(i); + if( bb instanceof FunNode f ) { + // skip until the function ends + while( !(_code._cfg.at(i) instanceof ReturnNode) ) { + i++; + } + + Node r = _code._cfg.at(i); + int end = _code._encoding._opStart[r._nid] + _code._encoding._opLen[r._nid]; + + Symbol func = new Symbol(f._name, text._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC); + func._size = end - func_start; + func._value = func_start; + symbols.push(func); + _funcs.put(f.sig(), func); + + // next function starts where the last one ends + func_start = end; + } + } + } + + public final HashMap _bigCons = new HashMap<>(); + private void encodeConstants(SymbolSection symbols, DataSection rdata) { + int cnt = 0; + for (Map.Entry e : _code._encoding._bigCons.entrySet()) { + if (_bigCons.get(e.getValue()) != null) { + continue; + } + + Symbol glob = new Symbol("GLOB$"+cnt, rdata._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC); + glob._value = rdata._contents.size(); + symbols.push(glob); + + SONType t = e.getValue(); + if ( t instanceof SONTypeFloat tf ) { + write8(rdata._contents, Double.doubleToLongBits(tf._con)); + } else { + throw Utils.TODO(); + } + + glob._size = rdata._contents.size() - glob._value; + _bigCons.put(e.getValue(), glob); + cnt++; + } + } + + public void export(String fname) throws IOException { + DataSection strtab = new DataSection(".strtab", 3 /* SHT_SYMTAB */); + // first byte is reserved for an empty string + strtab._contents.write(0); + pushSection(strtab); + + // place all the symbols + SymbolSection symbols = new SymbolSection(".symtab", 2 /* SHT_STRTAB */); + pushSection(symbols); + + // we've already constructed this entire section in the encoding phase + DataSection text = new DataSection(".text", 1 /* SHT_PROGBITS */, _code._encoding._bits); + text._flags = SHF_ALLOC | SHF_WRITE | SHF_EXECINSTR; + pushSection(text); + + DataSection rdata = new DataSection(".rodata", 1 /* SHT_PROGBITS */); + rdata._flags = SHF_ALLOC; + pushSection(rdata); + + // populate function symbols + encodeFunctions(symbols, text); + // populate big constants + // encodeConstants(symbols, rdata); + + // create .text relocations + DataSection text_rela = new DataSection(".rela.text", 4 /* SHT_RELA */); + for( Node n : _code._encoding._externals.keySet()) { + int nid = n._nid; + String extern = _code._encoding._externals.get(n); + int sym_id = _funcs.get(extern)._index; + int offset = _code._encoding._opStart[nid] + _code._encoding._opLen[nid] - 4; + + // u64 offset + write8(text_rela._contents, offset); + // u64 info + write8(text_rela._contents, ((long)sym_id << 32L) | 4L /* PLT32 */); + // i64 addend + write8(text_rela._contents, -4); + } + // relocations to constants + if (false) for (Map.Entry e : _code._encoding._bigCons.entrySet()) { + int nid = e.getKey()._nid; + int sym_id = _bigCons.get(e.getValue())._index; + int offset = _code._encoding._opStart[nid] + _code._encoding._opLen[nid] - 4; + + // u64 offset + write8(text_rela._contents, offset); + // u64 info + write8(text_rela._contents, ((long)sym_id << 32L) | 1L /* PC32 */); + // i64 addend + write8(text_rela._contents, -4); + } + text_rela._flags = SHF_INFO_LINK; + text_rela._link = 2; + text_rela._info = text._index; + pushSection(text_rela); + + // populate string table + for( Section s : _sections ) { s._name_pos = writeCString(strtab, s._name); } + for( Symbol s : symbols._symbols ) { s._name_pos = writeCString(strtab, s._name); } + for( Symbol s : symbols._loc ) { s._name_pos = writeCString(strtab, s._name); } + + int idx = 1; + for( Section s : _sections ) { + Symbol sym = new Symbol(s._name, idx++, SYM_BIND_LOCAL, SYM_TYPE_SECTION); + // we can reuse the same name pos from the actual section + sym._name_pos = s._name_pos; + sym._size = s.size(); + symbols.push(sym); + } + + int size = 64; // ELF header + // size of all section data + for( Section s : _sections ) { + s._file_offset = size; + size += s.size(); + } + // headers go at the very end + int shdr_offset = size; + // there's a "null" section at the start of the section headers + size += SECTION_HDR_SIZE*(1 + _sections.len()); + + // System.out.printf("Hello %d, %d, %s\n", size, shdr_offset); + ByteBuffer out = ByteBuffer.allocate(size); + out.order(ByteOrder.LITTLE_ENDIAN); + + // ELF header + out.put(new byte[]{ 0x7F, 'E', 'L', 'F', 2, 1, 1, 0 }); + out.put(new byte[8]); + out.putShort((short)1 /* ET_REL */); // type + out.putShort((short)62 /* AMD64 */); // machine + out.putInt(1); // version + out.putLong(0); // entry + out.putLong(0); // phoff + out.putLong(shdr_offset); // shoff + out.putInt(0); // flags + out.putShort((short)64); // ehsize + out.putShort((short)0); // phentsize + out.putShort((short)0); // phnum + out.putShort((short)64); // shentsize + out.putShort((short) (1 + _sections.len())); // shnum + out.putShort((short)1); // shstrndx + + // raw section data + for( Section s : _sections ) { + s.write(out); + } + + // "null" section + for( int i = 0; i < 16; i++ ) { + out.putInt(0); + } + + for( Section s : _sections ) { + s.writeHeader(out); + } + assert out.position() == size; + + BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fname)); + bos.write(out.array()); + bos.close(); + } + +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/Encoding.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/Encoding.java new file mode 100644 index 0000000..a30447d --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/Encoding.java @@ -0,0 +1,418 @@ +package com.compilerprogramming.ezlang.compiler.codegen; + +import com.compilerprogramming.ezlang.compiler.Ary; +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import com.compilerprogramming.ezlang.compiler.sontypes.*; +import java.io.ByteArrayOutputStream; +import java.util.*; + + +/** + * Instruction encodings + *

+ * This class holds the encoding bits, plus the relocation information needed + * to move the code to a non-zero offset, or write to an external file (ELF). + *

+ * There are also a bunch of generic utilities for managing bits and bytes + * common in all encodings. + */ +public class Encoding { + + // Top-level program graph structure + final CodeGen _code; + + // Instruction bytes. The registers are encoded already. Relocatable data + // is in a fixed format depending on the kind of relocation. + + // - RIP-relative offsets into the same chunk are just encoded with their + // check relative offsets; no relocation info is required. + + // - Fixed address targets in the same encoding will be correct for a zero + // base; moving the entire encoding requires adding the new base address. + + // - RIP-relative to external chunks have a zero offset; the matching + // relocation info will be used to patch the correct value. + + public static class BAOS extends ByteArrayOutputStream { + public byte[] buf() { return buf; } + void set( byte[] buf0, int count0 ) { buf=buf0; count=count0; } + }; + final public BAOS _bits = new BAOS(); + + public int [] _opStart; // Start of opcodes, by _nid + public byte[] _opLen; // Length of opcodes, by _nid + + Encoding( CodeGen code ) { _code = code; } + + // Shortcut to the defining register + public short reg(Node n) { + return _code._regAlloc.regnum(n); + } + + public Encoding add1( int op ) { _bits.write(op); return this; } + + public void add2( int op ) { + _bits.write(op ); + _bits.write(op>> 8); + } + // Little endian write of a 32b opcode + public void add4( int op ) { + _bits.write(op ); + _bits.write(op>> 8); + _bits.write(op>>16); + _bits.write(op>>24); + } + public void add8( long i64 ) { + add4((int) i64 ); + add4((int)(i64>>32)); + } + public int read4(int idx) { + byte[] buf = _bits.buf(); + return buf[idx] | (buf[idx+1]&0xFF) <<8 | (buf[idx+2]&0xFF)<<16 | (buf[idx+3]&0xFF)<<24; + } + public long read8(int idx) { + return (read4(idx) & 0xFFFFFFFFL) | read4(idx+4); + } + + // This buffer is invalid/moving until after all encodings are written + public byte[] bits() { return _bits.buf(); } + + // 4 byte little-endian write + public void patch4( int idx, int val ) { + byte[] buf = _bits.buf(); + buf[idx ] = (byte)(val ); + buf[idx+1] = (byte)(val>> 8); + buf[idx+2] = (byte)(val>>16); + buf[idx+3] = (byte)(val>>24); + } + + void pad8() { + while( (_bits.size()+7 & -8) > _bits.size() ) + _bits.write(0); + } + + + // Nodes need "relocation" patching; things done after code is placed. + // Record src and dst Nodes. + private final HashMap _internals = new HashMap<>(); + // Source is a Call, destination in the Fun. + public Encoding relo( CallNode call ) { + _internals.put(call,_code.link(call.tfp())); + return this; + } + public Encoding relo( ConstantNode con ) { + SONTypeFunPtr tfp = (SONTypeFunPtr)con._con; + _internals.put(con,_code.link(tfp)); + return this; + } + public void jump( CFGNode jmp, CFGNode dst ) { + while( dst.nOuts() == 1 ) // Skip empty blocks + dst = dst.uctrl(); + _internals.put(jmp,dst); + } + + + final HashMap _externals = new HashMap<>(); + public Encoding external( Node call, String extern ) { + _externals.put(call,extern); + return this; + } + + // Store t as a 32/64 bit constant in the code space; generate RIP-relative + // addressing to load it + + public final HashMap _bigCons = new HashMap<>(); + public final HashMap _cpool = new HashMap<>(); + public void largeConstant( Node relo, SONType t ) { + assert t.isConstant(); + _bigCons.put(relo,t); + } + + void encode() { + // Basic block layout: invert branches to keep blocks in-order; insert + // unconditional jumps. Attempt to keep backwards branches taken, + // forwards not-taken (this is the default prediction on most + // hardware). Layout is still RPO but with more restrictions. + basicBlockLayout(); + + // Write encoding bits in order into a big byte array. + // Record opcode start and length. + writeEncodings(); + + // Write any large constants into a constant pool; they + // are accessed by RIP-relative addressing. + writeConstantPool(); + + // Short-form RIP-relative support: replace long encodings with short + // encodings and compact the code, changing all the offsets. + compactShortForm(); + + // Patch RIP-relative and local encodings now. + patchLocalRelocations(); + } + + // Basic block layout: invert branches to keep blocks in-order; insert + // unconditional jumps. Attempt to keep backwards branches taken, + // forwards not-taken (this is the default prediction on most + // hardware). Layout is still RPO but with more restrictions. + private void basicBlockLayout() { + Ary rpo = new Ary<>(CFGNode.class); + BitSet visit = _code.visit(); + for( Node n : _code._start._outputs ) + if( n instanceof FunNode fun ) + _rpo_cfg(fun, visit, rpo); + rpo.add(_code._start); + + // Reverse in-place + for( int i=0; i< rpo.size()>>1; i++ ) + rpo.swap(i,rpo.size()-1-i); + visit.clear(); + _code._cfg = rpo; // Save the new ordering + } + + // Basic block layout. Now that RegAlloc is finished, no more spill code + // will appear. We can change our BB layout from RPO to something that + // minimizes actual branches, takes advantage of fall-through edges, and + // tries to help simple branch predictions: back branches are predicted + // taken, forward not-taken. + private void _rpo_cfg(CFGNode bb, BitSet visit, Ary rpo) { + if( visit.get(bb._nid) ) return; + visit.set(bb._nid); + if( bb.nOuts()==0 ) return; // StopNode + if( !(bb instanceof IfNode iff) ) { + CFGNode next = bb instanceof ReturnNode ? (CFGNode)bb.out(bb.nOuts()-1) : bb.uctrl(); + // If the *next* BB has already been visited, we may need an + // unconditional jump here + if( visit.get(next._nid) && !(next instanceof StopNode) ) { + // If all the blocks, in RPO order, to our target + // are empty, we will fall in and not need a jump. + if( next instanceof LoopNode || !isEmptyBackwardsScan(rpo,next) ) { + CFGNode jmp = _code._mach.jump(); + jmp.setDefX(0,bb); + next.setDef(next._inputs.find(bb),jmp); + rpo.add(jmp); + } + } + _rpo_cfg(next,visit,rpo); + } else { + boolean invert = false; + // Pick out the T/F projections + CProjNode t = iff.cproj(0); + CProjNode f = iff.cproj(1); + int tld = t.loopDepth(), fld = f.loopDepth(), bld = bb.loopDepth(); + // Decide entering or exiting a loop + if( tld==bld ) { + // if T is empty, keep + if( t.nOuts()>1 && + // Else swap so T is forward and exits, while F falls into next loop block + ((fld bld ) { // True enters a deeper loop + throw Utils.TODO(); + } else if( fld == bld && f.out(0) instanceof LoopNode ) { // Else True exits a loop + // if false is the loop backedge, make sure its true/taken + invert = true; + } // always forward and good + // Invert test and Proj fields + if( invert ) { + iff.invert(); + t.invert(); + f.invert(); + CProjNode tmp=f; f=t; t=tmp; // Swap t/f + } + + // Always visit the False side last (so True side first), so that + // when the False RPO visit returns, the IF is immediately next. + // When the RPO is reversed, the fall-through path will always be + // following the IF. + _rpo_cfg(t,visit,rpo); + _rpo_cfg(f,visit,rpo); + } + rpo.add(bb); + } + + private static boolean isEmptyBackwardsScan(Ary rpo, CFGNode next) { + for( int i=rpo._len-1; rpo.at(i)!=next; i-- ) + if( rpo.at(i).nOuts()!=1 ) + return false; + return true; + } + + // Write encoding bits in order into a big byte array. + // Record opcode start and length. + public FunNode _fun; // Currently encoding function + private void writeEncodings() { + _opStart= new int [_code.UID()]; + _opLen = new byte[_code.UID()]; + for( CFGNode bb : _code._cfg ) { + if( !(bb instanceof MachNode mach0) ) + _opStart[bb._nid] = _bits.size(); + else if( bb instanceof FunNode fun ) { + _fun = fun; // Currently encoding function + _opStart[bb._nid] = _bits.size(); + mach0.encoding( this ); + _opLen[bb._nid] = (byte) (_bits.size() - _opStart[bb._nid]); + } + for( Node n : bb._outputs ) { + if( n instanceof MachNode mach && !(n instanceof FunNode) ) { + _opStart[n._nid] = _bits.size(); + mach.encoding( this ); + _opLen[n._nid] = (byte) (_bits.size() - _opStart[n._nid]); + } + } + } + pad8(); + } + + // Write the constant pool + private void writeConstantPool() { + // TODO: Check for cpool dups + HashSet ts = new HashSet<>(); + for( SONType t : _bigCons.values() ) { + if( ts.contains(t) ) + throw Utils.TODO(); // Dup! Compress! + ts.add(t); + } + + // Write the 8-byte constants + for( Node relo : _bigCons.keySet() ) { + SONType t = _bigCons.get(relo); + if( t.log_size()==3 ) { + // Map from relo to constant start + _cpool.put(relo,_bits.size()); + long x = t instanceof SONTypeInteger ti + ? ti.value() + : Double.doubleToRawLongBits(((SONTypeFloat)t).value()); + add8(x); + } + } + + // Write the 4-byte constants + for( Node relo : _bigCons.keySet() ) { + SONType t = _bigCons.get(relo); + if( t.log_size()==2 ) { + // Map from relo to constant start + _cpool.put(relo,_bits.size()); + int x = t instanceof SONTypeInteger ti + ? (int)ti.value() + : Float.floatToRawIntBits((float)((SONTypeFloat)t).value()); + add4(x); + } + } + } + + // Short-form RIP-relative support: replace short encodings with long + // encodings and expand the code, changing all the offsets. + private void compactShortForm() { + int len = _code._cfg._len; + int[] oldStarts = new int[len]; + for( int i=0; i 0; + int delta = _opStart[target._nid] - _opStart[bb._nid]; + byte opLen = riprel.encSize(delta); + // Recorded size is smaller than the current size? + if( _opLen[bb._nid] < opLen ) { + // Start sliding the code down; record slide amount and new size + slide += opLen - _opLen[bb._nid]; + _opLen[bb._nid] = opLen; + } + } + } + + // CPool padding is non-linear; in rare cases padding can force a + // larger size... which will shrink the padding and allow the + // short form to work. Too bad. + if( !_cpool.isEmpty() ) + pad8(); + } + + + // Copy/slide the bits to make space for all the longer branches + int grow = _opStart[_code._cfg.at(len-1)._nid] - oldStarts[len-1]; + if( grow > 0 ) { // If no short-form ops, nothing to do here + int end = _bits.size(); + byte[] bits = new byte[end+grow]; + for( int i=len-1; i>=0; i-- ) { + int start = oldStarts[i]; + if( start==0 && i>1 ) continue; + int oldStart = oldStarts[i]; + int newStart = _opStart[_code._cfg.at(i)._nid]; + System.arraycopy(_bits.buf(),oldStart,bits,newStart,end-start); + end = start; + } + _bits.set(bits,bits.length); + } + } + + // Patch local encodings now + private void patchLocalRelocations() { + // Walk the local code-address relocations + for( Node src : _internals.keySet() ) { + Node dst = _internals.get(src); + int target = _opStart[dst._nid]; + int start = _opStart[src._nid]; + ((RIPRelSize)src).patch(this, start, _opLen[src._nid], target - start); + } + + for( Node src : _cpool.keySet() ) { + int target = _cpool.get(src); + int start = _opStart[src._nid]; + ((RIPRelSize)src).patch(this, start, _opLen[src._nid], target - start); + } + } + + + // Actual stack layout is up to each CPU. + // X86, with too many args & spills: + // | CALLER | + // | argN | // slot 1, required by callER + // +--------+ + // | RPC | // slot 0, required by callER + // | callee | // slot 3, callEE + // | callee | // slot 2, callEE + // | PAD16 | + // +--------+ + + // RISC/ARM, with too many args & spills: + // | CALLER | + // | argN | // slot 0, required by callER + // +--------+ + // | callee | // slot 3, callEE: might be RPC + // | callee | // slot 2, callEE + // | callee | // slot 1, callEE + // | PAD16 | + // +--------+ + private void frameSize() { + for( Node n : _code._start.outs() ) { + if( n instanceof FunNode fun ) { + throw Utils.TODO(); + } + } + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/GlobalCodeMotion.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/GlobalCodeMotion.java new file mode 100644 index 0000000..f0af74b --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/GlobalCodeMotion.java @@ -0,0 +1,281 @@ +package com.compilerprogramming.ezlang.compiler.codegen; + +import com.compilerprogramming.ezlang.compiler.Ary; +import com.compilerprogramming.ezlang.compiler.IterPeeps.WorkList; +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import com.compilerprogramming.ezlang.compiler.sontypes.*; +import java.util.*; + +public abstract class GlobalCodeMotion { + + // Arrange that the existing isCFG() Nodes form a valid CFG. The + // Node.use(0) is always a block tail (either IfNode or head of the + // following block). There are no unreachable infinite loops. + public static void buildCFG( CodeGen code ) { + Ary rpo = new Ary<>(CFGNode.class); + _rpo_cfg(null, code._start, code.visit(), rpo); + // Reverse in-place + for( int i=0; i< rpo.size()>>1; i++ ) + rpo.swap(i,rpo.size()-1-i); + // Set global CFG + code._cfg = rpo; + + schedEarly(code); + + // Break up shared global constants by functions + breakUpGlobalConstants(code); + + code._visit.clear(); + schedLate (code); + } + + // Post-Order of CFG + private static void _rpo_cfg(CFGNode def, Node use, BitSet visit, Ary rpo) { + if( use instanceof CallNode call ) call.unlink_all(); + if( !(use instanceof CFGNode cfg) || visit.get(cfg._nid) ) + return; // Been there, done that + if( def instanceof ReturnNode && use instanceof CallEndNode ) + return; + assert !( def instanceof CallNode && use instanceof FunNode ); + visit.set(cfg._nid); + for( Node useuse : cfg._outputs ) + _rpo_cfg(cfg,useuse,visit,rpo); + rpo.add(cfg); + } + + // Break up shared global constants by functions + private static void breakUpGlobalConstants( CodeGen code ) { + // For all global constants + for( int i=0; i< code._start.nOuts(); i++ ) { + Node con = code._start.out(i); + if( con instanceof MachNode mach && mach.isClone() ) { + // While constant has users in different functions + while( true ) { + // Find a function user, and another function + FunNode fun = null; + boolean done=true; + for( Node use : con.outs() ) { + FunNode fun2 = use.cfg0().fun(); + if( fun==null || fun==fun2 ) fun=fun2; + else { done=false; break; } + } + // Single function user, so this constant is not shared + if( done ) { i--; con.setDef(0,fun); break; } + // Move function users to a private constant + Node con2 = mach.copy(); // Private constant clone + con2._inputs.set(0,null); // Preserve edge invariants from clone + con2.setDef(0,fun); + // Move function users to this private constant + for( int j=0; j early.idepth() ) + early = n.in(i).cfg0(); // Latest/deepest input + n.setDef(0,early); + } + } + + // ------------------------------------------------------------------------ + private static void schedLate( CodeGen code ) { + CFGNode[] late = new CFGNode[code.UID()]; + Node[] ns = new Node[code.UID()]; + // Breadth-first scheduling + breadth(code._stop,ns,late); + + // Copy the best placement choice into the control slot + for( int i=0; i work = new WorkList<>(); + work.push(stop); + Node n; + outer: + while( (n = work.pop()) != null ) { + assert late[n._nid]==null; // No double visit + // These I know the late schedule of, and need to set early for loops + if( n instanceof CFGNode cfg ) late[n._nid] = cfg.blockHead() ? cfg : cfg.cfg(0); + else if( n instanceof PhiNode phi ) late[n._nid] = phi.region(); + else if( n instanceof ProjNode && n.in(0) instanceof CFGNode cfg ) late[n._nid] = cfg; + else { + + // All uses done? + for( Node use : n._outputs ) + if( use!=null && late[use._nid]==null ) + continue outer; // Nope, await all uses done + + // Loads need their memory inputs' uses also done + if( n instanceof LoadNode ld ) + for( Node memuse : ld.mem()._outputs ) + if( late[memuse._nid]==null && + // New makes new memory, never crushes load memory + !(memuse instanceof NewNode) && + // Load-use directly defines memory + (memuse._type instanceof SONTypeMem || + // Load-use indirectly defines memory + (memuse._type instanceof SONTypeTuple tt && tt._types[ld._alias] instanceof SONTypeMem)) ) + continue outer; + + // All uses done, schedule + _doSchedLate(n,ns,late); + } + + // Walk all inputs and put on worklist, as their last-use might now be done + for( Node def : n._inputs ) + if( def!=null && late[def._nid]==null ) { + work.push(def); + // if the def has a load use, maybe the load can fire + for( Node ld : def._outputs ) + if( ld instanceof LoadNode && late[ld._nid]==null ) + work.push(ld); + } + } + } + + private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late) { + // Walk uses, gathering the LCA (Least Common Ancestor) of uses + CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0(); + assert early != null; + CFGNode lca = null; + for( Node use : n._outputs ) + if( use != null ) + lca = use_block(n,use, late)._idom(lca,null); + + // Loads may need anti-dependencies, raising their LCA + if( n instanceof LoadNode load ) + lca = find_anti_dep(lca,load,early,late); + + // Walk up from the LCA to the early, looking for best place. This is + // the lowest execution frequency, approximated by least loop depth and + // deepest control flow. + CFGNode best = lca; + lca = lca.idom(); // Already found best for starting LCA + for( ; lca != early.idom(); lca = lca.idom() ) + if( better(lca,best) ) + best = lca; + assert !(best instanceof IfNode); + ns [n._nid] = n; + late[n._nid] = best; + } + + // Block of use. Normally from late[] schedule, except for Phis, which go + // to the matching Region input. + private static CFGNode use_block(Node n, Node use, CFGNode[] late) { + if( !(use instanceof PhiNode phi) ) + return late[use._nid]; + CFGNode found=null; + for( int i=1; i best.idepth() || best instanceof IfNode); + } + + private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late) { + // We could skip final-field loads here. + // Walk LCA->early, flagging Load's block location choices + for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() ) + cfg._anti = load._nid; + // Walk load->mem uses, looking for Stores causing an anti-dep + for( Node mem : load.mem()._outputs ) { + switch( mem ) { + case StoreNode st: + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st); + break; + case CallNode st: + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st); + break; + case PhiNode phi: + // Repeat anti-dep for matching Phi inputs. + // No anti-dep edges but may raise the LCA. + for( int i=1; i> BBOUTS = new IdentityHashMap<>(); + static void resetBBLiveOut() { + for( IdentityHashMap bbout : BBOUTS.values() ) + bbout.clear(); + } + static final IdentityHashMap TMP = new IdentityHashMap<>(); + + // Inteference Graph: Array of Bitsets + static final Ary IFG = new Ary<>(BitSet.class); + static void resetIFG() { + for( BitSet bs : IFG ) + if( bs!=null ) bs.clear(); + } + + // Set matching bit + private static void addIFG( LRG lrg0, LRG lrg1 ) { + short x0 = lrg0._lrg, x1 = lrg1._lrg; + // Triangulate + if( x0 < x1 ) _addIFG(x0,x1); + else _addIFG(x1,x0); + } + // Add lrg1 to lrg0's conflict set + private static void _addIFG( short x0, short x1 ) { + BitSet ifg = IFG.atX(x0); + if( ifg==null ) + IFG.setX(x0,ifg = new BitSet()); + ifg.set(x1); + } + + + private static final Ary WORK = new Ary<>(CFGNode.class); + static void pushWork(CFGNode bb) { + if( WORK.find(bb) == -1 ) + WORK.push(bb); + } + + // ------------------------------------------------------------------------ + // Visit all blocks, using the live-out LRGs per-block and doing a backwards + // walk over each block. At the end of the walk, push the live-out sets to + // prior blocks. Due to loops, no single visitation order suffices. This + // problem can be fairly efficiently solved with a proper LIVE calculation + // and bitsets. In the interests of simplicity (and assumption of smaller + // programs) I am skipping LIVE and directly computing it during the IFG + // building. + + // Start by setting the live-out of the exit block (to empty), and putting + // it on a worklist. + + // - Pull from worklist a block who's live-out has changed + // - Walk backwards, adding interferences and computing live. + // - At block head, "push" the new live-out to prior blocks. + // - - If they pick up new live-outs, put them on the worklist. + + public static boolean build( int round, RegAlloc alloc ) { + // Reset all to empty + resetBBLiveOut(); + resetIFG(); + + // Last block has nothing live out + assert WORK.isEmpty(); + for( CFGNode bb : alloc._code._cfg ) + if( bb.blockHead()) + WORK.push( bb ); + + // Process blocks until no more changes + while( !WORK.isEmpty() ) + do_block(round,alloc,WORK.pop()); + + return alloc.success(); + } + + // Walk one block backwards, compute live-in from live-out, and build IFG + static void do_block( int round, RegAlloc alloc, CFGNode bb ) { + assert bb.blockHead(); + IdentityHashMap live_out = BBOUTS.get(bb); + TMP.clear(); + if( live_out != null ) + TMP.putAll(live_out); // Copy bits to temp + + // A backwards walk over instructions in the basic block + for( int inum = bb.nOuts()-1; inum >= 0; inum-- ) { + Node n = bb.out(inum); + if( n.in(0) != bb ) continue; + // In a backwards walk, proj users come before the node itself + if( n instanceof MultiNode ) + for( Node proj : n.outs() ) + if( proj instanceof ProjNode ) + do_node(alloc,proj); + if( n instanceof MachNode ) + do_node(alloc,n); + } + + // The bb head kills register, e.g. a CallEnd and caller-save registers + if( bb instanceof MachNode mach ) + kills(alloc,mach); + + // Push live-sets backwards to priors in CFG. + if( bb instanceof RegionNode ) + for( int i=1; i1 || // Has many users OR + // Only 1 user but effective use is remote block + effUseBlk != n.cfg0() )) + // Then fail the clonable; it should split or move + alloc.fail(alloc.lrg((Node)m)); + // Else clonable cannot move + else if( !tlrg.sub(killMask) ) + alloc.fail(tlrg); + } + } + } + + // Check for self-conflict live ranges. These must split, and only happens + // during the first round a particular LRG splits. + private static void selfConflict(RegAlloc alloc, Node n, LRG lrg) { + selfConflict(alloc,n,lrg,TMP.get(lrg)); + } + private static void selfConflict(RegAlloc alloc, Node n, LRG lrg, Node prior) { + if( prior!=null && prior != n ) { + lrg.selfConflict(prior); + lrg.selfConflict(n); + alloc.fail(lrg); // 2 unrelated values live at once same live range; self-conflict + } + } + + // Merge TMP into bb's live-out set; if changes put bb on WORK + private static void mergeLiveOut( RegAlloc alloc, CFGNode priorbb, int i ) { + CFGNode bb = priorbb.cfg(i); + if( bb == null ) return; // Start has no prior + if( !bb.blockHead() ) bb = bb.cfg0(); + //if( i==0 && !(bb instanceof StartNode) ) bb = bb.cfg0(); + assert bb.blockHead(); + + // Lazy get live-out set for bb + IdentityHashMap lrgs = BBOUTS.computeIfAbsent( bb, k -> new IdentityHashMap<>() ); + + for( LRG lrg : TMP.keySet() ) { + Node def = TMP.get(lrg); + // Effective def comes from phi input from prior block + if( def instanceof PhiNode phi && phi.cfg0()==priorbb ) { + assert i!=0; + def = phi.in( i ); + } + Node def_bb = lrgs.get(lrg); + if( def_bb==null ) { + lrgs.put(lrg,def); + pushWork(bb); + } else { + // Alive twice with different definitions; self-conflict + selfConflict(alloc,def,lrg,def_bb); + } + } + } + + + // ------------------------------------------------------------------------ + // Color the inference graph. + + // Coloring works by removing "trivial" live ranges from the IFG - those + // live ranges with fewer neighbors than available colors (registers). + // These are trivial because even if every neighbor takes a unique color, + // there's at least one more available to color this live range. + + // If we hit a clique of non-trivial live ranges, we pick one to be "at + // risk" of not-coloring. Good choices include live ranges with a large + // area and few hot uses. + + // Then we reverse and put-back live ranges - picking a color as we go. + // If there's no spare color we'll have to spill this at-risk live range. + + public static boolean color(int round, RegAlloc alloc) { + int maxlrg = alloc._LRGS.length, nlrgs=0; + for( int i=1; i swork ) + swork = sptr; + + // Walk all neighbors and remove + if( lrg._adj != null ) { + for( LRG nlrg : lrg._adj ) { + // Remove and compress out neighbor + if( nlrg.removeCompress(lrg) ) { + // Neighbor is just exactly going trivial as 'lrg' is removed from IFG + // Find "j" position in the color_stack + int jx = swork; while( color_stack[jx] != nlrg ) jx++; + // Add trivial neighbor to trivial list. Pull lrg j out of + // unknown set, since its now in the trivial set + swap(color_stack, swork++, jx); + } + } + } + } + + // Reverse simplify (unstack the color stack), and set colors (registers) for live ranges + while( sptr > 0 ) { + LRG lrg = color_stack[--sptr]; + if( lrg==null ) continue; + RegMaskRW rmask = lrg._mask.copy(); + // Walk neighbors and remove adjacent colors + if( lrg._adj!=null ) { + for( LRG nlrg : lrg._adj ) { + nlrg.reinsert(lrg); + int reg = nlrg._reg; + if( reg!= -1 ) // Failed neighbors do not count + rmask.clr(reg); // Remove neighbor from my choices + } + } + // At-risk live-range did not color? + if( rmask.isEmpty() ) { + alloc.fail(lrg); + lrg._reg = -1; + } else { + // Pick first available register + short reg = rmask.firstReg(); + // Pick a "good" color from the choices. Typically, biased-coloring + // removes some over-spilling. + if( rmask.size() > 1 ) reg = biasColor(alloc,lrg,reg,rmask); + lrg._reg = reg; // Assign the register + } + } + + return alloc.success(); + } + + // Pick LRG from color stack + private static void pickColor(LRG[] color_stack, int sptr, int swork) { + // Out of trivial colorable, pick an at-risk to pull + if( sptr==swork ) + swap(color_stack,sptr,pickRisky(color_stack,sptr)); + // When coloring, we'd like to give more choices; so when coloring we'd + // like to see the single-def first (since no choices anyway), then + // non-split related (so more live ranges get colored), then + // split-related last, so they have more colors to bias towards. + + // Working in reverse, pick first split-related with many regs, then + // those with some regs, then single-def. + int bidx=sptr; + LRG best=color_stack[bidx]; + for( int idx = sptr+1; idx < swork; idx++ ) + if( betterLRG(best,color_stack[idx]) ) + best = color_stack[bidx=idx]; + if( bidx != sptr ) + swap(color_stack,sptr,bidx); // Pick best at sptr + } + + private static boolean betterLRG( LRG best, LRG lrg ) { + // If single-def varies, keep the not-single-def + if( best.size1() != lrg.size1() ) + return best.size1(); + // If hasSplit varies, keep the hasSplit + if( best.hasSplit() != lrg.hasSplit() ) + return lrg.hasSplit(); + // Keep large register count + return best.size() < lrg.size(); + } + + // Pick a live range that hasn't already spilled, or has a single-def- + // single-use that are not adjacent. + private static int pickRisky( LRG[] color_stack, int sptr ) { + int best=sptr; + int bestScore = pickRiskyScore(color_stack[best]); + for( int i=sptr+1; i bestScore ) + { best = i; bestScore = iScore; } + } + return best; + } + + // Pick a live range to pull, that might not color. + // + // Picking a live range with a very large span, with defs and uses outside + // loops means spilling a relative cheap live range and getting that + // register over a large area. + // + // Picking a live range that is very close to coloring might allow it to + // color despite being risky. + private static int pickRiskyScore( LRG lrg ) { + // Pick single-def clonables that are not right next to their single-use. + // Failing to color these will clone them closer to their uses. + if( !lrg._multiDef && lrg._machDef.isClone() ) { + Node def = ((Node)lrg._machDef); + Node use = ((Node)lrg._machUse); + CFGNode cfg = def.cfg0(); + if( cfg != use.cfg0() || // Different blocks OR + // Same block, but not close + cfg._outputs.find(def) < cfg._outputs.find(use)+1 ) + return 1000000; + } + + // Always pick callee-save registers as being very large area recovered + // and very cheap to spill. + if( lrg._machDef instanceof CalleeSaveNode ) + return 1000000-2; + if( lrg._splitDef != null && lrg._splitDef.in(1) instanceof CalleeSaveNode && + lrg._splitUse != null && lrg._splitUse.out(0) instanceof ReturnNode ) + return 1000000-1; + + // TODO: cost/benefit model. Perhaps counting loop-depth (freq) of def/use for cost + // and "area" for benefit + return 1000; + } + + private static short biasColor( RegAlloc alloc, LRG lrg, short reg, RegMask mask ) { + if( mask.size1() ) return reg; + // Check chain of splits up the def-chain. Take first allocated + // register, and if it's available in the mask, take it. + Node defSplit = lrg._splitDef, useSplit = lrg._splitUse; + int tidx, cnt=0; + + while( (tidx=biasable(defSplit)) != 0 || biasable(useSplit) != 0 ) { + if( cnt++ > 10 ) break; + + if( tidx != 0 ) { + short bias = biasColor( alloc, defSplit, mask ); + if( bias >= 0 ) return bias; // Good bias + if( bias == -2 ) defSplit = null; // Kill this side, no more searching + } else defSplit = null; + + if( biasable(useSplit) != 0 ) { + short bias = biasColor( alloc, useSplit, mask ); + if( bias >= 0 ) return bias; // Good bias + if( bias == -2 ) useSplit = null; // Kill this side, no more searching + } else useSplit = null; + + if( defSplit != null ) { + short bias = biasColorNeighbors( alloc, defSplit, mask ); + if( bias >= 0 ) return bias; + // Advance def side + defSplit = defSplit.in(tidx); + } + + if( useSplit != null ) { + short bias = biasColorNeighbors( alloc, useSplit, mask ); + if( bias >= 0 ) return bias; + useSplit = useSplit.out(0); + } + + } + return mask.firstReg(); + } + + private static int biasable(Node split) { + if( split instanceof SplitNode ) return 1; // Yes biasable, advance is slot 1 + if( split instanceof PhiNode ) return 1; // Yes biasable, advance is slot 1 + if( !(split instanceof MachNode mach) ) return 0; // Not biasable + return mach.twoAddress(); // Only biasable if 2-addr + } + + // 3-way return: + // - good bias reg, take it & exit + // - this path is cutoff; do not search here anymore + // - advance this side + private static short biasColor( RegAlloc alloc, Node split, RegMask mask ) { + short bias = alloc.lrg(split)._reg; + if( bias != -1 ) { + if( mask.test(bias) ) return bias; // Good bias + else return -2; // Kill this side + } else return -1; // Advance this side + } + + // Check if we can match "split" color, or else trim "mask" to colors + // "split" might get. + private static short biasColorNeighbors( RegAlloc alloc, Node split, RegMask mask ) { + LRG slrg = alloc.lrg(split); + if( slrg._adj == null ) return -1; // No trimming + + // Can I limit my own choices to valid neighbor choices? + for( LRG alrg : slrg._adj ) { + int reg = alrg._reg; + if( reg == -1 && alrg._mask.size1() ) + reg = alrg._mask.firstReg(); + if( reg != -1 ) { + mask.clr(alrg._reg); + if( mask.size1() ) + return mask.firstReg(); + } + } + return -1; // No obvious color choice, but mask got trimmed + } + + private static void swap( LRG[] ary, int x, int y ) { + LRG tmp = ary[x]; ary[x] = ary[y]; ary[y] = tmp; + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/LRG.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/LRG.java new file mode 100644 index 0000000..0662c5f --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/LRG.java @@ -0,0 +1,235 @@ +package com.compilerprogramming.ezlang.compiler.codegen; + +import com.compilerprogramming.ezlang.compiler.Ary; +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +import java.util.IdentityHashMap; + +/** A Live Range + *

+ * A live range is a set of nodes and edges which must get the same register. + * Live ranges form an interconnected web with almost no limits on their + * shape. Live ranges also gather the set of register constraints from all + * their parts. + *

+ * Most of the fields in this class are for spill heuristics, but there are a + * few key ones that define what a LRG is. LRGs have a unique dense integer + * `_lrg` number which names the LRG. New `_lrg` numbers come from the + * `RegAlloc._lrg_num` counter. LRGs can be unioned together - + * (this is the Union-Find algorithm) + * - and when this happens the lower numbered `_lrg` wins. Unioning only + * happens during `BuildLRG` and happens because either a `Phi` is forcing all + * its inputs and outputs into the same register, or because of a 2-address + * instruction. LRGs have matching `union` and `find` calls, and a set + * `_leader` field. + */ +public class LRG { + + // Dense live range numbers + final short _lrg; + + // U-F leader; null if leader + LRG _leader; + + // Choosen register + short _reg; + + // Count of single-register defs and uses + short _1regDefCnt, _1regUseCnt; + // Count of all defs, uses. Mostly interested 1 vs many + boolean _multiDef, _multiUse; + + // A sample MachNode def in the live range + public MachNode _machDef, _machUse; + short _uidx; // _machUse input + + // Mask set to empty via a kill-mask. This is usually a capacity kill, + // which means we need to split and spill into memory - which means even if + // the def-side has many registers, it MUST spill. + boolean _killed; + + // Some splits used in biased coloring + MachConcreteNode _splitDef, _splitUse; + + // All the self-conflicting defs for this live range + IdentityHashMap _selfConflicts; + + // AND of all masks involved; null if none have been applied yet + RegMask _mask; + + // Adjacent Live Range neighbors. Only valid during coloring + Ary _adj; + + void addNeighbor(LRG lrg) { + if( _adj==null ) _adj = new Ary<>(LRG.class); + _adj.push(lrg); + } + + // Remove and compress neighbor list; store ex-neighbor past + // end for unwinding Simplify. True if exactly going low-degree + boolean removeCompress( LRG lrg ) { + _adj.swap(_adj.find(lrg),_adj._len-1); + return _adj._len-- == _mask.size(); + } + + void reinsert( LRG lrg ) { + assert _adj._es[_adj._len]==lrg; + _adj._len++; + } + + // More registers than neighbors + boolean lowDegree() { return (_adj==null ? 0 : _adj._len) < _mask.size(); } + + LRG( short lrg ) { _lrg = lrg; _reg = -1; } + + boolean leader() { return _leader == null; } + + // Fast-path Find from the Union-Find algorithm + LRG find() { + if( _leader==null ) return this; // I am the leader + if( _leader._leader==null ) // I point to the leader + return _leader; + return _rollup(); + } + // Slow-path rollup of U-F + private LRG _rollup() { + LRG ldr = _leader._leader; + // Roll-up + while( ldr._leader!=null ) ldr = ldr._leader; + LRG l2 = this; + while( l2 != ldr ) { + LRG l3 = l2._leader; + l2._leader = ldr; + l2 = l3; + } + return ldr; + } + + // Union `this` and `lrg`, keeping the lower numbered _lrg. + // Includes a number of fast-path cutouts. + LRG union( LRG lrg ) { + assert leader(); + if( lrg==null ) return this; + lrg = lrg.find(); + if( lrg==this ) return this; + return _lrg < lrg._lrg ? _union(lrg) : lrg._union(this); + } + // Union `this` and `lrg`, folding together all stats. + private LRG _union( LRG lrg ) { + // Set U-F leader + lrg._leader = this; + // Fold together stats + if( _machDef==null ) { + _machDef = lrg._machDef; + } else if( lrg._machDef!=null ) { + if( _machDef != lrg._machDef ) _multiDef=true; + if( _1regDefCnt==0 ) + _machDef = lrg._machDef; + else if( _machDef==lrg._machDef ) + _1regDefCnt--; + } + _1regDefCnt += lrg._1regDefCnt; + _multiDef |= lrg._multiDef; + + if( _machUse==null ) { + _machUse = lrg._machUse; + } else if( lrg._machUse!=null ) { + if( _machUse != lrg._machUse ) _multiUse=true; + if( _1regUseCnt==0 ) { + _machUse = lrg._machUse; + _uidx = lrg._uidx; + } + else if( _machUse==lrg._machUse ) + _1regUseCnt--; + } + _1regUseCnt += lrg._1regUseCnt; + _multiUse |= lrg._multiUse; + + // Fold deepest Split + _splitDef = deepSplit(_splitDef,lrg._splitDef); + _splitUse = deepSplit(_splitUse,lrg._splitUse); + + // Fold together masks + RegMask mask = _mask.and(lrg._mask); + if( mask==null ) + mask = _mask.copy().and(lrg._mask); + _mask = mask; + return this; + } + + private static MachConcreteNode deepSplit( MachConcreteNode s0, MachConcreteNode s1 ) { + return s0==null || (s1!=null && s0.cfg0().loopDepth() < s1.cfg0().loopDepth()) ? s1 : s0; + } + + // Record any Mach def for spilling heuristics + LRG machDef( MachNode def, boolean size1 ) { + if( _machDef!=null && _machDef!=def ) + _multiDef = true; + if( _machDef==null || size1 ) + _machDef = def; + if( size1 ) + _1regDefCnt++; + if( def instanceof SplitNode split ) + _splitDef = deepSplit(_splitDef,split); + return this; + } + + // Record any Mach use for spilling heuristics + LRG machUse( MachNode use, short uidx, boolean size1 ) { + if( _machUse!=null && _machUse!=use ) + _multiUse = true; + if( _machUse==null || size1 ) + { _machUse = use; _uidx = uidx; } + if( size1 ) + _1regUseCnt++; + if( use instanceof SplitNode split ) + _splitUse = deepSplit(_splitUse,split); + return this; + } + + boolean hasSplit() { return _splitDef != null || _splitUse != null; } + short size() { return _mask.size(); } + boolean size1() { return _mask.size1(); } + + + void selfConflict( Node def ) { + if( _selfConflicts == null ) _selfConflicts = new IdentityHashMap<>(); + _selfConflicts.put(def,""); + } + + + // Record intersection of all register masks. + // True if still has registers + boolean and( RegMask mask ) { + RegMask mask2 = mask.and(_mask); + if( mask2==null ) + mask2 = _mask.copy().and(mask); + _mask = mask2; + return !_mask.isEmpty(); + } + // Remove this singular register + // True if still has registers + boolean clr( int reg ) { + if( _mask.clr(reg) ) return true; + _mask = _mask.copy(); // Need a mutable copy + return _mask.clr(reg); + } + // Subtract mask (AND complement) + // True if still has registers + boolean sub( RegMask mask ) { + RegMask mask2 = _mask.sub(mask); + if( mask2==null ) + mask2 = _mask.copy().sub(mask); + _mask = mask2; + return !_mask.isEmpty(); + } + + @Override public String toString() { return toString(new SB()).toString(); } + + public SB toString( SB sb ) { + sb.p("V").p(_lrg); + if( _mask!=null ) _mask.toString(sb); + return sb; + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/ListScheduler.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/ListScheduler.java new file mode 100644 index 0000000..220e7fb --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/ListScheduler.java @@ -0,0 +1,258 @@ +package com.compilerprogramming.ezlang.compiler.codegen; + +import com.compilerprogramming.ezlang.compiler.Ary; +import com.compilerprogramming.ezlang.compiler.IterPeeps.WorkList; +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import com.compilerprogramming.ezlang.compiler.sontypes.*; +import java.util.*; + +public abstract class ListScheduler { + + // eXtra per-node stuff for scheduling. + // Nodes are all block-local, and projections fold into their parent. + private static final IdentityHashMap XS = new IdentityHashMap<>(); + private static class XSched { + + Node _n; // Node this extra is for + int _bcnt; // Not-ready not-scheduled inputs + int _rcnt; // Ready not-scheduled inputs + boolean _ruse; // Node IS a "remote use", uses a other-block value + boolean _rdef; // Node IS a "remote def" of a value used in other block + boolean _single; // Defines a single register, likely to conflict + + static final Ary FREE = new Ary<>(XSched.class); + static void alloc(CFGNode bb, Node n) { + XSched x = FREE.pop(); + XS.put(n, (x==null ? new XSched() : x).init(bb,n)); + } + void free() { _n = null; FREE.push(this); } + + static XSched get(Node n) { return n==null ? null : XS.get(n instanceof ProjNode && !(n.in(0) instanceof CallEndNode) ? n.in(0) : n); } + + private XSched init(CFGNode bb, Node n) { + _n = n; + _bcnt = 0; + _ruse = _rdef = _single = false; + + // Count block-locals and remote inputs + if( !(n instanceof PhiNode) ) + for( Node def : n._inputs ) + if( def != null && def != bb ) { + if( def.cfg0()==bb ) _bcnt++; // Raise block-local input count + else if( !def.isMem() ) _ruse = true; // Remote register user + } + // Count remote outputs and allowed registers + if( n instanceof MultiNode ) + for( Node use : n._outputs ) + computeSingleRDef(bb,use); + else + computeSingleRDef(bb,n); + return this; + } + + private void computeSingleRDef(CFGNode bb, Node n) { + // See if this is 2-input, and that input is single-def + if( n instanceof MachNode mach && mach.twoAddress() != 0 ) { + XSched xs = XS.get(n.in(mach.twoAddress())); + if( xs != null ) + _single = xs._single; + } + // Visit all outputs + RegMaskRW rmask = !_single && n instanceof MachNode mach && mach.outregmap() != null ? mach.outregmap().copy() : null; + for( Node use : n._outputs ) { + // Remote use, so this is a remote def + if( use!=null && use.cfg0()!=bb ) _rdef = true; + // And mask between n's def mask and all n's users masks' + if( use instanceof MachNode mach && rmask != null ) + for( int i=1; i 0 || _rcnt > 0 ) return false; + if( _n instanceof MultiNode ) + for( Node use : _n._outputs ) + if( use instanceof ProjNode ) readyUp(use); + // anti-dep from a multireg directly + else _readyUp(use); + else + readyUp(_n); + return true; + } + private static void readyUp(Node n) { + for( Node use : n._outputs ) + _readyUp(use); + } + private static void _readyUp(Node use) { + XSched xs = get(use); + if( xs!=null && !(use instanceof PhiNode) ) + { assert xs._bcnt>0; xs._bcnt--; xs._rcnt++; } + } + + boolean decIsReady() { + assert _rcnt > 0; + _rcnt--; + return isReady(); + } + } + + + // List schedule every block + public static void sched( CodeGen code ) { + XS.clear(); + for( CFGNode bb : code._cfg ) + if( bb.blockHead() ) + local(bb,false); + } + + // Classic list scheduler + private static void local( CFGNode bb, boolean trace ) { + assert XS.isEmpty(); + int len = bb.nOuts(); + + // Count block-locals, remote inputs and outputs + for( int i=0; i 0; + + // Classic list scheduler. Behind sched is scheduled. Between sched + // and ready have zero counts and are ready to schedule. Ahead of + // ready is undefined. + // + // As nodes are scheduled, the nodes they use decrement their bcnts[]. Once + // that gets to 0, all the nodes using it are scheduled, so it's ready. + int sched = 0; + while( sched < len ) { + assert sched < ready; + int pick = best(bb._outputs,sched,ready,trace); // Pick best + Node best = bb._outputs.swap(pick,sched++); // Swap best to head of ready and move into the scheduled set + // Lower ready count of users, and since projections are ready as + // soon as their multi is ready, lower those now too. + for( Node use : best._outputs ) + if( use instanceof ProjNode ) + for( Node useuse : use._outputs ) + ready = ready(bb,useuse,ready); + else + ready = ready(bb,use,ready); + } + + // Reset for next block + for( XSched x : XS.values() ) + x.free(); + XS.clear(); + } + + private static int ready(CFGNode bb, Node use, int ready) { + if( use!=null && use.in(0)==bb && !(use instanceof PhiNode) && XSched.get(use).decIsReady() ) + bb._outputs.set(ready++, use); // Became ready, move into ready set + //bb._outputs.swap(ready++,use); // Became ready, move into ready set + return ready; + } + + + // Pick best between sched and ready. + private static int best( Ary blk, int sched, int ready, boolean trace ) { + int pick = sched; + Node p = blk.at(pick); + int score = score(p); + if( trace ) { System.out.printf("%4d N%d ",score,p._nid); System.out.println(p); } + for( int i=sched+1; i score ) + { score = nscore; pick = i; p = n; } + } + if( trace ) { System.out.printf("Pick: N%d %s\n\n",p._nid,p); } + return pick; + } + + // Highest score wins. Max at 1000, min at 10, except specials. + private static final int[] CNT = new int[3]; + static int score( Node n ) { + if( n instanceof ProjNode ) return 1001; // Pinned at block entry + if( n instanceof CProjNode ) return 1001; // Pinned at block entry + if( n instanceof PhiNode ) return 1000; + if( n instanceof CFGNode ) return 1; // Pinned at block exit + + int score = 500; + // Ideal nodes ignore register pressure scheduling + if( !(n instanceof MachNode) ) + return score; + + // Register pressure local scheduling: avoid overlapping specialized + // registers usages. + + // Subtract 10 (delay) if this op forces a live range to exist that + // cannot be resolved immediately; i.e., live count goes up. Scale to + // 20 if multi-def. + + // Subtract 100 if this op forces a single-def live range to exist + // which might conflict on the same register with other live ranges. + // Defines a single register based on def & uses, and the output is not + // ready. Scale to 200 for multi-def. + CNT[1]=CNT[2]=0; + XSched xn = XSched.get(n); + // If defining a remote value, just generically stall alot. Value is + // used in a later block, can we delay until the end of this block? + if( xn._rdef ) score = 200; // If defining a remote value, just generically stall alot + boolean flags = false; + if( n instanceof MultiNode ) { + for( Node use : n._outputs ) + flags |= singleUseNotReady( use, xn._single ); + } else + flags |= singleUseNotReady( n, xn._single ); + score += -10 * Math.min( CNT[1], 2 ); + score += -100 * Math.min( CNT[2], 2 ); + if( flags ) return 10; + + // Add 10 if this op ends a single-def live range, add 20 for 2 or more. + // Scale to 100,200 if the single-def is also single-register. + CNT[1]=CNT[2]=0; + for( int i=1; i1 && n instanceof MachNode mach && mach.isClone() ) + return false; + for( Node use : n.outs() ) { + XSched xu = XSched.get(use); + if( xu != null && xu._n instanceof CFGNode ) return true; + if( xu != null && xu._bcnt > 0 ) + CNT[single ? 2 : 1]++; // Since bcnt>0 or CFG, stall until user is more ready + } + return false; + } + +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/Machine.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/Machine.java new file mode 100644 index 0000000..273ac75 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/Machine.java @@ -0,0 +1,48 @@ +package com.compilerprogramming.ezlang.compiler.codegen; + +import com.compilerprogramming.ezlang.compiler.nodes.*; + +abstract public class Machine { + // Human readable machine name. Something like "x86-64" or "arm" or "risc5" + public abstract String name(); + // Default opcode size for printing; 4 bytes for most 32-bit risc chips + public int defaultOpSize() { return 4; } + // Create a split op; any register to any register, including stack slots + public abstract SplitNode split( String kind, byte round, LRG lrg); + // List of caller-save registers + public abstract RegMask callerSave(); + // List of callee-save registers + public abstract RegMask calleeSave(); + // Return a MachNode unconditional branch + public abstract CFGNode jump(); + // Break an infinite loop + public abstract IfNode never( CFGNode ctrl ); + // Instruction select from ideal nodes + public abstract Node instSelect( Node n ); + + // Convert a register to a zero-based stack *slot*, or -1. + // Stack slots are assumed 8 bytes each. + // Actual stack layout is up to each CPU. + // X86, with too many args & spills: + // | CALLER | + // | argN | // slot 1, required by callER + // +--------+ + // | RPC | // slot 0, required by callER + // | callee | // slot 3, callEE + // | callee | // slot 2, callEE + // | PAD16 | + // +--------+ + + // RISC/ARM, with too many args & spills: + // | CALLER | + // | argN | // slot 0, required by callER + // +--------+ + // | callee | // slot 3, callEE: might be RPC + // | callee | // slot 2, callEE + // | callee | // slot 1, callEE + // | PAD16 | + // +--------+ + public abstract int stackSlot( int reg ); + // Human-readable name for a register number, e.g. "RAX" or "R0" + public abstract String reg( int reg ); +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RIPRelSize.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RIPRelSize.java new file mode 100644 index 0000000..0d35458 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RIPRelSize.java @@ -0,0 +1,14 @@ +package com.compilerprogramming.ezlang.compiler.codegen; + +// This Node rip-relative encodings has sizes that vary by delta +public interface RIPRelSize { + // delta is the distance from the opcode *start* to the target start. Each + // hardware target adjusts as needed, X86 measures from the opcode *end* + // and small jumps are 2 bytes, so they'll need to subtract 2. + byte encSize(int delta); + + // Patch full encoding in. Used for both short and long forms. delta + // again is from the opcode *start*. + void patch( Encoding enc, int opStart, int opLen, int delta ); + +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RegAlloc.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RegAlloc.java new file mode 100644 index 0000000..634ebf1 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RegAlloc.java @@ -0,0 +1,526 @@ +package com.compilerprogramming.ezlang.compiler.codegen; + +import com.compilerprogramming.ezlang.compiler.Ary; +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import java.util.Arrays; +import java.util.IdentityHashMap; + +/** + * "Briggs/Chaitin/Click". + * Graph coloring. + *

+ * Every def and every use have a bit-set of allowed registers. + * Fully general to bizarre chips. + * Multiple outputs fully supported, e.g. add-with-carry or combined div/rem or "xor rax,rax" setting both rax and flags. + *

+ * Intel 2-address accumulator-style ops fully supported. + * Op-to-stack-spill supported. + * All addressing modes supported. + * Register pairs could be supported with some grief. + *

+ * Splitting instead of spilling (simpler implementation). + * Stack slots are "just another register" (tiny stack frames, simpler implementation). + * Stack/unstack during coloring with a marvelous trick (simpler implementation). + *

+ * Both bitset and adjacency list formats for the interference graph; one of + * the few times it's faster to change data structures mid-flight rather than + * just wrap one of the two. + *

+ * Liveness computation and interference graph built in the same one pass (one + * fewer passes per round of coloring). + *

+ * Single-register def or use live ranges deny neighbors their required + * register and thus do not interfere, vs interfering and denying the color + * and coloring time. Basically all function calls do this, but many oddball + * registers also, e.g. older div/mod/mul ops. (5x smaller IFG, the only + * O(n^2) part of this operation). + */ + +public class RegAlloc { + // Main Coloring Algorithm: + // Repeat until colored: + // Build Live Ranges (LRGs) + // - Intersect allowed registers + // If hard conflicts (LRGs with no allowed registers) + // - Pre-split conflicted LRGs, and repeat + // Build Interference Graph (IFG) + // - Self conflicts split now + // Color (remove trivial first then any until empty; reverse assign colors + // - If color fails: + // - - Split uncolorable LRGs + + // Top-level program graph structure + final CodeGen _code; + + public int _spills, _spillScaled; + + // ----------------------- + // Live ranges with self-conflicts or no allowed registers + private final IdentityHashMap _failed = new IdentityHashMap<>(); + void fail( LRG lrg ) { + assert lrg.leader(); + _failed.put(lrg,""); + } + boolean success() { return _failed.isEmpty(); } + + + // ----------------------- + // Map from Nodes to Live Ranges + private final IdentityHashMap _lrgs = new IdentityHashMap<>(); + short _lrg_num; + + // Define a new LRG, and assign n + LRG newLRG( Node n ) { + LRG lrg = lrg(n); + if( lrg!=null ) return lrg; + lrg = new LRG(_lrg_num++); + LRG old = _lrgs.put(n,lrg); assert old==null; + return lrg; + } + + // LRG for n + LRG lrg( Node n ) { + LRG lrg = _lrgs.get(n); + if( lrg==null ) return null; + LRG lrg2 = lrg.find(); + if( lrg != lrg2 ) + _lrgs.put(n,lrg2); + return lrg2; + } + + // Find LRG for n.in(idx), and also map n to it + LRG lrg2( Node n, int idx ) { + LRG lrg = lrg(n.in(idx)); + return union(lrg,n); + } + + // Union any lrg for n with lrg and map to the union + LRG union( LRG lrg, Node n ) { + LRG lrgn = _lrgs.get(n); + LRG lrg3 = lrg.union(lrgn); + _lrgs.put(n,lrg3); + return lrg3; + } + + // Force all unified to roll up; collect live ranges + LRG[] _LRGS; + void unify() { + Ary lrgs = new Ary<>(LRG.class); + for( Node n : _lrgs.keySet() ) { + LRG lrg = lrg(n); + lrgs.setX(lrg._lrg,lrg); + } + _LRGS = lrgs.asAry(); + // Remove unified lrgs from failed set also + for( LRG lrg : _failed.keySet().toArray(new LRG[0]) ) { + if( !lrg.leader() ) { + LRG lrg2 = lrg.find(); + _failed.remove(lrg); + _failed.put(lrg2,""); + } + } + } + + + public short regnum( Node n ) { + LRG lrg = lrg(n); + return lrg==null ? -1 : lrg._reg; + } + + // Printable register number for node n + String reg( Node n ) { + LRG lrg = lrg(n); + if( lrg==null ) return null; + if( lrg._reg == -1 ) return "V"+lrg._lrg; + return _code._mach.reg(lrg._reg); + } + + // ----------------------- + RegAlloc( CodeGen code ) { _code = code; } + + public void regAlloc() { + // Insert callee-save registers + FunNode lastFun=null; + for( CFGNode bb : _code._cfg ) { + if( bb instanceof FunNode fun ) + insertCalleeSave(lastFun=fun); + // Leaf routine, or not? + // X86 requires 16b RSP aligned if NOT leaf + if( bb instanceof CallNode ) lastFun._hasCalls = true; + } + + + // Top driver: repeated rounds of coloring and splitting. + byte round=0; + while( !graphColor(round) ) { + split(round); + if( round >= 7 ) // Really expect to be done soon + throw Utils.TODO("Allocator taking too long"); + round++; + } + postColor(); // Remove no-op spills + } + + private boolean graphColor(byte round) { + _failed.clear(); + _lrgs.clear(); + _lrg_num = 1; + _LRGS=null; + + return + // Build Live Ranges + BuildLRG.run(round,this) && // if no hard register conflicts + // Build Interference Graph + IFG.build(round,this) && // If no self conflicts or uncolorable + // Conservative coalesce copies + Coalesce.coalesce(round,this) && + // Color attempt + IFG.color(round,this); // If colorable + } + + // Insert callee-save registers. Walk the callee-save RegMask ignoring any + // Parms, then insert a Parm and an edge from the Ret to the Parm with the + // callee-save register. + private void insertCalleeSave( FunNode fun ) { + RegMask saves = _code._mach.calleeSave(); + ReturnNode ret = fun.ret(); + + for( short reg = saves.firstReg(); reg != -1; reg = saves.nextReg(reg) ) { + ret.addDef(new CalleeSaveNode(fun,reg,_code._mach.reg(reg))); + assert ((MachNode)ret).regmap(ret.nIns()-1).firstReg()==reg; + } + } + + + // ----------------------- + // Split conflicted live ranges. + void split(byte round) { + + // In C2, all splits are handling in one pass over the program. Here, + // in the name of clarity, we'll handle each failing live range + // independently... which generally requires a full pass over the + // program for each failing live range. i.e., might be a lot of + // passes. + for( LRG lrg : _failed.keySet() ) + split(round,lrg); + } + + // Split this live range + boolean split( byte round, LRG lrg ) { + assert lrg.leader(); // Already rolled up + + if( lrg._selfConflicts != null ) + return splitSelfConflict(round,lrg); + + // Register mask when empty; split around defs and uses with limited + // register masks. + if( lrg._mask.isEmpty() && (!lrg._multiDef || lrg._1regUseCnt==1) ) { + if( lrg._1regDefCnt <= 1 && + lrg._1regUseCnt <= 1 && + (lrg._1regDefCnt + lrg._1regUseCnt) > 0 ) + return splitEmptyMaskSimple(round,lrg); + // Default to splitByLoop + //return splitEmptyMask(round,lrg); + } + + // Generic split-by-loop depth. + return splitByLoop(round,lrg); + } + + // Split live range with an empty mask. Specifically forces splits at + // single-register defs or uses and not elsewhere. + boolean splitEmptyMaskSimple( byte round, LRG lrg ) { + // Live range has a single-def single-register, and/or a single-use + // single-register. Split after the def and before the use. Does not + // require a full pass. + + // Split just after def + if( lrg._1regDefCnt==1 && !lrg._machDef.isClone() ) + // Force must-split, even if a prior split same block because register + // conflicts. Example: + // alloc + // V1/rax - forced by alloc + // alloc + // V2/rax - kills prior RAX + // st4 [V1],len - No good, must split around + makeSplit("def/empty1",round,lrg).insertAfter((Node)lrg._machDef, false/*true*/); + // Split just before use + if( lrg._1regUseCnt==1 || (lrg._1regDefCnt==1 && ((Node)lrg._machDef).nOuts()==1) ) + insertBefore((Node)lrg._machUse,lrg._uidx,"use/empty1",round,lrg,true); + return true; + } + + // Split live range with an empty mask. Specifically forces splits at + // single-register defs or uses everywhere. + boolean splitEmptyMask( byte round, LRG lrg ) { + findAllLRG(lrg); + // If no single-use or single-def, assume this is a complete register + // kill and force spilling everywhere. + boolean all = lrg._killed || (lrg._1regDefCnt + lrg._1regUseCnt)==0; + for( Node n : _ns ) { + if( !(n instanceof MachNode mach) ) continue; + // Find def of spilling live range; spilling everywhere, OR + // single-register DEF and not cloneable (since these will clone + // before every use) + if( lrg(n)==lrg && (all || (!mach.isClone() && mach.outregmap().size1() )) ) + makeSplit(n,"def/empty2",round,lrg).insertAfter(n,true); + // Find all uses + for( int i=1; i x._nid - y._nid ); + + // For all conflicts + for( Node def : conflicts ) { + assert lrg(def)==lrg; // Might be conflict use-side + // Split before each use that extends the live range; i.e. is a + // Phi or two-address + for( int i=0; i>32); + + + // If the minLoopDepth is less than the maxLoopDepth: for-all defs and + // uses, if at minLoopDepth or lower, split after def and before use. + for( Node n : _ns ) { + if( n instanceof SplitNode ) continue; // Ignoring splits; since spilling need to split in a deeper loop + if( n.isDead() ) continue; // Some Clonable went dead by other spill changes + // If this is a 2-address commutable op (e.g. AddX86, MulX86) and the rhs has only a single user, + // commute the inputs... which chops the LHS live ranges' upper bound to just the RHS. + if( n instanceof MachNode mach && lrg(n)==lrg && mach.twoAddress()==1 && mach.commutes() && n.in(2).nOuts()==1 ) + n.swap12(); + + if( lrg(n)==lrg && // This is a LRG def + // At loop boundary, or splitting in inner loop + (min==max || n.cfg0().loopDepth() <= min) ) { + // Cloneable constants will be cloned at uses, not after def + if( !(n instanceof MachNode mach && mach.isClone()) && + // Single user is already a split + !(n.nOuts()==1 && n.out(0) instanceof SplitNode) ) + // Split after def in min loop nest + makeSplit("def/loop",round,lrg).insertAfter(n,false); + } + + // PhiNodes check all CFG inputs + if( n instanceof PhiNode phi && !(n instanceof ParmNode)) { + for( int i=1; i>32); + int d = cfg.loopDepth(); + min = Math.min(min,d); + max = Math.max(max,d); + return ((long)max<<32) | min; + } + + // Find all members of a live range, both defs and uses + private final Ary _ns = new Ary<>(Node.class); + void findAllLRG( LRG lrg ) { + _ns.clear(); + int wd = 0; + _ns.push((Node)lrg._machDef); + _ns.push((Node)lrg._machUse); + while( wd < _ns._len ) { + Node n = _ns.at(wd++); + if( lrg(n)!=lrg ) continue; + for( Node def : n._inputs ) + if( lrg(def)==lrg && _ns.find(def)== -1 ) + _ns.push(def); + for( Node use : n._outputs ) + if( _ns.find(use)== -1 ) + _ns.push(use); + } + for( Node n : _ns ) assert !n.isDead(); + } + + void insertBefore(Node n, int i, String kind, byte round, LRG lrg, boolean skip) { + Node def = n.in(i); + // Effective block for use + CFGNode cfg = n instanceof PhiNode phi ? phi.region().cfg(i) : n.cfg0(); + // Def is a split ? + if( skip && def instanceof SplitNode ) { + boolean singleReg = n instanceof MachNode mach && mach.regmap(i).size1(); + // Same block, multiple registers, split is only used by n, + // assume this is good enough and do not split again. + if( cfg==def.cfg0() && def.nOuts()==1 && !singleReg ) + return; + } + makeSplit(def,kind,round,lrg).insertBefore(n, i); + // Skip split-of-split same block + if( skip && def instanceof SplitNode && cfg==def.cfg0() ) + n.in(i).setDefOrdered(1,def.in(1)); + } + void insertBefore(Node n, int i, String kind, byte round, LRG lrg) { + insertBefore(n,i,kind,round,lrg,true); + } + + private Node makeSplit( Node def, String kind, byte round, LRG lrg ) { + Node split = def instanceof MachNode mach && mach.isClone() + ? mach.copy() + : _code._mach.split(kind,round,lrg); + _lrgs.put(split,lrg); + return split; + } + private SplitNode makeSplit( String kind, byte round, LRG lrg ) { + SplitNode split = _code._mach.split(kind,round,lrg); + _lrgs.put(split,lrg); + return split; + } + + + // ----------------------- + // POST PASS: Remove empty spills that biased-coloring made + private void postColor() { + int maxSlot = -1; + for( CFGNode bb : _code._cfg ) { // For all ops + if( bb instanceof FunNode ) + maxSlot = -1; + if( bb instanceof ReturnNode ret ) + ret.fun()._maxSlot = (short)maxSlot; + for( int j=0; j= 0; + } + } + } + + private boolean splitBypass( CFGNode bb, int j, Node lo, int defreg ) { + // Attempt to bypass split + Node hi = lo.in(1); + while( true ) { + if( !(hi instanceof SplitNode && lo.cfg0() == hi.cfg0()) ) + return false; + if( lrg(hi.in(1))._reg==defreg ) + break; + hi = hi.in(1); + } + // Check no clobbers + for( int idx = j-1; bb.out(idx) != hi; idx++) { + Node n = bb.out(idx); + if( lrg(n)!=null && lrg(n)._reg == defreg ) + return false; // Clobbered + } + lo.setDefOrdered(1,hi.in(1)); + return true; + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RegMask.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RegMask.java new file mode 100644 index 0000000..a566187 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/codegen/RegMask.java @@ -0,0 +1,142 @@ +package com.compilerprogramming.ezlang.compiler.codegen; + +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.SB; + +/** RegMask + * A "register mask" - 1 bit set for each allowed register. In addition + * "stack slot" registers may be allowed, effectively making the set infinite. + *

+ * For smaller and simpler machines it suffices to make such masks an i64 or + * i128 (64 or 128 bit integers), and this presentation is by far the better + * way to go... if all register allocations can fit in this bit limitation. + * The allocator will need bits for stack-based parameters and for splits + * which cannot get a register. For a 32-register machine like the X86, add 1 + * for flags - gives 33 registers. Using a Java `long` has 64 bits, leaving + * 31 for spills and stack passing. This is adequate for nearly all + * allocations; only the largest allocations will run this out. However, if + * we move to a chip with 64 registers we'll immediately run out, and need at + * least a 128 bit mask. Since you cannot *return* a 128 bit value directly + * in Java, Simple will pick up a `RegMask` class object. +*/ +public class RegMask { + + long _bits0, _bits1; + + private static final RegMask EMPTY = new RegMask(0L); + public static final RegMask FULL = new RegMask(-1L); + + public RegMask(int bit) { + if( bit < 64 ) _bits0 = 1L<> reg)&1)==0; + if( reg < 128 ) return ((_bits1 >> reg)&1)==0; + return true; + } + + + public short firstReg() { + return (short)(_bits0 != 0 + ? Long.numberOfTrailingZeros(_bits0) + : Long.numberOfTrailingZeros(_bits1)+64); + } + public short nextReg(short reg) { + if( reg<64 ) { + long bits0 = _bits0 >> (reg+1); + if( bits0!=0 ) + return (short)(reg+1+Long.numberOfTrailingZeros(bits0)); + reg=64; + } + long bits1 = _bits1 >> (reg-64+1); + if( bits1!=0 ) + return (short)(reg+1+Long.numberOfTrailingZeros(bits1)); + return -1; + } + + boolean isEmpty() { return _bits0==0 && _bits1==0; } + + public boolean test( int reg ) { + return ((reg<64 ? (_bits0 >> reg) : (_bits1 >> (reg-64))) & 1) != 0; + } + + // checks if the 2 masks have at least 1 bit in common + public boolean overlap( RegMask mask ) { return (_bits0 & mask._bits0)!=0 || (_bits1 & mask._bits1)!=0; } + + // Defensive writable copy + public RegMaskRW copy() { return new RegMaskRW( _bits0, _bits1 ); } + + // Has exactly 1 bit set + public boolean size1() { + return ((_bits0 & -_bits0)==_bits0 && _bits1==0) || + ((_bits1 & -_bits1)==_bits1 && _bits0==0); + } + + // Cardinality + public short size() { return (short)(Long.bitCount(_bits0)+Long.bitCount(_bits1)); } + + @Override public String toString() { return toString(new SB()).toString(); } + public SB toString(SB sb) { + Machine mach = CodeGen.CODE._mach; + if( _bits0==0 && _bits1==0 ) return sb.p("[]"); + sb.p("["); + for( int i=0; i<64; i++ ) + if( ((_bits0 >> i)&1) != 0 ) + sb.p(mach.reg(i)).p(","); + for( int i=0; i<64; i++ ) + if( ((_bits1 >> i)&1) != 0 ) + sb.p(mach.reg(i+64)).p(","); + return sb.unchar().p("]"); + } +} + +// Mutable regmask(writable) +class RegMaskRW extends RegMask { + public RegMaskRW(long x, long y) { super(x,y); } + // clears bit at position r. Returns true if the mask is still not empty. + public boolean clr(int r) { + if( r < 64 ) _bits0 &= ~(1L<<(r )); + else _bits1 &= ~(1L<<(r-64)); + return _bits0!=0 || _bits1!=0; + } + @Override RegMaskRW and( RegMask mask ) { + if( mask==null ) return this; + _bits0 &= mask._bits0; + _bits1 &= mask._bits1; + return this; + } + @Override RegMaskRW sub( RegMask mask ) { + if( mask==null ) return this; + _bits0 &= ~mask._bits0; + _bits1 &= ~mask._bits1; + return this; + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/AddFNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/AddFNode.java new file mode 100644 index 0000000..d8ecc97 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/AddFNode.java @@ -0,0 +1,44 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFloat; + +import java.util.BitSet; + +public class AddFNode extends Node { + public AddFNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + + @Override public String label() { return "AddF"; } + + @Override public String glabel() { return "+"; } + + @Override + public StringBuilder _print1(StringBuilder sb, BitSet visited) { + in(1)._print0(sb.append("("), visited); + in(2)._print0(sb.append("+"), visited); + return sb.append(")"); + } + + @Override + public SONType compute() { + if (in(1)._type instanceof SONTypeFloat i0 && + in(2)._type instanceof SONTypeFloat i1) { + if (i0.isConstant() && i1.isConstant()) + return SONTypeFloat.constant(i0.value()+i1.value()); + } + return in(1)._type.meet(in(2)._type); + } + + @Override + public Node idealize() { + Node lhs = in(1); + SONType t2 = in(2)._type; + + // Add of 0. + if ( t2.isConstant() && t2 instanceof SONTypeFloat i && i.value()==0 ) + return lhs; + + return null; + } + @Override Node copy(Node lhs, Node rhs) { return new AddFNode(lhs,rhs); } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/AddNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/AddNode.java new file mode 100644 index 0000000..0098b61 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/AddNode.java @@ -0,0 +1,200 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.Compiler; +import com.compilerprogramming.ezlang.compiler.sontypes.*; + +import java.util.BitSet; + +import static com.compilerprogramming.ezlang.compiler.Compiler.con; + +public class AddNode extends Node { + public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + + @Override public String label() { return "Add"; } + + @Override public String glabel() { return "+"; } + + @Override + public StringBuilder _print1(StringBuilder sb, BitSet visited) { + in(1)._print0(sb.append("("), visited); + in(2)._print0(sb.append("+"), visited); + return sb.append(")"); + } + + + @Override + public SONType compute() { + SONType t1 = in(1)._type, t2 = in(2)._type; + if( t1.isHigh() || t2.isHigh() ) + return SONTypeInteger.TOP; + if( t1 instanceof SONTypeInteger i1 && + t2 instanceof SONTypeInteger i2 ) { + if (i1.isConstant() && i2.isConstant()) + return SONTypeInteger.constant(i1.value()+i2.value()); + // Fold ranges like {0-1} + {2-3} into {2-4}. + if( !overflow(i1._min,i2._min) && + !overflow(i1._max,i2._max) ) + return SONTypeInteger.make(i1._min+i2._min,i1._max+i2._max); + } + return SONTypeInteger.BOT; + } + + 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 Node idealize () { + Node lhs = in(1); + Node rhs = in(2); + if( rhs instanceof AddNode add && add.err()!=null ) + return null; + SONType t2 = rhs._type; + + // Add of 0. We do not check for (0+x) because this will already + // canonicalize to (x+0) + if( t2 == SONTypeInteger.ZERO ) + return lhs; + + // Add of same to a multiply by 2 + if( lhs==rhs ) + return new ShlNode(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( lhs.in(2).addDep(this)._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 null; + } + + // 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== SONTypeInteger.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 hi._nid; + } + + @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); } + @Override Node copyF() { return new AddFNode(null,null); } +// FIXME Dibyendu +// @Override public Parser.ParseException err() { +// if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return null; +// if( !(in(1)._type instanceof SONTypeInteger) ) return Parser.error("Cannot '"+label()+"' " + in(1)._type,null); +// if( !(in(2)._type instanceof SONTypeInteger) ) return Parser.error("Cannot '"+label()+"' " + in(2)._type,null); +// return null; +// } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/AndNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/AndNode.java new file mode 100644 index 0000000..f36997f --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/AndNode.java @@ -0,0 +1,58 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeInteger; + +public class AndNode extends LogicalNode { + public AndNode(Node lhs, Node rhs) { super(lhs, rhs); } + + @Override public String label() { return "And"; } + @Override public String op() { return "&"; } + + @Override public String glabel() { return "&"; } + + @Override + public SONType compute() { + SONType t1 = in(1)._type, t2 = in(2)._type; + if( t1.isHigh() || t2.isHigh() ) + return SONTypeInteger.TOP; + if( t1 instanceof SONTypeInteger i0 && + t2 instanceof SONTypeInteger i1 ) { + if( i0.isConstant() && i1.isConstant() ) + return SONTypeInteger.constant(i0.value()&i1.value()); + // Sharpen allowed bits if either value is narrowed + long mask = i0.mask() & i1.mask(); + return mask < 0 ? SONTypeInteger.BOT : SONTypeInteger.make(0,mask); + } + return SONTypeInteger.BOT; + } + + @Override + public Node idealize() { + Node lhs = in(1); + Node rhs = in(2); + SONType t1 = lhs._type; + SONType t2 = rhs._type; + if( !(t1 instanceof SONTypeInteger && t2 instanceof SONTypeInteger 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 null; + } + @Override Node copy(Node lhs, Node rhs) { return new AndNode(lhs,rhs); } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/BoolNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/BoolNode.java new file mode 100644 index 0000000..9c9b744 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/BoolNode.java @@ -0,0 +1,135 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.Compiler; +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.sontypes.*; +import java.util.BitSet; +import static com.compilerprogramming.ezlang.compiler.sontypes.SONTypeInteger.*; + +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 + public String glabel() { return op(); } + + @Override + public StringBuilder _print1(StringBuilder sb, BitSet visited) { + in(1)._print0(sb.append("("), visited); + in(2)._print0(sb.append(op()), visited); + return sb.append(")"); + } + + @Override + public SONTypeInteger compute() { + SONType t1 = in(1)._type; + SONType t2 = in(2)._type; + // Exactly equals? + if( t1.isHigh() || t2.isHigh() ) + return BOOL.dual(); + if( in(1)==in(2) ) + // LT fails, both EQ and LE succeed + return this instanceof LT ? FALSE : TRUE; + if( t1 instanceof SONTypeInteger i1 && + t2 instanceof SONTypeInteger i2 ) + return doOp(i1,i2); + if( t1 instanceof SONTypeFloat f1 && + t2 instanceof SONTypeFloat f2 && + f1.isConstant() && f2.isConstant() ) + return doOp(f1.value(), f2.value()) ? TRUE : FALSE; + return BOOL; + } + + SONTypeInteger doOp(SONTypeInteger t1, SONTypeInteger 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 ? Compiler.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 SONTypeFloat ? 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 == SONType.NIL) ) + return new NotNode(in(1)); + } + + // Do we have ((x * (phi cons)) * con) ? + // Do we have ((x * (phi cons)) * (phi cons)) ? + // Push constant up through the phi: x * (phi con0*con0 con1*con1...) + Node phicon = AddNode.phiCon(this,this instanceof EQ); + if( phicon!=null ) return phicon; + + return null; + } + + public static class EQ extends BoolNode { + public EQ(Node lhs, Node rhs) { super(lhs,rhs); } + public String op() { return "=="; } + SONTypeInteger doOp(SONTypeInteger i1, SONTypeInteger 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 LT extends BoolNode { + public LT(Node lhs, Node rhs) { super(lhs,rhs); } + public String op() { return "<" ; } + public String glabel() { return "<"; } + SONTypeInteger doOp(SONTypeInteger i1, SONTypeInteger 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 "<="; } + public String glabel() { return "<="; } + SONTypeInteger doOp(SONTypeInteger i1, SONTypeInteger 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); } boolean doOp(double lhs, double rhs) { return lhs == rhs; } public boolean isFloat() { return true; } } + public static class LTF extends LT { public LTF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs < rhs; } public boolean isFloat() { return true; } } + public static class LEF extends LE { public LEF(Node lhs, Node rhs) { super(lhs,rhs); } boolean doOp(double lhs, double rhs) { return lhs <= rhs; } public boolean isFloat() { return true; } } + + // 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<" ; } + public String glabel() { return "u<"; } + SONTypeInteger doOp(SONTypeInteger i1, SONTypeInteger 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CFGNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CFGNode.java new file mode 100644 index 0000000..3876fd2 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CFGNode.java @@ -0,0 +1,181 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.Compiler; +import com.compilerprogramming.ezlang.compiler.sontypes.*; +import java.util.BitSet; + +/** Control Flow Graph Nodes + *

+ * CFG nodes have a immediate dominator depth (idepth) and a loop nesting + * depth(loop_depth). + *

+ * idepth is computed lazily upon first request, and is valid even in the + * Parser, and is used by peepholes during parsing and afterward. + *

+ * loop_depth is computed after optimization as part of scheduling. + * + * Start - Block head; has constants and trailing Funs + * Region - Block head; has Phis. Includes Fun and Parms. + * CallEnd - Block head; followed by CProj and Proj; also linked RPC Parms + * CProj - Block head + * + * If - Block tail; followed by CProj same block + * Call - Block tail; followed by CallEnd and linked Funs + * New/intrinsic - If followed by CProj: block tail else mid-block; followed by Proj + * + */ +public abstract class CFGNode extends Node { + + public CFGNode(Node... nodes) { super(nodes); } + public CFGNode(CFGNode cfg) { + super(cfg); + if( cfg != null ) { + _idepth = cfg._idepth; + _ltree = cfg._ltree; + _pre = cfg._pre; + } + } + + public CFGNode cfg(int idx) { return (CFGNode)in(idx); } + + // Block head is Start, Region, CProj, but not e.g. If, Return, Stop + public boolean blockHead() { return false; } + + // Get the one control following; error to call with more than one e.g. an + // IfNode or other multi-way branch. + public CFGNode uctrl() { + CFGNode c = null; + for( Node n : _outputs ) + if( n instanceof CFGNode cfg ) + { assert c==null; c = cfg; } + return c; + } + + + // ------------------------------------------------------------------------ + /** + * 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. + *

+ * See {@link ...} + */ + public int _idepth; + public int idepth() { return _idepth==0 ? (_idepth=idom().idepth()+1) : _idepth; } + + // Return the immediate dominator of this Node and compute dom tree depth. + public CFGNode idom(Node dep) { return cfg(0); } + public final CFGNode idom() { return idom(null); } + + // Return the LCA of two idoms + public CFGNode _idom(CFGNode rhs, Node dep) { + if( rhs==null ) return this; + CFGNode lhs = this; + while( lhs != rhs ) { + var comp = lhs.idepth() - rhs.idepth(); + if( comp >= 0 ) lhs = ((CFGNode)lhs.addDep(dep)).idom(); + if( comp <= 0 ) rhs = ((CFGNode)rhs.addDep(dep)).idom(); + } + return lhs; + } + + // Anti-dependence field support + public int _anti; // Per-CFG field to help find anti-deps + + // Find nearest enclosing FunNode + public FunNode fun() { + CFGNode cfg = this; + while( !(cfg instanceof FunNode fun) ) + cfg = cfg.idom(); + return fun; + } + + // ------------------------------------------------------------------------ + // Loop nesting + public LoopNode loop() { return _ltree._head; } + public int loopDepth() { return _ltree==null ? 0 : _ltree.depth(); } + + LoopTree _ltree; + public int _pre; // Pre-order numbers for loop tree finding + private 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 is a LoopNode which contains all at depth 1. + public void buildLoopTree(StopNode stop) { + _ltree = stop._ltree = Compiler.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++; + // 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 == SONType.XCONTROL || // Do not walk dead control + usecfg._type == SONTypeTuple.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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CProjNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CProjNode.java new file mode 100644 index 0000000..9e2f384 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CProjNode.java @@ -0,0 +1,66 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.Compiler; +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeTuple; +import java.util.BitSet; + +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 public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(_label); } + + @Override public boolean blockHead() { return true; } + + public CFGNode ctrl() { return cfg(0); } + + @Override + public SONType compute() { + SONType t = ctrl()._type; + return t instanceof SONTypeTuple tt ? tt._types[_idx] : SONType.BOTTOM; + } + + @Override + public Node idealize() { + if( ctrl()._type instanceof SONTypeTuple tt ) { + if( tt._types[_idx]== SONType.XCONTROL ) + return Compiler.XCTRL; // We are dead + if( ctrl() instanceof IfNode && tt._types[1-_idx]== SONType.XCONTROL ) // Only true for IfNodes + return ctrl().in(0); // We become our input control + } + + // Flip a negating if-test, to remove the not + if( ctrl() instanceof IfNode iff && iff.pred().addDep(this) 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 + boolean eq( Node n ) { return _idx == ((CProjNode)n)._idx; } + + @Override + int hash() { return _idx; } + +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CallEndNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CallEndNode.java new file mode 100644 index 0000000..c705f53 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CallEndNode.java @@ -0,0 +1,100 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.Compiler; +import com.compilerprogramming.ezlang.compiler.codegen.CodeGen; +import com.compilerprogramming.ezlang.compiler.sontypes.*; +import java.util.BitSet; + +/** + * CallEnd + */ +public class CallEndNode extends CFGNode implements MultiNode { + + // When set true, this Call/CallEnd/Fun/Return is being trivially inlined + private boolean _folding; + public final SONTypeRPC _rpc; + + public CallEndNode(CallNode call) { super(new Node[]{call}); _rpc = SONTypeRPC.constant(_nid); } + public CallEndNode(CallEndNode cend) { super(cend); _rpc = cend._rpc; } + + @Override public String label() { return "CallEnd"; } + @Override public boolean blockHead() { return true; } + + public CallNode call() { return (CallNode)in(0); } + + @Override + public StringBuilder _print1(StringBuilder sb, BitSet visited) { + sb.append("cend( "); + sb.append( in(0) instanceof CallNode ? "Call, " : "----, "); + for( int i=1; i1 ) { + assert nIns()==2; // Linked exactly once for a constant + ret = ((SONTypeTuple)in(1)._type).ret(); // Return type + } + } + return SONTypeTuple.make(call._type, SONTypeMem.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 SONTypeFunPtr 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) + CFGNode idom = call; + while( !(idom instanceof FunNode fun2) ) + idom = idom.idom(); + if( idom != fun ) { + // Trivial inline: rewrite + _folding = true; + // Rewrite Fun so the normal RegionNode ideal collapses + fun._folding = true; + fun.setDef(1, Compiler.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); + return this; + } + } else { + fun.addDep(this); + } + } else { // Function ptr has multiple users (so maybe multiple call sites) + fptr.addDep(this); + } + } + + return null; + } + + @Override public Node pcopy(int idx) { + return _folding ? in(1).in(idx) : null; + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CallNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CallNode.java new file mode 100644 index 0000000..52b3bf1 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CallNode.java @@ -0,0 +1,165 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.codegen.CodeGen; +import com.compilerprogramming.ezlang.compiler.IterPeeps; +import com.compilerprogramming.ezlang.compiler.Compiler; +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFunPtr; +import com.compilerprogramming.ezlang.exceptions.CompilerException; + +import java.util.BitSet; + +/** + * Call + */ +public class CallNode extends CFGNode { + + public CallNode(Node... nodes) { super(nodes); } + public CallNode(CallNode call) { super(call); } + + @Override public String label() { return "Call"; } + + @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { + String fname = name(); + if( fname == null ) fptr()._print0(sb,visited); + else sb.append(fname); + sb.append("( "); + for( int i=2; i0 && 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 SONType 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 SONTypeFunPtr 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 = SONTypeFunPtr.nextFIDX(fidxs) ) { + int fidx = Long.numberOfTrailingZeros(fidxs); + SONTypeFunPtr 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 + boolean linked( FunNode fun ) { + for( Node n : fun._inputs ) + if( n == this ) + return true; + return false; + } + + + // Link so this calls fun + private 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 parm ) + use.delDef(idx); + fun.delDef(idx); + cend().delDef(cend()._inputs.find(fun.ret())); + assert !linked(fun); + i--; + } + } + + @Override + public CompilerException err() { + if( !(fptr()._type instanceof SONTypeFunPtr tfp) ) + throw Utils.TODO(); + if( !tfp.notNull() ) + return Compiler.error( "Might be null calling "+tfp); + if( nargs() != tfp.nargs() ) + return Compiler.error( "Expecting "+tfp.nargs()+" arguments, but found "+nargs()); + + // Check for args + for( int i=0; i + * 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 solely to allow visitation. + *

+ * The Constant's value is the value stored in it. + */ + +public class ConstantNode extends Node { + public final SONType _con; + public ConstantNode( SONType type ) { + super(new Node[]{CodeGen.CODE._start}); + _con = _type = type; + } + public ConstantNode( Node con, SONType t ) { super(con); _con = t; } + public ConstantNode( ConstantNode con ) { super(con); _con = con._type; } + + public static Node make( SONType type ) { + if( type== SONType. CONTROL ) return new CtrlNode(); + if( type== SONType.XCONTROL ) return new XCtrlNode(); + return new ConstantNode(type); + } + + @Override public String label() { return "#"+_con; } + @Override public String glabel() { return _con.gprint(new SB().p("#")).toString(); } + + @Override + public String uniqueName() { return "Con_" + _nid; } + + @Override + public StringBuilder _print1(StringBuilder sb, BitSet visited) { + if( _con instanceof SONTypeFunPtr tfp && tfp.isConstant() ) { + FunNode fun = CodeGen.CODE.link(tfp); + if( fun._name != null ) + return sb.append("{ ").append(fun._name).append("}"); + } + return sb.append(_con.print(new SB())); + } + + @Override public boolean isConst() { return true; } + + @Override + public SONType compute() { return _con; } + + @Override + public Node idealize() { return null; } + + @Override + boolean eq(Node n) { + ConstantNode con = (ConstantNode)n; // Contract + return _con==con._con; + } + + @Override + int hash() { return _con.hashCode(); } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CtrlNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CtrlNode.java new file mode 100644 index 0000000..6450cdd --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/CtrlNode.java @@ -0,0 +1,14 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.codegen.CodeGen; +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import java.util.BitSet; + +public class CtrlNode extends CFGNode { + public CtrlNode() { super(CodeGen.CODE._start); } + @Override public String label() { return "Ctrl"; } + @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Cctrl"); } + @Override public boolean isConst() { return true; } + @Override public SONType compute() { return SONType.CONTROL; } + @Override public Node idealize() { return null; } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/DivFNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/DivFNode.java new file mode 100644 index 0000000..0813fa8 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/DivFNode.java @@ -0,0 +1,41 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFloat; + +import java.util.BitSet; + +public class DivFNode extends Node { + public DivFNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + + @Override public String label() { return "DivF"; } + + @Override public String glabel() { return "/"; } + + @Override + public StringBuilder _print1(StringBuilder sb, BitSet visited) { + in(1)._print0(sb.append("("), visited); + in(2)._print0(sb.append("/"), visited); + return sb.append(")"); + } + + @Override + public SONType compute() { + if (in(1)._type instanceof SONTypeFloat i0 && + in(2)._type instanceof SONTypeFloat i1) { + if (i0.isConstant() && i1.isConstant()) + return SONTypeFloat.constant(i0.value()/i1.value()); + } + return in(1)._type.meet(in(2)._type); + } + + @Override + public Node idealize() { + // Div of constant + if( in(2)._type instanceof SONTypeFloat f && f.isConstant() ) + return new MulFNode(in(1),new ConstantNode(SONTypeFloat.constant(1.0/f.value())).peephole()); + + return null; + } + @Override Node copy(Node lhs, Node rhs) { return new DivFNode(lhs,rhs); } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/DivNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/DivNode.java new file mode 100644 index 0000000..9c51b6e --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/DivNode.java @@ -0,0 +1,46 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeInteger; +import java.util.BitSet; + +public class DivNode extends Node { + public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + + @Override public String label() { return "Div"; } + + @Override public String glabel() { return "//"; } + + @Override + public StringBuilder _print1(StringBuilder sb, BitSet visited) { + in(1)._print0(sb.append("("), visited); + in(2)._print0(sb.append("/"), visited); + return sb.append(")"); + } + + @Override + public SONType compute() { + SONType t1 = in(1)._type, t2 = in(2)._type; + if( t1.isHigh() || t2.isHigh() ) + return SONTypeInteger.TOP; + if( t1 instanceof SONTypeInteger i1 && + t2 instanceof SONTypeInteger i2 ) { + if (i1.isConstant() && i2.isConstant()) + return i2.value() == 0 + ? SONTypeInteger.ZERO + : SONTypeInteger.constant(i1.value()/i2.value()); + } + return SONTypeInteger.BOT; + } + + @Override + public Node idealize() { + // Div of 1. + if( in(2)._type == SONTypeInteger.TRUE ) + return in(1); + return null; + } + + @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); } + @Override Node copyF() { return new DivFNode(null,null); } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/FRefNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/FRefNode.java new file mode 100644 index 0000000..a48814f --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/FRefNode.java @@ -0,0 +1,37 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.Compiler; +import com.compilerprogramming.ezlang.compiler.Var; +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import com.compilerprogramming.ezlang.exceptions.CompilerException; + +import java.util.BitSet; + +/** + * A Forward Reference. Its any final constant, including functions. When + * the Def finally appears its plugged into the forward reference, which then + * peepholes to the Def. + */ +public class FRefNode extends ConstantNode { + public static final SONType FREF_TYPE = SONType.BOTTOM; + public final Var _n; + public FRefNode( Var n ) { super(FREF_TYPE); _n = n; } + + @Override public String label() { return "FRef"+_n; } + + @Override public String uniqueName() { return "FRef_" + _nid; } + + @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { + return sb.append("FRef_").append(_n); + } + + @Override public Node idealize() { + // When FRef finds its definition, idealize to it + return nIns()==1 ? null : in(1); + } + + public CompilerException err() { return Compiler.error("Undefined name '"+_n._name+"'"); } + + @Override boolean eq(Node n) { return this==n; } + @Override int hash() { return _n._idx; } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/FunNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/FunNode.java new file mode 100644 index 0000000..a0eb0be --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/FunNode.java @@ -0,0 +1,199 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFunPtr; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeTuple; + +import java.util.BitSet; +import static com.compilerprogramming.ezlang.compiler.codegen.CodeGen.CODE; + +public class FunNode extends RegionNode { + + // When set true, this Call/CallEnd/Fun/Return is being trivially inlined + boolean _folding; + + private SONTypeFunPtr _sig; // Initial signature + private ReturnNode _ret; // Return pointer + + public String _name; // Debug name + + public FunNode(SONTypeFunPtr sig, Node... nodes ) { super(nodes); _sig = sig; } + public FunNode( FunNode fun ) { + super( fun ); + if( fun!=null ) { + _sig = fun.sig(); + _name = fun._name; + } else { + _sig = SONTypeFunPtr.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; } + + // Signature can improve over time + public SONTypeFunPtr sig() { return _sig; } + public void setSig( SONTypeFunPtr sig ) { + assert sig.isa(_sig); + if( _sig != sig ) { + CODE.add(this); + _sig = sig; + } + } + + @Override + public SONType compute() { + // Only dead if no callers after SCCP + return SONType.CONTROL; + } + + @Override + public Node idealize() { + + // Some linked path dies + Node progress = deadPath(); + 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 SONTypeTuple tt && tt.ret() != _sig.ret() ) + //throw Utils.TODO(); + return null; + + // 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 down to a single input, become that input + if( 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 + @Override public int idepth() { return (_idepth=1); } + // Bypass Region idom, always assume idom is Start + @Override public CFGNode idom(Node dep) { return cfg(1); } + + // Always in-progress until we run out of unknown callers + public boolean unknownCallers() { return 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 ">="; + case "<=" -> ">" ; + case "==" -> "!="; + case "!=" -> "=="; + case ">" -> "<="; + case ">=" -> "<" ; + default -> throw Utils.TODO(); + }; + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/LoadNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/LoadNode.java new file mode 100644 index 0000000..fdec37a --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/LoadNode.java @@ -0,0 +1,209 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.sontypes.*; +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(String name, int alias, SONType glb, Node mem, Node ptr, Node off) { + super(name, alias, glb, mem, ptr, off); + } + + // GraphVis DOT code (must be valid Java identifiers) and debugger labels + @Override public String label() { return "ld_"+mlabel(); } + // GraphVis node-internal labels + @Override public String glabel() { return "." +_name; } + + @Override + public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } + + @Override + public SONType compute() { + if( mem()._type instanceof SONTypeMem mem ) { + // Update declared forward ref to the actual + if( _declaredType.isFRef() && mem._t instanceof SONTypeMemPtr tmp && !tmp.isFRef() ) + _declaredType = tmp; + // No lifting if ptr might null-check + if( err()==null ) + return _declaredType.join(mem._t); + } + return _declaredType; + } + + @Override + public Node idealize() { + Node ptr = ptr(); + Node mem = mem(); + + // Simple Load-after-Store on same address. + if( mem instanceof StoreNode st && + ptr == st.ptr() && off() == st.off() ) { // Must check same object + assert _name.equals(st._name); // Equiv class aliasing is perfect + return extend(st.val()); + } + + // Simple Load-after-New on same address. + if( mem instanceof ProjNode p && p.in(0) instanceof NewNode nnn && + ptr == nnn.proj(1) ) // Must check same object + return zero(nnn); // Load zero from new + + // Uplift control to a prior dominating load. + for( Node memuse : mem._outputs ) + // Find a prior load, has same mem,ptr,off but higher ctrl + if( memuse != this && memuse instanceof LoadNode ld && ptr==ld.ptr() && off()==ld.off() && + cfg0()!=null && cfg0()._idom(ld.cfg0(),this) == ld.cfg0() ) // Higher control means load is legal earlier + return ld; + + // Load-after-Store on same address, but bypassing provably unrelated + // stores. This is a more complex superset of the above two peeps. + // "Provably unrelated" is really weak. + if( ptr instanceof ReadOnlyNode ro ) + ptr = ro.in(1); + outer: + while( true ) { + switch( mem ) { + case StoreNode st: + if( ptr == st.ptr().addDep(this) && off() == st.off() ) + return extend(castRO(st.val())); // Proved equal + // Can we prove unequal? Offsets do not overlap? + if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap + !neverAlias(ptr,st.ptr()) ) // And might alias + break outer; // Cannot tell, stop trying + // Pointers cannot overlap + mem = st.mem(); // Proved never equal + break; + case PhiNode phi: + // Assume related + phi.addDep(this); + break outer; + case ConstantNode top: break outer; // Assume shortly dead + case ProjNode mproj: // Memory projection + switch( mproj.in(0) ) { + case NewNode nnn1: + if( ptr instanceof ProjNode pproj && pproj.in(0) == mproj.in(0) ) + return zero(nnn1); + if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode) ) + break outer; // Cannot tell, ptr not related to New + mem = nnn1.in(nnn1.findAlias(_alias));// Bypass unrelated New + break; + case StartNode start: break outer; + case CallEndNode cend: break outer; // TODO: Bypass no-alias call + default: throw Utils.TODO(); + } + break; + case MemMergeNode merge: mem = merge.alias(_alias); break; + + default: + throw Utils.TODO(); + } + } + + // Push a Load up through a Phi, as long as it collapses on at least + // one arm. If at a Loop, the backedge MUST collapse - else we risk + // spinning the same transform around the loop indefinitely. + // BEFORE (2 Sts, 1 Ld): AFTER (1 St, 0 Ld): + // if( pred ) ptr.x = e0; val = pred ? e0 + // else ptr.x = e1; : e1; + // val = ptr.x; ptr.x = val; + if( mem() instanceof PhiNode memphi && memphi.region()._type == SONType.CONTROL && memphi.nIns()== 3 && + // Offset can be hoisted + off() instanceof ConstantNode && + // Pointer can be hoisted + hoistPtr(ptr,memphi) ) { + + // Profit on RHS/Loop backedge + if( profit(memphi,2) || + // Else must not be a loop to count profit on LHS. + (!(memphi.region() instanceof LoopNode) && profit(memphi,1)) ) { + Node ld1 = ld(1); + Node ld2 = ld(2); + return new PhiNode(_name,_declaredType,memphi.region(),ld1,ld2); + } + } + + return null; + } + + // Load a flavored zero from a New + private Node zero(NewNode nnn) { + SONTypeStruct ts = nnn._ptr._obj; + SONType zero = ts._fields[ts.findAlias(_alias)]._type.makeZero(); + return castRO(new ConstantNode(zero).peephole()); + } + + private Node ld( int idx ) { + Node mem = mem(), ptr = ptr(); + return new LoadNode(_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole(); + } + + private static boolean neverAlias( Node ptr1, Node ptr2 ) { + return ptr1.in(0) != ptr2.in(0) && + // Unrelated allocations + ptr1 instanceof ProjNode && ptr1.in(0) instanceof NewNode && + ptr2 instanceof ProjNode && ptr2.in(0) instanceof NewNode; + } + + private static boolean hoistPtr(Node ptr, PhiNode memphi ) { + // Can I hoist ptr above the Region? + if( !(memphi.region() instanceof RegionNode r) ) + return false; // Dead or dying Region/Phi + // If ptr from same Region, then yes, just use hoisted split pointers + if( ptr instanceof PhiNode pphi && pphi.region() == r ) + return true; + + // No, so can we lift this ptr? + CFGNode cptr = ptr.cfg0(); + if( cptr != null ) + // Pointer is controlled high + // TODO: Really needs to be the LCA of all inputs is high + return cptr.idepth() <= r.idepth(); + + // Dunno without a longer walk + return false; + } + + // Profitable if we find a matching Store on this Phi arm. + private boolean profit(PhiNode phi, int idx) { + Node px = phi.in(idx); + if( px==null ) return false; + if( px._type instanceof SONTypeMem mem && mem._t.isHighOrConst() ) return true; + if( px instanceof StoreNode st1 && ptr()==st1.ptr() && off()==st1.off() ) return true; + px.addDep(this); + return false; + } + + // Read-Only is a deep property, and cannot be cast-away + private Node castRO(Node rez) { + if( ptr()._type.isFinal() && !rez._type.isFinal() ) + return new ReadOnlyNode(rez).peephole(); + return rez; + } + + // When a load bypasses a store, the store might truncate bits - and the + // load will need to zero/sign-extend. + private Node extend(Node val) { + if( !(_declaredType instanceof SONTypeInteger ti) ) return val; + if( ti._min==0 ) // Unsigned + return new AndNode(val,con(ti._max)); + // Signed extension + int shift = Long.numberOfLeadingZeros(ti._max)-1; + Node shf = con(shift); + if( shf._type== SONTypeInteger.ZERO ) + return val; + Node shl = new ShlNode(val,shf.keep()).peephole(); + return new SarNode(shl,shf.unkeep()); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/LogicalNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/LogicalNode.java new file mode 100644 index 0000000..1b93813 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/LogicalNode.java @@ -0,0 +1,28 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.Compiler; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeInteger; +import com.compilerprogramming.ezlang.exceptions.CompilerException; + +import java.util.BitSet; + +public abstract class LogicalNode extends Node { + + public LogicalNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + abstract String op(); + + @Override + public StringBuilder _print1(StringBuilder sb, BitSet visited) { + in(1)._print0(sb.append("("), visited); + in(2)._print0(sb.append(op()), visited); + return sb.append(")"); + } + + + @Override public CompilerException err() { + if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return null; + if( !(in(1)._type instanceof SONTypeInteger) ) return Compiler.error("Cannot '"+op()+"' " + in(1)._type); + if( !(in(2)._type instanceof SONTypeInteger) ) return Compiler.error("Cannot '"+op()+"' " + in(2)._type); + return null; + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/LoopNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/LoopNode.java new file mode 100644 index 0000000..721926c --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/LoopNode.java @@ -0,0 +1,80 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; + +public class LoopNode extends RegionNode { + public LoopNode( Node entry ) { super(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 SONType compute() { + if( inProgress() ) return SONType.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 _idepth==0 ? (_idepth=idom().idepth()+1) : _idepth; } + // Bypass Region idom, same as the default idom() using use in(1) instead of in(0) + @Override public CFGNode idom(Node dep) { return entry(); } + + // If this is an unreachable loop, it may not have an exit. If it does not + // (i.e., infinite loop), force an exit to make it reachable. + public StopNode forceExit( FunNode fun, StopNode stop ) { + // Walk the backedge, then immediate dominator tree util we hit this + // Loop again. If we ever hit a CProj from an If (as opposed to + // directly on the If) we found our exit. + CFGNode x = back(); + while( x != this ) { + if( x instanceof CProjNode exit && exit.in(0) instanceof IfNode iff ) { + CFGNode other = iff.cproj(1-exit._idx); + if( other!=null && other.loopDepth() < loopDepth() ) + return stop; // Found an exit, not an infinite loop + } + x = x.idom(); + } + // Found a no-exit loop. Insert an exit + NeverNode iff = new NeverNode(back()); + for( Node use : _outputs ) + if( use instanceof PhiNode ) + iff.addDef(use); + CProjNode t = new CProjNode(iff,0,"True" ).init(); + CProjNode f = new CProjNode(iff,1,"False").init(); + setDef(2,f); + + // 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(SONType.TOP).peephole(); + ReturnNode ret = fun.ret(); + Node ctrl = ret.ctrl(), mem = ret.mem(), expr = ret.expr(); + if( ctrl._type != SONType.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 + ctrl = new RegionNode(null,ctrl).init(); + mem = new PhiNode((RegionNode)ctrl,mem ).init(); + expr = new PhiNode((RegionNode)ctrl,expr).init(); + } + // Append new Never exit + ctrl.addDef(t ); + mem .addDef(top); + expr.addDef(top); + } else { + ctrl = t; + mem = top; + expr = top; + } + ret.setDef(0,ctrl); + ret.setDef(1,mem ); + ret.setDef(2,expr); + + return stop; + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/MachConcreteNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/MachConcreteNode.java new file mode 100644 index 0000000..82aa6e0 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/MachConcreteNode.java @@ -0,0 +1,27 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import com.compilerprogramming.ezlang.compiler.Utils; + +import java.util.BitSet; + +// Generic machine-specific class, has a few Node implementations that have to +// exist (abstract) but are not useful past the optimizer. +public abstract class MachConcreteNode extends Node implements MachNode { + + public MachConcreteNode(Node node) { super(node); } + public MachConcreteNode(Node[]nodes) { super(nodes); } + + @Override public String label() { return op(); } + @Override public SONType compute () { throw Utils.TODO(); } + @Override public Node idealize() { throw Utils.TODO(); } + + @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { + sb.append("(").append(op()).append(","); + for( int i=1; i= nIns() ) addDef(null); + return setDef(alias,st); + } + + // Read or update from memory. + // A shared implementation allows us to create lazy phis both during + // lookups and updates; the lazy phi creation is part of chapter 8. + Node _mem( int alias, Node st ) { + // Memory projections are made lazily; if one does not exist + // then it must be START.proj(1) + Node old = alias(alias); + if( old instanceof ScopeNode loop ) { + MemMergeNode loopmem = loop.mem(); + Node memdef = loopmem.alias(alias); + // Lazy phi! + old = memdef instanceof PhiNode phi && loop.ctrl()==phi.region() + // Loop already has a real Phi, use it + ? memdef + // 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. + : loopmem.alias(alias, new PhiNode(Compiler.memName(alias), SONTypeMem.BOT,loop.ctrl(),loopmem._mem(alias,null),null).peephole() ); + alias(alias,old); + } + // Memory projections are made lazily; expand as needed + return st==null ? old : alias(alias,st); // Not lazy, so this is the answer + } + + + void _merge( MemMergeNode that, RegionNode r) { + int len = Math.max(nIns(),that.nIns()); + for( int i = 2; i < len; i++) + if( alias(i) != that.alias(i) ) { // No need for redundant Phis + // If we are in lazy phi mode we need to a lookup + // by alias as it will trigger a phi creation + Node lhs = this._mem(i,null); + Node rhs = that._mem(i,null); + alias(i, new PhiNode(Compiler.memName(i), lhs._type.glb().meet(rhs._type.glb()), r, lhs, rhs).peephole()); + } + } + + // Fill in the backedge of any inserted Phis + void _endLoopMem( ScopeNode scope, MemMergeNode back, MemMergeNode exit ) { + Node exit_def = exit.alias(1); + for( int i=1; i 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/NeverNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/NeverNode.java new file mode 100644 index 0000000..b60f7a1 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/NeverNode.java @@ -0,0 +1,20 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.Compiler; +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeTuple; + +import java.util.BitSet; + +// "Never true" for infinite loop exits +public class NeverNode extends IfNode { + public NeverNode(Node ctrl) { super(ctrl,Compiler.ZERO); } + + @Override public String label() { return "Never"; } + + @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Never"); } + + @Override public SONType compute() { return SONTypeTuple.IF_BOTH; } + + @Override public Node idealize() { return null; } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/NewNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/NewNode.java new file mode 100644 index 0000000..364cea0 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/NewNode.java @@ -0,0 +1,83 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.sontypes.*; + +import java.util.BitSet; + +/** + * Allocation! Allocate a chunk of memory, and pre-zero it. + * The inputs include control and size, and ALL aliases being set. + * The output is large tuple, one for every alias plus the created pointer. + * New is expected to be followed by projections for every alias. + */ +public class NewNode extends Node implements MultiNode { + + public final SONTypeMemPtr _ptr; + public final int _len; + + public NewNode(SONTypeMemPtr ptr, Node... nodes) { + super(nodes); + assert !ptr.nullable(); + _ptr = ptr; + _len = ptr._obj._fields.length; + // Control in slot 0 + assert nodes[0]._type== SONType.CONTROL || nodes[0]._type == SONType.XCONTROL; + // Malloc-length in slot 1 + assert nodes[1]._type instanceof SONTypeInteger || nodes[1]._type== SONType.NIL; + for( int i=0; i<_len; i++ ) + // Memory slices for all fields. + assert nodes[2+i]._type.isa(SONTypeMem.BOT); + } + + public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; _len = nnn._len; } + + public Node mem (int idx) { return in(idx+2); } + + @Override public String label() { return "new_"+glabel(); } + @Override public String glabel() { + return _ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str(); + } + + @Override + public StringBuilder _print1(StringBuilder sb, BitSet visited) { + sb.append("new "); + return sb.append(_ptr._obj.str()); + } + + int findAlias(int alias) { + int idx = _ptr._obj.findAlias(alias); + assert idx!= -1; // Error, caller should be calling + return idx+2; // Skip ctrl, size + } + + // 0 - ctrl + // 1 - byte size + // 2-len+2 - aliases, one per field + // len+2 - 2*len+2 - initial values, one per field + public Node size() { return in(1); } + + @Override + public SONTypeTuple compute() { + Field[] fs = _ptr._obj._fields; + SONType[] ts = new SONType[fs.length+2]; + ts[0] = SONType.CONTROL; + ts[1] = _ptr; + for( int i=0; i + * 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 final Ary _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. + *

+ * 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 final Ary _outputs; + + + /** + * Current computed type for this Node. This value changes as the graph + * changes and more knowledge is gained about the program. + */ + public SONType _type; + + + Node(Node... inputs) { + _nid = CODE.getUID(); // allocate unique dense ID + _inputs = new Ary<>(Node.class); + Collections.addAll(_inputs,inputs); + _outputs = new Ary<>(Node.class); + for( Node n : _inputs ) + if( n != null ) + n.addUse( this ); + } + + // Make a Node using the existing arrays of nodes. + // Used by any pass rewriting all Node classes but not the edges. + Node( Node n ) { + assert CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.InstSelect.ordinal(); + _nid = CODE.getUID(); // allocate unique dense ID + _inputs = new Ary<>(n==null ? new Node[0] : n._inputs.asAry()); + _outputs = new Ary<>(Node.class); + _type = n==null ? SONType.BOTTOM : n._type; + _deps = null; + _hash = 0; + } + + // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" + public abstract String label(); + + // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" + public String uniqueName() { + // Get rid of $ as graphviz doesn't like it + String label = label().replaceAll("\\$", ""); + return label + _nid; + } + + // Graphical label, e.g. "+" or "Region" or "==" + public String glabel() { return label(); } + + // Extra fun stuff, for assembly printing. Jump labels, parser locations, + // variable types, etc. + public String comment() { return null; } + + // ------------------------------------------------------------------------ + + // Debugger Printing. + + // {@code toString} is what you get in the debugger. It has to print 1 + // line (because this is what a debugger typically displays by default) and + // has to be robust with broken graph/nodes. + @Override + public final String toString() { return print(); } + + // This is a *deep* print. We print with a mutually recursive tik-tok + // style; the common _print0 calls the per-Node _print1, which calls back + // to _print0; + public final String print() { + return _print0(new StringBuilder(), new BitSet()).toString(); + } + + // This is the common print: check for repeats, check for DEAD and print + // "DEAD" else call the per-Node print1. + public final StringBuilder _print0(StringBuilder sb, BitSet visited) { + if (visited.get(_nid) && !(this instanceof ConstantNode) ) + return sb.append(label()); + visited.set(_nid); + return isDead() + ? sb.append(uniqueName()).append(":DEAD") + : _print1(sb, visited); + } + // Every Node implements this; a partial-line recursive print + abstract public StringBuilder _print1(StringBuilder sb, BitSet visited); + + public String p(int depth) { return IRPrinter.prettyPrint(this,depth); } + + public boolean isConst() { return false; } + + // ------------------------------------------------------------------------ + // Graph Node & Edge manipulation + + /** + * Gets the ith input node + * @param i Offset of the input node + * @return Input node or null + */ + public Node in(int i) { return _inputs.get(i); } + public Node out(int i) { return _outputs.get(i); } + public final Ary 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 def into a Node. Keeps the edges correct, by removing + * the corresponding use->def edge. This may make the original + * def go dead. This function is co-recursive with {@link #kill}. + *

+ + * This method is the normal path for altering a Node, because it does the + * proper default edge maintenance. It also immediately 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 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 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 + Node insertDef(int idx, Node new_def) { + _inputs.add(idx,null); + return setDef(idx,new_def); + } + + + /** + * Add a new def to an existing Node. Keep the edges correct by + * adding the corresponding def->use 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 addUse(Node n) { _outputs.add(n); return (N)this; } + + // Remove node 'use' from 'def's (i.e. our) output list, by compressing the list in-place. + // Return true if the output list is empty afterward. + // Error is 'use' does not exist; ok for 'use' to be null. + protected boolean delUse( Node use ) { + _outputs.del(_outputs.find(use)); + return _outputs.isEmpty(); + } + + // Shortcut for "popping" until n nodes. A "pop" is basically a + // setDef(last,null) followed by lowering the nIns() count. + 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 uses, by setting all of its defs + * to null. This may recursively kill more Nodes, and is basically dead + * code elimination. + */ + public void kill( ) { + unlock(); + moveDepsToWorklist(); + assert isUnused(); // Has no uses, so it is dead + _type=null; // Flag as dead + while( nIns()>0 ) { // Set all inputs to null, recursively killing unused Nodes + Node old_def = _inputs.removeLast(); + // Revisit neighbor because removed use + if( old_def != null && CODE.add(old_def).delUse(this) ) + old_def.kill(); // If we removed the last use, the old def is now dead + } + assert isDead(); // Really dead now + } + + // Mostly used for asserts and printing. + public boolean isDead() { return isUnused() && nIns()==0 && _type==null; } + + // Shortcuts to stop DCE mid-parse + // Add bogus null use to keep node alive + public N keep() { return addUse(null); } + // Remove bogus null. + public 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(); + } + + + // Replace self with nnn in the graph, making 'this' go dead + public void subsume( Node nnn ) { + assert nnn!=this; + while( nOuts() > 0 ) { + Node n = _outputs.removeLast(); + n.unlock(); + int idx = n._inputs.find(this); + n._inputs.set(idx,nnn); + nnn.addUse(n); + CODE.addAll(n._outputs); + } + kill(); + } + + // Replace uses of `def` with `this`, and insert `this` immediately after + // `def` in the basic block. + public void insertAfter( Node def, boolean must ) { + 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); + for( int j=def.nOuts()-1; j>=0; j-- ) { + // Can we avoid a split of a split? 'this' split is used by + // another split in the same block. + if( !must && def.out(j) instanceof SplitNode split && def.out(j).cfg0()==cfg && + !split._kind.contains("self") ) + continue; + Node use = def._outputs.del(j); + use.unlock(); + int idx = use._inputs.find(def); + use._inputs.set(idx,this); + addUse(use); + } + if( nIns()>1 ) setDef(1,def); + } + + // 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); + i = cfg.nOuts()-1; + } else { + i = cfg._outputs.find(use); + } + cfg._outputs.insert(this,i); + _inputs.set(0,cfg); + if( _inputs._len > 1 && this instanceof SplitNode ) + setDefOrdered(1,use.in(uidx)); + use.setDefOrdered(uidx,this); + } + + public void setDefOrdered(int idx, Node def) { + // If old is dying, remove from CFG ordered + Node old = in(idx); + if( old!=null && old.nOuts()==1 ) { + CFGNode cfg = old.cfg0(); + if( cfg!=null ) { + cfg._outputs.remove(cfg._outputs.find(old)); + old._inputs.set(0,null); + } + } + setDef(idx,def); + } + + public void removeSplit() { + CFGNode cfg = cfg0(); + cfg._outputs.remove(cfg._outputs.find(this)); + _inputs.set(0,null); + if( _inputs._len > 1 ) subsume(in(1)); + } + + // ------------------------------------------------------------------------ + // Graph-based optimizations + + /** + * Try to peephole at this node and return a better replacement Node. + * Always returns some not-null Node (often this). + */ + public final Node peephole( ) { +// if( _type==null ) // Brand-new node, never peeped before +// JSViewer.show(); + Node n = peepholeOpt(); +// if( n!=null ) // Made progress? +// JSViewer.show(); // Show again + return n==null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); // Cannot return null for no-progress + } + + /** + * Try to peephole at this node and return a better replacement Node if + * possible. We compute a {@link SONType} and then check and replace: + *

    + *
  • if the Type {@link SONType#isConstant}, we replace with a {@link ConstantNode}
  • + *
  • in a future chapter we will look for a + * Common Subexpression + * to eliminate.
  • + *
  • 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.
  • + * + * Unlike peephole above, this explicitly returns null for no-change, or not-null + * for a better replacement (which can be this). + *
+ */ + public final Node peepholeOpt( ) { + CODE.iterCnt(); + // Compute initial or improved Type + SONType 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( _type.isHighOrConst() && !isConst() ) + 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 + * Monotonic + * as it is part of a Monotone Analysis Framework. + * See for example this set of slides. + *

+ * 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. + *

+ * {@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 SONType compute(); + + // Set the type. Assert monotonic progress. + // If changing, add users to worklist. + public SONType setType(SONType type) { + SONType old = _type; + assert old==null || type.isa(old); // Since _type not set, can just re-run this in assert in the debugger + if( old == type ) return old; + _type = type; // Set _type late for easier assert debugging + CODE.addAll(_outputs); + moveDepsToWorklist(); + return old; + } + + public 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: + *

   ary[idx+1]
+ * might turn into Sea-of-Nodes IR: + *
   (ary+12)+((idx+1) * 4)
+ * This expression can then be idealized into: + *
   ary + ((idx*4) + (12 + (1*4)))
+ * And more folding: + *
   ary + ((idx<<2) + 16)
+ * And during code-gen: + *
   MOV4 Rary,[Ridx<<2 +16] // or some such hardware-specific notation 
+ *

+ * {@link #idealize} has a very specific calling convention: + *

    + *
  • If NO change is made, return {@code null} + *
  • If ANY change is made, return not-null; this can be {@code this} + *
  • The returned Node does NOT call {@link #peephole} on itself; the {@link #peephole} call will recursively peephole it. + *
  • Any NEW nodes that are not directly returned DO call {@link #peephole}. + *
+ *

+ * Examples: + * + * + * + * + * + *
before after return comment
{@code (x+5) }{@code (x+5) }{@code null }No change
{@code (5+x) }{@code (x+5) }{@code this }Swapped arguments
{@code ((x+1)+2)}{@code (x+(1+2))}{@code (x+_) }Returns 2 new Nodes
+ * + * 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. + *

+ * Since idealize calls peephole and peephole calls idealize, you must be + * careful that all idealizations are monotonic: 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 _deps; + + /** + * Add a node to the list of dependencies. Only add it if its 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. + */ + Node addDep( Node dep ) { + // Running peepholes during the big assert cannot have side effects + // like adding dependencies. + if( CODE._midAssert ) return this; + if( dep == null ) return this; + if( _deps==null ) _deps = new Ary<>(Node.class); + if( _deps .find(dep) != -1 ) return this; // Already on list + if( _inputs .find(dep) != -1 ) return this; // No need for deps on immediate neighbors + if( _outputs.find(dep) != -1 ) return this; + _deps.add(dep); + return this; + } + + // Move the dependents onto a worklist, and clear for future dependents. + public void moveDepsToWorklist( ) { + if( _deps==null ) return; + CODE.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>13) ^ n._nid; + if( hash==0 ) hash = 0xDEADBEEF; // Bad hash, so use some junky thing + return (_hash=hash); + } + // Subclasses add extra hash info (such as ConstantNodes constant) + int hash() { return 0; } + + // ------------------------------------------------------------------------ + // + + /** Is this Node Memory related */ + public boolean isMem() { return false; } + + /** Pinned in the schedule; these are data nodes whose input#0 is not allowed to change */ + public boolean isPinned() { return false; } + + // 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 E walk( Function pred ) { + assert CODE._visit.isEmpty(); + E rez = _walk(pred); + CODE._visit.clear(); + return rez; + } + + private E _walk( Function 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/NotNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/NotNode.java new file mode 100644 index 0000000..5649814 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/NotNode.java @@ -0,0 +1,31 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.sontypes.*; +import java.util.BitSet; + +public class NotNode extends Node { + public NotNode(Node in) { super(null, in); } + + @Override public String label() { return "Not"; } + + @Override public String glabel() { return "!"; } + + @Override + public StringBuilder _print1(StringBuilder sb, BitSet visited) { + in(1)._print0(sb.append("(!"), visited); + return sb.append(")"); + } + + @Override + public SONTypeInteger compute() { + SONType t0 = in(1)._type; + if( t0.isHigh() ) return SONTypeInteger.BOOL.dual(); + if( t0 == SONType.NIL || t0 == SONTypeInteger.ZERO ) return SONTypeInteger.TRUE; + if( t0 instanceof SONTypeNil tn && tn.notNull() ) return SONTypeInteger.FALSE; + if( t0 instanceof SONTypeInteger i && (i._min > 0 || i._max < 0) ) return SONTypeInteger.FALSE; + return SONTypeInteger.BOOL; + } + + @Override + public Node idealize() { return null; } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/OrNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/OrNode.java new file mode 100644 index 0000000..fc76f23 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/OrNode.java @@ -0,0 +1,53 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.Compiler; +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeInteger; + +public class OrNode extends LogicalNode { + public OrNode(Node lhs, Node rhs) { super(lhs, rhs); } + + @Override public String label() { return "Or"; } + @Override public String op() { return "|"; } + + @Override public String glabel() { return "|"; } + + @Override + public SONType compute() { + SONType t1 = in(1)._type, t2 = in(2)._type; + if( t1.isHigh() || t2.isHigh() ) + return SONTypeInteger.TOP; + if( t1 instanceof SONTypeInteger i0 && + t2 instanceof SONTypeInteger i1 ) { + if( i0.isConstant() && i1.isConstant() ) + return SONTypeInteger.constant(i0.value()|i1.value()); + } + return SONTypeInteger.BOT; + } + + @Override + public Node idealize() { + Node lhs = in(1); + Node rhs = in(2); + SONType t1 = lhs._type; + SONType 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 SONTypeInteger i && i.value()==0 ) + return lhs; + + // Move constants to RHS: con*arg becomes arg*con + if ( t1.isConstant() && !t2.isConstant() ) + return swap12(); + + // Do we have ((x | (phi cons)) | con) ? + // Do we have ((x | (phi cons)) | (phi cons)) ? + // Push constant up through the phi: x | (phi con0|con0 con1|con1...) + Node phicon = AddNode.phiCon(this,true); + if( phicon!=null ) return phicon; + + return null; + } + @Override Node copy(Node lhs, Node rhs) { return new OrNode(lhs,rhs); } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ParmNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ParmNode.java new file mode 100644 index 0000000..b755949 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ParmNode.java @@ -0,0 +1,47 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import java.util.BitSet; + +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, SONType declaredType, Node... inputs) { + super(label,declaredType,inputs); + _idx = idx; + } + public ParmNode(ParmNode parm) { super(parm, parm._label, parm._declaredType); _idx = parm._idx; } + + @Override public String label() { return MemOpNode.mlabel(_label); } + + @Override public String glabel() { return _label; } + + public FunNode fun() { return (FunNode)in(0); } + + @Override + public StringBuilder _print1(StringBuilder sb, BitSet visited) { + if( "main".equals(fun()._name) && _label.equals("arg") ) + return sb.append("arg"); + sb.append("Parm_").append(_label).append("("); + for( Node in : _inputs ) { + if (in == null) sb.append("____"); + else in._print0(sb, visited); + sb.append(","); + } + sb.setLength(sb.length()-1); + sb.append(")"); + return sb; + } + + + @Override + public Node idealize() { + if( inProgress() ) return null; + return super.idealize(); + } + + // Always in-progress until we run out of unknown callers + @Override public boolean inProgress() { return fun().inProgress(); } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/PhiNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/PhiNode.java new file mode 100644 index 0000000..5523b17 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/PhiNode.java @@ -0,0 +1,205 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.sontypes.*; +import com.compilerprogramming.ezlang.exceptions.CompilerException; + +import java.util.BitSet; + +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. + final SONType _declaredType; + + public PhiNode(String label, SONType declaredType, Node... inputs) { super(inputs); _label = label; assert declaredType!=null; _declaredType = declaredType; } + public PhiNode(PhiNode phi, String label, SONType declaredType) { super(phi); _label = label; _declaredType = declaredType; } + public PhiNode(PhiNode phi) { super(phi); _label = phi._label; _declaredType = phi._declaredType; } + + public PhiNode(RegionNode r, Node sample) { + super(new Node[]{r}); + _label = ""; + _declaredType = sample._type; + while( nIns() < r.nIns() ) + addDef(sample); + } + + @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); } + + @Override public String glabel() { return "φ_"+_label; } + + @Override + public StringBuilder _print1(StringBuilder sb, BitSet visited) { + if( !(region() instanceof RegionNode r) || r.inProgress() ) + sb.append("Z"); + sb.append("Phi("); + for( Node in : _inputs ) { + if (in == null) sb.append("____"); + else in._print0(sb, visited); + sb.append(","); + } + sb.setLength(sb.length()-1); + sb.append(")"); + return sb; + } + + public CFGNode region() { return (CFGNode)in(0); } + @Override public boolean isMem() { return _declaredType instanceof SONTypeMem; } + @Override public boolean isPinned() { return true; } + + @Override + public SONType compute() { + if( !(region() instanceof RegionNode r) ) + return region()._type== SONType.XCONTROL ? (_type instanceof SONTypeMem ? SONTypeMem.TOP : SONType.TOP) : _type; + // During parsing Phis have to be computed type pessimistically. + if( r.inProgress() ) return _declaredType; + // Set type to local top of the starting type + SONType t = _declaredType.glb().dual();//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. + if( r.in(i).addDep(this)._type != SONType.XCONTROL ) + t = t.meet(in(i)._type); + return t; + } + + @Override + public Node idealize() { + if( !(region() instanceof RegionNode r ) ) + return in(1); // Input has collapse to e.g. starting control. + if( r.inProgress() || r.nIns()<=1 ) + return null; // Input is in-progress + + // If we have only a single unique input, become it. + Node live = singleUniqueInput(); + if (live != null) + return live; + + // No bother if region is going to fold dead paths soon + for( int i=1; i 0 && idom.in(0) != iff ) idom = idom.idom(); + if( idom instanceof CProjNode proj && proj._idx==1 ) + return val; + } + } + } + + return null; + } + + private boolean same_op() { + for( int i=2; i1 && in(nIns()-1) == null; } + + // Never equal if inProgress + @Override boolean eq( Node n ) { return !inProgress(); } + +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ReturnNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ReturnNode.java new file mode 100644 index 0000000..b44328e --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ReturnNode.java @@ -0,0 +1,131 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.Compiler; +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.sontypes.*; +import com.compilerprogramming.ezlang.exceptions.CompilerException; + +import java.util.BitSet; + +/** + * The Return node has two inputs. The first input is a control node and the + * second is the data node that supplies the return value. + *

+ * In this presentation, Return functions as a Stop node, since multiple return statements are not possible. + * The Stop node will be introduced in Chapter 6 when we implement if statements. + *

+ * The Return's output is the value from the data node. + */ +public class ReturnNode extends CFGNode { + + public FunNode _fun; + + public ReturnNode(Node ctrl, Node mem, Node data, Node rpc, FunNode fun ) { + super(ctrl, mem, data, rpc); + _fun = fun; + } + public ReturnNode( ReturnNode ret, FunNode fun ) { super(ret); _fun = fun; } + + public Node ctrl() { return in(0); } + public Node mem () { return in(1); } + public Node expr() { return in(2); } + public Node rpc () { return in(3); } + public FunNode fun() { return _fun; } + + @Override + public String label() { return "Return"; } + + @Override + public StringBuilder _print1(StringBuilder sb, BitSet visited) { + sb.append("return "); + if( expr()==null ) sb.append("----"); + else expr()._print0(sb, visited); + return sb.append(";"); + } + + // No one unique control follows; can be many call end sites + @Override public CFGNode uctrl() { return null; } + + @Override + public SONType compute() { + if( inProgress () ) return SONTypeTuple.RET; // In progress + if( _fun.isDead() ) return SONTypeTuple.RET.dual(); // Dead another way + return SONTypeTuple.make(ctrl()._type,mem()._type,expr()._type); + } + + @Override public Node idealize() { + if( inProgress () ) return null; + if( _fun.isDead() ) return null; + +// // Upgrade signature based on return type +// SONType ret = expr()._type; +// SONTypeFunPtr fcn = _fun.sig(); +// assert ret.isa(fcn.ret()); +// if( ret != fcn.ret() ) +// _fun.setSig(fcn.makeFrom(ret)); + + // If dead (cant be reached; infinite loop), kill the exit values + if( ctrl()._type== SONType.XCONTROL && + !(mem() instanceof ConstantNode && expr() instanceof ConstantNode) ) { + Node top = new ConstantNode(SONType.TOP).peephole(); + setDef(1,top); + setDef(2,top); + return this; + } + + return null; + } + + public boolean inProgress() { + return ctrl().getClass() == RegionNode.class && ((RegionNode)ctrl()).inProgress(); + } + + // Gather parse-time return types for error reporting + private SONType mt = SONType.TOP; + 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 + SONType t = expr._type; + mt = mt.meet(t); + ti |= t instanceof SONTypeInteger x; + tf |= t instanceof SONTypeFloat x; + tp |= t instanceof SONTypeMemPtr x; + tn |= t== SONType.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._declaredType == SONTypeMem.BOT; + PhiNode rez = (PhiNode)r.out(1); assert rez._declaredType == SONType.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 CompilerException err() { + return expr()._type/*mt*/== SONType.BOTTOM ? mixerr(ti,tf,tp,tn) : null; + } + + static CompilerException mixerr( boolean ti, boolean tf, boolean tp, boolean tn) { + if( !ti && !tf && !tp && !tn ) + return Compiler.error("No defined return type"); + 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 Compiler.error(sb.unchar(5).toString()); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/RoundF32Node.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/RoundF32Node.java new file mode 100644 index 0000000..aa9a698 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/RoundF32Node.java @@ -0,0 +1,38 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFloat; + +import java.util.BitSet; + +public class RoundF32Node extends Node { + public RoundF32Node(Node lhs) { super(null, lhs); } + + @Override public String label() { return "RoundF32"; } + + @Override public String glabel() { return "(f32)"; } + + @Override + public StringBuilder _print1(StringBuilder sb, BitSet visited) { + return in(1)._print0(sb.append("((f32)"), visited).append(")"); + } + + @Override + public SONType compute() { + if (in(1)._type instanceof SONTypeFloat i0 && i0.isConstant() ) + return SONTypeFloat.constant((float)i0.value()); + return in(1)._type; + } + + @Override + public Node idealize() { + Node lhs = in(1); + SONType t1 = lhs._type; + + // RoundF32 of float + if( t1 instanceof SONTypeFloat tf && tf._sz==32 ) + return lhs; + + return null; + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/SarNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/SarNode.java new file mode 100644 index 0000000..1a823dd --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/SarNode.java @@ -0,0 +1,48 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeInteger; + +public class SarNode extends LogicalNode { + public SarNode(Node lhs, Node rhs) { super(lhs, rhs); } + + @Override public String label() { return "Sar"; } + @Override public String op() { return ">>"; } + + @Override public String glabel() { return ">>"; } + + @Override + public SONType compute() { + SONType t1 = in(1)._type, t2 = in(2)._type; + if( t1.isHigh() || t2.isHigh() ) + return SONTypeInteger.TOP; + if (t1 instanceof SONTypeInteger i0 && + t2 instanceof SONTypeInteger i1) { + if( i0 == SONTypeInteger.ZERO ) + return SONTypeInteger.ZERO; + if( i0.isConstant() && i1.isConstant() ) + return SONTypeInteger.constant(i0.value()>>i1.value()); + if( i1.isConstant() ) { + int log = (int)i1.value(); + return SONTypeInteger.make(-1L<<(63-log),(1L<<(63-log))-1); + } + } + return SONTypeInteger.BOT; + } + + @Override + public Node idealize() { + Node lhs = in(1); + Node rhs = in(2); + SONType t2 = rhs._type; + + // Sar of 0. + if( t2.isConstant() && t2 instanceof SONTypeInteger i && (i.value()&63)==0 ) + return lhs; + + // TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5) + + return null; + } + @Override Node copy(Node lhs, Node rhs) { return new SarNode(lhs,rhs); } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ScopeNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ScopeNode.java new file mode 100644 index 0000000..c3109cc --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ScopeNode.java @@ -0,0 +1,429 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.CodeGen; +import com.compilerprogramming.ezlang.compiler.sontypes.*; +import java.util.*; + +/** + * The Scope node is purely a parser helper - it tracks names to nodes with a + * stack of variables. + */ +public class ScopeNode extends MemMergeNode { + + /** + * The control is a name that binds to the currently active control + * node in the graph + */ + public static final String CTRL = "$ctrl"; + public static final String ARG0 = "arg"; + public static final String MEM0 = "$mem"; + + // All active/live variables in all nested scopes, all run together + public final Ary _vars; + + // Size of each nested lexical scope + public final Ary _lexSize; + + // Lexical scope is one of normal Block, constructor or function + public enum Kind { Block, Constructor, Function }; + public final Ary _kinds; + + // Extra guards; tested predicates and casted results + private final Ary _guards; + + // A new ScopeNode + public ScopeNode() { + super(true); + _vars = new Ary<>(Var .class); + _lexSize= new Ary<>(Integer.class); + _kinds = new Ary<>(Kind .class); + _guards = new Ary<>(Node .class); + } + + @Override public String label() { return "Scope"; } + + @Override + public StringBuilder _print1(StringBuilder sb, BitSet visited) { + sb.append("Scope[ "); + int j=1; + for( int i=0; i N ctrl(N n) { return setDef(0,n); } + public Node mem(Node n) { return setDef(1,n); } + + public void push(Kind kind) { + assert _lexSize._len==_kinds._len; + _lexSize.push(_vars.size()); + _kinds .push(kind); + } + + // Pop a lexical scope + public void pop() { + assert _lexSize._len==_kinds._len; + promote(); + int n = _lexSize.pop(); + _kinds.pop(); + popUntil(n); + _vars.setLen(n); + } + + + // 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() { + int n = _lexSize.last(); + for( int i=n; i=0 && v._idx<_lexSize.at(i); i-- ) + if( _kinds.at(i)==Kind.Function ) + return true; + return false; + } + + + // 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. + int find( String name ) { + for( int i=_vars.size()-1; i>=0; i-- ) + if( _vars.at(i)._name.equals(name) ) + return i; + return -1; + } + + /** + * Create a new variable name in the current scope + */ + public boolean define(String name, SONType declaredType, boolean xfinal, Node init) { + assert _lexSize.isEmpty() || name.charAt(0)!='$' ; // Later scopes do not define memory + if( _lexSize._len > 0 ) + for( int i=_vars.size()-1; i>=_lexSize.last(); i-- ) { + Var n = _vars.at(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); // 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,init==Compiler.XCTRL); + _vars.add(v); + // Creating a forward reference + if( init==Compiler.XCTRL ) + init = new FRefNode(v).init(); + addDef(init); + return true; + } + + // Read from memory + public Node mem( int alias ) { return mem()._mem(alias,null); } + // Write to memory + public void mem( int alias, Node st ) { mem()._mem(alias,st); } + + + /** + * Lookup a name in all scopes starting from most deeply nested. + * + * @param name Name to be looked up + * @return null if not found, or the implementing Node + */ + public Var lookup( String name ) { + int idx = find(name); + // -1 is missed in all scopes, not found + return idx == -1 ? null : update(_vars.at(idx),null); + } + + /** + * If the name is present in any scope, then redefine else null + * + * @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(_vars.at(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,new PhiNode(v._name, v.lazyGLB(), 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). + *

+ * 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. + *

+ * 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._lexSize.addAll(_lexSize); + 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 input is a shallow copy + MemMergeNode memdup = new MemMergeNode(true), mem = mem(); + memdup.addDef(null); + memdup.addDef(loop ? this : mem.in(1)); + for( int i=2; 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 (x << i) + (c << i) + if( lhs instanceof AddNode add && add.addDep(this).in(2)._type instanceof SONTypeInteger c && c.isConstant() ) { + long sum = c.value() << shl.value(); + if( Integer.MIN_VALUE <= sum && sum <= Integer.MAX_VALUE ) + return new AddNode(new ShlNode(add.in(1),rhs).peephole(), Compiler.con(sum) ); + } + } + + // TODO: x << 3 << (y ? 1 : 2) ==> x << (y ? 4 : 5) + + return null; + } + @Override Node copy(Node lhs, Node rhs) { return new ShlNode(lhs,rhs); } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ShrNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ShrNode.java new file mode 100644 index 0000000..c4b2a9b --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/ShrNode.java @@ -0,0 +1,45 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.Compiler; +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeInteger; + +public class ShrNode extends LogicalNode { + public ShrNode(Node lhs, Node rhs) { super(lhs, rhs); } + + @Override public String label() { return "Shr"; } + @Override public String op() { return ">>>"; } + + @Override public String glabel() { return ">>>"; } + + @Override + public SONType compute() { + SONType t1 = in(1)._type, t2 = in(2)._type; + if( t1.isHigh() || t2.isHigh() ) + return SONTypeInteger.TOP; + if (t1 instanceof SONTypeInteger i0 && + t2 instanceof SONTypeInteger i1 ) { + if( i0 == SONTypeInteger.ZERO ) + return SONTypeInteger.ZERO; + if( i0.isConstant() && i1.isConstant() ) + return SONTypeInteger.constant(i0.value()>>>i1.value()); + } + return SONTypeInteger.BOT; + } + + @Override + public Node idealize() { + Node lhs = in(1); + Node rhs = in(2); + SONType t2 = rhs._type; + + // Shr of 0. + if( t2.isConstant() && t2 instanceof SONTypeInteger i && (i.value()&63)==0 ) + return lhs; + + // TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5) + + return null; + } + @Override Node copy(Node lhs, Node rhs) { return new ShrNode(lhs,rhs); } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/SplitNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/SplitNode.java new file mode 100644 index 0000000..9a980ec --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/SplitNode.java @@ -0,0 +1,22 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.CodeGen; +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import java.util.BitSet; + +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; } + @Override public String op() { return "mov"; } + @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { + return in(1)._print0(sb.append("mov("),visited).append(")"); + } + @Override public SONType 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/StartNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/StartNode.java new file mode 100644 index 0000000..ac9053f --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/StartNode.java @@ -0,0 +1,57 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.codegen.CodeGen; +import com.compilerprogramming.ezlang.compiler.Compiler; +import com.compilerprogramming.ezlang.compiler.sontypes.*; + +import java.util.BitSet; + +import static com.compilerprogramming.ezlang.compiler.Utils.TODO; + +/** + * The Start node represents the start of the function. + * + * Start initially has 1 input (arg) from outside and the initial control. + * In ch10 we also add mem aliases as structs get defined; each field in struct + * adds a distinct alias to Start's tuple. + */ +public class StartNode extends LoopNode implements MultiNode { + + final SONType _arg; + + public StartNode(SONType arg) { super((Node)null); _arg = arg; _type = compute(); } + public StartNode(StartNode start) { super(start); _arg = start==null ? null : start._arg; } + + @Override public String label() { return "Start"; } + + @Override + public StringBuilder _print1(StringBuilder sb, BitSet visited) { + return sb.append(label()); + } + + @Override public boolean blockHead() { return true; } + @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", its 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 SONTypeTuple compute() { + return SONTypeTuple.make(SONType.CONTROL, SONTypeMem.TOP,_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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/StopNode.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/StopNode.java new file mode 100644 index 0000000..1d13a7f --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/StopNode.java @@ -0,0 +1,63 @@ +package com.compilerprogramming.ezlang.compiler.nodes; + +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import java.util.BitSet; + +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 + public StringBuilder _print1(StringBuilder sb, BitSet visited) { + // For the sake of many old tests, and single value prints as "return val" + if( ret()!=null ) return ret()._print0(sb,visited); + sb.append("Stop[ "); + for( Node ret : _inputs ) + ret._print0(sb, visited).append(" "); + return sb.append("]"); + } + + @Override public boolean blockHead() { return true; } + + + // If a single Return, return it. + // Otherwise, null because ambiguous. + public ReturnNode ret() { + return nIns()==1 && in(0) instanceof ReturnNode ret ? ret : null; + } + + @Override + public SONType compute() { + return SONType.BOTTOM; + } + + @Override + public Node idealize() { + int len = nIns(); + for( int i=0; i> ").p(code.reg(in(2))); + } + +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/AsrIARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/AsrIARM.java new file mode 100644 index 0000000..05a508c --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/AsrIARM.java @@ -0,0 +1,31 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.CodeGen; +import com.compilerprogramming.ezlang.compiler.codegen.Encoding; +import com.compilerprogramming.ezlang.compiler.codegen.RegMask; +import com.compilerprogramming.ezlang.compiler.nodes.MachConcreteNode; +import com.compilerprogramming.ezlang.compiler.nodes.MachNode; +import com.compilerprogramming.ezlang.compiler.nodes.Node; + +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.RMASK; } + @Override public void encoding( Encoding enc ) { + short rd = enc.reg(this); + short rn = enc.reg(in(1)); + assert _imm > 0; + enc.add4(arm.imm_shift(0b1001001101,_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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/BranchARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/BranchARM.java new file mode 100644 index 0000000..12ce1c2 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/BranchARM.java @@ -0,0 +1,65 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +// 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); + 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 invert() { _bop = invert(_bop); } + + // Encoding is appended into the byte array; size is returned + @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 + enc.jump(this,cproj(0)); + // B.cond + enc.add4( arm.b_cond(0b01010100, 0, arm.make_condition(_bop)) ); + } + + // Delta is from opcode start + @Override public byte encSize(int delta) { + 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 ) { + if( opLen==4 ) { + enc.patch4(opStart,arm.b_cond(0b01010100, delta, arm.make_condition(_bop))); + } else { + throw Utils.TODO(); + } + } + + @Override public void asm(CodeGen code, SB sb) { + String src = code.reg(in(1)); + if( src!="flags" ) sb.p(src).p(" "); + CFGNode prj = cproj(0); + while( prj.nOuts() == 1 ) + prj = prj.uctrl(); // Skip empty blocks + sb.p(label(prj)); + } + @Override public String comment() { return "L"+cproj(1)._nid; } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/CallARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/CallARM.java new file mode 100644 index 0000000..d16a25f --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/CallARM.java @@ -0,0 +1,47 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFunPtr; + +public class CallARM extends CallNode implements MachNode, RIPRelSize { + final SONTypeFunPtr _tfp; + final String _name; + + CallARM(CallNode call, SONTypeFunPtr tfp) { + super(call); + _inputs.pop(); // Pop constant target + assert tfp.isConstant(); + _tfp = tfp; + _name = CodeGen.CODE.link(tfp)._name; + } + + @Override public String op() { return "call"; } + @Override public String label() { return op(); } + @Override public String name() { return _name; } + @Override public SONTypeFunPtr tfp() { return _tfp; } + @Override public RegMask regmap(int i) { return arm.callInMask(_tfp,i); } + @Override public RegMask outregmap() { return null; } + + @Override public void encoding( Encoding enc ) { + enc.relo(this); + // BL + enc.add4(arm.b(0b100101,0)); // Target patched at link time + } + + // Delta is from opcode start, but X86 measures from the end of the 5-byte encoding + @Override public byte encSize(int delta) { return 4; } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + enc.patch4(opStart,arm.b(0b100101,delta)); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(_name); + for( int i=0; i> i) & 0xFFFF; + if (block == 0) nb0++; + if (block == 0xFFFF) nb1++; + } + int pattern; + int op; + if(nb0 >= nb1) { + // More 0 blocks then F blocks, use movz + pattern = 0; + op = 0b110100101; + } else { + // More F blocks then 0 blocks, use movn + pattern = 0xFFFF; + op = 0b100100101; + } + int invert = pattern; + for (int i=0; i<4; i++) { + int block = (int)x & 0xFFFF; + x >>= 16; + if (block != pattern) { + enc.add4(arm.mov(op, i, block ^ invert, self)); + op = 0b111100101; + invert = 0; + } + } + if (op != 0b111100101) { + // All blocks are the same, special case + enc.add4(arm.mov(op, 0, 0, self)); + } + } + + // Human-readable form appended to the SB. Things like the encoding, + // indentation, leading address or block labels not printed here. + // Just something like "ld4\tR17=[R18+12] // Load array base". + // General form: "op\tdst=src+src" + @Override public void asm(CodeGen code, SB sb) { + String reg = code.reg(this); + _con.print(sb.p(reg).p(" = #")); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LoadARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LoadARM.java new file mode 100644 index 0000000..6d11b2f --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LoadARM.java @@ -0,0 +1,33 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.LoadNode; +import com.compilerprogramming.ezlang.compiler.nodes.Node; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFloat; + +// Load memory addressing on ARM +// Support imm, reg(direct), or reg+off(indirect) addressing +// Base = base - base pointer, offset is added to base +// idx = null +// off = off - offset added to base + +public class LoadARM extends MemOpARM{ + LoadARM(LoadNode ld,Node base, Node idx, int off) { + super(ld, base, idx, off, 0); + } + @Override public String op() { return "ld"+_sz; } + @Override public RegMask outregmap() { return arm.MEM_MASK; } + // ldr(immediate - unsigned offset) | ldr(register) + @Override public void encoding( Encoding enc ) { + if(_declaredType == SONTypeFloat.F32 || _declaredType == SONTypeFloat.F64) { + ldst_encode(enc, 0b1111110101, 0b11111100011, this, true); + } else { + ldst_encode(enc, 0b1111100101, 0b11111000011, this, false); + } + } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(","); + asm_address(code,sb); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LslARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LslARM.java new file mode 100644 index 0000000..0596fc2 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LslARM.java @@ -0,0 +1,19 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +// 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.RMASK; } + + @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,0b1000); } + @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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LslIARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LslIARM.java new file mode 100644 index 0000000..9201580 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LslIARM.java @@ -0,0 +1,33 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.CodeGen; +import com.compilerprogramming.ezlang.compiler.codegen.Encoding; +import com.compilerprogramming.ezlang.compiler.codegen.RegMask; +import com.compilerprogramming.ezlang.compiler.nodes.MachConcreteNode; +import com.compilerprogramming.ezlang.compiler.nodes.MachNode; +import com.compilerprogramming.ezlang.compiler.nodes.Node; + +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.RMASK; } + @Override public void encoding( Encoding enc ) { + short rd = enc.reg(this); + short rn = enc.reg(in(1)); + assert _imm > 0; + // UBFM , , #(- MOD 64), #(63-) + // immr must be (- MOD 64) = 64 - shift + enc.add4(arm.imm_shift(0b1101001101, 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LsrARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LsrARM.java new file mode 100644 index 0000000..afd84ba --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LsrARM.java @@ -0,0 +1,19 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +// 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.RMASK; } + + @Override public void encoding( Encoding enc ) { arm.shift_reg(enc,this,0b1001); } + @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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LsrIARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LsrIARM.java new file mode 100644 index 0000000..72629ff --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/LsrIARM.java @@ -0,0 +1,29 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm; + + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +// 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.RMASK; } + @Override public void encoding( Encoding enc ) { + short rd = enc.reg(this); + short rn = enc.reg(in(1)); + assert _imm > 0; + enc.add4(arm.imm_shift(0b1101001101,_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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/MemOpARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/MemOpARM.java new file mode 100644 index 0000000..70dfdee --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/MemOpARM.java @@ -0,0 +1,67 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import com.compilerprogramming.ezlang.compiler.sontypes.*; + +import java.lang.StringBuilder; +import java.util.BitSet; + + +public abstract class MemOpARM extends MemOpNode implements MachNode { + final int _off; // Limit 9 bits sized, or (13 bits< 0 ? "addi" : "ret"; + } + // Correct Nodes outside the normal edges + @Override public void postSelect(CodeGen code) { + FunNode fun = (FunNode)rpc().in(0); + _fun = fun; + fun.setRet(this); + } + @Override public RegMask regmap(int i) { return arm.retMask(_fun.sig(),i); } + @Override public RegMask outregmap() { return null; } + // RET + @Override public void encoding( Encoding enc ) { + int frameAdjust = ((FunARM)fun())._frameAdjust; + if( frameAdjust > 0 ) + enc.add4(arm.imm_inst(0b1001000100, (frameAdjust*-8)&0xFFF, arm.RSP, arm.RSP)); + enc.add4(arm.ret(0b1101011001011111000000)); + } + + @Override public void asm(CodeGen code, SB sb) { + int frameAdjust = ((FunARM)fun())._frameAdjust; + if( frameAdjust>0 ) + sb.p("rsp += #").p(frameAdjust*-8).p("\nret"); + // Post code-gen, just print the "ret" + if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() ) + // Prints return reg (either X0 or D0), RPC (always R30) and + // then the callee-save registers. + for( int i=2; i> 12; + // patch upper 20 bits via adrp + enc.patch4(opStart, arm.adrp(1, adrp_delta & 0b11, 0b10000, adrp_delta >> 2, rpc)); + // low 12 bits via add + enc.patch4(next, arm.imm_inst_l(enc, this, 0b1001000100, delta & 0xfff)); + } else { + throw Utils.TODO(); + } + } + + @Override public void asm(CodeGen code, SB sb) { + String reg = code.reg(this); + _con.print(sb.p(reg).p(" #")); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/UJmpARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/UJmpARM.java new file mode 100644 index 0000000..fa566b5 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/UJmpARM.java @@ -0,0 +1,48 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import java.util.BitSet; + +// unconditional jump +public class UJmpARM extends CFGNode implements MachNode, RIPRelSize { + UJmpARM() { } + @Override public String op() { return "jmp"; } + @Override public String label() { return op(); } + @Override public StringBuilder _print1( StringBuilder sb, BitSet visited ) { + return sb.append("jmp "); + } + @Override public RegMask regmap(int i) {return null; } + @Override public RegMask outregmap() { return null; } + @Override public SONType compute() { throw Utils.TODO(); } + @Override public Node idealize() { throw Utils.TODO(); } + @Override public void encoding( Encoding enc ) { + enc.jump(this,uctrl()); + int body = arm.b(0b01010100, 0); + enc.add4(body); + } + + // Delta is from opcode start + @Override public byte encSize(int delta) { + if( -(1<<26) <= delta && delta < (1<<26) ) return 4; + // 2 word encoding needs a tmp register, must teach RA + throw Utils.TODO(); + } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + if( opLen==4 ) { + enc.patch4(opStart,arm.b(0b01010100, 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/XorARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/XorARM.java new file mode 100644 index 0000000..ac9e38b --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/XorARM.java @@ -0,0 +1,19 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +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.RMASK; } + @Override public void encoding( Encoding enc ) { arm.r_reg(enc,this,0b11001010); } + // 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/XorIARM.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/XorIARM.java new file mode 100644 index 0000000..2dc314a --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/XorIARM.java @@ -0,0 +1,27 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm; + +import com.compilerprogramming.ezlang.compiler.nodes.MachConcreteNode; +import com.compilerprogramming.ezlang.compiler.nodes.MachNode; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +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.RMASK; } + + // General form: "xori rd = rs1 ^ imm" + @Override public void encoding( Encoding enc ) { arm.imm_inst_n(enc,this,0b110100100,_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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/arm.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/arm.java new file mode 100644 index 0000000..4f01b8d --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/arm/arm.java @@ -0,0 +1,662 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.arm; + +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import com.compilerprogramming.ezlang.compiler.sontypes.*; +import java.io.ByteArrayOutputStream; + +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) + static final int X0 = 0, X1 = 1, X2 = 2, X3 = 3, X4 = 4, X5 = 5, X6 = 6, X7 = 7; + static final int X8 = 8, X9 = 9, X10 = 10, X11 = 11, X12 = 12, X13 = 13, X14 = 14, X15 = 15; + static final int X16 = 16, X17 = 17, X18 = 18, X19 = 19, X20 = 20, X21 = 21, X22 = 22, X23 = 23; + static final int X24 = 24, X25 = 25, X26 = 26, X27 = 27, X28 = 28, X29 = 29, X30 = 30, RSP = 31; + + // Floating point registers + static final int D0 = 32, D1 = 33, D2 = 34, D3 = 35, D4 = 36, D5 = 37, D6 = 38, D7 = 39; + static final int D8 = 40, D9 = 41, D10 = 42, D11 = 43, D12 = 44, D13 = 45, D14 = 46, D15 = 47; + static final int D16 = 48, D17 = 49, D18 = 50, D19 = 51, D20 = 52, D21 = 53, D22 = 54, D23 = 55; + static final int D24 = 56, D25 = 57, D26 = 58, D27 = 59, D28 = 60, D29 = 61, D30 = 62, D31 = 63; + + static final int MAX_REG = 64; + static final int D_OFFSET = 31; + static final int FLAGS = 64; + + 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 reg( int reg ) { + return reg < REGS.length ? REGS[reg] : "[rsp+"+(reg-REGS.length)*8+"]"; + } + // Stack slots, in units of 8 bytes. + @Override public int stackSlot( int reg ) { + return reg < REGS.length ? -1 : reg-REGS.length; + } + + // 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< "!="; + case "!=" -> "=="; + case ">" -> "<="; + case "<" -> ">="; + case ">=" -> "<"; + case "<=" -> ">"; + default -> throw new IllegalStateException("Unexpected value: " + op); + }; + } + public enum COND { + EQ, + NE, + CS, + CC, + MI, + PL, + VS, + VC, + HI, + LS, + GE, + LT, + GT, + LE, + AL, + NV + } + + // True if signed 9-bit immediate + private static boolean imm9(SONTypeInteger ti) { + // 55 = 64-9 + return ti.isConstant() && ((ti.value()<<55)>>55) == ti.value(); + } + // True if signed 12-bit immediate + private static boolean imm12(SONTypeInteger 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(SONTypeInteger ti) { + if( !ti.isConstant() ) return -1; + if( !ti.isConstant() ) return -1; + long val = ti.value(); + if (val == 0 || val == -1) return -1; // Special cases are not allowed + int immr = 0; + // Rotate until we have 0[...]1 + while (val < 0 || (val & 1)==0) { + val = (val >>> 63) | (val << 1); + immr++; + } + int size = 32; + long pattern = val; + // Is upper half of pattern the same as the lower? + while ((pattern & ((1L<> size)) { + // Then only take one half + pattern >>= size; + size >>= 1; + } + size <<= 1; + int imms = Long.bitCount(pattern); + // Pattern should now be zeros followed by ones 0000011111 + if (pattern != (1L<=0 && imm12 >= 0 && rn >=0 && rd>=0; // Caller zeros high order bits + return (opcode << 22) | (imm12 << 10) | (rn << 5) | rd; + } + + public static int imm_shift(int opcode, int imm, int imms, int rn, int rd) { + return (opcode << 22) | (1 << 22) | (imm << 16) | (imms << 10) | (rn << 5) | rd; + } + + public static void imm_inst(Encoding enc, Node n, int opcode, int imm12) { + short self = enc.reg(n); + short reg1 = enc.reg(n.in(1)); + int body = imm_inst(opcode, imm12&0xFFF, reg1, self); + enc.add4(body); + } + + public static void imm_inst_n(Encoding enc, Node n, int opcode, int imm13) { + short self = enc.reg(n); + short reg1 = enc.reg(n.in(1)); + + int body = imm_inst_n(opcode, imm13, reg1, self); + enc.add4(body); + } + + // nth bit comes from immediate and not opcode + public static int imm_inst_n(int opcode, int imm13, int rn, int rd) { + assert 0 <= imm13 && imm13 <= 0x1FFF; + return (opcode << 23) | (imm13 << 10) | (rn << 5) | rd; + } + + public static int imm_inst_l(Encoding enc, Node n, int opcode, int imm12) { + short self = enc.reg(n); + short reg1 = enc.reg(n.in(1)); + int body = imm_inst(opcode, imm12&0xFFF, reg1, self); + return body; + } + + // for normal add, reg1, reg2 cases (reg-to-reg) + // using shifted-reg form + public static int r_reg(int opcode, int shift, int rm, int imm6, int rn, int rd) { + return (opcode << 24) | (shift << 21) | (rm << 16) | (imm6 << 10) << (rn << 5) | rd; + } + public static void r_reg(Encoding enc, Node n, int opcode) { + short self = enc.reg(n); + short reg1 = enc.reg(n.in(1)); + short reg2 = enc.reg(n.in(2)); + int body = r_reg(opcode, 0, reg2, 0, reg1, self); + enc.add4(body); + } + + public static int shift_reg(int opcode, int rm, int op2, int rn, int rd) { + return (opcode << 21) | (rm << 16) | (op2 << 10) | (rn << 5) | rd; + } + public static void shift_reg(Encoding enc, Node n, int op2) { + short self = enc.reg(n); + short reg1 = enc.reg(n.in(1)); + short reg2 = enc.reg(n.in(2)); + int body = shift_reg(0b10011010110, reg2, op2, reg1, self); + enc.add4(body); + } + + // MUL can be considered an alias for MADD with the third operand Ra being set to 0 + public static int madd(int opcode, int rm, int ra, int rn, int rd) { + return (opcode << 21) | (rm << 16) | (ra << 10) | (rn << 5) | rd; + } + public static void madd(Encoding enc, Node n, int opcode, int ra) { + short self = enc.reg(n); + short reg1 = enc.reg(n.in(1)); + short reg2 = enc.reg(n.in(2)); + int body = madd(opcode, reg2, ra, reg1, self); + enc.add4(body); + } + + // encodes movk, movn, and movz + public static int mov(int opcode, int shift, int imm16, int rd) { + return (opcode << 23) | (shift << 21) | (imm16 << 5) | rd; + } + + public static int mov_reg(int opcode, int src, int dst) { + return (opcode << 21) | (src << 16) | 0b11111 << 5 | dst; + } + + public static int ret(int opcode) { + return (opcode << 10); + } + + // FMOV (scalar, immediate) + public static int f_mov(int opcode, int ftype, int imm8, int rd) { + return (opcode << 24) | (ftype << 21) |(imm8 << 13) | (128 << 5) | rd; + } + + public static int f_scalar(int opcode, int ftype, int rm, int op, int rn, int rd) { + return (opcode << 24) | (ftype << 22) | (1 << 21) | (rm << 16) | (op << 10) | (rn << 5) | rd; + } + + public static int f_mov_reg(int opcode, int rn, int rd) { + return (opcode << 24) | (0b01100000010000 << 10) | (rn << 5) | rd; + } + public static int f_mov_general(int opcode, int ftype, int rmode, int opcode1, int rn, int rd) { + return (opcode << 24) |(ftype << 22) | (1 << 21) | (rmode << 19) | (opcode1 << 16) | (rn << 5) | rd; + } + public static void f_scalar(Encoding enc, Node n, int op ) { + short self = (short)(enc.reg(n) -D_OFFSET); + short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET); + short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET); + int body = f_scalar(0b00011110, 1, reg2, op, reg1, self); + enc.add4(body); + } + + // share same encoding with LDR (literal, SIMD&FP) flavour + public static int load_pc(int opcode, int offset, int rt) { + return (opcode << 24) | (offset << 5) | rt; + } + // int l + public static int adrp(int op, int imlo,int opcode, int imhi, int rd) { + return (op << 31) | (imlo << 29) |(opcode << 24) | (imhi << 5) | rd; + } + + public static int load_adr(int opcode, int offset, int base, int rt) { + return (opcode << 22) | (offset << 10) | (base << 5) | rt; + } + + // [Rptr+Roff] + public static int indr_adr(int opcode, int off, STORE_LOAD_OPTION option, int s, int ptr, int rt) { + return (opcode << 21) | (off << 16) | (option.ordinal() << 13) | (s << 12) | (2 << 10) | (ptr << 5) | rt; + } + // [Rptr+imm9] + public static int load_str_imm(int opcode, int imm12, int ptr, int rt) { + return (opcode << 22) | (imm12 << 10) |(ptr << 5) | rt; + } + + // encoding for vcvt, size is encoded in operand + // , + // F32.S32 + //encoded as op = 0b00, size = 0b10. + // VCVT..

, + // opcode is broken down into 4 pieces + public static int f_convert(int opcode_1, int opcode_2, int opcode_3, int opcode_4, int vd, int vm) { + return (opcode_1 << 28) | (opcode_2 << 24) | (opcode_3 << 20) | (opcode_4 << 16) | + (vd << 12) | (0x01100010 << 4) | vm; + } + public static int float_cast(int opcode, int ftype, int rn, int rd) { + return (opcode << 24) | (ftype << 22) | (2176 << 10) | (rn << 5) | rd; + } + + + // ftype = 3 + public static int f_cmp(int opcode, int ftype, int rm, int rn) { + return (opcode << 24) | (ftype << 21) | (rm << 16) | (8 << 10) | (rn << 5) | 8; + } + public static void f_cmp(Encoding enc, Node n) { + short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET); + short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET); + int body = f_cmp(0b00011110, 3, reg1, reg2); + enc.add4(body); + } + + public static COND make_condition(String bop) { + return switch (bop) { + case "==" -> COND.EQ; + case "!=" -> COND.NE; + case "<" -> COND.LE; + case "<=" -> COND.LT; + case ">=" -> COND.GT; + case ">" -> COND.GE; + default -> throw Utils.TODO(); + }; + } + // Todo: maybe missing zero here after delta << 5 + public static int b_cond(int opcode, int delta, COND cond) { + // 24-5 == 19bits offset range + assert -(1<<19) <= delta && delta < (1<<19); + delta &= (1L<<19)-1; // Zero extend + return (opcode << 24) | (delta << 5) | cond.ordinal(); + } + + public static int cond_set(int opcode, int rm, COND cond, int rn, int rd) { + return (opcode << 21) | (rm << 16) | (cond.ordinal() << 12) | (rn << 5) | rd; + } + + // Branch with Link to Register calls a subroutine at an address in a register, setting register X30 to PC+4. + public static int blr(int opcode, int rd) { + return opcode << 10 | rd << 5; + } + public static int b(int opcode, int delta) { + assert -(1<<26) <= delta && delta < (1<<26); + delta &= (1L<<26)-1; // Zero extend + return (opcode << 26) | delta; + } + + static RegMask callInMask(SONTypeFunPtr tfp, int idx ) { + if( idx==0 ) return RPC_MASK; + if( idx==1 ) return null; + // Count floats in signature up to index + int fcnt=0; + for( int i=2; i= XMMS.length ) + throw Utils.TODO(); + RegMask[] cargs = CALLINMASK; + if( tfp.nargs()-fcnt >= cargs.length ) + throw Utils.TODO(); + return 0; // No stack args + } + + static final long CALLEE_SAVE = + 1L< 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 CastNode(cast); + case CallEndNode cend -> new CallEndARM(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 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 PhiNode phi -> new PhiNode(phi); + case ProjNode prj -> new ProjARM(prj); + case ReadOnlyNode read -> new ReadOnlyNode(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 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, invert(bool.op())); + } + private Node _cmp(BoolNode bool) { + if( bool instanceof BoolNode.EQF || + bool instanceof BoolNode.LTF || + bool instanceof BoolNode.LEF ) + return new CmpFARM(bool); + return bool.in(2) instanceof ConstantNode con && con._con instanceof SONTypeInteger 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 + if( !(iff.in(1) instanceof BoolNode) ) + iff.setDef(1,new BoolNode.EQ(iff.in(1),new ConstantNode(SONTypeInteger.ZERO))); + return new BranchARM(iff, invert(((BoolNode)iff.in(1)).op())); + } + + private Node add(AddNode add) { + return add.in(2) instanceof ConstantNode off && off._con instanceof SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger ti -> new IntARM(con); + case SONTypeFloat tf -> new FloatARM(con); + case SONTypeFunPtr tfp -> new TFPARM(con); + case SONTypeMemPtr tmp -> new ConstantNode(con); + case SONTypeNil tn -> throw Utils.TODO(); + // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. + case SONType t -> new ConstantNode(con); + }; + } + + private Node call(CallNode call){ + return call.fptr() instanceof ConstantNode con && con._con instanceof SONTypeFunPtr 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 SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger 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) { + Node xval = st.val() instanceof ConstantNode con && con._con == SONTypeInteger.ZERO ? null : st.val(); + return new StoreARM(address(st),st.ptr(),idx,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 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 SONTypeMemPtr; // Base ptr always, not some derived + if( mop.off() instanceof ConstantNode con && con._con instanceof SONTypeInteger ti && imm9(ti) ) { + off = (int)ti.value(); + assert off == ti.value(); // In 32-bit range + } else { + idx = mop.off(); + } + return mop; + } + + +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AUIPC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AUIPC.java new file mode 100644 index 0000000..5a848a1 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AUIPC.java @@ -0,0 +1,41 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.ConstantNode; +import com.compilerprogramming.ezlang.compiler.nodes.MachNode; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFunPtr; + +// Add upper 20bits to PC. Immediate comes from the relocation info. +public class AUIPC extends ConstantNode implements MachNode, RIPRelSize { + AUIPC( SONTypeFunPtr 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((SONTypeFunPtr)_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(0x17, 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AddFRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AddFRISC.java new file mode 100644 index 0000000..7e9058a --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AddFRISC.java @@ -0,0 +1,17 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +// 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AddIRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AddIRISC.java new file mode 100644 index 0000000..1de9018 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AddIRISC.java @@ -0,0 +1,21 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.nodes.Node; + +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.I_TYPE; } + @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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AddRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AddRISC.java new file mode 100644 index 0000000..4b163a2 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AddRISC.java @@ -0,0 +1,21 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AndIRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AndIRISC.java new file mode 100644 index 0000000..b37f960 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AndIRISC.java @@ -0,0 +1,12 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.nodes.Node; + +public class AndIRISC extends ImmRISC { + AndIRISC( Node and, int imm) { super(and,imm); } + @Override public String op() { return "andi"; } + @Override public String glabel() { return "&"; } + @Override int opcode() { return riscv.I_TYPE; } + @Override int func3() {return 7;} +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AndRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AndRISC.java new file mode 100644 index 0000000..01677f0 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/AndRISC.java @@ -0,0 +1,16 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +public class AndRISC extends MachConcreteNode implements MachNode { + 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,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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/BranchRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/BranchRISC.java new file mode 100644 index 0000000..a6bfb29 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/BranchRISC.java @@ -0,0 +1,76 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import java.util.BitSet; + +// Conditional branch such as: BEQ +public class BranchRISC extends IfNode implements MachNode, RIPRelSize { + String _bop; + // label is obtained implicitly + public BranchRISC( IfNode iff, String bop, Node n1, Node n2 ) { + super(iff); + _bop = bop; + _inputs.setX(1,n1); + _inputs.setX(2,n2); + } + + @Override public String op() { return "b"+_bop; } + @Override public String label() { return op(); } + @Override public String comment() { return "L"+cproj(1)._nid; } + @Override public RegMask regmap(int i) { return riscv.RMASK; } + @Override public RegMask outregmap() { return null; } + @Override public void invert() { + if( _bop.equals("<") || _bop.equals("<=") ) + swap12(); // Cannot invert the test, so swap the operands + else + _bop = invert(_bop); + } + + @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { + sb.append("if( "); + if( in(1)==null ) sb.append("0"); + else in(1)._print0(sb,visited); + sb.append(_bop); + if( in(2)==null ) sb.append("0"); + else in(2)._print0(sb,visited); + return sb.append(" )"); + } + + @Override public void encoding( Encoding enc ) { + enc.jump(this,cproj(0)); + // 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( -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 ) { + 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) { + 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); + while( prj.nOuts() == 1 ) + prj = prj.uctrl(); // Skip empty blocks + sb.p(label(prj)); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/CallEndRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/CallEndRISC.java new file mode 100644 index 0000000..32cbb5b --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/CallEndRISC.java @@ -0,0 +1,23 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.CallEndNode; +import com.compilerprogramming.ezlang.compiler.nodes.MachNode; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFunPtr; + +public class CallEndRISC extends CallEndNode implements MachNode { + final SONTypeFunPtr _tfp; + CallEndRISC( CallEndNode cend ) { + super(cend); + _tfp = (SONTypeFunPtr)(cend.call().fptr()._type); + } + @Override public String op() { return "cend"; } + @Override public String label() { return op(); } + @Override public RegMask regmap(int i) { return null; } + @Override public RegMask outregmap() { return null; } + @Override public RegMask outregmap(int idx) { return idx == 2 ? riscv.retMask(_tfp,2) : null; } + @Override public RegMask killmap() { return riscv.riscCallerSave(); } + @Override public void encoding( Encoding enc ) { } + @Override public void asm(CodeGen code, SB sb) { } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/CallRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/CallRISC.java new file mode 100644 index 0000000..67ff05d --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/CallRISC.java @@ -0,0 +1,62 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFunPtr; + +public class CallRISC extends CallNode implements MachNode, RIPRelSize { + final SONTypeFunPtr _tfp; + final String _name; + CallRISC( CallNode call, SONTypeFunPtr tfp ) { + super(call); + assert tfp.isConstant(); + _inputs.pop(); // Pop constant target + _tfp = tfp; + _name = CodeGen.CODE.link(tfp)._name; + } + + @Override public String op() { return "call"; } + @Override public String label() { return op(); } + @Override public RegMask regmap(int i) { + // Last call input is AUIPC + if( i == nIns()-1 ) return riscv.RMASK; + return riscv.callInMask(_tfp,i); + } + @Override public RegMask outregmap() { return riscv.RPC_MASK; } + @Override public String name() { return _name; } + @Override public SONTypeFunPtr tfp() { return _tfp; } + + @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.relo(this); + short rpc = enc.reg(this); + enc.add4(riscv.j_type(riscv.J_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.J_JAL, rpc, delta)); + } else { + throw Utils.TODO(); + } + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(_name).p(" "); + for( int i=0; i>20; // Sign extend 12 bits + in(1)._print0(sb.append("( "), visited); + return sb.append(String.format(" %s #%d )",glabel(),imm12)); + } + + abstract int opcode(); + abstract int func3(); + + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this ); + short src = enc.reg(in(1)); + int body = riscv.i_type(opcode(), dst, func3(), src, _imm12 & 0xFFF); + enc.add4(body); + } + + // 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)(((SONTypeInteger)lui._con).value()) + imm12); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/IntRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/IntRISC.java new file mode 100644 index 0000000..635155e --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/IntRISC.java @@ -0,0 +1,29 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.ConstantNode; +import com.compilerprogramming.ezlang.compiler.nodes.MachNode; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeInteger; + +// 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); + SONTypeInteger ti = (SONTypeInteger)_con; + // Explicit truncation of larger immediates; this will sign-extend on + // load and this is handled during instruction selection. + enc.add4(riscv.i_type(riscv.I_TYPE, dst, 0, riscv.ZERO, (int)(ti.value() & 0xFFF))); + } + @Override public void asm(CodeGen code, SB sb) { + String reg = code.reg(this); + _con.print(sb.p(reg).p(" = #")); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/LUI.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/LUI.java new file mode 100644 index 0000000..a85eba9 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/LUI.java @@ -0,0 +1,32 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.ConstantNode; +import com.compilerprogramming.ezlang.compiler.nodes.MachNode; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeInteger; + +// Load upper 20bits. +public class LUI extends ConstantNode implements MachNode { + public LUI( int imm20 ) { + super(SONTypeInteger.constant(imm20)); + assert riscv.imm20Exact((SONTypeInteger)_con); + } + @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)((SONTypeInteger)_con).value()); } + @Override public String op() { return "lui"; } + @Override public void encoding( Encoding enc ) { + long x = ((SONTypeInteger)_con).value(); + int imm20 = (int)(x>>12) & 0xFFFFF; + short dst = enc.reg(this); + int lui = riscv.u_type(0x17, dst, imm20); + enc.add4(lui); + } + + @Override public void asm(CodeGen code, SB sb) { + String reg = code.reg(this); + sb.p(reg).p(" = #").hex4((int)(((SONTypeInteger)_con).value())); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/LoadRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/LoadRISC.java new file mode 100644 index 0000000..d3e6479 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/LoadRISC.java @@ -0,0 +1,28 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +// 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 { + 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 = xreg(enc); + short ptr = enc.reg(ptr()); + int body = riscv.i_type(opcode(enc), dst, func3(), ptr, _off); + enc.add4(body); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(","); + asm_address(code,sb); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/MemOpRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/MemOpRISC.java new file mode 100644 index 0000000..23988e6 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/MemOpRISC.java @@ -0,0 +1,76 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import com.compilerprogramming.ezlang.compiler.sontypes.*; +import java.util.BitSet; + +public abstract class MemOpRISC extends MemOpNode implements MachNode { + final int _off; // Limit 12 bits + final char _sz = (char)('0'+(1<<_declaredType.log_size())); + MemOpRISC(MemOpNode mop, Node base, int off, Node val) { + super(mop,mop); + assert base._type instanceof SONTypeMemPtr; + _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; + } + + @Override public String label() { return op(); } + Node val() { return in(4); } // Only for stores + + @Override public StringBuilder _printMach(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } + + @Override public SONType 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; + if( _declaredType == SONTypeInteger. I8 ) func3=0; // LB SB + if( _declaredType == SONTypeInteger.I16 ) func3=1; // LH SH + if( _declaredType == SONTypeInteger.I32 ) func3=2; // LW SW + if( _declaredType == SONTypeInteger.BOT ) func3=3; // LD SD + if( _declaredType == SONTypeInteger. U8 ) func3=4; // LBU + if( _declaredType == SONTypeInteger.BOOL) func3=4; // LBU + if( _declaredType == SONTypeInteger.U16 ) func3=5; // LHU + if( _declaredType == SONTypeInteger.U32 ) func3=6; // LWU + // float + if(_declaredType == SONTypeFloat.F32) func3 = 2; // fLW fSW + if(_declaredType == SONTypeFloat.F64) func3 = 3; // fLD fSD + if( _declaredType instanceof SONTypeMemPtr ) func3=6; // 4 byte pointers, assumed unsigned? + if( func3 == -1 ) throw Utils.TODO(); + return func3; + } + + // 7 bits, 00 000 11 or 00 001 11 for FP + int opcode(Encoding enc) { + if( enc.reg(this) < riscv.F_OFFSET ) return 3; + else if( this instanceof StoreRISC ) + // opcode is the same for 32 bit and 64 bits + return 39; + return 7; + } + short xreg(Encoding enc) { + short xreg = enc.reg(this ); + return xreg < riscv.F_OFFSET ? xreg : (short)(xreg-riscv.F_OFFSET); + } + + // 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/MulFRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/MulFRISC.java new file mode 100644 index 0000000..56cbf7b --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/MulFRISC.java @@ -0,0 +1,17 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +// 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/MulRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/MulRISC.java new file mode 100644 index 0000000..1930da7 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/MulRISC.java @@ -0,0 +1,17 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +// mulh signed multiply instruction(no-imm form) +public class MulRISC extends MachConcreteNode implements MachNode{ + 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/NewRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/NewRISC.java new file mode 100644 index 0000000..a561f3b --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/NewRISC.java @@ -0,0 +1,34 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import com.compilerprogramming.ezlang.compiler.print.ASMPrinter; + +public class NewRISC extends NewNode implements MachNode { + // A pre-zeroed chunk of memory. + NewRISC( NewNode nnn ) { super(nnn); } + @Override public String op() { return "alloc"; } + @Override public RegMask regmap(int i) { + return i==1 ? riscv.A0_MASK : null; // Size input or mem aliases + } + @Override public RegMask outregmap(int i) { return i == 1 ? riscv.A0_MASK : null; } + @Override public RegMask outregmap() { return null; } + @Override public RegMask killmap() { return riscv.riscCallerSave(); } + + // Encoding is appended into the byte array; size is returned + @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.u_type(0x17, riscv.A1, 0)); + enc.add4(riscv.i_type(0x67, riscv.RPC, 0, riscv.A1, 0)); + } + + // General form: "alloc #bytes PC" + @Override public void asm(CodeGen code, SB sb) { + sb.p("auipc a1=#calloc\n"); + sb.p("call a1+#calloc, a0"); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/NotRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/NotRISC.java new file mode 100644 index 0000000..94d2a92 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/NotRISC.java @@ -0,0 +1,16 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.NotNode; + +public class NotRISC extends ImmRISC { + NotRISC(NotNode not) { super(not,1); } + @Override public RegMask regmap(int i) { return riscv.RMASK; } + @Override public RegMask outregmap() { return riscv.RMASK; } + @Override public String op() { return "not"; } + // sltiu: x 0 ? "addi" : "ret"; + } + // Correct Nodes outside the normal edges + @Override public void postSelect(CodeGen code) { + FunNode fun = (FunNode)rpc().in(0); + _fun = fun; + fun.setRet(this); + } + @Override public RegMask regmap(int i) { return riscv.retMask(_fun.sig(),i); } + @Override public RegMask outregmap() { return null; } + @Override public void encoding( Encoding enc ) { + int frameAdjust = fun()._frameAdjust; + if( frameAdjust > 0 ) + enc.add4(riscv.i_type(riscv.I_TYPE, riscv.SP, 0, riscv.SP, (frameAdjust*-8) & 0xFFF)); + short rpc = enc.reg(rpc()); + enc.add4(riscv.i_type(0x67, riscv.ZERO, 0, rpc, 0)); + } + + @Override public void asm(CodeGen code, SB sb) { + int frameAdjust = fun()._frameAdjust; + if( frameAdjust>0 ) + sb.p("rsp += #").p(frameAdjust*-8).p("\nret"); + // Post code-gen, just print the "ret" + if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() ) + // Prints return reg (either A0 or FA0), RPC (always R1) and then + // the callee-save registers. + for( int i=2; i= 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.computeStackSlot(dst - riscv.MAX_REG)*8; + // store 64 bit values + enc.add4(riscv.s_type(39, 3, riscv.SP, dst, off)); + } + if( src >= riscv.MAX_REG ) { + // Load from SP + int off = enc._fun.computeStackSlot(src - riscv.MAX_REG)*8; + enc.add4(riscv.i_type(7, dst, 3,riscv.SP, off)); + } + // 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 + // Can do: FPR->GPR->FPR + enc.add4(riscv.r_type(0b1010011, dst, 0, src, 0, 0b1110001)); + enc.add4(riscv.r_type(0b1010011, dst, 0, src, 0, 0b0100000)); + } else if( dstX && !srcX ) { + //GPR->FPR + // fmv.d.x + enc.add4(riscv.r_type(0b1010011, dst, 0, src, 0, 0b0100000)); + } else if( !dstX && srcX ) { + //FPR->GPR + //fmv.x.d + enc.add4(riscv.r_type(0b1010011, dst, 0, src, 0, 0b1110001)); + } + + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SraIRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SraIRISC.java new file mode 100644 index 0000000..95b66c7 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SraIRISC.java @@ -0,0 +1,12 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.nodes.Node; + +public class SraIRISC extends ImmRISC { + SraIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { return riscv.I_TYPE; } + @Override int func3() { return 5;} + @Override public String glabel() { return ">>"; } + @Override public String op() { return "srai"; } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SraRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SraRISC.java new file mode 100644 index 0000000..9d919cb --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SraRISC.java @@ -0,0 +1,17 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +// Right Shift Arithmetic +public class SraRISC extends MachConcreteNode implements MachNode { + 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,5,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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SrlIRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SrlIRISC.java new file mode 100644 index 0000000..a19c72b --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SrlIRISC.java @@ -0,0 +1,12 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.nodes.Node; + +public class SrlIRISC extends ImmRISC { + SrlIRISC( Node and, int imm, boolean pop) { super(and,imm,pop); } + @Override int opcode() { return riscv.I_TYPE; } + @Override int func3() { return 5; } + @Override public String glabel() { return ">>>"; } + @Override public String op() { return "srli"; } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SrlRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SrlRISC.java new file mode 100644 index 0000000..37b7059 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SrlRISC.java @@ -0,0 +1,17 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +// Right Shift Logical +public class SrlRISC extends MachConcreteNode implements MachNode { + 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/StoreRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/StoreRISC.java new file mode 100644 index 0000000..7f16a1b --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/StoreRISC.java @@ -0,0 +1,46 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.codegen.RegMask; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +// Store memory addressing on ARM +// Support imm, reg(direct), or reg+off(indirect) addressing +// Base = base - base pointer, offset is added to base +// idx = null +// off = off - offset added to base) +// imm = imm - immediate value to store +// val = Node of immediate value to store(null if its a constant immediate) + +//e.g s.cs[0] = 67; // C +// base = s.cs, off = 4, imm = 67, val = null + +// sw rs2,offset(rs1) +public class StoreRISC extends MemOpRISC { + StoreRISC( StoreNode st, Node base, int off, Node val ) { super(st, base, off, val); } + @Override public String op() { return "st"+_sz; } + @Override public RegMask regmap(int i) { + // 0 - ctrl + if( i==1 ) return null; // mem + if( i==2 ) return riscv.RMASK; // ptr + // 2 - index + if( i==4 ) return riscv.MEM_MASK; // Wide mask to store GPR and FPR + throw Utils.TODO(); + } + @Override public RegMask outregmap() { return null; } + + @Override public void encoding( Encoding enc ) { + short val = xreg(enc); + short ptr = enc.reg(ptr()); + int body = riscv.s_type(opcode(enc), func3()&3, ptr, val, _off); + enc.add4(body); + } + + @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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SubFRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SubFRISC.java new file mode 100644 index 0000000..dd20d7a --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SubFRISC.java @@ -0,0 +1,17 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +// 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SubRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SubRISC.java new file mode 100644 index 0000000..652210f --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/SubRISC.java @@ -0,0 +1,16 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +public class SubRISC extends MachConcreteNode implements MachNode { + 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/TFPRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/TFPRISC.java new file mode 100644 index 0000000..e0cf2df --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/TFPRISC.java @@ -0,0 +1,54 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.ConstantNode; +import com.compilerprogramming.ezlang.compiler.nodes.MachNode; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFunPtr; + +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); + SONTypeFunPtr tfp = (SONTypeFunPtr)_con; + // auipc t0,0 + int auipc = riscv.u_type(0b0010111, dst, 0); + // addi t1,t0 + #0 + int addi = riscv.i_type(0b0010011, dst, 0, dst, 0); + enc.add4(auipc); + enc.add4(addi); + } + + @Override public byte encSize(int delta) { + if( -(1L<<11) <= delta && delta < (1L<<11) ) return 4; + 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 ) { + // AUIPC (upper 20 bits) + // opstart of add + int next = opStart + opLen; + enc.patch4(opStart,riscv.u_type(0b0010111, rpc, delta)); + // addi(low 12 bits) + enc.patch4(next,riscv.i_type(0b0010011, rpc, 0, rpc, delta & 0xFFF)); + // addi + } else { + throw Utils.TODO(); + } + } + + @Override public void asm(CodeGen code, SB sb) { + _con.print(sb.p(code.reg(this)).p(" #")); + } + +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/UJmpRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/UJmpRISC.java new file mode 100644 index 0000000..ca5087c --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/UJmpRISC.java @@ -0,0 +1,49 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import java.util.BitSet; + +// unconditional jump +public class UJmpRISC extends CFGNode implements MachNode, RIPRelSize { + //UJmpRISC() { } + @Override public String op() { return "jmp"; } + @Override public String label() { return op(); } + @Override public StringBuilder _print1( StringBuilder sb, BitSet visited ) { + return sb.append("jmp "); + } + @Override public RegMask regmap(int i) {return null; } + @Override public RegMask outregmap() { return null; } + @Override public SONType 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.J_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.J_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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/XorIRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/XorIRISC.java new file mode 100644 index 0000000..0f2338c --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/XorIRISC.java @@ -0,0 +1,12 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.nodes.Node; + +public class XorIRISC extends ImmRISC { + XorIRISC( Node and, int imm) { super(and,imm); } + @Override int opcode() { return riscv.I_TYPE; } + @Override int func3() { return 4; } + @Override public String glabel() { return "^"; } + @Override public String op() { return "xori"; } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/XorRISC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/XorRISC.java new file mode 100644 index 0000000..2542201 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/XorRISC.java @@ -0,0 +1,16 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +public class XorRISC extends MachConcreteNode implements MachNode { + 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/riscv.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/riscv.java new file mode 100644 index 0000000..7cead1f --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/riscv/riscv.java @@ -0,0 +1,557 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv; + +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import com.compilerprogramming.ezlang.compiler.sontypes.*; + +public class riscv extends Machine { + + public riscv(CodeGen code) { + if( !"SystemV".equals(code._callingConv) ) + throw new IllegalArgumentException("Unknown calling convention "+code._callingConv); + } + + @Override public String name() {return "riscv";} + + // Using ABI names instead of register names + public static int ZERO = 0, RPC= 1, SP = 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 + static int F0 = 32, F1 = 33, F2 = 34, F3 = 35, F4 = 36, F5 = 37, F6 = 38, F7 = 39; + static int FS0 = 40, FS1 = 41, FA0 = 42, FA1 = 43, FA2 = 44, FA3 = 45, FA4 = 46, FA5 = 47; + static int FA6 = 48, FA7 = 49, FS2 = 50, FS3 = 51, FS4 = 52, FS5 = 53, FS6 = 54, FS7 = 55; + static int FS8 = 56, FS9 = 57, FS10 = 58, FS11 = 59, FT8 = 60, FT9 = 61, FT10 = 62, FT11 = 63; + + static final int MAX_REG = 64; + + static final int F_OFFSET = 32; + + static final String[] REGS = new String[] { + "zero","rpc" , "sp" , "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" + }; + + // 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 SP + 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<> 1) & 0x3FF; + int imm11 = (delta>>11) & 1; + int imm12_19 = (delta>>12) & 0xFF; + int imm20 = (delta>>19) & 1; + int bits = imm20<<20 | imm12_19 << 12 | imm11 << 11 | imm10_01; + return bits << 12 | rd << 7 | opcode; + } + + + public static int i_type(int opcode, int rd, int func3, int rs1, int imm12) { + assert opcode >= 0 && rd >=0 && func3 >=0 && rs1 >=0 && imm12 >= 0; // Zero-extend by caller + return (imm12 << 20) | (rs1 << 15) | (func3 << 12) | (rd << 7) | opcode; + } + + + // S-type instructions(store) + public static int s_type(int opcode, int offset1, int func3, int rs1, int rs2, int offset2) { + return (offset2 << 25) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (offset1 << 7) | opcode; + } + public static int s_type(int opcode, int func3, int rs1, int rs2, int imm12) { + assert imm12 >= 0; // Masked to high zero bits by caller + int imm_lo = imm12 & 0x1F; + int imm_hi = imm12 >> 5; + return (imm_hi << 25) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (imm_lo << 7) | opcode; + } + + // BRANCH + public static int b_type(int opcode, int func3, short rs1, short rs2, int delta) { + assert -4*1024 <= delta && delta < 4*1024; + assert (delta&1)==0; // Low bit is always zero, not encoded + // Messy branch offset encoding + // 31 30 29 28 27 26 25 24-20 19-15 14-12 11 10 9 8 7 6-0 + // 12 10 9 8 7 6 5 SRC2 SRC1 FUNC3 4 3 2 1 11 OP + int imm4_1 = (delta>> 1) & 0xF; + int imm10_5= (delta>> 5) &0x3F; + int imm11 = (delta>>11) & 1; + int imm12 = (delta>>12) & 1; + int imm5 = imm4_1<<1 | imm11; + int imm7 = imm12<<6 | imm10_5; + return (imm7 << 25 ) | (rs2 << 20) | (rs1 << 15) | (func3 << 12) | (imm5 << 7) | opcode; + } + + public enum RM { + RNE, // Round to Nearest, ties to Even + RTZ, // Round towards Zero + RDN, // Round Down + RUP, // Round Up + DIRECT, // Round to Nearest, ties to Max Magnitude + RESERVED1, // Reserved for futue use + RESERVED2, // Reserved for future use + DYN, // In instruction’s rm field, selects dynamic rounding mode; In Rounding Mode register, reserved + } + + // Since opcode is the same just return back func3 + static public int jumpop(String op) { + return switch(op) { + case "==" -> 0x0; + case "!=" -> 0x1; + case "<" -> 0x4; + case "<=" -> 0x5; + case "u<" -> 0x6; + case "u<=" -> 0x7; + default -> throw Utils.TODO(); + }; + } + + // Since opcode is the same just return back func3 + static public int fsetop(String op) { + return switch(op) { + case "<" -> 1; + case "<=" -> 0; + case "==" -> 2; + default -> throw Utils.TODO(); + }; + } + + static RegMask callInMask( SONTypeFunPtr tfp, int idx ) { + if( idx==0 ) return RPC_MASK; + if( idx==1 ) return null; + // Count floats in signature up to index + int fcnt=0; + for( int i=2; i= XMMS.length ) + throw Utils.TODO(); + RegMask[] cargs = CALLINMASK; + if( tfp.nargs()-fcnt >= cargs.length ) + throw Utils.TODO(); + return 0; // No stack args + } + + // callee saved(riscv) + static final long CALLEE_SAVE = + (1L<< S0) | (1L<< S1) | (1L<< S2 ) | (1L<< S3 ) | + (1L<< S4) | (1L<< S5) | (1L<< S6 ) | (1L<< S7 ) | + (1L<< S8) | (1L<< S9) | (1L<< S10) | (1L<< S11) | + (1L<>52) == ti.value(); + } + // True if HIGH 20-bit signed immediate, with all zeros low. + public static boolean imm20Exact(SONTypeInteger 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 CastRISC(cast); + case CallEndNode cend -> new CallEndRISC(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 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 PhiNode phi -> new PhiNode(phi); + case ProjNode prj -> prj(prj); + case ReadOnlyNode read -> new ReadOnlyNode(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 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 off && off._con instanceof SONTypeInteger 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 SONTypeInteger 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 SONTypeFunPtr 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 y - swap; y < x + // x >= y - swap and invert; !(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 SONTypeInteger ti && imm12(ti) + ? new SetIRISC(bool, (int)ti.value(),false) + : new SetRISC(bool); + // x <= y - flip and invert; !(y < x); `slt tmp=y,x; xori dst=tmp,#1` + case "<=" -> new XorIRISC(new SetRISC(bool.swap12()),1); + // x == y - sub and vs0 == `sub tmp=x-y; sltu dst=tmp,#1` + case "==" -> new SetIRISC(new SubRISC(bool),1,true); + default -> throw Utils.TODO(); + }; + } + + private Node con( ConstantNode con ) { + if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs + return switch( con._con ) { + case SONTypeInteger 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 + throw Utils.TODO(); + } + case SONTypeFloat tf -> new FltRISC(con); + case SONTypeFunPtr tfp -> new TFPRISC(con); + case SONTypeMemPtr tmp -> throw Utils.TODO(); + case SONTypeNil tn -> throw Utils.TODO(); + // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. + case SONType t -> t==SONType.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.invert(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); + } + + private Node or(OrNode or) { + if( or.in(2) instanceof ConstantNode con && con._con instanceof SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger ti && imm12(ti) + ? new AddIRISC(sub, (int)(-ti.value()),true) + : new SubRISC(sub); + } + + private Node i2f8(ToFloatNode tfn) { + assert tfn.in(1)._type instanceof SONTypeInteger ti; + return new I2F8RISC(tfn); + } + + private Node prj(ProjNode prj) { + 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 == SONTypeInteger.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 SONTypeMemPtr; // Base ptr always, not some derived + if( mop.off() instanceof ConstantNode con && con._con instanceof SONTypeInteger ti && imm12(ti) ) { + off = (int)ti.value(); + } else { + base = new AddRISC(base,mop.off()); + base._type = mop.ptr()._type; + } + return base; + } + +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddFMemX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddFMemX86.java new file mode 100644 index 0000000..92c8f15 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddFMemX86.java @@ -0,0 +1,44 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +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 ) { + // addsd xmm0, DWORD PTR [rdi+0xc] + short dst = enc.reg(this ); + short ptr = enc.reg(ptr()); + short idx = enc.reg(idx()); + // F opcode + enc.add1(0xF2); + // rex prefix must come next (REX.W is not set) + x86_64_v2.rexF(dst, ptr, idx, false, enc); + + // FP ADD + enc.add1(0x0F).add1(0x58); + + x86_64_v2.indirectAdr(_scale, idx, ptr, _off, dst, enc); + } + + // General form: "add dst = src + [base + idx<<2 + 12]" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = "); + sb.p(val()==null ? "#"+_imm : code.reg(val())).p(" + "); + asm_address(code,sb); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddFX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddFX86.java new file mode 100644 index 0000000..6681c1c --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddFX86.java @@ -0,0 +1,33 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +public class AddFX86 extends MachConcreteNode implements MachNode { + AddFX86( Node addf ) { super(addf); } + @Override public String op() { return "addf"; } + @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; } + @Override public RegMask outregmap() { return x86_64_v2.XMASK; } + @Override public int twoAddress() { return 1; } + @Override public boolean commutes() { return true; } + + @Override public void encoding( Encoding enc ) { + // F2 0F 58 /r ADDSD xmm1, xmm2/m64 + short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET); + short src = (short)(enc.reg(in(2)) - x86_64_v2.XMM_OFFSET); + // Fopcode + enc.add1(0xF2); + // rex prefix must come next (REX.W is not set) + x86_64_v2.rexF(dst, src, 0, false, enc); + + enc.add1(0x0F).add1(0x58); + + enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src)); + } + + // General form: "addf dst += src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" += ").p(code.reg(in(2))); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddIX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddIX86.java new file mode 100644 index 0000000..7659e35 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddIX86.java @@ -0,0 +1,13 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.nodes.Node; + +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; } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddMemX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddMemX86.java new file mode 100644 index 0000000..89dbcd9 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddMemX86.java @@ -0,0 +1,35 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +public class AddMemX86 extends MemOpX86 { + AddMemX86( AddNode add, LoadNode ld , Node base, Node idx, int off, int scale, int imm, Node val ) { + super(add,ld, base, idx, off, scale, imm, val ); + } + @Override public String op() { return "add"+_sz; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + @Override public int twoAddress() { return 4; } + @Override public void encoding( Encoding enc ) { + // add something to register from memory + // add eax,DWORD PTR [rdi+0xc] + // REX.W + 03 /r ADD r64, r/m64 + short dst = enc.reg(this ); + short ptr = enc.reg(ptr()); + short idx = enc.reg(idx()); + + enc.add1(x86_64_v2.rex(dst, ptr, idx)); + // opcode + enc.add1(0x03); + + x86_64_v2.indirectAdr(_scale, idx, ptr, _off, dst, enc); + } + + // General form: "add dst = 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddX86.java new file mode 100644 index 0000000..fe1808b --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AddX86.java @@ -0,0 +1,11 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.nodes.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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AndIX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AndIX86.java new file mode 100644 index 0000000..446da90 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AndIX86.java @@ -0,0 +1,11 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.nodes.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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AndX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AndX86.java new file mode 100644 index 0000000..3c09cc6 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/AndX86.java @@ -0,0 +1,11 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.nodes.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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/CallEndX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/CallEndX86.java new file mode 100644 index 0000000..4cf1f22 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/CallEndX86.java @@ -0,0 +1,23 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.CallEndNode; +import com.compilerprogramming.ezlang.compiler.nodes.MachNode; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFunPtr; + +public class CallEndX86 extends CallEndNode implements MachNode { + final SONTypeFunPtr _tfp; + CallEndX86( CallEndNode cend ) { + super(cend); + _tfp = (SONTypeFunPtr)(cend.call().fptr()._type); + } + @Override public String op() { return "cend"; } + @Override public String label() { return op(); } + @Override public RegMask regmap(int i) { return null; } + @Override public RegMask outregmap() { return null; } + @Override public RegMask outregmap(int idx) { return idx == 2 ? x86_64_v2.retMask(_tfp,2) : null; } + @Override public RegMask killmap() { return x86_64_v2.x86CallerSave(); } + @Override public void encoding( Encoding enc ) { } + @Override public void asm(CodeGen code, SB sb) { } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/CallRX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/CallRX86.java new file mode 100644 index 0000000..82a9999 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/CallRX86.java @@ -0,0 +1,33 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.CallNode; +import com.compilerprogramming.ezlang.compiler.nodes.MachNode; + +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); // Normal argument + } + @Override public RegMask outregmap() { return null; } + @Override public void encoding( Encoding enc ) { + // FF /2 CALL r/m64 + // calls the function in the register + short src = enc.reg(fptr()); + enc.add1(0xFF); + enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT,2,src)); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(fptr())).p(" "); + for( int i=0; i= 8 ) enc.add1(x86_64_v2.rex(dst, dst, 0)); + enc.add1(0x33); // opcode + enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst)); + return; + } + + // Simply move the constant into a GPR + // Conditional encoding based on 64 or 32 bits + //REX.W + C7 /0 id MOV r/m64, imm32 + + long imm = ((SONTypeInteger)_con).value(); + if (Integer.MIN_VALUE <= imm && imm < 0) { + // We need sign extension, so use imm32 into 64 bit register + // REX.W + C7 /0 id MOV r/m64, imm32 + enc.add1(x86_64_v2.rex(0, dst, 0)); + enc.add1(0xC7); // 32 bits encoding + enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0x00, dst)); + enc.add4((int)imm); + } else if (0 <= imm && imm <= 0xFFFFFFFFL) { + // We want zero extension into 64 bit register + // so move 32 bit into 32 bit register which zeros + // the upper bits in the 64 bit register. + // B8+ rd id MOV r32, imm32 + if (dst >= 8) enc.add1(0x41); + enc.add1(0xB8 + (dst & 0x07)); + enc.add4((int)imm); + } else { + // Just write the full 64 bit constant + // REX.W + B8+ rd io MOV r64, imm64 + enc.add1(x86_64_v2.rex(0, dst, 0)); + enc.add1(0xB8 + (dst & 0x07)); + enc.add8(imm); // 64 bits encoding + } + } + + // Human-readable form appended to the SB. Things like the encoding, + // indentation, leading address or block labels not printed here. + // Just something like "ld4\tR17=[R18+12] // Load array base". + // General form: "op\tdst=src+src" + @Override public void asm(CodeGen code, SB sb) { + String reg = code.reg(this); + if( _con == SONType.NIL || _con == SONTypeInteger.ZERO ) + sb.p(reg).p(",").p(reg); + else + _con.print(sb.p(reg).p(" = #")); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/JmpX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/JmpX86.java new file mode 100644 index 0000000..426dde7 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/JmpX86.java @@ -0,0 +1,69 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +// 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); + 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 invert() { _bop = invert(_bop); } + + @Override public void encoding( Encoding enc ) { + enc.jump(this,cproj(0)); + int op = x86_64_v2.jumpop(_bop); + enc.add1(op-16); // Short form jump + enc.add1(0); // Offset + //enc.add1(0x0F).add1(x86_64_v2.jumpop(_bop)); + //enc.add4(0); // Offset patched later + } + + // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding + @Override public byte encSize(int delta) { return (byte)(x86_64_v2.imm8(delta-2) ? 2 : 6); } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + byte[] bits = enc.bits(); + if( opLen==2 ) { + assert bits[opStart] == x86_64_v2.jumpop(_bop)-16; + delta -= 2; // Offset from opcode END + assert (byte)delta==delta; + bits[opStart+1] = (byte)delta; + } else { + assert bits[opStart] == x86_64_v2.jumpop(_bop)-16; + delta -= 6; // Offset from opcode END + bits[opStart] = 0x0F; + bits[opStart+1] = (byte)x86_64_v2.jumpop(_bop); + enc.patch4(opStart+2,delta); + } + } + + @Override public void asm(CodeGen code, SB sb) { + String src = code.reg(in(1)); + if( src!="flags" ) sb.p(src).p(" "); + CFGNode prj = cproj(0); // 0 is True is jump target + while( prj.nOuts() == 1 && !(prj instanceof ReturnNode) ) + prj = prj.uctrl(); // Skip empty blocks + sb.p(label(prj)); + } + + @Override public String comment() { return "L"+cproj(1)._nid; } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/LeaX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/LeaX86.java new file mode 100644 index 0000000..3050699 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/LeaX86.java @@ -0,0 +1,55 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +public class LeaX86 extends MachConcreteNode implements MachNode { + final int _scale; + final int _offset; + LeaX86( Node add, Node base, Node idx, int scale, int offset ) { + super(add); + assert scale==0 || scale==1 || scale==2 || scale==3; + _inputs.pop(); + _inputs.pop(); + _inputs.push(base); + _inputs.push(idx); + _scale = scale; + _offset = offset; + } + + @Override public String op() { return "lea"; } + @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + + @Override public void encoding( Encoding enc ) { + // REX.W + 8D /r LEA r64,m + short dst = enc.reg(this); + short ptr = enc.reg(in(1)); + short idx = enc.reg(in(2)); + // ptr is null + // just do: [(index * s) + disp32] + if( ptr == -1 ) { + enc.add1(x86_64_v2.rex(dst, 0, idx)); + enc.add1(0x8D); // opcode + enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT, dst, 0x04)); + enc.add1(x86_64_v2.sib(_scale, idx, x86_64_v2.RBP)); + enc.add4(_offset); + return; + } + + enc.add1(x86_64_v2.rex(dst, ptr, idx)); + enc.add1(0x8D); // opcode + // rsp is hard-coded here(0x04) + x86_64_v2.indirectAdr(_scale, idx, ptr, _offset, dst, enc); + } + + // General form: "lea dst = base + 4*idx + 12" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = "); + if( in(1) != null ) + sb.p(code.reg(in(1))).p(" + "); + sb.p(code.reg(in(2))).p("<<").p(_scale); + if( _offset!=0 ) sb.p(" + #").p(_offset); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/LoadX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/LoadX86.java new file mode 100644 index 0000000..1f5dc56 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/LoadX86.java @@ -0,0 +1,80 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.LoadNode; +import com.compilerprogramming.ezlang.compiler.nodes.Node; +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFloat; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeInteger; + +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()); + + if (_declaredType != SONTypeInteger.U32 && _declaredType != SONTypeFloat.F32 && _declaredType != SONTypeFloat.F64) { + enc.add1(x86_64_v2.rex(dst, ptr, idx)); + } + + if(_declaredType == SONTypeFloat.F32) { + // F3 0F 10 /r MOVSS xmm1, m32 + enc.add1(0xF3); + enc.add1(0x0F); + enc.add1(0x10); + dst -= (short)x86_64_v2.XMM_OFFSET; + } + if(_declaredType == SONTypeFloat.F64) { + // F2 0F 10 /r MOVSD xmm1, m64 + enc.add1(0xF2); + enc.add1(0x0F); + enc.add1(0x10); + dst -= (short)x86_64_v2.XMM_OFFSET; + } + if(_declaredType == SONTypeInteger.I8) { + // sign extend: REX.W + 0F BE /r MOVSX r64, r/m8 + enc.add1(0x0F); + enc.add1(0xBE); + } else if(_declaredType == SONTypeInteger.I16) { + // sign extend: REX.W + 0F BF /r MOVSX r64, r/m16 + enc.add1(0x0F); + enc.add1(0xBF); + } else if(_declaredType == SONTypeInteger.I32) { + // sign extend: REX.W + 63 /r MOVSXD r64, r/m32 + enc.add1(0x63); + } else if(_declaredType == SONTypeInteger.U8) { + // zero extend: REX.W + 0F B6 /r MOVZX r64, r/m8 + enc.add1(0x0F); + enc.add1(0xB6); + } else if(_declaredType == SONTypeInteger.U16) { + // zero extend: REX.W + 0F B7 /r MOVZX r64, r/m16 + enc.add1(0x0F); + enc.add1(0xB7); + } else if(_declaredType == SONTypeInteger.U32) { + // zero extend: 8B /r MOV r32, r/m32 + enc.add1(0x8B); + } else if(_declaredType == SONTypeInteger.BOT) { + // REX.W + 8B /r MOV r64, r/m64 + enc.add1(0x8B); + } + + // includes modrm internally + x86_64_v2.indirectAdr(_scale, idx, ptr, _off, dst, enc); + } + + // General form: "ldN dst,[base + idx<<2 + 12]" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(","); + asm_address(code,sb); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MemAddX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MemAddX86.java new file mode 100644 index 0000000..1096341 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MemAddX86.java @@ -0,0 +1,39 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +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 void encoding( Encoding enc ) { + // add something to memory + // REX.W + 01 /r | REX.W + 81 /0 id + // ADD [mem], imm32/reg + short ptr = enc.reg(ptr()); + short idx = enc.reg(idx()); + short src = enc.reg(val()); + + enc.add1(x86_64_v2.rex(src, ptr, idx)); + // opcode + enc.add1( src == -1 ? 0x01 : 0x81 ); + + // includes modrm + x86_64_v2.indirectAdr(_scale, idx, ptr, _off, src == -1 ? 0 : src, enc); + if( src == -1 ) enc.add4(_imm); + } + // General form: "add [base + idx<<2 + 12] += src" + @Override public void asm(CodeGen code, SB sb) { + asm_address(code,sb); + if( val()==null ) { + if( _imm != 1 && _imm != -1 ) sb.p(" += #").p(_imm); + } else sb.p(" += ").p(code.reg(val())); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MemOpX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MemOpX86.java new file mode 100644 index 0000000..f511e97 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MemOpX86.java @@ -0,0 +1,85 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import com.compilerprogramming.ezlang.compiler.sontypes.*; + +import java.lang.StringBuilder; +import java.util.BitSet; + +// Generic X86 memory operand base. +// inputs: +// ctrl - will appear for possibly RCE (i.e. array ops) +// mem - memory dependence edge +// base - Basic object pointer +// idx/null - Scaled index offset, or null if none +// val/null - Value to store, as part of a 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 = (char)('0'+(1<<_declaredType.log_size())); + MemOpX86( Node op, MemOpNode mop, Node base, Node idx, int off, int scale, int imm ) { + super(op,mop); + assert base._type instanceof SONTypeMemPtr && !(base instanceof AddNode); + assert (idx==null && scale==0) || (idx!=null && 0<= scale && scale<=3); + + // Copy memory parts from eg 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; + } + + // Store-based flavors have a value edge + MemOpX86( Node op, MemOpNode mop, Node base, Node idx, int off, int scale, int imm, Node val ) { + this(op,mop,base,idx,off,scale,imm); + _inputs.setX(4,val); + } + + Node idx() { return in(3); } + Node val() { return in(4); } // Only for stores, including op-to-memory + + @Override public StringBuilder _printMach(StringBuilder sb, BitSet visited) { + return sb.append(".").append(_name); + } + + @Override public String label() { return op(); } + @Override public SONType 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 x86_64_v2.MEM_MASK; // value in GPR or XMM + throw Utils.TODO(); + } + + // "[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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MulFX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MulFX86.java new file mode 100644 index 0000000..73bca29 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MulFX86.java @@ -0,0 +1,32 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +public class MulFX86 extends MachConcreteNode implements MachNode { + MulFX86( Node mulf) { super(mulf); } + @Override public String op() { return "mulf"; } + @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; } + @Override public RegMask outregmap() { return x86_64_v2.XMASK; } + @Override public int twoAddress() { return 1; } + @Override public boolean commutes() { return true; } + + @Override public void encoding( Encoding enc ) { + // F2 0F 59 /r MULSD xmm1,xmm2/m64 + short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET); + short src = (short)(enc.reg(in(2)) - x86_64_v2.XMM_OFFSET); + + // Fopcode + enc.add1(0xF2); + // rex prefix must come next (REX.W is not set) + x86_64_v2.rexF(dst, src, 0, false, enc); + + enc.add1(0x0F).add1(0x59).add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src)); + } + + // General form: "mulf dst *= src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" *= ").p(code.reg(in(2))); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MulIX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MulIX86.java new file mode 100644 index 0000000..c53599d --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MulIX86.java @@ -0,0 +1,11 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.nodes.Node; + +public class MulIX86 extends ImmX86 { + MulIX86( Node add, int imm ) { super(add,imm); } + @Override public String op() { return "muli"; } + @Override public String glabel() { return "*"; } + @Override int opcode() { return 0x69; } + @Override int mod() { return 0; } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MulX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MulX86.java new file mode 100644 index 0000000..04c9a51 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/MulX86.java @@ -0,0 +1,29 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +public class MulX86 extends MachConcreteNode implements MachNode { + MulX86( Node mul ) { super(mul); } + @Override public String op() { return "mul"; } + @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + @Override public int twoAddress() { return 1; } + @Override public boolean commutes() { return true; } + + @Override public void encoding( Encoding enc ) { + // REX.W + 0F AF /r IMUL r64, r/m64 + short dst = enc.reg(this ); // src1 + short src = enc.reg(in(2)); // src2 + enc.add1(x86_64_v2.rex(dst, src, 0)); + enc.add1(0x0F); // opcode + enc.add1(0xAF); // opcode + enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src)); + } + + // General form: "mul dst *= src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))).p(" * ").p(code.reg(in(2))); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/NewX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/NewX86.java new file mode 100644 index 0000000..6d5639b --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/NewX86.java @@ -0,0 +1,30 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +public class NewX86 extends NewNode implements MachNode { + // A pre-zeroed chunk of memory. + NewX86( NewNode nnn ) { super(nnn); } + @Override public String op() { return "alloc"; } + // Size and pointer result in standard calling convention; null for all the + // memory aliases edges + @Override public RegMask regmap(int i) { return i == 1 ? x86_64_v2.RDI_MASK : null; } + @Override public RegMask outregmap(int i) { return i == 1 ? x86_64_v2.RAX_MASK : null; } + @Override public RegMask outregmap() { return null; } + @Override public RegMask killmap() { return x86_64_v2.x86CallerSave(); } + + @Override public void encoding( Encoding enc ) { + enc.external(this,"calloc"); + // E8 cd CALL rel32; + enc.add1(0xE8); + enc.add4(0); // offset + } + + // General form: "alloc #bytes" + @Override public void asm(CodeGen code, SB sb) { + sb.p("#calloc, ").p(code.reg(size())); + } + +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/NotX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/NotX86.java new file mode 100644 index 0000000..3a270d9 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/NotX86.java @@ -0,0 +1,39 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +public class NotX86 extends MachConcreteNode implements MachNode { + NotX86(NotNode not) { super(not); } + @Override public String op() { return "not"; } + @Override public RegMask regmap(int i) { return x86_64_v2.RMASK; } + @Override public RegMask outregmap() { return x86_64_v2.RMASK; } + @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } + + @Override public void encoding( Encoding enc ) { + assert !(in(1) instanceof NotNode); // Cleared out by peeps + assert !(in(1) instanceof BoolNode); // Cleared out by peeps + short dst = enc.reg(this ); + short src = enc.reg(in(1)); + + // Pre-zero using XOR dst,dst; since zero'd will not have a + // byte-dependency from the setz. Can skip REX is dst is low 8, makes + // this a 32b xor, which will also zero the high bits. + if( dst >= 8 ) enc.add1(x86_64_v2.rex(dst, dst, 0)); + enc.add1(0x33); // opcode + enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst)); + + // test rdi,rdi + enc.add1(x86_64_v2.rex(src, src, 0)); + enc.add1(0x85); + enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, src, src)); + + // setz (sete dil) + enc.add1(x86_64_v2.rex(dst, 0, 0)); + enc.add1(0x0F); + enc.add1(0x94); + enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, 0)); + } + @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/OrIX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/OrIX86.java new file mode 100644 index 0000000..cac195d --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/OrIX86.java @@ -0,0 +1,11 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.nodes.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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/OrX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/OrX86.java new file mode 100644 index 0000000..99a73db --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/OrX86.java @@ -0,0 +1,11 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.nodes.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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ParmX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ParmX86.java new file mode 100644 index 0000000..33995f1 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ParmX86.java @@ -0,0 +1,17 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.codegen.Encoding; +import com.compilerprogramming.ezlang.compiler.codegen.RegMask; +import com.compilerprogramming.ezlang.compiler.nodes.MachNode; +import com.compilerprogramming.ezlang.compiler.nodes.ParmNode; + +public class ParmX86 extends ParmNode implements MachNode { + final RegMask _rmask; + ParmX86( ParmNode parm ) { + super(parm); + _rmask = x86_64_v2.callInMask(fun().sig(),_idx); + } + @Override public RegMask regmap(int i) { return null; } + @Override public RegMask outregmap() { return _rmask; } + @Override public void encoding( Encoding enc ) { } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ProjX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ProjX86.java new file mode 100644 index 0000000..f5c765c --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ProjX86.java @@ -0,0 +1,15 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.codegen.Encoding; +import com.compilerprogramming.ezlang.compiler.codegen.RegMask; +import com.compilerprogramming.ezlang.compiler.nodes.MachNode; +import com.compilerprogramming.ezlang.compiler.nodes.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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/RegX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/RegX86.java new file mode 100644 index 0000000..fc49513 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/RegX86.java @@ -0,0 +1,25 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +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 int twoAddress() { return 1; } + abstract int opcode(); + @Override public void encoding( Encoding enc ) { + // REX.W + 01 /r + short dst = enc.reg(this ); // src1 + short src = enc.reg(in(2)); // src2 + enc.add1(x86_64_v2.rex(dst, src, 0)); + enc.add1(opcode()); // opcode + enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src)); + } + // General form: "add dst += src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" ").p(glabel()).p("= ").p(code.reg(in(2))); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/RetX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/RetX86.java new file mode 100644 index 0000000..ce4d36f --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/RetX86.java @@ -0,0 +1,41 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +public class RetX86 extends ReturnNode implements MachNode { + RetX86( ReturnNode ret, FunNode fun ) { super(ret, fun); fun.setRet(this); } + @Override public String op() { + return ((FunX86)fun())._frameAdjust > 0 ? "addi" : "ret"; + } + // Correct Nodes outside the normal edges + @Override public void postSelect(CodeGen code) { + FunNode fun = (FunNode)rpc().in(0); + _fun = fun; + fun.setRet(this); + } + @Override public RegMask regmap(int i) { return x86_64_v2.retMask(_fun.sig(),i); } + @Override public RegMask outregmap() { return null; } + @Override public void encoding( Encoding enc ) { + int frameAdjust = ((FunX86)fun())._frameAdjust; + if( frameAdjust > 0 ) { + enc.add1( 0x83 ); + enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0, x86_64_v2.RSP) ); + enc.add1(frameAdjust*-8); + } + enc.add1(0xC3); + } + + @Override public void asm(CodeGen code, SB sb) { + int frameAdjust = ((FunX86)fun())._frameAdjust; + if( frameAdjust>0 ) + sb.p("rsp += #").p(frameAdjust*-8).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 (always [rsp-4]) and + // then the callee-save registers. + for( int i=2; i>"; } + @Override int opcode() { return 0xC1; } + @Override int mod() { return 7; } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SarX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SarX86.java new file mode 100644 index 0000000..7b91b69 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SarX86.java @@ -0,0 +1,24 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.Node; + +public class SarX86 extends RegX86 { + SarX86( Node add ) { super(add); } + @Override public String op() { return "sar"; } + @Override public String glabel() { return ">>"; } + @Override public RegMask regmap(int i) { + if (i == 1) return x86_64_v2.WMASK; + if (i == 2) return x86_64_v2.RCX_MASK; + throw Utils.TODO(); + } + @Override int opcode() { return 0xD3; } + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this ); // src1 + short src = enc.reg(in(2)); // src2 + enc.add1(x86_64_v2.rex(0, dst, 0)); + enc.add1(opcode()); // opcode + enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 7, dst)); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SetX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SetX86.java new file mode 100644 index 0000000..7ee2305 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SetX86.java @@ -0,0 +1,50 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +// 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 <,<=,== + // Constructor expects input is an X86 and not an Ideal node. + SetX86( 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 x86_64_v2.FLAGS_MASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + + @Override public void encoding( Encoding enc ) { + // REX + 0F 94 + short dst = enc.reg(this ); + + // Optional rex, for dst + if( dst >= 8 ) enc.add1(x86_64_v2.rex(0, dst, 0)); + enc.add1(0x0F); // opcode + enc.add1(switch (_bop) { + case "==" -> 0x94; // SETE + case "<" -> 0x9C; // SETL + case "<=" -> 0X9E; // SETLE + default -> throw Utils.TODO(); + }); + enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0, dst)); + + // low 8 bites are set, now zero extend for next instruction + if( dst >= 8 ) enc.add1(x86_64_v2.rex(0, dst, 0)); + enc.add1(0x0F); // opcode + enc.add1(0xB6); // opcode + enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst)); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)); + String src = code.reg(in(1)); + if( src!="flags" ) sb.p(" = ").p(src); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShlIX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShlIX86.java new file mode 100644 index 0000000..ee8dfb6 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShlIX86.java @@ -0,0 +1,11 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.nodes.Node; + +public class ShlIX86 extends ImmX86 { + ShlIX86( Node add, int imm ) { super(add,imm); } + @Override public String op() { return "shli"; } + @Override public String glabel() { return "<<"; } + @Override int opcode() { return 0xC1; } + @Override int mod() { return 4; } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShlX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShlX86.java new file mode 100644 index 0000000..8aeb594 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShlX86.java @@ -0,0 +1,24 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.Node; + +public class ShlX86 extends RegX86 { + ShlX86( Node add ) { super(add); } + @Override public String op() { return "shl"; } + @Override public String glabel() { return "<<"; } + @Override public RegMask regmap(int i) { + if (i == 1) return x86_64_v2.WMASK; + if (i == 2) return x86_64_v2.RCX_MASK; + throw Utils.TODO(); + } + @Override int opcode() { return 0xD3; } + @Override public final void encoding( Encoding enc ) { + short dst = enc.reg(this ); // src1 + short src = enc.reg(in(2)); // src2 + enc.add1(x86_64_v2.rex(0, dst, 0)); + enc.add1(opcode()); // opcode + enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 4, dst)); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShrIX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShrIX86.java new file mode 100644 index 0000000..de39108 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShrIX86.java @@ -0,0 +1,11 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.nodes.Node; + +public class ShrIX86 extends ImmX86 { + ShrIX86( Node add, int imm ) { super(add,imm); } + @Override public String op() { return "shri"; } + @Override public String glabel() { return ">>>"; } + @Override int opcode() { return 0xC1; } + @Override int mod() { return 5; } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShrX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShrX86.java new file mode 100644 index 0000000..d0a42f5 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/ShrX86.java @@ -0,0 +1,24 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.Node; + +public class ShrX86 extends RegX86 { + ShrX86( Node add ) { super(add); } + @Override public String op() { return "shr"; } + @Override public String glabel() { return ">>>"; } + @Override public RegMask regmap(int i) { + if (i == 1) return x86_64_v2.WMASK; + if (i == 2) return x86_64_v2.RCX_MASK; + throw Utils.TODO(); + } + @Override int opcode() { return 0xD3; } + @Override public final void encoding( Encoding enc ) { + short dst = enc.reg(this ); // src1 + short src = enc.reg(in(2)); // src2 + enc.add1(x86_64_v2.rex(0, dst, 0)); + enc.add1(opcode()); // opcode + enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 5, dst)); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SplitX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SplitX86.java new file mode 100644 index 0000000..49129a2 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SplitX86.java @@ -0,0 +1,99 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.Node; +import com.compilerprogramming.ezlang.compiler.nodes.SplitNode; + +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; } + + // Need to handle 8 cases: . reg->reg, reg->xmm, reg->flags, xmm->reg, xmm->xmm, xmm->flags, flags->reg, flags->xmm, + // flags->flags. + @Override public void encoding( Encoding enc ) { + // REX.W + 8B /r MOV r64, r/m64 + short dst = enc.reg(this ); + short src = enc.reg(in(1)); + + if( dst == x86_64_v2.FLAGS ) { + // mov reg, flags + // push rcx + // popf (Pop the top of the stack into the FLAGS register) + // 50+rd PUSH r64 + enc.add1(0x50 + src); + // popf + enc.add1(0x9D); + return; + } + if( src == x86_64_v2.FLAGS ) { + // mov flags, reg + // pushf; pop reg + enc.add1(0x9C); + // 58+ rd POP r64 + enc.add1(0x58 + dst); + return; + } + + // Flag for being either XMM or stack + boolean dstX = dst >= x86_64_v2.XMM_OFFSET; + boolean srcX = src >= x86_64_v2.XMM_OFFSET; + + // Stack spills + if( dst >= x86_64_v2.MAX_REG ) { + if( src >= x86_64_v2.MAX_REG ) + throw Utils.TODO(); // Very rare stack-stack move + // TODO: Missing FP 0x66 prefix + if( srcX ) { src -= (short)x86_64_v2.XMM_OFFSET; enc.add1(0x66); } + int off = enc._fun.computeStackSlot(dst - x86_64_v2.MAX_REG)*8; + enc.add1(x86_64_v2.rex(src, x86_64_v2.RSP, -1)); + enc.add1( 0x89 ); + x86_64_v2.indirectAdr(0, (short)-1, (short)x86_64_v2.RSP, off, src, enc); + return; + } + if( src >= x86_64_v2.MAX_REG ) { + if( dstX ) { dst -= (short)x86_64_v2.XMM_OFFSET; enc.add1(0x66); } + int off = enc._fun.computeStackSlot(src - x86_64_v2.MAX_REG)*8; + enc.add1(x86_64_v2.rex(dst, x86_64_v2.RSP, -1)); + enc.add1( 0x8B ); + x86_64_v2.indirectAdr(0, (short)-1, (short)x86_64_v2.RSP, off, dst, enc); + return; + } + + // reg-reg move. Adjust numbering for GPR vs FPR reg set + if( dstX ) dst -= (short) x86_64_v2.XMM_OFFSET; + if( srcX ) src -= (short) x86_64_v2.XMM_OFFSET; + + // 0x66 if moving between register classes + if( dstX ^ srcX ) enc.add1(0x66); + enc.add1(x86_64_v2.rex(dst, src, 0)); + + // pick opcode based on regs + if( !dstX && !srcX ) { + // reg->reg (MOV r64, r/m64) + enc.add1(0x8B); + } else if( dstX && srcX ) { + // xmm->xmm (NP 0F 28 /r MOVAPS xmm1, xmm2/m128) + enc.add1(0x0F); + enc.add1(0x28); + } else if( dstX && !srcX ) { + // xmm->reg (66 REX.W 0F 6E /r MOVQ xmm, r/m64) + enc.add1(0x0F); + enc.add1(0x6E); + } else if( !dstX && srcX ) { + // reg->xmm(66 REX.W 0F 7E /r MOVQ r/m64, xmm) + enc.add1(0x0F); + enc.add1(0x7E); + } + + enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src)); + } + + // General form: "mov dst = src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p(code.reg(in(1))); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/StoreX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/StoreX86.java new file mode 100644 index 0000000..501121f --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/StoreX86.java @@ -0,0 +1,63 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.ConstantNode; +import com.compilerprogramming.ezlang.compiler.nodes.Node; +import com.compilerprogramming.ezlang.compiler.nodes.StoreNode; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFloat; + +import java.util.BitSet; + +public class StoreX86 extends MemOpX86 { + StoreX86( StoreNode st, Node base, Node idx, int off, int scale, int imm, Node val ) { + super(st,st, base, idx, off, scale, imm, val); + } + @Override public String op() { return "st"+_sz; } + @Override public StringBuilder _printMach(StringBuilder sb, BitSet visited) { + Node val = val(); + sb.append(".").append(_name).append("="); + if( val==null ) sb.append(_imm); + else val._print0(sb,visited); + return sb.append(";"); + } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return null; } + @Override public void encoding( Encoding enc ) { + // REX.W + C7 /0 id MOV r/m64, imm32 | + // REX.W + 89 /r MOV r/m64, r64 + short ptr = enc.reg(ptr()); + short idx = enc.reg(idx()); + short src = enc.reg(val()); + + int imm_op = x86_64_v2.selectOpcodeForImmStore(_imm); + if(src == -1 && _imm != 0) { + if(imm_op == -1) {enc.add1(x86_64_v2.rex(src, ptr, idx));enc.add1(0xC7); } + else enc.add1(imm_op); + } else { + if(_declaredType == SONTypeFloat.F32) {src -= (short)x86_64_v2.XMM_OFFSET; enc.add1(0xF3); enc.add1(0x0F); enc.add1(0x11);} + else if(_declaredType == SONTypeFloat.F64) {src -= (short)x86_64_v2.XMM_OFFSET; enc.add1(0xF2); enc.add1(0x0F); enc.add1(0x11);} + else if(_declaredType.log_size() == 0) enc.add1(0x88); + else if(_declaredType.log_size() == 1) enc.add1(0x89); + else if(_declaredType.log_size() == 2) enc.add1(0x89); + else if(_declaredType.log_size() == 3) {enc.add1(x86_64_v2.rex(src, ptr, idx)); enc.add1(0x89);} + } + + x86_64_v2.indirectAdr(_scale, idx, ptr, _off, src, enc); + if( src == -1 ) { + switch (x86_64_v2.imm_size(_imm)) { + case 8: enc.add1(_imm); break; + case 16: enc.add2(_imm); break; + case 32: enc.add4(_imm); break; + case 64: enc.add8(_imm); break; + } + } + } + + // 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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SubFX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SubFX86.java new file mode 100644 index 0000000..2b03bc0 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SubFX86.java @@ -0,0 +1,31 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; + +public class SubFX86 extends MachConcreteNode implements MachNode { + SubFX86( Node subf) { super(subf); } + @Override public String op() { return "subf"; } + @Override public RegMask regmap(int i) { assert i==1 || i==2; return x86_64_v2.XMASK; } + @Override public RegMask outregmap() { return x86_64_v2.XMASK; } + @Override public int twoAddress() { return 1; } + + @Override public void encoding( Encoding enc ) { + // F2 0F 5C /r SUBSD xmm1, xmm2/m64 + short dst = (short)(enc.reg(this ) - x86_64_v2.XMM_OFFSET); + short src = (short)(enc.reg(in(2)) - x86_64_v2.XMM_OFFSET); + // Fopcode + enc.add1(0xF2); + // rex prefix must come next (REX.W is not set) + x86_64_v2.rexF(dst, src, 0, false, enc); + + enc.add1(0x0F).add1(0x5C); + enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src)); + } + + // General form: "subf dst -= src" + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" -= ").p(code.reg(in(2))); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SubX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SubX86.java new file mode 100644 index 0000000..e5156a4 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/SubX86.java @@ -0,0 +1,10 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.nodes.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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/TFPX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/TFPX86.java new file mode 100644 index 0000000..4861b8d --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/TFPX86.java @@ -0,0 +1,66 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import com.compilerprogramming.ezlang.compiler.nodes.cpus.riscv.riscv; +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFunPtr; + +// Function constants +public class TFPX86 extends ConstantNode implements MachNode, RIPRelSize { + TFPX86( ConstantNode con ) { super(con); } + @Override public String op() { + return _con == SONType.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 == SONType.NIL ? x86_64_v2.FLAGS_MASK : null; + } + + @Override public void encoding( Encoding enc ) { + short dst = enc.reg(this); + // Short form for zero + if( _con==SONType.NIL ) { + // XOR dst,dst. Can skip REX is dst is low 8, makes this a 32b + // xor, which will also zero the high bits. + if( dst >= 8 ) enc.add1(x86_64_v2.rex(dst, dst, 0)); + enc.add1(0x33); // opcode + enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst)); + return; + } + + // lea to load function pointer address + // lea rax, [rip+disp32] + enc.relo(this); + enc.add1(x86_64_v2.rex(dst, 0, 0)); + enc.add1(0x8D); // opcode + enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT, dst, 0b101)); + enc.add4(0); + } + + @Override public byte encSize(int delta) { + return 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 == SONType.NIL ) + sb.p(reg).p(",").p(reg); + else + _con.print(sb.p(reg).p(" = #")); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/UJmpX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/UJmpX86.java new file mode 100644 index 0000000..c70a884 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/UJmpX86.java @@ -0,0 +1,53 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.*; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import java.util.BitSet; + +// unconditional jump +public class UJmpX86 extends CFGNode implements MachNode, RIPRelSize { + UJmpX86() { } + @Override public String op() { return "jmp"; } + @Override public String label() { return op(); } + @Override public StringBuilder _print1( StringBuilder sb, BitSet visited ) { + return sb.append("jmp "); + } + @Override public RegMask regmap(int i) {return null; } + @Override public RegMask outregmap() { return null; } + @Override public SONType compute() { throw Utils.TODO(); } + @Override public Node idealize() { throw Utils.TODO(); } + @Override public void encoding( Encoding enc ) { + enc.jump(this,uctrl()); + // Short-form jump + enc.add1(0xEB).add1(0); + //// E9 cd JMP rel32 + //enc.add1(0xE9).add4(0); + } + + // Delta is from opcode start, but X86 measures from the end of the 2-byte encoding + @Override public byte encSize(int delta) { return (byte)(x86_64_v2.imm8(delta-2) ? 2 : 5); } + + // Delta is from opcode start + @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { + byte[] bits = enc.bits(); + if( opLen==2 ) { + assert bits[opStart] == (byte)0xEB; + delta -= 2; // Offset from opcode END + assert (byte)delta==delta; + bits[opStart+1] = (byte)delta; + } else { + assert bits[opStart] == (byte)0xEB; + bits[opStart] = (byte)0xE9; // Long form + delta -= 5; // Offset from opcode END + enc.patch4(opStart+1,delta); + } + } + + @Override public void asm(CodeGen code, SB sb) { + CFGNode target = uctrl(); + assert target.nOuts() > 1; // Should optimize jmp to empty targets + sb.p(label(target)); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/XorIX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/XorIX86.java new file mode 100644 index 0000000..ab7ceb8 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/XorIX86.java @@ -0,0 +1,11 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.nodes.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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/XorX86.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/XorX86.java new file mode 100644 index 0000000..155c5ba --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/XorX86.java @@ -0,0 +1,11 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.nodes.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/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/x86_64_v2.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/x86_64_v2.java new file mode 100644 index 0000000..a7691d7 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/nodes/cpus/x86_64_v2/x86_64_v2.java @@ -0,0 +1,678 @@ +package com.compilerprogramming.ezlang.compiler.nodes.cpus.x86_64_v2; + +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.codegen.*; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import com.compilerprogramming.ezlang.compiler.sontypes.*; + +import java.io.ByteArrayOutputStream; + +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; + + 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); + + static final long SPILLS = -(1L << MAX_REG); + static final RegMask SPLIT_MASK = new RegMask(WR_BITS | FP_BITS | (1L< 0x84; + case "!=" -> 0x85; + case ">" -> 0x8F; + case "<" -> 0x8C; + case "<=" -> 0x8E; + case ">=" -> 0X8D; + default -> throw new IllegalArgumentException("Too many arguments"); + }; + } + + public static int modrm(MOD mod, int reg, int m_r) { + // combine all the bits + return (mod.ordinal() << 6) | ((reg & 0x07) << 3) | m_r & 0x07; + } + + // 00 000 000 + // same bit-layout as modrm + public static int sib(int scale, int index, int base) { + return (scale << 6) | ((index & 0x07) << 3) | base & 0x07; + } + + // reg1 is reg(R) + // reg2 is r/mem(B) + // reg3 is X(index) + // reg4 is X(base) + + // 0 denotes no direct register + public static int rex(int reg, int ptr, int idx, boolean wide) { + // assuming 64 bit by default so: 0100 1000 + int rex = wide ? REX_W : REX; + if( 8 <= reg && reg <= 15 ) rex |= 0b00000100; // REX.R + if( 8 <= ptr && ptr <= 15 ) rex |= 0b00000001; // REX.B + if( 8 <= idx && idx <= 15 ) rex |= 0b00000010; // REX.X + return rex; + } + // return opcode for optimised immediate store + public static int selectOpcodeForImmStore(long imm) { + if(imm8(imm)) return 0xC6; + if(imm16(imm)) return 0xC7; + if(imm32(imm)) return 0xC7; + return -1; + } + // return the size of the immediate + public static int imm_size(long imm) { + if(imm8(imm)) return 8; + if(imm16(imm)) return 16; + if(imm32(imm)) return 32; + return 64; + } + + public static int rex(int reg, int ptr, int idx) { + return rex(reg, ptr, idx, true); + } + + // rex for floats. Return size (0 or 1) + // don't need to use REX if 0x40. + public static byte rexF(int reg, int ptr, int idx, boolean wide, Encoding enc) { + int rex = rex(reg, ptr, idx, wide); + if (rex == REX) return 0; + enc.add1(rex); + return 1; + } + // Function used for encoding indirect memory addresses + // Does not always generate SIB byte e.g index == -1. + // -1 denotes empty value, not set - note 0 is different from -1 as it can represent rax. + // Looks for best mod locally + public static void indirectAdr( int scale, short index, short base, int offset, int reg, Encoding enc ) { + // Assume indirect + assert 0 <= base && base < 16; + assert index != RSP; + + MOD mod = MOD.INDIRECT; + // is 1 byte enough or need more? + if( offset != 0 ) + mod = imm8(offset) + ? MOD.INDIRECT_disp8 + : MOD.INDIRECT_disp32; + + // needs to pick optimal displacement mod if we want to encode base + if( mod == MOD.INDIRECT && (base == RBP || base == R13) ) + mod = MOD.INDIRECT_disp8; + + // special encoding for [base +offset] + if( index == -1 ) { + // Case for mov reg, [disp] (load) + enc.add1(modrm(mod, reg == -1 ? 0 : reg, base)); + } else { + // rsp is hard-coded here(0x04) + enc.add1(modrm(mod, reg, 0x04)); + enc.add1(sib(scale, index, base)); + } + + if( mod == MOD.INDIRECT_disp8 ) { + enc.add1(offset); + } else if( mod == MOD.INDIRECT_disp32 ) { + enc.add4(offset); + } + } + + // Calling conv metadata + public int GPR_COUNT_CONV_WIN64 = 4; // RCX, RDX, R9, R9 + public int XMM_COUNT_CONV_WIN64 = 4; // XMM0L, XMM1L, XMM2L, XMM3L + + public int GPR_COUNT_CONV_SYSTEM_V = 6; // RDI, RSI, RDX, RCX, R8, R9 + public int XMM_COUNT_CONV_SYSTEM_V = 4; // XMM0, XMM1, XMM2, XMM3 .... + // Human-readable name for a register number, e.g. "RAX". + // Hard crash for bad register number, fix yer bugs! + 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", + }; + @Override public String reg( int reg ) { + return reg < REGS.length ? REGS[reg] : "[rsp+"+(reg-REGS.length)*8+"]"; + } + + // Stack slots, in units of 8 bytes. + @Override public int stackSlot( int reg ) { + return reg < REGS.length ? -1 : reg-REGS.length; + } + + + // WIN64(param passing) + static RegMask[] CALLINMASK_WIN64 = new RegMask[] { + RCX_MASK, + RDX_MASK, + R08_MASK, + R09_MASK, + }; + + // SystemV(param passing) + static RegMask[] CALLINMASK_SYSTEMV = new RegMask[] { + RDI_MASK, + RSI_MASK, + RDX_MASK, + RCX_MASK, + R08_MASK, + R09_MASK, + }; + + // Limit of float args passed in registers + static RegMask[] XMMS4 = new RegMask[]{ + new RegMask(XMM0), new RegMask(XMM1), new RegMask(XMM2), new RegMask(XMM3), + }; + + // Map from function signature and argument index to register. + // Used to set input registers to CallNodes, and ParmNode outputs. + static RegMask callInMask( SONTypeFunPtr tfp, int idx ) { + if( idx==0 ) return RPC_MASK; + if( idx==1 ) return null; + // Count floats in signature up to index + int fcnt=0; + for( int i=2; i CALLINMASK_SYSTEMV; + case "Win64" -> CALLINMASK_WIN64; + default -> throw new IllegalArgumentException("Unknown calling convention: "+CodeGen.CODE._callingConv); + }; + if( idx-2-fcnt < cargs.length ) + return cargs[idx-2-fcnt]; + } + throw Utils.TODO(); // Pass on stack slot + } + + // Return the max stack slot used by this signature, or 0 + static short maxSlot( SONTypeFunPtr tfp ) { + // Count floats in signature up to index + int fcnt=0; + for( int i=0; i= XMMS4.length ) + throw Utils.TODO(); + RegMask[] cargs = switch( CodeGen.CODE._callingConv ) { + case "SystemV" -> CALLINMASK_SYSTEMV; + case "Win64" -> CALLINMASK_WIN64; + default -> throw new IllegalArgumentException("Unknown calling convention: "+CodeGen.CODE._callingConv); + }; + if( tfp.nargs()-fcnt >= cargs.length ) + throw Utils.TODO(); + return 0; // No stack args + } + + // Return single int/ptr register. Used by CallEnd output and Return input. + static RegMask retMask( SONTypeFunPtr tfp ) { + return tfp.ret() instanceof SONTypeFloat ? XMM0_MASK : RAX_MASK; + } + + + // caller saved(systemv) + 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 RegMask SYSTEM5_CALLER_SAVE_MASK = new RegMask(SYSTEM5_CALLER_SAVE); + + // caller saved(win64) + static final long WIN64_CALLER_SAVE = + (1L<< RAX) | (1L<< RCX) | (1L<< RDX) | + (1L<< R08) | (1L<< R09) | (1L<< R10) | (1L<< R11) | + (1L< SYSTEM5_CALLER_SAVE_MASK; + case "Win64" -> WIN64_CALLER_SAVE_MASK; + default -> throw new IllegalArgumentException("Unknown calling convention: " + CodeGen.CODE._callingConv); + }; + } + + @Override public RegMask callerSave() { return x86CallerSave(); } + + static final RegMask SYSTEM5_CALLEE_SAVE_MASK; + static final RegMask WIN64_CALLEE_SAVE_MASK; + + static { + long callee = ~SYSTEM5_CALLER_SAVE; + // Remove the spills + callee &= (1L< SYSTEM5_CALLEE_SAVE_MASK; + case "Win64" -> WIN64_CALLEE_SAVE_MASK; + default -> throw new IllegalArgumentException("Unknown calling convention: "+CodeGen.CODE._callingConv); + }; + } + @Override public RegMask calleeSave() { return x86CalleeSave(); } + + static final RegMask[] WIN64_RET_MASKS, SYS5_RET_MASKS; + static { + WIN64_RET_MASKS = makeRetMasks( WIN64_CALLEE_SAVE_MASK); + SYS5_RET_MASKS = makeRetMasks(SYSTEM5_CALLEE_SAVE_MASK); + } + private static RegMask[] makeRetMasks(RegMask mask) { + int nSaves = mask.size(); + RegMask[] masks = new RegMask[4 + nSaves]; + masks[0] = null; + masks[1] = null; // Memory + masks[2] = null; // Varies, either XMM0 or RAX + masks[3] = RPC_MASK; + short reg = mask.firstReg(); + for( int i=0; i SYS5_RET_MASKS; + case "Win64" -> WIN64_RET_MASKS; + default -> throw new IllegalArgumentException("Unknown calling convention: "+CodeGen.CODE._callingConv); + }; + return masks[i]; + } + + // Create a split op; any register to any register, including stack slots + @Override public SplitNode split(String kind, byte round, LRG lrg) { + return new SplitX86(kind, round); + } + + // Return a MachNode unconditional branch + @Override public CFGNode jump() { + return new UJmpX86(); + } + + // Break an infinite loop + @Override + public IfNode never(CFGNode ctrl) { + throw Utils.TODO(); + } + + // 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 CallEndNode cend -> new CallEndX86(cend); + case CallNode call -> call(call); + case CastNode cast -> new CastX86(cast); + case CProjNode c -> new CProjNode(c); + 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 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 PhiNode phi -> new PhiNode(phi); + case ProjNode prj -> prj(prj); + case ReadOnlyNode read -> new ReadOnlyNode(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 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 imm16(long imm) {return -32768 <= imm && imm <= 32767; } + 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, imm(rhs), val); + +// if(rhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3) { +// throw Utils.TODO(); // Swap load sides +// } + + // Attempt a full LEA-style break down. + // Returns one of AddX86, AddIX86, LeaX86, or LHS + if( rhs instanceof ConstantNode off && off._con instanceof SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger 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 SONTypeFunPtr 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 invert; + private Node cmp( BoolNode bool ) { + invert = false; + Node cmp = _cmp(bool); + return new SetX86(cmp, invert ? IfNode.invert(bool.op()) : bool.op()); + } + + private Node _cmp(BoolNode bool) { + // Float variant + if( bool.isFloat() ) + return new CmpFX86(bool); + Node lhs = bool.in(1); + Node rhs = bool.in(2); + + // Vs memory + if( lhs instanceof LoadNode ld && ld.nOuts() == 1 ) + return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(rhs), val, false); + + if( rhs instanceof LoadNode ld && ld.nOuts() == 1 ) + 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 SONTypeInteger ti && imm32(ti.value()) ) + return new CmpIX86(bool, (int)ti.value()); + + if( lhs instanceof ConstantNode con && con._con instanceof SONTypeInteger ti && imm32(ti.value()) ) { + invert = true; + return new CmpIX86(bool, (int)ti.value(), 0.5); + } + + // 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 SONTypeInteger ti -> new IntX86(con); + case SONTypeFloat tf -> new FltX86(con); + case SONTypeFunPtr tfp -> new TFPX86(con); + case SONTypeMemPtr tmp -> throw Utils.TODO(); + case SONTypeNil tn -> throw Utils.TODO(); + // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. + case SONType t -> t == SONType.NIL ? new IntX86(con) : new ConstantNode(con); + }; + } + + private Node i2f8(ToFloatNode tfn) { + assert tfn.in(1)._type instanceof SONTypeInteger 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 + BoolNode bool; + if( iff.in(1) instanceof BoolNode bool0 ) bool = bool0; + else iff.setDef(1, bool=new BoolNode.EQ(iff.in(1), new ConstantNode(SONTypeInteger.ZERO))); + return new JmpX86(iff, bool.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 SONTypeInteger 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 SONTypeInteger ti && imm32(ti.value()) ) + return new OrIX86(or, (int)ti.value()); + return new OrX86(or); + } + + private Node prj( ProjNode prj ) { + return new ProjX86(prj); + } + + private Node sar(SarNode sar) { + if( sar.in(2) instanceof ConstantNode con && con._con instanceof SONTypeInteger ti ) + return new SarIX86(sar, (int)(ti.value() & 0x03f) ); + return new SarX86(sar); + } + + private Node shl(ShlNode shl) { + if( shl.in(2) instanceof ConstantNode con && con._con instanceof SONTypeInteger ti ) + return new ShlIX86(shl, (int)(ti.value() & 0x03f) ); + return new ShlX86(shl); + } + + private Node shr( ShrNode shr ) { + if( shr.in(2) instanceof ConstantNode con && con._con instanceof SONTypeInteger ti ) + return new ShrIX86(shr, (int)(ti.value() & 0x03f) ); + 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 && + ld.in(0) == st.in(0) && + ld.mem() == st.mem() && + ld.ptr() == st.ptr() && + ld.off() == st.off()) { + if( op instanceof AddNode ) + return new MemAddX86(address(st), st.ptr(), idx, off, scale, imm(op.in(2)), val); + throw Utils.TODO(); + } + } + + return new StoreX86(address(st), st.ptr(), idx, off, scale, imm(st.val()), val); + } + + private Node sub (SubNode sub ){ + return sub.in(2) instanceof ConstantNode con && con._con instanceof SONTypeInteger 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 SONTypeInteger 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 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 SONTypeMemPtr; // Base ptr always, not some derived + if(mop.off() instanceof AddNode add && add.in(2) instanceof ConstantNode con && con._con instanceof SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger 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 SONTypeInteger ti) { + val = null; + imm = (int) ti.value(); + assert imm == ti.value(); // In 32-bit range + } else { + val = xval; + } + return imm; + } + +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/print/ASMPrinter.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/print/ASMPrinter.java new file mode 100644 index 0000000..3c6df73 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/print/ASMPrinter.java @@ -0,0 +1,254 @@ +package com.compilerprogramming.ezlang.compiler.print; + +import com.compilerprogramming.ezlang.compiler.codegen.CodeGen; +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.codegen.Encoding; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import com.compilerprogramming.ezlang.compiler.sontypes.SONType; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeFunPtr; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeMem; +import com.compilerprogramming.ezlang.compiler.sontypes.SONTypeRPC; + +public abstract class ASMPrinter { + + public static SB print(SB sb, CodeGen code) { + if( code._cfg==null ) + return sb.p("Need _cfg set, run after GCM"); + + // instruction address + int iadr = 0; + for( int i=0; i iadr ) + iadr++; + + // constant pool + Encoding enc = code._encoding; + if( enc!=null && !enc._bigCons.isEmpty() ) { + sb.p("--- Constant Pool ------").nl(); + for( Node relo : enc._bigCons.keySet() ) { + SONType t = enc._bigCons.get(relo); + if( t.log_size()==3 ) { + sb.hex2(iadr).p(" ").hex8(enc.read8(iadr)).p(" "); + t.print(sb).nl(); + iadr += 8; + } + } + for( Node relo : enc._bigCons.keySet() ) { + SONType t = enc._bigCons.get(relo); + if( t.log_size()==2 ) { + sb.hex2(iadr).p(" ").hex4(enc.read4(iadr)).fix(9,""); + t.print(sb).nl(); + iadr += 4; + } + } + } + + + return sb; + } + + private static int print(int iadr, SB sb, CodeGen code, FunNode fun, int cfgidx) { + // Function header + sb.nl().p("---"); + if( fun._name != null ) sb.p(fun._name).p(" "); + fun.sig().print(sb); + sb.p("---------------------------").nl(); + + if( fun._frameAdjust != 0 ) + iadr = doInst(iadr,sb,code,cfgidx,fun,true,true); + while( !(code._cfg.at(cfgidx) instanceof ReturnNode) ) + iadr = doBlock(iadr,sb,code,fun,cfgidx++); + + // Function separator + sb.p("---"); + fun.sig().print(sb); + sb.p("---------------------------").nl(); + return iadr; + } + + static private final int opWidth = 5; + static private final int argWidth = 30; + static int doBlock(int iadr, SB sb, CodeGen code, FunNode fun, int cfgidx) { + final int encWidth = code._mach.defaultOpSize()*2; + CFGNode bb = code._cfg.at(cfgidx); + if( bb != fun && !(bb instanceof IfNode) && !(bb instanceof CallEndNode) && !(bb instanceof CallNode) && !(bb instanceof CProjNode && bb.in(0) instanceof CallEndNode )) + sb.p(label(bb)).p(":").nl(); + if( bb instanceof CallNode ) return iadr; + final boolean postAlloc = code._phase.ordinal() > CodeGen.Phase.RegAlloc.ordinal(); + final boolean postEncode= code._phase.ordinal() >=CodeGen.Phase.Encoding.ordinal(); + + boolean once=false; + for( Node n : bb.outs() ) { + if( !(n instanceof PhiNode phi) ) continue; + if( phi._type instanceof SONTypeMem || phi._type instanceof SONTypeRPC ) continue; // Nothing for the hidden ones + // Post-RegAlloc phi prints all on one line + if( postAlloc ) { + if( !once ) { once=true; sb.fix(4," ").p(" ").fix(encWidth,"").p(" "); } + sb.p(phi._label).p(':').p(code.reg(phi)).p(','); + } else { + // Pre-RegAlloc phi prints one line per + sb.fix(4," ").p(" ").fix(encWidth,"").p(" ").fix(opWidth,phi._label).p(" ").p(code.reg(phi)); + if( phi.getClass() == PhiNode.class ) { + sb.p(" = phi( "); + for( int i=1; i1 ) + break; // Has code in the block, need to jump around + // No code in the block, can fall through it + } + sb.hex2(iadr++).p(" ").fix(encWidth,"??").p(" ").fix(opWidth,"JMP").p(" ").fix(argWidth,label(cfg)).nl(); + return iadr; + } + + // ProjNodes following a multi (e.g. Call or New results), + // get indent slightly and just print their index & node# + if( n instanceof ProjNode proj ) { + if( proj._type instanceof SONTypeMem ) return iadr; // Nothing for the hidden ones + sb.fix(4," ").p(" ").fix(encWidth,"").p(" ").fix(opWidth,proj._label==null ? "---" : proj._label).p(" ").p(code.reg(n)).nl(); + return iadr; + } + + // ADDR ENCODING Op--- dst = src op src // Comment + // 1234 abcdefgh ld4 RAX = [Rbase + off] // Comment + sb.hex2(iadr).p(" "); + + // Encoding + int fatEncoding = 0; + if( code._encoding != null ) { + int size = code._encoding._opLen[n._nid]; + for( int i=0; i>1); i++ ) + sb.hex1(code._encoding._bits.buf()[iadr++]); + for( int i=size*2; i>1); // Not-printed parts of encoding + } else + sb.fix(encWidth,""); + sb.p(" "); + + // Op; generally "ld4" or "call" + sb.fix(opWidth, n instanceof MachNode mach ? mach.op() : n.label()).p(" "); + + // General asm args + String isMultiOp = null; + if( n instanceof MachNode mach ) { + int old = sb.len(); + mach.asm(code,sb); + int len = sb.len(); + isMultiOp = isMultiOp(sb,old,len); + sb.fix(argWidth-(len-old),""); // Pad out + + } else if( !(n._type instanceof SONTypeMem) ) { + // Room for some inputs + sb.fix(5, n._nid+":" ); + int off = 0; + for( int i=0; i= argWidth ) break; + } + if( off < argWidth ) sb.fix(argWidth-off,""); + } + sb.p(" "); + + // Comment + String comment = n.comment(); + if( comment!=null ) sb.p("// ").p(comment); + + sb.nl(); + + // Printing more op bits than fit + if( isMultiOp != null && code._encoding != null ) { + // Multiple ops, template style, no RA, no scheduling. Print out + // one-line-per-newline, with encoding bits up front. + int size = code._encoding._opLen[n._nid]; + int off = Math.min(size,dopz); + while( isMultiOp!=null ) { + sb.hex2(iadr).p(" "); + int len = Math.min(size-off,dopz); + for( int i=0; i 0 ) { + // Extra bytes past the default encoding width, all put on a line by + // themselves. X86 special for super long encodings + sb.hex2(iadr).p(" "); + for( int i=0; i xScopes) { + + // Since the graph has cycles, we need to create a flat list of all the + // nodes in the graph. + Collection all = findAll(xScopes, stop, scope); + StringBuilder sb = new StringBuilder(); + sb.append("digraph chapter18 {\n"); + sb.append("/*\n"); + sb.append(stop._src); + sb.append("\n*/\n"); + + // To keep the Scopes below the graph and pointing up into the graph we + // need to group the Nodes in a subgraph cluster, and the scopes into a + // different subgraph cluster. THEN we can draw edges between the + // scopes and nodes. If we try to cross subgraph cluster borders while + // still making the subgraphs DOT gets confused. + sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes + + // CNC - turned off Apr/8/2024, gives more flex in the layout and + // removes some of the more ludicrous layout choices. + // Preserve node input order + //sb.append("\tordering=\"in\";\n"); + + // Merge multiple edges hitting the same node. Makes common shared + // nodes much prettier to look at. + sb.append("\tconcentrate=\"true\";\n"); + + // Force nested scopes to order + sb.append("\tcompound=\"true\";\n"); + + // Just the Nodes first, in a cluster no edges + nodes(sb, all); + + // Now the scopes, in a cluster no edges + if( xScopes != null ) + for( ScopeNode xscope : xScopes ) + if( !xscope.isDead() ) + scope( sb, xscope ); + + // Walk the Node edges + nodeEdges(sb, all); + + // Walk the active Scope edges + if( xScopes != null ) + for( ScopeNode xscope : xScopes ) + if( !xscope.isDead() ) + scopeEdges( sb, xscope ); + + sb.append("}\n"); + return sb.toString(); + } + + private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection all) { + if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster + return; + // Just the Nodes first, in a cluster no edges + sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name + for (Node n : all) { + if (n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode || n==Compiler.XCTRL) + continue; // Do not emit, rolled into MultiNode or Scope cluster already + if( _separateControlCluster && doCtrl && !(n instanceof CFGNode) ) continue; + if( _separateControlCluster && !doCtrl && (n instanceof CFGNode) ) continue; + sb.append("\t\t").append(n.uniqueName()).append(" [ "); + String lab = n.glabel(); + if (n instanceof MultiNode) { + // Make a box with the MultiNode on top, and all the projections on the bottom + sb.append("shape=plaintext label=<\n"); + sb.append("\t\t\t\n"); + sb.append("\t\t\t\n"); + sb.append("\t\t\t"); + boolean doProjTable = false; + n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : (x._nid - y._nid)); + for (Node use : n._outputs) { + if (use instanceof ProjNode proj) { + if (!doProjTable) { + doProjTable = true; + sb.append("
").append(lab).append("
").append("\n"); + sb.append("\t\t\t\t").append("\n"); + sb.append("\t\t\t\t"); + } + sb.append(""); + } + if (use instanceof CProjNode proj) { + if (!doProjTable) { + doProjTable = true; + sb.append(""); + } + sb.append("\n"); + sb.append("\t\t\t
").append(proj.glabel()).append("").append("\n"); + sb.append("\t\t\t\t").append("\n"); + sb.append("\t\t\t\t"); + } + sb.append(""); + } + } + if (doProjTable) { + sb.append("").append("\n"); + sb.append("\t\t\t\t
").append(proj.glabel()).append("
").append("\n"); + sb.append("\t\t\t
>\n\t\t"); + + } else { + // control nodes have box shape + // other nodes are ellipses, i.e. default shape + if( n instanceof CFGNode ) sb.append("shape=box style=filled fillcolor=yellow "); + else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow "); + sb.append("label=\"").append(lab).append("\" "); + } + sb.append("];\n"); + } + if (!_separateControlCluster) { + // Force Region & Phis to line up + for (Node n : all) { + if (n instanceof RegionNode region) { + sb.append("\t\t{ rank=same; "); + sb.append(region.uniqueName()).append(";"); + for (Node phi : region._outputs) + if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";"); + sb.append("}\n"); + } + } + } + sb.append("\t}\n"); // End Node cluster + } + + private void nodes(StringBuilder sb, Collection all) { + nodesByCluster(sb, true, all); + nodesByCluster(sb, false, all); + } + + // Build a nested scope display, walking the _prev edge + private void scope( StringBuilder sb, ScopeNode scope ) { + sb.append("\tnode [shape=plaintext];\n"); + int last = scope.nIns(); + int max = scope._lexSize.size(); + for( int i = 0; i < max; i++ ) { + int level = max-i-1; + String scopeName = makeScopeName(scope, level); + sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name + // Special for memory ScopeMinNode + if( level==0 ) { + MemMergeNode n = scope.mem(); + sb.append("\t\t").append(n.uniqueName()).append(" [label=\"").append(n.glabel()).append("\"];\n"); + } + sb.append("\t\t").append(scopeName).append(" [label=<\n"); + sb.append("\t\t\t\n"); + // Add the scope level + sb.append("\t\t\t"); + int lexStart=scope._lexSize.at(level); + for( int j=lexStart; j").append(v._name).append(""); + } + last = lexStart; + sb.append("\n"); + sb.append("\t\t\t
").append(level).append("
>];\n"); + } + // Scope clusters nest, so the graphics shows the nested scopes, so + // they are not closed as they are printed; so they just keep nesting. + // We close them all at once here. + sb.append( "\t}\n".repeat( max ) ); // End all Scope clusters + } + + private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } + private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } + + // Walk the node edges + private void nodeEdges(StringBuilder sb, Collection all) { + // All them edge labels + sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); + for( Node n : all ) { + // Do not display the Constant->Start edge; + // ProjNodes handled by Multi; + // ScopeNodes are done separately + if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Compiler.XCTRL ) + continue; + for( int i=0; idef edge from Phi to Region. + sb.append('\t').append(n.uniqueName()); + sb.append(" -> "); + sb.append(def.uniqueName()); + sb.append(" [style=dotted taillabel=").append(i).append("];\n"); + } else if( def != null ) { + // Most edges land here use->def + sb.append('\t').append(n.uniqueName()).append(" -> "); + if( def instanceof CProjNode proj ) { + String mname = proj.ctrl().uniqueName(); + sb.append(mname).append(":p").append(proj._idx); + } else if( def instanceof ProjNode proj ) { + String mname = proj.in(0).uniqueName(); + sb.append(mname).append(":p").append(proj._idx); + } else sb.append(def.uniqueName()); + // Number edges, so we can see how they track + sb.append("[taillabel=").append(i); + // The edge from New to ctrl is just for anchoring the New + if ( n instanceof NewNode ) + sb.append(" color=green"); + // control edges are colored red + else if( def instanceof CFGNode ) + sb.append(" color=red"); + else if( def.isMem() ) + sb.append(" color=blue"); + // Backedges do not add a ranking constraint + if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) ) + sb.append(" constraint=false"); + sb.append("];\n"); + } + } + } + } + + // Walk the scope edges + private void scopeEdges( StringBuilder sb, ScopeNode scope ) { + sb.append("\tedge [style=dashed color=cornflowerblue];\n"); + int level=0; + for( var v : scope._vars ) { + Node def = scope.in(v._idx); + while( def instanceof ScopeNode lazy ) + def = lazy.in(v._idx); + if( def==null ) continue; + while( level < scope._lexSize.size() && v._idx >= scope._lexSize.at(level) ) + level++; + String scopeName = makeScopeName(scope, level-1); + sb.append("\t") + .append(scopeName).append(":") + .append('"').append(makePortName(scopeName, v._name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted + .append(" -> "); + if( def instanceof CProjNode proj ) { + sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); + } else if( def instanceof ProjNode proj ) { + sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); + } else sb.append(def.uniqueName()); + sb.append(";\n"); + } + } + + /** + * Finds all nodes in the graph. + */ + public static Collection findAll(Stack ss, Node... ns) { + final HashMap all = new HashMap<>(); + for( Node n : ns ) + walk(all, n); + if( ss != null ) + for( Node n : ss ) + walk(all, n); + return all.values(); + } + + /** + * Walk a subgraph and populate distinct nodes in the all list. + */ + private static void walk(HashMap all, Node n) { + if(n == null ) return; + if (all.get(n._nid) != null) return; // Been there, done that + all.put(n._nid, n); + for (Node c : n._inputs) + walk(all, c); + for( Node c : n._outputs ) + walk(all, c); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/print/IRPrinter.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/print/IRPrinter.java new file mode 100644 index 0000000..a69581d --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/print/IRPrinter.java @@ -0,0 +1,327 @@ +package com.compilerprogramming.ezlang.compiler.print; + +import com.compilerprogramming.ezlang.compiler.Ary; +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.Utils; +import com.compilerprogramming.ezlang.compiler.codegen.CodeGen; +import com.compilerprogramming.ezlang.compiler.nodes.*; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashMap; + +public abstract class IRPrinter { + + // Print a node on 1 line, columnar aligned, as: + // NNID NNAME DDEF DDEF [[ UUSE UUSE ]] TYPE + // 1234 sssss 1234 1234 1234 1234 1234 1234 tttttt + public static SB printLine( Node n, SB sb ) { + sb.p("%4d %-7.7s ".formatted(n._nid,n.label())); + if( n._inputs==null ) + return sb.p("DEAD\n"); + for( Node def : n._inputs ) + sb.p(def==null ? "____" : "%4d".formatted(def._nid)) + // Lazy Phi indicator + .p(n instanceof MemMergeNode && def instanceof MemMergeNode ? "^" : " "); + for( int i = n._inputs.size(); i<4; i++ ) + sb.p(" "); + sb.p(" [[ "); + for( Node use : n._outputs ) + sb.p(use==null ? "____ " : "%4d ".formatted(use._nid)); + int lim = 6 - Math.max(n._inputs.size(),4); + for( int i = n._outputs.size(); i CodeGen.Phase.Schedule.ordinal() + ? _prettyPrintScheduled( node, depth ) + : _prettyPrint( node, depth ); + } + + // Another bulk pretty-printer. Makes more effort at basic-block grouping. + private static String _prettyPrint( Node node, int depth ) { + // First, a Breadth First Search at a fixed depth. + BFS bfs = new BFS(node,depth); + // Convert just that set to a post-order + ArrayList rpos = new ArrayList<>(); + BitSet visit = new BitSet(); + for( int i=bfs._lim; i< bfs._bfs.size(); i++ ) + postOrd( bfs._bfs.get(i), null, rpos, visit, bfs._bs); + // Reverse the post-order walk + SB sb = new SB(); + boolean gap=false; + for( int i=rpos.size()-1; i>=0; i-- ) { + Node n = rpos.get(i); + if( n instanceof CFGNode || n instanceof MultiNode ) { + if( !gap ) sb.p("\n"); // Blank before multihead + if( n instanceof FunNode fun ) + fun.sig().print(sb.p("--- ").p(fun._name==null ? "" : fun._name).p(" "),false).p("----------------------\n"); + printLine( n, sb ); // Print head + while( --i >= 0 ) { + Node t = rpos.get(i); + if( !(t.in(0) instanceof MultiNode) ) { i++; break; } + printLine( t, sb ); + } + if( n instanceof ReturnNode ret ) { + FunNode fun = ret.fun(); + sb.p("--- "); + if( fun != null ) + fun.sig().print(sb.p(fun._name==null ? "" : fun._name).p(" "),false); + sb.p("----------------------\n"); + } + if( !(n instanceof CallNode) ) { + sb.p("\n"); // Blank after multitail + gap = true; + } + } else { + printLine( n, sb ); + gap = false; + } + } + return sb.toString(); + } + + private static void postOrd(Node n, Node prior, ArrayList rpos, BitSet visit, BitSet bfs) { + if( !bfs.get(n._nid) ) + return; // Not in the BFS visit + if( n instanceof FunNode && !(prior instanceof StartNode) ) + return; // Only visit Fun from Start + if( visit.get(n._nid) ) return; // Already post-order walked + visit.set(n._nid); + // First walk the CFG, then everything + if( n instanceof CFGNode ) { + for( Node use : n._outputs ) + // Follow CFG, not across call/function borders, and not around backedges + if( use instanceof CFGNode && !(n instanceof CallNode && use instanceof FunNode) && + use.nOuts() >= 1 && !(use._outputs.get(0) instanceof LoopNode) ) + postOrd(use, n, rpos,visit,bfs); + for( Node use : n._outputs ) + // Follow CFG, not across call/function borders + if( use instanceof CFGNode && !(n instanceof CallNode && use instanceof FunNode) ) + postOrd(use,n,rpos,visit,bfs); + } + // Follow all outputs + for( Node use : n._outputs ) + if( use != null && + !(n instanceof CallNode && use instanceof FunNode) && + (n instanceof FunNode || !(use instanceof ParmNode)) ) + postOrd(use, n, rpos,visit,bfs); + // Post-order + rpos.add(n); + } + + // Breadth-first search, broken out in a class to keep in more independent. + // Maintains a root-set of Nodes at the limit (or past by 1 if MultiHead). + public static class BFS { + // A breadth first search, plus MultiHeads for any MultiTails + public final ArrayList _bfs; + public final BitSet _bs; // Visited members by node id + public final int _depth; // Depth limit + public final int _lim; // From here to _bfs._len can be roots for a reverse search + public BFS( Node base, int d ) { + _depth = d; + _bfs = new ArrayList<>(); + _bs = new BitSet(); + + add(base); // Prime the pump + int idx=0, lim=1; // Limit is where depth counter changes + while( idx < _bfs.size() ) { // Ran out of nodes below depth + Node n = _bfs.get(idx++); + for( Node def : n._inputs ) + if( def!=null && !_bs.get(def._nid) ) + add(def); + if( idx==lim ) { // Depth counter changes at limit + if( --d < 0 ) + break; // Ran out of depth + lim = _bfs.size(); // New depth limit + } + } + // Toss things past the limit except multi-heads + while( idx < _bfs.size() ) { + Node n = _bfs.get(idx); + if( n instanceof MultiNode ) idx++; + else del(idx); + } + // Root set is any node with no inputs in the visited set + lim = _bfs.size(); + for( int i=_bfs.size()-1; i>=0; i-- ) + if( !any_visited(_bfs.get(i)) ) + swap( i,--lim); + _lim = lim; + } + void swap( int x, int y ) { + if( x==y ) return; + Node tx = _bfs.get(x); + Node ty = _bfs.get(y); + _bfs.set(x,ty); + _bfs.set(y,tx); + } + void add(Node n) { + _bfs.add(n); + _bs.set(n._nid); + } + void del(int idx) { + _bs.clear(_bfs.get(idx)._nid); + Utils.del(_bfs, idx); + } + boolean any_visited( Node n ) { + for( Node def : n._inputs ) + if( def!=null && _bs.get(def._nid) ) + return true; + return false; + } + } + + public static String _prettyPrint( CodeGen code ) { + SB sb = new SB(); + // Print the Start "block" + printLine(code._start,sb); + for( Node n : code._start._outputs ) + printLine(n,sb); + sb.nl(); + + // Skip start, stop + for( int i=1; i ds = new HashMap<>(); + Ary ns = new Ary<>(Node.class); + _walk(ds,ns,node,depth); + // Remove data projections, these are force-printed behind their multinode head + for( int i=0; i bns = new Ary<>(Node.class); + while( !ds.isEmpty() ) { + CFGNode blk = null; + for( Node n : ns ) { + CFGNode cfg = n instanceof CFGNode cfg0 && cfg0.blockHead() ? cfg0 : n.cfg0(); + if( blk==null || cfg.idepth() < blk.idepth() ) + blk = cfg; + } + Integer d = ds.remove(blk._nid); + ns.del(ns.find(blk)); + + // Print block header + sb.p("%-13.13s".formatted(label(blk)+":")); + sb.p( " ".repeat(4) ).p(" [[ "); + if( blk instanceof StartNode ) ; + else if( blk instanceof RegionNode || blk instanceof StopNode ) + for( int i=(blk instanceof StopNode ? 3 : 1); i ds, Ary ns, Node node, int d ) { + Integer nd = ds.get(node._nid); + if( nd!=null && d <= nd ) return; // Been there, done that + Integer old = ds.put(node._nid,d) ; + if( old == null ) + ns.add(node); + if( d == 0 ) return; // Depth cutoff + for( Node def : node._inputs ) + if( def != null && + !(node instanceof LoopNode loop && loop.back()==def) && + // Don't walk into or out of functions + !(node instanceof CallEndNode && def instanceof ReturnNode) && + !(node instanceof FunNode && def instanceof CallNode) && + !(node instanceof ParmNode && !(def instanceof FunNode)) + ) + _walk(ds,ns,def,d-1); + } + + static String label( CFGNode blk ) { + if( blk instanceof StartNode ) return "START"; + return (blk instanceof LoopNode ? "LOOP" : "L")+blk._nid; + } + static void label( SB sb, CFGNode blk ) { + if( !blk.blockHead() ) blk = blk.cfg(0); + sb.p( "%-9.9s ".formatted( label( blk ) ) ); + } + static void printLine( Node n, SB sb, Ary bns, int i, HashMap ds, Ary ns ) { + printLine( n, sb ); + if( i != -1 ) bns.del(i); + ds.remove(n._nid); + int idx = ns.find(n); + if( idx!=-1 ) ns.del(idx); + } + +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/Field.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/Field.java new file mode 100644 index 0000000..d4cf783 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/Field.java @@ -0,0 +1,74 @@ +package com.compilerprogramming.ezlang.compiler.sontypes; + +import com.compilerprogramming.ezlang.compiler.SB; + +import java.util.ArrayList; + +/** + * Represents a field in a struct. This is not a Type in the type system. + */ +public class Field extends SONType { + + // The pair {fieldName,type} uniquely identifies a field. + + // Field name + public final String _fname; + // Type of the field + public final SONType _type; + // Unique memory alias, not sensibly part of a "type" but very convenient here. + public final int _alias; + // Field must be written to exactly once, no more, no less + public final boolean _final; + + public Field(String fname, SONType type, int alias, boolean xfinal ) { + super(TFLD); + _fname = fname; + _type = type; + _alias = alias; + _final = xfinal; + } + // Make with existing alias + public static Field make(String fname, SONType type, int alias, boolean xfinal ) { + return new Field(fname,type,alias,xfinal).intern(); + } + public Field makeFrom( SONType type ) { + return type == _type ? this : new Field(_fname,type,_alias,_final).intern(); + } + @Override public Field makeRO() { return _final ? this : make(_fname,_type.makeRO(),_alias,true); } + @Override public boolean isFinal() { return _final && _type.isFinal(); } + + public static final Field TEST = make("test", SONType.NIL,-2,false); + public static final Field TEST2= make("test", SONType.NIL,-2,true); + public static void gather(ArrayList ts) { ts.add(TEST); ts.add(TEST2); } + + @Override Field xmeet( SONType that ) { + Field fld = (Field)that; // Invariant + assert _fname.equals(fld._fname); + assert _alias==fld._alias; + return make(_fname,_type.meet(fld._type),_alias,_final | fld._final); + } + + @Override + public Field dual() { return make(_fname,_type.dual(),_alias,!_final); } + + @Override public Field glb() { + SONType glb = _type.glb(); + return (glb==_type && _final) ? this : make(_fname,glb,_alias,true); + } + + // Override in subclasses + int hash() { return _fname.hashCode() ^ _type.hashCode() ^ _alias ^ (_final ? 1024 : 0); } + + boolean eq(SONType t) { + Field f = (Field)t; + return _fname.equals(f._fname) && _type==f._type && _alias==f._alias && _final==f._final; + } + + + @Override + public SB print( SB sb ) { + return _type.print(sb.p(_final?"":"!").p(_fname).p(":").p(_alias).p(" : ")); + } + + @Override public String str() { return _fname; } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONType.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONType.java new file mode 100644 index 0000000..ba7eaac --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONType.java @@ -0,0 +1,235 @@ +package com.compilerprogramming.ezlang.compiler.sontypes; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.Utils; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * These types are part of a Monotone Analysis Framework, + * @see see for example this set of slides. + *

+ * The types form a lattice; @see a symmetric complete bounded (ranked) lattice. + *

+ * This wild lattice theory will be needed later to allow us to easily beef up + * the analysis and optimization of the Simple compiler... but we don't need it + * now, just know that it is coming along in a later Chapter. + *

g + * One of the fun things here is that while the theory is deep and subtle, the + * actual implementation is darn near trivial and is generally really obvious + * what we're doing with it. Right now, it's just simple integer math to do + * simple constant folding e.g. 1+2 == 3 stuff. + */ + +public class SONType { + static final HashMap INTERN = new HashMap<>(); + + // ---------------------------------------------------------- + // Simple types are implemented fully here. "Simple" means: the code and + // type hierarchy are simple, not that the Type is conceptually simple. + static final byte TBOT = 0; // Bottom (ALL) + static final byte TTOP = 1; // Top (ANY) + static final byte TCTRL = 2; // Ctrl flow bottom + static final byte TXCTRL = 3; // Ctrl flow top (mini-lattice: any-xctrl-ctrl-all) + static final byte TNIL = 4; // low null of all flavors + static final byte TXNIL = 5; // high or choice null + static final byte TSIMPLE = 6; // End of the Simple Types + + static final byte TPTR = 7; // All nil-able scalar values + static final byte TINT = 8; // All Integers; see TypeInteger + static final byte TFLT = 9; // All Floats ; see TypeFloat + static final byte TMEMPTR =10; // Memory pointer to a struct type + static final byte TFUNPTR =11; // Function pointer; unique signature and code address (just a bit) + static final byte TTUPLE =12; // Tuples; finite collections of unrelated Types, kept in parallel + static final byte TMEM =13; // All memory (alias 0) or A slice of memory - with specific alias + static final byte TSTRUCT =14; // Structs; tuples with named fields + static final byte TFLD =15; // Named fields in structs + static final byte TRPC =16; // Return Program Control (Return PC or RPC) + + public final byte _type; + + public boolean is_simple() { return _type < TSIMPLE; } + private static final String[] STRS = new String[]{"Bot","Top","Ctrl","~Ctrl","null","~nil"}; + protected SONType(byte type) { _type = type; } + + public static final SONType BOTTOM = new SONType( TBOT ).intern(); // ALL + public static final SONType TOP = new SONType( TTOP ).intern(); // ANY + public static final SONType CONTROL = new SONType( TCTRL ).intern(); // Ctrl + public static final SONType XCONTROL = new SONType( TXCTRL ).intern(); // ~Ctrl + public static final SONType NIL = new SONType( TNIL ).intern(); // low null of all flavors + public static final SONType XNIL = new SONType( TXNIL ).intern(); // high or choice null + public static SONType[] gather() { + ArrayList ts = new ArrayList<>(); + ts.add(BOTTOM); + ts.add(CONTROL); + ts.add(NIL); + ts.add(XNIL); + SONTypeNil.gather(ts); + SONTypePtr.gather(ts); + SONTypeInteger.gather(ts); + SONTypeFloat.gather(ts); + SONTypeMemPtr.gather(ts); + SONTypeFunPtr.gather(ts); + SONTypeMem.gather(ts); + Field.gather(ts); + SONTypeStruct.gather(ts); + SONTypeTuple.gather(ts); + SONTypeRPC.gather(ts); + int sz = ts.size(); + for( int i = 0; i < sz; i++ ) + ts.add(ts.get(i).dual()); + return ts.toArray(new SONType[ts.size()]); + } + + // Is high or on the lattice centerline. + public boolean isHigh () { return _type==TTOP || _type==TXCTRL || _type==TXNIL; } + public boolean isHighOrConst() { return isHigh() || isConstant(); } + + // Strict constant values, things on the lattice centerline. + // Excludes both high and low values + public boolean isConstant() { return _type==TNIL; } + + // ---------------------------------------------------------- + + // Notes on Type interning: At the moment it is not easy to reset the + // interned types because we hold static references to several types and + // these are scattered around. This means the INTERN cache will retain all + // types from every run of the Parser. For this to work correctly types + // must be rigorous about defining when they are the same. Also types need + // to be immutable once defined. The rationale for interning is + // *correctness* with cyclic type definitions. Simple structural recursive + // checks go exponential with merely sharing, but with cycles they will + // stack overflow and crash. Interning means we do not need to have sharing + // checks with every type compare, only during interning. + + // Factory method which interns "this" + public T intern() { + T nnn = (T)INTERN.get(this); + if( nnn==null ) + INTERN.put(nnn=(T)this,this); + return nnn; + } + + private int _hash; // Hash cache; not-zero when set. + @Override + public final int hashCode() { + if( _hash!=0 ) return _hash; + _hash = hash(); + if( _hash==0 ) _hash = 0xDEADBEEF; // Bad hash from subclass; use some junk thing + return _hash; + } + // Override in subclasses + int hash() { return _type; } + + @Override + public final boolean equals( Object o ) { + if( o==this ) return true; + if( !(o instanceof SONType t)) return false; + if( _type != t._type ) return false; + return eq(t); + } + // Overridden in subclasses; subclass can assume "this!=t" and java classes are same + boolean eq(SONType t) { return true; } + + + // ---------------------------------------------------------- + public final SONType meet(SONType t) { + // Shortcut for the self case + if( t == this ) return this; + // Same-type is always safe in the subclasses + if( _type==t._type ) return xmeet(t); + // TypeNil vs TypeNil meet + if( this instanceof SONTypeNil ptr0 && t instanceof SONTypeNil ptr1 ) + return ptr0.nmeet(ptr1); + // Reverse; xmeet 2nd arg is never "is_simple" and never equal to "this". + if( is_simple() ) return this.xmeet(t ); + if( t.is_simple() ) return t .xmeet(this); + return SONType.BOTTOM; // Mixing 2 unrelated types + } + + // Compute meet right now. Overridden in subclasses. + // Handle cases where 'this.is_simple()' and unequal to 't'. + // Subclassed xmeet calls can assert that '!t.is_simple()'. + SONType xmeet(SONType t) { + assert is_simple(); // Should be overridden in subclass + // ANY meet anything is thing; thing meet ALL is ALL + if( _type==TBOT || t._type==TTOP ) return this; + if( _type==TTOP || t._type==TBOT ) return t; + + // RHS TypeNil vs NIL/XNIL + if( _type== TNIL ) return t instanceof SONTypeNil ptr ? ptr.meet0() : (t._type==TXNIL ? SONTypePtr.PTR : BOTTOM); + if( _type== TXNIL ) return t instanceof SONTypeNil ptr ? ptr.meetX() : (t._type== TNIL ? SONTypePtr.PTR : BOTTOM); + // 'this' is only {TCTRL,TXCTRL} + // Other non-simple RHS things bottom out + if( !t.is_simple() ) return BOTTOM; + // If RHS is NIL/XNIL + if( t._type==TNIL || t._type==TXNIL ) return BOTTOM; + // Both are {TCTRL,TXCTRL} and unequal to each other + return _type==TCTRL || t._type==TCTRL ? CONTROL : XCONTROL; + } + + public SONType dual() { + return switch( _type ) { + case TBOT -> TOP; + case TTOP -> BOTTOM; + case TCTRL ->XCONTROL; + case TXCTRL-> CONTROL; + case TNIL ->XNIL; + case TXNIL -> NIL; + default -> throw Utils.TODO(); // Should not reach here + }; + } + + // ---------------------------------------------------------- + // Our lattice is defined with a MEET and a DUAL. + // JOIN is dual of meet of both duals. + public final SONType join(SONType t) { + if( this==t ) return this; + return dual().meet(t.dual()).dual(); + } + + // True if this "isa" t; e.g. 17 isa TypeInteger.BOT + public boolean isa( SONType t ) { return meet(t)==t; } + + // True if this "isa" t up to named structures + public boolean shallowISA( SONType t ) { return isa(t); } + + public SONType nonZero() { return SONTypePtr.NPTR; } + + // Make a zero version of this type, 0 for integers and null for pointers. + public SONType makeZero() { return SONType.NIL; } + + /** Compute greatest lower bound in the lattice */ + public SONType glb() { return SONType.BOTTOM; } + + // Is forward-reference + public boolean isFRef() { return false; } + + // All reachable struct Fields are final + public boolean isFinal() { return true; } + + // Make all reachable struct Fields final + public SONType makeRO() { return this; } + + // ---------------------------------------------------------- + + // Size in bits to hold an instance of this type. + // Sizes are expected to be between 1 and 64 bits. + // Size 0 means this either takes no space (such as a known-zero field) + // or isn't a scalar to be stored in memory. + public int log_size() { throw Utils.TODO(); } + + // ---------------------------------------------------------- + // Useful in the debugger, which calls toString everywhere. + // This is a more verbose dev-friendly print. + @Override + public final String toString() { + return str(); + } + + public SB print(SB sb) { return sb.p(str()); } + public SB gprint(SB sb) { return print(sb); } + + // This is used by error messages, and is a shorted print. + public String str() { return STRS[_type]; } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeFloat.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeFloat.java new file mode 100644 index 0000000..c4d2dba --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeFloat.java @@ -0,0 +1,94 @@ +package com.compilerprogramming.ezlang.compiler.sontypes; + +import com.compilerprogramming.ezlang.compiler.Utils; +import java.util.ArrayList; + +/** + * Float Type + */ +public class SONTypeFloat extends SONType { + + public final static SONTypeFloat ONE = constant(1.0); + public final static SONTypeFloat FZERO = constant(0.0); + public final static SONTypeFloat F32 = make(32, 0); + public final static SONTypeFloat F64 = make(64, 0); + + // - high -64, high -32, con 0, low +32, low +64 + public final byte _sz; + + /** + * The constant value or 0 + */ + public final double _con; + + private SONTypeFloat(byte sz, double con) { + super(TFLT); + _sz = sz; + _con = con; + } + private static SONTypeFloat make(int sz, double con) { + return new SONTypeFloat((byte)sz,con).intern(); + } + + public static SONTypeFloat constant(double con) { return make(0, con); } + + public static void gather(ArrayList ts) { ts.add(F64); ts.add(F32); ts.add(constant(3.141592653589793)); } + + @Override public String str() { + return switch( _sz ) { + case -64 -> "~flt"; + case -32 -> "~f32"; + case 0 -> ""+_con+((float)_con==_con ? "f" : ""); + case 32 -> "f32"; + case 64 -> "flt"; + default -> throw Utils.TODO(); + }; + } + private boolean isF32() { return ((float)_con)==_con; } + + @Override public boolean isHigh () { return _sz< 0; } + @Override public boolean isConstant() { return _sz==0; } + @Override public int log_size() { return _sz==32 || _sz==-32 ? 2 : 3; } + + public double value() { return _con; } + + @Override + public SONTypeFloat xmeet(SONType other) { + SONTypeFloat f = (SONTypeFloat)other; + // Invariant from caller: 'this' != 'other' and same class (TypeFloat). + SONTypeFloat i = (SONTypeFloat)other; // Contract + // Larger size in i1, smaller in i0 + SONTypeFloat i0 = _sz < i._sz ? this : i; + SONTypeFloat i1 = _sz < i._sz ? i : this; + + if( i1._sz== 64 ) return F64; + if( i0._sz==-64 ) return i1; + if( i1._sz== 32 ) + return i0._sz==0 && !i0.isF32() ? F64 : F32; + if( i1._sz!= 0 ) return i1; + // i1 is a constant + if( i0._sz==-32 ) + return i1.isF32() ? i1 : F64; + // Since both are constants, and are never equals (contract) unequals + // constants fall to bottom + return i0.isF32() && i1.isF32() ? F32 : F64; + } + + @Override + public SONType dual() { + return isConstant() ? this : make(-_sz,0); // Constants are a self-dual + } + + @Override public SONType makeZero() { return FZERO; } + @Override public SONType glb() { return F64; } + + @Override + int hash() { return (int)(Double.hashCode(_con) ^ _sz ^ (1<<17)); } + @Override + public boolean eq( SONType t ) { + SONTypeFloat i = (SONTypeFloat)t; // Contract + // Allow NaN to check for equality + return _sz==i._sz && Double.doubleToLongBits(_con)==Double.doubleToLongBits(i._con); + } + +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeFunPtr.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeFunPtr.java new file mode 100644 index 0000000..2d81a60 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeFunPtr.java @@ -0,0 +1,106 @@ +package com.compilerprogramming.ezlang.compiler.sontypes; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.Utils; +import java.util.ArrayList; + +/** + * Represents a Pointer to a function. + *

+ * Functions have argument types and a return type, making a function + * signature. Within a signature there can be many instances of functions, and + * each function is labeled with a small integer constant. A TFP can represent + * a single signature and a set of functions; the set might contain only 1 + * function or the zeroth function (the null ptr). + *

+ * Functions with different signatures cannot be mixed, and will result in a + * bottom function type, which can only be null-checked. + */ +public class SONTypeFunPtr extends SONTypeNil { + // A TypeFunPtr is Signature and a set of functions. + public final SONTypeTuple _sig; + public final SONType _ret; + // Cheesy easy implementation for a small set; 1 bit per unique function + // within the TypeTuple. Can be upgraded to a BitSet for larger classes of + // functions. Negative means "these 63 concrete bits plus infinite unknown more" + public final long _fidxs; // 63 unique functions per signature + + public SONTypeFunPtr(byte nil, SONTypeTuple sig, SONType ret, long fidxs) { + super(TFUNPTR,nil); + assert sig != null; + _sig = sig; + _ret = ret; + _fidxs = fidxs; + } + + public static SONTypeFunPtr make(byte nil, SONTypeTuple sig, SONType ret, long fidxs ) { return new SONTypeFunPtr(nil,sig,ret,fidxs).intern(); } + public static SONTypeFunPtr make(boolean nil, SONTypeTuple sig, SONType ret ) { return make((byte)(nil ? 3 : 2),sig,ret,-1); } + @Override + SONTypeFunPtr makeFrom(byte nil ) { return nil ==_nil ? this : make( nil,_sig,_ret, _fidxs); } + public SONTypeFunPtr makeFrom(SONType ret ) { return ret ==_ret ? this : make( _nil,_sig, ret, _fidxs); } + public SONTypeFunPtr makeFrom(int fidx ) { return make((byte)2, _sig,_ret,1L< ts) { ts.add(TEST); ts.add(TEST0); ts.add(BOT); ts.add(MAIN); } + + @Override + SONType xmeet(SONType t) { + SONTypeFunPtr that = (SONTypeFunPtr) t; + return SONTypeFunPtr.make(xmeet0(that),(SONTypeTuple)_sig.meet(that._sig), _ret.meet(that._ret), _fidxs | that._fidxs); + } + + @Override + public SONTypeFunPtr dual() { return SONTypeFunPtr.make(dual0(), _sig.dual(), _ret.dual(), ~_fidxs); } + + // RHS is NIL; do not deep-dual when crossing the centerline + @Override public SONType meet0() { return _nil==3 ? this : make((byte)3,_sig,_ret,_fidxs); } + + @Override public SONTypeFunPtr glb() { return make((byte)3,_sig,_ret,-1L); } + + @Override public boolean isHigh () { return _nil <= 1 || (_nil==2 && _fidxs==0); } + @Override public boolean isConstant() { return (_nil==2 && Long.bitCount(_fidxs)==1) || (_nil==3 && _fidxs==0); } + + @Override public int log_size() { return 2; } // (1<<2)==4-byte pointers + + public SONType arg(int i) { return _sig._types[i]; } + public long fidxs() { return _fidxs; } + public SONType ret() { return _ret; } + public int nargs() { return _sig._types.length; } + public int fidx() { assert Long.bitCount(_fidxs)==1; return Long.numberOfTrailingZeros(_fidxs); } + + @Override + int hash() { return Utils.fold(_sig.hashCode() ^ _ret.hashCode() ^ _fidxs ^ super.hash()); } + + @Override + boolean eq(SONType t) { + SONTypeFunPtr ptr = (SONTypeFunPtr)t; // Invariant + return _sig == ptr._sig && _ret == ptr._ret && _fidxs == ptr._fidxs && super.eq(ptr); + } + + @Override public String str() { return print(new SB()).toString(); } + @Override public SB print(SB sb) { return _print(sb,false,true); } + public SB print(SB sb, boolean n) { return _print(sb,false,n); } + @Override public SB gprint(SB sb) { return _print(sb,true ,true); } + private static SB _print(SB sb, boolean g, SONType t) { return g ? t.gprint(sb) : t.print(sb); } + private SB _print(SB sb, boolean g, boolean n) { + sb.p(x()).p("{ "); + if( _sig._types!=null ) + for( SONType t : _sig._types ) + _print(sb,g,t).p(" "); + _print(sb.p(g ? "→ " : "-> "),g,_ret).p(" #"); + if( isHigh() ) sb.p("~"); + long fidxs = isHigh() ? ~_fidxs : _fidxs; + String fidx = fidxs==0 ? "" + : Long.bitCount(fidxs) == 1 ? ""+Long.numberOfTrailingZeros(fidxs) + : fidxs == -1 ? "ALL" + : "b"+Long.toBinaryString(fidxs); // Just some function bits + return sb.p(fidx).p("}").p(q()); + } + + // Usage: for( long fidxs=fidxs(); fidxs!=0; fidxs=nextFIDX(fidxs) { int fidxs = Long.numberOfTrailingZeros(fidxs); ... } + public static long nextFIDX(long fidxs) { return fidxs & (fidxs-1); } + +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeInteger.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeInteger.java new file mode 100644 index 0000000..5030b57 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeInteger.java @@ -0,0 +1,127 @@ +package com.compilerprogramming.ezlang.compiler.sontypes; + +import com.compilerprogramming.ezlang.compiler.Utils; +import java.util.ArrayList; + +/** + * Integer Type + */ +public class SONTypeInteger extends SONType { + + public final static SONTypeInteger ZERO= make(0,0); + public final static SONTypeInteger FALSE=ZERO; + public final static SONTypeInteger TRUE= make(1,1); + + public final static SONTypeInteger I1 = make(-1,0); + public final static SONTypeInteger I8 = make(-128,127); + public final static SONTypeInteger I16 = make(-32768,32767); + public final static SONTypeInteger I32 = make(-1L<<31,(1L<<31)-1); + public final static SONTypeInteger BOT = make(Long.MIN_VALUE,Long.MAX_VALUE); + public final static SONTypeInteger TOP = BOT.dual(); + + public final static SONTypeInteger U1 = make(0,1); + public final static SONTypeInteger BOOL= U1; + public final static SONTypeInteger U8 = make(0,255); + public final static SONTypeInteger U16 = make(0,65535); + public final static SONTypeInteger U32 = make(0,(1L<<32)-1); + + /** + * Describes an integer *range* - everything from min to max; both min and + * max are inclusive. If min==max, this is a constant. + *
+ * If min <= max, this is a below center (towards bottom). + * If min > max, this is an above center (towards top). + */ + public final long _min, _max; + + private SONTypeInteger(long min, long max) { + super(TINT); + _min = min; + _max = max; + } + + // Strict non-zero contract + public static SONTypeInteger make(long lo, long hi) { return new SONTypeInteger(lo,hi).intern(); } + + public static SONTypeInteger constant(long con) { return make(con, con); } + + public static void gather(ArrayList ts) { ts.add(I32); ts.add(BOT); ts.add(U1); ts.add(I1); ts.add(U8); } + + @Override public String str() { + if( isConstant() ) return ""+_min; + long lo = _min, hi = _max; + String x = ""; + if( hi < lo ) { + lo = _max; hi = _min; + x = "~"; + } + return x+_str(lo,hi); + } + private static String _str(long lo, long hi) { + if( lo==Long.MIN_VALUE && hi==Long.MAX_VALUE ) return "int"; + if( lo== 0 && hi== 1 ) return "bool"; + if( lo== -1 && hi== 0 ) return "i1"; + if( lo== -128 && hi== 127 ) return "i8"; + if( lo== -32768 && hi== 32767 ) return "i16"; + if( lo== -1L<<31 && hi==(1L<<31)-1 ) return "i32"; + if( lo== 0 && hi== 255 ) return "u8"; + if( lo== 0 && hi== 65535 ) return "u16"; + if( lo== 0 && hi==(1L<<32)-1 ) return "u32"; + return "["+lo+"-"+hi+"]"; + } + + @Override public boolean isHigh () { return _min > _max; } + @Override public boolean isConstant() { return _min == _max; } + + @Override public int log_size() { + if( this==I8 || this==U8 || this==BOOL ) return 0; // 1<<0 == 1 bytes + if( this==I16 || this==U16 ) return 1; // 1<<1 == 2 bytes + if( this==I32 || this==U32 ) return 2; // 1<<2 == 4 bytes + if( this==BOT ) return 3; // 1<<3 == 8 bytes + if( isHighOrConst() ) return 0; + throw Utils.TODO(); + } + + public long value() { assert isConstant(); return _min; } + + // AND-mask of forced zeros. e.g. unsigned types will return their mask; + // u8 will return 0xFF. But also a range of 16-18 (0x10-0x12) will return + // 0x13 - no value in the range {16,17,18} will allow bit 0x04 to be set. + public long mask() { + if( isHigh() ) return 0; + if( isConstant() ) return _min; + // Those bit positions which differ min to max + long x = _min ^ _max; + // Highest '1' bit in the differ set. Since the range is from min to + // max, all values below here are possible. + long ff1 = Long.highestOneBit(x); + // Make a all-1's mask from ff1, and set over the same bits (either min + // or max is ok). + long mask = _min | (ff1-1) | ff1; + return mask; + } + + @Override + public SONType xmeet(SONType other) { + // Invariant from caller: 'this' != 'other' and same class (TypeInteger) + SONTypeInteger i = (SONTypeInteger)other; // Contract + return make(Math.min(_min,i._min), Math.max(_max,i._max)); + } + + @Override public SONTypeInteger dual() { return make(_max,_min); } + + @Override public SONTypeInteger nonZero() { + if( isHigh() ) return this; + if( this==ZERO ) return null; // No sane answer + if( _min==0 ) return make(1,Math.max(_max,1)); // specifically good on BOOL + if( _max==0 ) return make(_min,-1); + return this; + } + @Override public SONType makeZero() { return ZERO; } + @Override public SONTypeInteger glb() { return BOT; } + @Override int hash() { return Utils.fold(_min) * Utils.fold(_max); } + @Override public boolean eq( SONType t ) { + SONTypeInteger i = (SONTypeInteger)t; // Contract + return _min==i._min && _max==i._max; + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeMem.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeMem.java new file mode 100644 index 0000000..aed4569 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeMem.java @@ -0,0 +1,72 @@ +package com.compilerprogramming.ezlang.compiler.sontypes; + +import com.compilerprogramming.ezlang.compiler.SB; + +import java.util.ArrayList; + + +/** + * Represents a slice of memory corresponding to a set of aliases + */ +public class SONTypeMem extends SONType { + + // Which slice of memory? + // 0 means TOP, no slice. + // 0 means BOT, all memory. + // N means slice#N. + public final int _alias; + public final SONType _t; // Memory contents, some scalar type + + private SONTypeMem(int alias, SONType t) { + super(TMEM); + assert alias!=0 || (t== SONType.TOP || t== SONType.BOTTOM); + _alias = alias; + _t = t; + } + + public static SONTypeMem make(int alias, SONType t) { return new SONTypeMem(alias,t).intern(); } + public static final SONTypeMem TOP = make(0, SONType.TOP ); + public static final SONTypeMem BOT = make(0, SONType.BOTTOM); + + public static void gather(ArrayList ts) { ts.add(make(1, SONType.NIL)); ts.add(make(1, SONTypeInteger.ZERO)); ts.add(BOT); } + + @Override + SONTypeMem xmeet(SONType t) { + SONTypeMem that = (SONTypeMem) t; // Invariant: TypeMem and unequal + if( this==TOP ) return that; + if( that==TOP ) return this; + if( this==BOT ) return BOT; + if( that==BOT ) return BOT; + int alias = _alias==that._alias ? _alias : 0; + SONType mt = _t.meet(that._t); + return make(alias,mt); + } + + @Override + public SONType dual() { + return make(_alias,_t.dual()); + } + + @Override public boolean isHigh() { return _t.isHigh(); } + @Override public SONType glb() { return make(_alias,_t.glb()); } + + @Override int hash() { return 9876543 + _alias + _t.hashCode(); } + + @Override boolean eq(SONType t) { + SONTypeMem that = (SONTypeMem) t; // Invariant + return _alias == that._alias && _t == that._t; + } + + @Override public SB print(SB sb) { + sb.p("#"); + if( _alias==0 ) return sb.p(_t._type==TTOP ? "TOP" : "BOT"); + return _t.print(sb.p(_alias).p(":")); + } + @Override public SB gprint(SB sb) { + sb.p("#"); + if( _alias==0 ) return sb.p(_t._type==TTOP ? "TOP" : "BOT"); + return _t.gprint(sb.p(_alias).p(":")); + } + + @Override public String str() { return print(new SB()).toString(); } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeMemPtr.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeMemPtr.java new file mode 100644 index 0000000..7b0f1c8 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeMemPtr.java @@ -0,0 +1,107 @@ +package com.compilerprogramming.ezlang.compiler.sontypes; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.Utils; +import java.util.ArrayList; + +/** + * Represents a Pointer to memory. + * + * Null is generic pointer to non-existent memory. + * *void is a non-Null pointer to all possible refs, both structs and arrays. + * Pointers can be to specific struct and array types, or a union with Null. + * The distinguished *$BOT ptr represents union of *void and Null. + * The distinguished *$TOP ptr represents the dual of *$BOT. + */ +public class SONTypeMemPtr extends SONTypeNil { + // A TypeMemPtr is pair (obj,nil) + // where obj is a TypeStruct, possibly TypeStruct.BOT/TOP + // where nil is an explicit null is allowed or not + + // Examples: + // (Person,false) - a not-nil Person + // (Person,true ) - a Person or a nil + // (BOT ,false) - a not-nil void* (unspecified struct) + // (TOP ,true ) - a nil + + public final SONTypeStruct _obj; + + public SONTypeMemPtr(byte nil, SONTypeStruct obj) { + super(TMEMPTR,nil); + assert obj!=null; + _obj = obj; + } + static SONTypeMemPtr make(byte nil, SONTypeStruct obj) { return new SONTypeMemPtr(nil, obj).intern(); } + public static SONTypeMemPtr makeNullable(SONTypeStruct obj) { return make((byte)3, obj); } + public static SONTypeMemPtr make(SONTypeStruct obj) { return new SONTypeMemPtr((byte)2, obj).intern(); } + public SONTypeMemPtr makeFrom(SONTypeStruct obj) { return obj==_obj ? this : make(_nil, obj); } + public SONTypeMemPtr makeNullable() { return makeFrom((byte)3); } + @Override + SONTypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil,_obj); } + @Override public SONTypeMemPtr makeRO() { return makeFrom(_obj.makeRO()); } + @Override public boolean isFinal() { return _obj.isFinal(); } + + // An abstract pointer, pointing to either a Struct or an Array. + // Can also be null or not, so 4 choices {TOP,BOT} x {nil,not} + public static SONTypeMemPtr BOT = make((byte)3, SONTypeStruct.BOT); + public static SONTypeMemPtr TOP = BOT.dual(); + public static SONTypeMemPtr NOTBOT = make((byte)2, SONTypeStruct.BOT); + + public static SONTypeMemPtr TEST= make((byte)2, SONTypeStruct.TEST); + public static void gather(ArrayList ts) { ts.add(NOTBOT); ts.add(BOT); ts.add(TEST); } + + @Override + public SONTypeNil xmeet(SONType t) { + SONTypeMemPtr that = (SONTypeMemPtr) t; + return SONTypeMemPtr.make(xmeet0(that), (SONTypeStruct)_obj.meet(that._obj)); + } + + @Override + public SONTypeMemPtr dual() { return SONTypeMemPtr.make( dual0(), _obj.dual()); } + + // RHS is NIL; do not deep-dual when crossing the centerline + @Override + SONType meet0() { return _nil==3 ? this : make((byte)3,_obj); } + + + // True if this "isa" t up to named structures + @Override public boolean shallowISA( SONType t ) { + if( !(t instanceof SONTypeMemPtr that) ) return false; + if( this==that ) return true; + if( xmeet0(that)!=that._nil ) return false; + if( _obj==that._obj ) return true; + if( _obj._name.equals(that._obj._name) ) + return true; // Shallow, do not follow matching names, just assume ok + throw Utils.TODO(); // return _obj.shallowISA(that._obj); + } + + @Override public SONTypeMemPtr glb() { return make((byte)3,_obj.glb()); } + // Is forward-reference + @Override public boolean isFRef() { return _obj.isFRef(); } + + @Override public int log_size() { return 2; } // (1<<2)==4-byte pointers + + @Override int hash() { return _obj.hashCode() ^ super.hash(); } + + @Override boolean eq(SONType t) { + SONTypeMemPtr ptr = (SONTypeMemPtr)t; // Invariant + return _obj == ptr._obj && super.eq(ptr); + } + + @Override public String str() { + if( this== NOTBOT) return "*void"; + if( this== BOT) return "*void?"; + return x()+"*"+_obj.str()+q(); + } + + @Override public SB print(SB sb) { + if( this== NOTBOT) return sb.p("*void"); + if( this== BOT) return sb.p("*void?"); + return _obj.print(sb.p(x()).p("*")).p(q()); + } + @Override public SB gprint(SB sb) { + if( this== NOTBOT) return sb.p("*void"); + if( this== BOT) return sb.p("*void?"); + return _obj.gprint(sb.p(x()).p("*")).p(q()); + } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeNil.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeNil.java new file mode 100644 index 0000000..3193c0c --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeNil.java @@ -0,0 +1,60 @@ +package com.compilerprogramming.ezlang.compiler.sontypes; + +import java.util.ArrayList; + +/** + * Nil-able Scalar types + */ +public abstract class SONTypeNil extends SONType { + // 0 = high-subclass choice nil + // 1 = high-subclass no nil + // 2 = low -subclass no nil + // 3 = low -subclass also nil + final byte _nil; + + SONTypeNil(byte t, byte nil ) { super(t); _nil = nil; } + + public static void gather(ArrayList ts) { } + + abstract SONTypeNil makeFrom(byte nil); + + byte xmeet0(SONTypeNil that) { return (byte)Math.max(_nil,that._nil); } + + byte dual0() { return (byte)(3-_nil); } + + // RHS is NIL + abstract SONType meet0(); + + // RHS is XNIL + SONType meetX() { + return _nil==0 ? XNIL : (_nil<=2 ? SONTypePtr.NPTR : SONTypePtr.PTR); + } + + SONType nmeet(SONTypeNil tn) { + // Invariants: both are TypeNil subclasses and unequal classes. + // If this is TypePtr, we went to TypePtr.nmeet and not here. + // If that is TypePtr, this is not (invariant); reverse and go again. + if( tn instanceof SONTypePtr ts ) return ts.nmeet(this); + + // Two mismatched TypeNil, no Scalar. + if( _nil==0 && tn._nil==0 ) return XNIL; + if( _nil<=2 && tn._nil<=2 ) return SONTypePtr.NPTR; + return SONTypePtr.PTR; + } + + @Override public boolean isHigh () { return _nil <= 1; } + @Override public boolean isConstant () { return false; } + @Override public boolean isHighOrConst() { return isHigh() || isConstant(); } + + @Override public SONType glb() { return SONType.NIL; } + + public boolean notNull() { return _nil==1 || _nil==2; } + public boolean nullable() { return _nil==3; } + + final String q() { return _nil == 1 || _nil == 2 ? "" : "?"; } + final String x() { return isHigh() ? "~" : ""; } + + int hash() { return _nil<<17; } + + boolean eq(SONTypeNil ptr) { return _nil == ptr._nil; } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypePtr.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypePtr.java new file mode 100644 index 0000000..dda8dd2 --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypePtr.java @@ -0,0 +1,56 @@ +package com.compilerprogramming.ezlang.compiler.sontypes; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.Utils; +import java.util.ArrayList; + +/** + * Represents a Scalar; a single register-sized value. + */ +public class SONTypePtr extends SONTypeNil { + + private SONTypePtr(byte nil) { super(TPTR,nil); } + + // An abstract pointer, pointing to either a Struct or an Array. + // Can also be null or not, so 4 choices {TOP,BOT} x {nil,not} + public static SONTypePtr XPTR = new SONTypePtr((byte)0).intern(); + public static SONTypePtr XNPTR= new SONTypePtr((byte)1).intern(); + public static SONTypePtr NPTR = new SONTypePtr((byte)2).intern(); + public static SONTypePtr PTR = new SONTypePtr((byte)3).intern(); + private static final SONTypePtr[] PTRS = new SONTypePtr[]{XPTR,XNPTR,NPTR,PTR}; + + public static void gather(ArrayList ts) { ts.add(PTR); ts.add(NPTR); } + + SONTypeNil makeFrom(byte nil) { throw Utils.TODO(); } + + @Override public SONTypeNil xmeet(SONType t) { + SONTypePtr that = (SONTypePtr) t; + return PTRS[xmeet0(that)]; + } + + @Override public SONTypePtr dual() { return PTRS[dual0()]; } + + // High scalar loses, low scalar wins + @Override + SONTypeNil nmeet(SONTypeNil tn) { + if( _nil==0 ) return tn; // High scalar loses + if( _nil==1 ) return tn.makeFrom(xmeet0(tn)); // High scalar loses + if( _nil==2 ) return tn._nil==3 ? PTR : NPTR; // Low scalar wins + return PTR; // this + } + + + // RHS is NIL + @Override + SONType meet0() { return isHigh() ? NIL : PTR; } + // RHS is XNIL + // 0->xscalar, 1->nscalar, 2->nscalar, 3->scalar + @Override + SONType meetX() { return _nil==0 ? XNIL : (_nil==3 ? PTR : NPTR); } + + @Override public SONTypePtr glb() { return PTR; } + + private static final String[] STRS = new String[]{"~ptr","~nptr","nptr","ptr"}; + @Override public String str() { return STRS[_nil]; } + @Override public SB print(SB sb) { return sb.p(str()); } +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeRPC.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeRPC.java new file mode 100644 index 0000000..82f0e2f --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeRPC.java @@ -0,0 +1,97 @@ +package com.compilerprogramming.ezlang.compiler.sontypes; + +import java.util.ArrayList; +import java.util.HashSet; + +/** + * Return Program Control or Return PC or RPC + */ +public class SONTypeRPC extends SONType { + + // A set of CallEndNode IDs (or StopNode); commonly just one. + // Basically a sparse bit set + final HashSet _rpcs; + + // If true, invert the meaning of the bits + final boolean _any; + + private SONTypeRPC(boolean any, HashSet rpcs) { + super(TRPC); + _any = any; + _rpcs = rpcs; + } + private static SONTypeRPC make(boolean any, HashSet rpcs) { + return new SONTypeRPC(any,rpcs).intern(); + } + + public static SONTypeRPC constant(int cend) { + HashSet rpcs = new HashSet<>(); + rpcs.add(cend); + return make(false,rpcs); + } + + public static final SONTypeRPC BOT = make(true,new HashSet<>()); + private static final SONTypeRPC TEST2 = constant(2); + private static final SONTypeRPC TEST3 = constant(2); + + public static void gather(ArrayList ts) { ts.add(BOT); ts.add(TEST2); ts.add(TEST3); } + + @Override public String str() { + if( _rpcs.isEmpty() ) + return _any ? "$[ALL]" : "$[]"; + if( _rpcs.size()==1 ) + for( Integer rpc : _rpcs ) + return _any ? "$[-"+rpc+"]" : "$["+rpc+"]"; + return "$["+(_any ? "-" : "")+_rpcs+"]"; + } + + @Override public boolean isConstant() { + return !_any && _rpcs.size()==1; + } + + @Override + public SONTypeRPC xmeet(SONType other) { + SONTypeRPC rpc = (SONTypeRPC)other; + // If the two sets are equal, the _any must be unequal (invariant), + // so they cancel and all bits are set. + if( _rpcs.equals(rpc._rpcs) ) + return BOT; + // Classic union of bit sets (which might be infinite). + HashSet lhs = _rpcs, rhs = rpc._rpcs; + // Smaller on left + if( lhs.size() > rhs.size() ) { lhs = rpc._rpcs; rhs = _rpcs; } + + HashSet rpcs = new HashSet<>(); + boolean any = true; + // If both sets are infinite, intersect. + if( _any && rpc._any ) { + for( Integer i : lhs ) if( rhs.contains(i) ) rpcs.add(i); + + } else if( !_any && !rpc._any ) { + // if neither set is infinite, union. + rpcs.addAll( lhs ); + rpcs.addAll( rhs ); + any = false; + } else { + // If one is infinite, subtract the other from it. + HashSet sub = _any ? rpc._rpcs : _rpcs; + HashSet inf = _any ? _rpcs : rpc._rpcs; + for( Integer i : inf ) + if( inf.contains(i) && !sub.contains(i) ) + rpcs.add(i); + } + return make(any,rpcs); + } + + @Override + public SONType dual() { return make(!_any,_rpcs); } + + @Override + int hash() { return _rpcs.hashCode() ^ (_any ? -1 : 0) ; } + @Override + public boolean eq( SONType t ) { + SONTypeRPC rpc = (SONTypeRPC)t; // Contract + return _any==rpc._any && _rpcs.equals(rpc._rpcs); + } + +} diff --git a/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeStruct.java b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeStruct.java new file mode 100644 index 0000000..f1f15ea --- /dev/null +++ b/seaofnodes/src/main/java/com/compilerprogramming/ezlang/compiler/sontypes/SONTypeStruct.java @@ -0,0 +1,259 @@ +package com.compilerprogramming.ezlang.compiler.sontypes; + +import com.compilerprogramming.ezlang.compiler.SB; +import com.compilerprogramming.ezlang.compiler.Utils; +import java.util.ArrayList; + +/** + * Represents a struct type. + */ +public class SONTypeStruct extends SONType { + + // A Struct has a name and a set of fields; the fields themselves have + // names, types and aliases. The name has no semantic meaning, but is + // useful for debugging. Briefly during parsing a Struct can be a + // forward-ref; in this case the _fields array is null. + + // Its illegal to attempt to load a field from a forward-ref struct. + + // Example: "int rez = new S.x; struct S { int x; } return rez;" // Error, S not defined + // Rewrite: "struct S { int x; } int rez = new S.x; return rez;" // Ok, no forward ref + // + // During the normal optimization run, struct types "bottom out" at further + // struct references, so we don't have to handle e.g. cyclic types. The + // "bottom out" is again the forward-ref struct. + public final String _name; + public Field[] _fields; + + public SONTypeStruct(String name, Field[] fields) { + super(TSTRUCT); + _name = name; + _fields = fields; + } + + // All fields directly listed + public static SONTypeStruct make(String name, Field... fields) { return new SONTypeStruct(name, fields).intern(); } + public static final SONTypeStruct TOP = make("$TOP",new Field[0]); + public static final SONTypeStruct BOT = make("$BOT",new Field[0]); + public static final SONTypeStruct TEST = make("test",new Field[]{Field.TEST}); + // Forward-ref version + public static SONTypeStruct makeFRef(String name) { return make(name, (Field[])null); } + // Make a read-only version + @Override public SONTypeStruct makeRO() { + if( isFinal() ) return this; + Field[] flds = new Field[_fields.length]; + for( int i=0; i ts) { ts.add(TEST); ts.add(BOT); ts.add(S1); ts.add(S2); ts.add(ARY); } + + // Find field index by name + public int find(String fname) { + for( int i=0; i<_fields.length; i++ ) + if( _fields[i]._fname.equals(fname) ) + return i; + return -1; + } + public Field field(String fname) { + int idx = find(fname); + return idx== -1 ? null : _fields[idx]; + } + + // Find field index by alias + public int findAlias( int alias ) { + for( int i=0; i<_fields.length; i++ ) + if( _fields[i]._alias==alias ) + return i; + return -1; + } + + + @Override + SONType xmeet(SONType t) { + SONTypeStruct that = (SONTypeStruct) t; + if( this==TOP ) return that; + if( that==TOP ) return this; + if( this==BOT ) return BOT; + if( that==BOT ) return BOT; + + // Within the same compilation unit, struct names are unique. If the + // names differ, its different structs. Across many compilation units, + // structs with the same name but different field layouts can be + // interned... which begs the question: + // "What is the meet of structs from two different compilation units?" + // And the answer is: "don't ask". + if( !_name.equals(that._name) ) + return BOT; // It's a struct; that's about all we know + if( this._fields==null ) return that; + if( that._fields==null ) return this; + if( _fields.length != that._fields.length ) return BOT; + // Just do field meets + Field[] flds = new Field[_fields.length]; + for( int i=0; i<_fields.length; i++ ) { + Field f0 = _fields[i], f1 = that._fields[i]; + if( !f0._fname.equals(f1._fname) || f0._alias != f1._alias ) + return BOT; + flds[i] = (Field)f0.meet(f1); + } + return make(_name,flds); + } + + @Override + public SONTypeStruct dual() { + if( this==TOP ) return BOT; + if( this==BOT ) return TOP; + if( _fields == null ) return this; + Field[] flds = new Field[_fields.length]; + for( int i=0; i<_fields.length; i++ ) + flds[i] = _fields[i].dual(); + return make(_name,flds); + } + + // Keeps the same struct, but lower-bounds all fields. + @Override public SONTypeStruct glb() { + if( _glb() ) return this; + // Need to glb each field + Field[] flds = new Field[_fields.length]; + for( int i=0; i<_fields.length; i++ ) + flds[i] = _fields[i].glb(); + return make(_name,flds); + } + private boolean _glb() { + if( _fields!=null ) + for( Field f : _fields ) + if( f.glb() != f ) + return false; + return true; + } + + // Is forward-reference + @Override public boolean isFRef() { return _fields==null; } + // All fields are final + @Override public boolean isFinal() { + if( _fields==null ) return true; + for( Field fld : _fields ) + if( !fld.isFinal() ) + return false; + return true; + } + + @Override + boolean eq(SONType t) { + SONTypeStruct ts = (SONTypeStruct)t; // Invariant + if( !_name.equals(ts._name) ) + return false; +// if( _fields == ts._fields ) return true; +// if( _fields==null || ts._fields==null ) return false; +// if( _fields.length!=ts._fields.length ) +// return false; +// for( int i = 0; i < _fields.length; i++ ) +// if( _fields[i] != ts._fields[i] ) +// return false; + return true; + } + + @Override + int hash() { + long hash = _name.hashCode(); +// if( _fields != null ) +// for( Field f : _fields ) +// hash = Utils.rot(hash,13) ^ f.hashCode(); +// return Utils.fold(hash); + return (int) hash; + } + + @Override + public SB print(SB sb) { + sb.p(_name); + if( _fields == null ) return sb; // Forward reference struct, just print the name + if( isAry() ) return sb; // Skip printing generic array fields + sb.p(" {"); + for( Field f : _fields ) + f._type.print(sb).p(f._final ? " " : " !").p(f._fname).p("; "); + return sb.p("}"); + } + @Override public SB gprint( SB sb ) { return sb.p(_name); } + + @Override public String str() { return _name; } + + + public boolean isAry() { return _fields.length==2 && _fields[1]._fname.equals("[]"); } + + public int aryBase() { + assert isAry(); + if( _offs==null ) _offs = offsets(); + return _offs[1]; + } + public int aryScale() { + assert isAry(); + return _fields[1]._type.log_size(); + } + + + // Field offsets as packed byte offsets + private int[] _offs; // Lazily computed and not part of the type semantics + public int offset(int idx) { + if( _offs==null ) _offs = offsets(); + return _offs[idx]; + } + private int[] offsets() { // Field byte offsets + // Compute a layout for a collection of fields + assert _fields != null; // No forward refs + + // Array layout is different: len,[pad],body... + if( isAry() ) + return _offs = new int[]{ 0, _fields[1]._type.log_size() < 3 ? 4 : 8 }; + + // Compute a layout + int[] cnts = new int[4]; // Count of fields at log field size + for( Field f : _fields ) + cnts[f._type.log_size()]++; // Log size is 0(byte), 1(i16/u16), 2(i32/f32), 3(i64/dbl) + int off = 0, idx = 0; // Base common struct fields go here, e.g. Mark/Klass + // Compute offsets to the start of each power-of-2 aligned fields. + int[] offs = new int[4]; + for( int i=3; i>=0; i-- ) { + offs[i] = off; + off += cnts[i]<